学习自:算法-排序
约定
待排序的元素需要实现 Java 的 Comparable 接口,该接口有 compareTo() 方法,可以用它来判断两个元素的大小关系。
使用辅助函数 less() 和 swap() 来进行比较和交换的操作,使得代码的可读性和可移植性更好。
排序算法的成本模型是比较和交换的次数。
package allSort; 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; } }
选择排序
从数组中选择最小的元素,将它与数组的第一个元素交换位置。再从数组剩下的元素中选择出最小的元素,将它与数组的第二个元素交换位置。不断进行这样的操作。直至将真个数组排序。
package allSort; public class Selection<T extends Comparable<T>> extends Sort<T> { @Override public void sort(T[] nums) { int l = nums.length; for (int i = 0; i < l - 1; i++) { int min = i; for (int j = i + 1; j < l; j++) { if (less(nums[j], nums[min])) { min = j; } } swap(nums, i, min); } } }
冒泡排序
从左到右不断交换相邻逆序的元素,在一轮的循环之后,可以让未排序的最大元素上浮到右侧。
在一轮循环中,如果没有发生交换,那么说明数组已经是有序的,此时可以直接退出。
package allSort; public class Bubble<T extends Comparable<T>> extends Sort<T> { @Override public void sort(T[] nums) { boolean flag = true; int l = nums.length; for (int i = 0; i < l - 1 && flag; i++) { flag = false; for (int j = 0; j < l - 1; j++) { if (less(nums[j + 1], nums[j])) { flag = true; swap(nums, j + 1, j); } } } } }
插入排序
每次都将当前元素插入到左侧已经排序的数组中,使得插入之后左侧数组依然有序。
对于数组 {3, 5, 2, 4, 1},它具有以下逆序:(3, 2), (3, 1), (5, 2), (5, 4), (5, 1), (2, 1), (4, 1),插入排序每次只能交换相邻元素,令逆序数量减少 1,因此插入排序需要交换的次数为逆序数量。
插入排序的时间复杂度取决于数组的初始顺序,如果数组已经部分有序了,那么逆序较少,需要的交换次数也就较少,时间复杂度较低。
package allSort; public class Insertion<T extends Comparable<T>> extends Sort<T> { @Override public void sort(T[] nums) { int l = nums.length; for (int i = 1; i < l; i++) { for (int j = i; j > 0 && less(nums[j], nums[j - 1]); j--) { swap(nums, j, j - 1); } } } }
希尔排序
对于大规模的数组,插入排序很慢,因为它只能交换相邻的元素,每次只能将逆序数量减少 1。希尔排序的出现就是为了解决插入排序的这种局限性,它通过交换不相邻的元素,每次可以将逆序数量减少大于 1。
希尔排序使用插入排序对间隔 h 的序列进行排序。通过不断减小 h,最后令 h=1,就可以使得整个数组是有序的(就用增量来将数组进行分隔,直到增量为1。底层干的还是插入排序干的活~)。
(注意增量h的确定)
package allSort; 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 -= h) { swap(nums, j, j - h); } } h = h / 3; } } }
归并排序
归并排序的思想是将数组分成两部分,分别进行排序,然后归并起来。采用递归。
归并方法将数组中两个已经排序的部分归并成一个
package allSort; public abstract class MergeSort<T extends Comparable<T>> extends Sort<T> { protected T[] aux; /** * 合并两个有序数组 * @param nums 有序数组 * @param l 起始下标 * @param m 分割下标 * @param h 最后的下标 */ protected void merge(T[] nums, int l, int m, int h) { int i = l; int j = m + 1; for (int k = l; k <= h; k++) { aux[k] = nums[k]; } for (int k = l; k <= h; k++) { if (i > m) { nums[k] = aux[j++]; } else if (j > h) { nums[k] = aux[i++]; } else if (aux[i].compareTo(aux[j]) <= 0) { nums[k] = aux[i++]; } else { nums[k] = aux[j++]; } } } }
自顶向下归并排序:
将一个大数组分成两个小数组去求解。
因为每次都将问题对半分成两个子问题,这种对半分的算法复杂度一般为 O(NlogN)。
package allSort; // 自顶向下归并排序 public class Up2DownMergeSort<T extends Comparable<T>> extends MergeSort<T> { @Override public void sort(T[] nums) { aux = (T[]) new Comparable[nums.length]; sort(nums, 0, nums.length - 1); } private void sort(T[] nums, int l, int h) { if (h <= l) { return; } int mid = l + (h - l) / 2; sort(nums, l, mid); sort(nums,mid + 1, h); merge(nums, l, mid, h); } }
自底向上归并排序:
快速排序
学习自:排序-快速排序
快排运用了二分的思想,首先选择一个基准,定义左右两端指针,先从左到右进行扫描直到,R[hi] < temp,将R[hi]移动至lo所在位置 从右往左进行扫描,直到R[lo] > temp,将R[lo]移动到hi所在位置上,左右端指针在排序过程中从数组的两端往中间进行靠近,直到hi == lo。而快速排序则要进行多次快排过程,直到划分的区间最后长度仅为1。
package allSort; public class QuickSort<T extends Comparable<T>> extends Sort<T>{ @Override public void sort(T[] nums) { int i = 0, j = nums.length-1; QuickSort1(nums, i, j); } void QuickSort1(T[] nums, int l, int h){ int i = l, j = h; T temp; if(i<j){ temp=nums[i]; while(i!=j){ while (j>i && less(temp, nums[j])){ j--; } nums[i] = nums[j]; while (i<j && less(nums[i], temp)){ i++; } nums[j] = nums[i]; } nums[i] = temp; QuickSort1(nums, l,i-1); QuickSort1(nums,i+1, h); } } }
开头学习资源的实现(QuickSort2,思路与上面一摸一样):
package allSort; public class QuickSort<T extends Comparable<T>> extends Sort<T>{ @Override public void sort(T[] nums) { int i = 0, j = nums.length-1; // QuickSort1(nums, i, j); QuickSort2(nums, i, j); } void QuickSort1(T[] nums, int l, int h){ int i = l, j = h; T temp; if(i<j){ temp=nums[i]; while(i!=j){ while (j>i && less(temp, nums[j])){ j--; } nums[i] = nums[j]; while (i<j && less(nums[i], temp)){ i++; } nums[j] = nums[i]; } nums[i] = temp; QuickSort1(nums, l,i-1); QuickSort1(nums,i+1, h); } } private void QuickSort2(T[] nums, int l, int h){ if(h<=l){ return; } int j = partition(nums, l, h); QuickSort2(nums, l, j-1); QuickSort2(nums, j+1, h); } private int partition(T[] nums, int l, int h){ int i = l, j = h+1; T temp = nums[l]; if(l<h){ while(less(nums[++i], temp) && i!=h); while(less(temp, nums[--j])); if(i<=j){ swap(nums, i, j); } } swap(nums,l,j); return j; } }
堆排序
开头学习资源讲解:
堆中某个节点的值总是大于等于或小于等于其子节点的值,并且堆是一颗完全二叉树。
堆可以用数组来表示,这是因为堆是完全二叉树,而完全二叉树很容易就存储在数组中。位置 k 的节点的父节点位置为 k/2,而它的两个子节点的位置分别为 2k 和 2k+1。数组索引从1开始,这里不使用数组索引为 0 的位置,是为了更清晰地描述节点的位置关系。
上浮:在堆中,当一个节点比父节点大,那么需要交换这个两个节点。交换后还可能比它新的父节点大,因此需要不断地进行比较和交换操作,把这种操作称为上浮。
下沉:类似地,当一个节点比子节点来得小,也需要不断地向下进行比较和交换操作,把这种操作称为下沉。一个节点如果有两个子节点,应当与两个子节点中最大那个节点进行交换。
堆排序:
构建堆:无序数组建立堆最直接的方法是从左到右遍历数组进行上浮操作。一个更高效的方法是从右至左进行下沉操作,如果一个节点的两个节点都已经是堆有序,那么进行下沉操作可以使得这个节点为根节点的堆有序。叶子节点不需要进行下沉操作,可以忽略叶子节点的元素,因此只需要遍历一半的元素即可。
交换堆顶元素与最后一个元素:交换之后需要进行下沉操作维持堆的有序状态。
package allSort; public class HeapSort<T extends Comparable<T>> extends Sort<T> { @Override public void sort(T[] nums) { int N = nums.length - 1; for (int k = N / 2; k >= 1; k--) { sink(nums, k, N); } while (N > 1) { swap(nums, 1, N--); sink(nums, 1, N); } } private void sink(T[] nums, int k, int N) { while (2 * k <= N) { int j = 2 * k; if (j < N && less(nums, j, j + 1)) { j++; } if (!less(nums, k, j)) { break; } swap(nums, k, j); k = j; } } private boolean less(T[] nums, int i, int j) { return nums[i].compareTo(nums[j]) < 0; } }
测试代码:
由于上面堆排序的下标从1开始,因此测试堆排序时需要对应处理一下数组。
package allSort; public class Test { public static void main(String[] args) { Integer[] a = new Integer[]{12, 3, 6, 2, 7, 5}; // Selection s1 = new Selection(); // s1.sort(a); // Bubble b1 = new Bubble(); // b1.sort(a); // Insertion i1 = new Insertion(); // i1.sort(a); // Shell s1 = new Shell(); // s1.sort(a); // Up2DownMergeSort u1 = new Up2DownMergeSort(); // u1.sort(a); // Down2UpMergeSort d1 = new Down2UpMergeSort(); // d1.sort(a); // QuickSort q1 = new QuickSort(); // q1.sort(a); // HeapSort h1 = new HeapSort(); // h1.sort(a); for (Integer s : a) { System.out.print(s + " "); } } }