• 排序


    排序

    算法 稳定性 时间复杂度 空间复杂度
    选择排序 N (N^2) 1
    冒泡排序 Y (N^2) 1
    插入排序 Y (N~N^2) 1
    希尔排序 N (N^{(1.3-2)}) 1
    归并排序 Y (NlogN) N
    快速排序 N (NlogN) logN
    堆排序 N (NlogN) 1
    public abstract class Sort<T extends Comparable<T>> {
    
    	public abstract void sort(T[] nums);
    
    	protected boolean less(T v, T w) {
    		return v.compareTo(w) < 0;
    	}
    
    	protected void swap(T[] a, int i, int j) {
    		T t = a[i];
    		a[i] = a[j];
    		a[j] = t;
    	}
    }
    

    选择排序

    从数组中 选择最小元素,将它与数组的第一个元素 交换 位置。再从数组剩下的元素中选择出最小的元素,将它与数组的第二个元素交换位置。不断进行这样的操作,直到将整个数组排序。

    • (N^2 /2) 次比较
    • (N) 次交换
    • 不稳定
    • 运行时间与输入无关,即对一个已经排序的数组也需要这么多的比较和交换操作
    public class Selection<T extends Comparable<T>> extends Sort<T> {
    
    	@Override
    	public void sort(T[] nums) {
    		int len = nums.length;
    		for (int i = 0; i < len - 1; i++) {
    			int min = i;
    			for (int j = i + 1; j < len; j++) {
    				if (less(nums[j], nums[min])) {
    					min = j;
    				}
    			}
    			swap(nums, i, min);
    		}
    	}
    }
    
    

    冒泡排序

    从左到右不断 交换相邻逆序 的元素,在一轮的循环之后,可以让未排序的最大元素上浮到右侧。
    优化:在一轮循环中,如果没有发生交换,那么说明数组已经是有序的。

    • 最好 (N-1) 次比较 (0) 次交换
    • 最差 (N(N-1)/2) 次比较 (N(N-1)/2) 次交换
    • 稳定
    public class Bubble<T extends Comparable<T>> extends Sort<T> {
    
    	@Override
    	public void sort(T[] nums) {
    		int n = nums.length;
    		boolean isSorted = false;
    		for (int i = n - 1; !isSorted && i > 0; i--) {
    			isSorted = true;
    			for (int j = 0; j < i; j++) {
    				if (less(nums[j + 1], nums[j])) {
    					swap(nums, j, j + 1);
    					isSorted = false;
    				}
    			}
    		}
    	}
    }
    

    插入排序

    每次都将当前元素 插入到左侧已经排序 的数组中,使得左侧数组依然有序。

    插入排序每次只能交换相邻元素,令逆序数量减少 1,因此插入排序需要交换的次数为逆序数量。

    • 平均 (N^2/4) 次比较 (N^2/4) 次交换
    • 最坏 (N^2/2) 次比较 (N^2/2) 次交换
    • 最好 (N-1) 次比较 (0) 次交换
    • 稳定
    • 运行时间取决于数组的初始顺序
    public class Insertion<T extends Comparable<T>> extends Sort<T> {
    
    	@Override
    	public void sort(T[] nums) {
    		int n = nums.length;
    		for (int i = 1; i < n; i++) {
    			for (int j = i; j > 0 && less(nums[j], nums[j - 1]); j--) {
    				swap(nums, j, j - 1);
    			}
    		}
    	}
    
    	public static void main(String[] args) {
    		Sort.test(new Insertion<Integer>());
    	}
    }
    

    希尔排序

    希尔排序使用 插入排序对间隔 h 的序列 进行排序。通过不断减小 h,最后令 h=1,就可以使得整个数组是有序。
    希尔排序通过交换不相邻的元素,每次可以将逆序数量减少大于 1。

    插入排序每次只能交换相邻元素,令逆序数量减少 1,因此插入排序需要交换的次数为逆序数量。

    • 不稳定
    public class Shell<T extends Comparable<T>> extends Sort<T> {
    
    	@Override
    	public void sort(T[] nums) {
    		int n = nums.length;
    
    		int h = 1;
    		while (h < n / 3) {
    			h = 3 * h + 1;
    		}
    
    		while (h >= 1) {
    			for (int i = h; i < n; i++) {
    				for (int j = i; j >= h && less(nums[j], nums[j - h]); j = j - h) {
    					swap(nums, j, j - h);
    				}
    			}
    			h /= 3;
    		}
    	}
    }
    

    归并排序

    分而治之 ,将问题分解成几个规模较小的问题,再合并子问题获得答案。

    • 稳定
    • 时间复杂度:(Ο(nlogn))
    public class MergeSort<T extends Comparable<T>> extends Sort<T> {
    
    	private T[] tmp;
    
    	@Override
    	public void sort(T[] nums) {
    		int n = nums.length;
    		tmp = Arrays.copyOf(nums, n);
    		sortTopDown(nums, 0, n - 1);
    //		sortDownTop(nums, n);
    	}
    
    	private void sortTopDown(T[] nums, int l, int r) {
    		if (l >= r)
    			return;
    		int m = ((r - l) >> 1) + l;
    		sortTopDown(nums, l, m);
    		sortTopDown(nums, m + 1, r);
    		merge(nums, l, m, r);
    	}
    
    	private void sortDownTop(T[] nums, int n) {
    		for (int d = 1; d < n; d += d) {
    			for (int l = 0; l < n - d; l += d + d) {
    				merge(nums, l, l + d - 1, Math.min(l + d + d - 1, n - 1));
    			}
    		}
    	}
    
    	private void merge(T[] nums, int l, int m, int r) {
    		int i = l;
    		int j = m + 1;
    		int index = l;
    		while (i <= m && j <= r) {
    			if (!less(nums[j], nums[i])) {
    				tmp[index++] = nums[i++];
    			} else {
    				tmp[index++] = nums[j++];
    			}
    		}
    		while (i <= m) {
    			tmp[index++] = nums[i++];
    		}
    		while (j <= r) {
    			tmp[index++] = nums[j++];
    		}
    		for (index = l; index <= r; index++) {
    			nums[index] = tmp[index];
    		}
    	}
    }
    

    快速排序

    快速排序通过一个 切分元素 将数组分为两个子数组,左子数组小于等于切分元素,右子数组大于等于切分元素,将这两个子数组排序也就将整个数组排序了。

    public class QuickSort<T extends Comparable<T>> extends Sort<T> {
    
    	@Override
    	public void sort(T[] nums) {
    		sort(nums, 0, nums.length - 1);
    	}
    
    	private void sort(T[] nums, int l, int r) {
    		if (l >= r) {
    			return;
    		}
    		int index = partition(nums, l, r);
    		sort(nums, l, index - 1);
    		sort(nums, index + 1, r);
    	}
    
    	private int partition(T[] nums, int l, int r) {
    		T tmp = nums[r];
    		int i = l;
    		int j = r - 1;
    		while (true) {
    			// 相等时即停止移动指针
    			while (i < r && less(nums[i], tmp))
    				i++;
    			while (j >= l && less(tmp, nums[j]))
    				j--;
    			if (i >= j)
    				break;
    			swap(nums, i++, j--);
    		}
    		swap(nums, i, r);
    		return i;
    	}
    }
    

    :若指针指向的元素与参考值相等仍继续移动,假设极端情况:数组中所有元素都相等。那么i会移动到最后一个位置,导致数组切分失衡,复杂度变为 (O(n^2))。因此相等时就应停止移动,虽然多了许多交换操作,但能够保证切分均衡。

    改进 :三向切分。lt左侧小于切分元素,gt右侧大于切分元素。

    private void threeWayQuickSort(T[] nums, int l, int r) {
    	if (l >= r) {
    		return;
    	}
    	int lt = l, i = l + 1, gt = r;
    	T tmp = nums[l];
    	while (i <= gt) {
    		int cmp = nums[i].compareTo(tmp);
    		if (cmp < 0) {
    			swap(nums, lt++, i++);
    		} else if (cmp > 0) {
    			swap(nums, i, gt--);
    		} else {
    			i++;
    		}
    	}
    	sort(nums, l, lt - 1);
    	sort(nums, gt + 1, r);
    }
    

    堆排序

    堆是 完全二叉树 ,可以用数组来表示。
    位置 k 的节点的父节点位置为 k/2,而它的两个子节点的位置分别为 2k 和 2k+1。(不使用数组索引为 0 的位置)

    • 堆的高度为 (logN),因此在堆中插入元素和删除最大元素的复杂度都为 (logN)
    • 对于堆排序,由于要对 N 个节点进行下沉操作,因此复杂度为 (NlogN)
    • 不稳定
    • 正常来说,堆排序是 原地排序 ,这里为了实现简单,下标从1开始,使用了辅助数组。
    public class HeapSort<T extends Comparable<T>> extends Sort<T> {
    
    	@Override
    	public void sort(T[] nums) {
    		int n = nums.length;
    		T[] tmp = (T[]) new Comparable[n + 1];
    		System.arraycopy(nums, 0, tmp, 1, n);
    
    		for (int i = n / 2; i >= 1; i--) {
    			sink(tmp, i, n);
    		}
    		for (int i = 1; i <= n; i++) {
    			swap(tmp, 1, n - i + 1);
    			sink(tmp, 1, n - i);
    		}
    
    		System.arraycopy(tmp, 1, nums, 0, n);
    	}
    
    	private void sink(T[] nums, int k, int n) {
    		while (k <= n / 2) {
    			int t = 2 * k;
    			if (t + 1 <= n && less(nums[t], nums[t + 1])) {
    				t++;
    			}
    			if (!less(nums[k], nums[t]))
    				break;
    			swap(nums, k, t);
    			k = t;
    		}
    	}
    }
    
  • 相关阅读:
    SpringBoot实现原理
    常见Http状态码大全
    forward(转发)和redirect(重定向)有什么区别
    1094. Car Pooling (M)
    0980. Unique Paths III (H)
    1291. Sequential Digits (M)
    0121. Best Time to Buy and Sell Stock (E)
    1041. Robot Bounded In Circle (M)
    0421. Maximum XOR of Two Numbers in an Array (M)
    0216. Combination Sum III (M)
  • 原文地址:https://www.cnblogs.com/JL916/p/12540045.html
Copyright © 2020-2023  润新知