排序算法总结
排序方法:
public class SortMethods { /* -------- 插入排序 -------- */ //原理:将数组分为前后两个部分,每次从后面的未排序的部分中取出最前面的一个元素,插入到前面已经排序好的部分的合适位置处。 public void insertSort(int[] A) { for (int i = 0; i < A.length; i++) //[0,i-1]表示已排序的前半部,[i,length-1]表示未排序的后半部 for (int j = i; j > 0; j--) //取出未排序部分的第一个元素,让其一步一步的向前移动,直至找到第一个比它小的停下,插入完成 if (A[j-1] > A[j]) { int tmp = A[j - 1]; A[j - 1] = A[j]; A[j] = tmp; } } /* -------- 选择排序 -------- */ //原理:将数组分为两个部分,前部已排序,后部未排序。每次从未排序的部分找最小的元素,放到前部的最后 public void selectSort(int[] A) { for(int i=0; i<A.length; i++){ //[0,i-1]是排序好的,每次从[i,length-1]取最小,与A[i]交换 int minIndex = i; for(int j=i; j<A.length; j++) //利用循环,找到[i,length-1]中的最小元素 if(A[j] < A[minIndex]) minIndex = j; int temp = A[i]; A[i] = A[minIndex]; //交换A[i]与找到的最小元素 A[minIndex] = temp; } } /* -------- 冒泡排序 -------- */ //原理:大元素作为bubble,不断的向右移动,直至合适的位置 public void bubbleSort(int[] A) { for(int i=0; i<A.length; i++) //取出一个元素A[i]作为bubble,让其向后移动 for(int j=i+1; j<A.length; j++) //用A[i]跟它后面的所有元素相比较,使其上浮,直至碰到比它更大的元素 if(A[i] > A[j]){ int temp = A[j]; A[j] = A[i]; A[i] = temp; } } /* ---- 简单排序算法的总结与分析 ---- * 数学上证明:对于N个互异数所组成的数组而言,其平均逆序数是 N(N-1)/4。 * 一般的简单排序算法,都是通过比较相邻的两个元素并交换的。这种交换每次只能消除一个逆序。 * 那么就得到一般简单排序算法的下界:Ω(N2),即其处理一般的数组,最好情况也就是N方。 * 故,为了使一个排序算法以亚二次的或O(N2)的时间运行,必须要使它的每次交换不止消除一个逆序 ---- */ /* -------- 希尔排序 -------- */ //原理:通过比较并交换相距一定间隔的元素来实现排序。使用一个增量序列:h1,h2,h3...ht, 在使用hk进行排序后, //要保证数组中相隔hk的元素都有序,即A[i]<A[i+hk]。只要h1=1(全为1的序列就退化为简单排序),就一定能实现排序, //对shell排序来说,增量序列的选取会影响算法的性能,下面算法选取序列:ht=N/2, hk=h(k+1)/2, h1=1 public void shellSort(int[] A) { for(int gap=A.length/2; gap>0; gap/=2) //增量序列 for(int i=gap; i<A.length; i++){ //对A[i], A[i-h], A[i-2h]...A[i-n*h]执行插入排序 int temp = A[i]; int j; for(j=i; j>=gap && temp<A[j-gap]; j -= gap) A[j] = A[j-gap]; A[j] = temp; } } //对每个增量序列中的元素,取A[i], A[i-gap], A[i-2gap]...A[i-n*gap]的子序列运用插入排序 //插入排序的另一种实现: /* * for(int i=1; i<A.length; i++){ * int temp = A[i]; * int j; * for(j=i; j>0 && temp<A[j-1]; j--) //大于A[i]的都向后移 * A[j] = A[j-1]; * A[j] = temp; //此时A[j-1]是小于A[i]的第一个元素,执行插入 */ /* -------- 堆排序 -------- */ //原理:对N个元素建立堆(优先队列),花费O(N)的线性时间。然后执行N次的deleteMin操作,每次花费O(logN) //故利用heap能实现O(NlogN)的排序。但是算法需要额外的附加数组(heap的底层可由数组实现),故还需N个存储空间。 //下面的堆排序算法回避了使用额外数组的问题,方法是在每次deleteMin之后,堆缩小1,然后放入min元素。但是这样每次 //都把最小元素放入到数组的最后了,故改变堆序性,使用MAX堆,每次将堆中的MAX取出,堆缩1,然后max放到堆缩小所空出的位置 //算法描述:首先建立一个Max堆,堆序性为root节点最大。然后将当前堆中的第一个元素与最后一个元素交换,即将MAX拿到数组最后, //原先堆的最后一个元素成为新的root,然后讲堆的大小缩1,并将新root下滤,保持堆序性,重复这个过程即完成排序。 public void heapSort(int[] A) { for (int i = A.length / 2; i >= 0; i--) { //buildheap,遍历节点,然后下滤 percDown(A, i, A.length); } for (int i = A.length - 1; i > 0; i--) { int tmp = A[0]; A[0] = A[i]; //交换当前堆的第一个元素与最后一个元素 A[i] = tmp; percDown(A, 0, i); //将新的root下滤,并利用i的递减来实现堆的缩小 } } private int leftChild(int i) { //取左子节点 return 2 * i + 1; } private void percDown(int[] a, int i, int n) { //将node(i)下滤方法,使用的是MAX堆,根节点为最大的元素 int child; //a[i]为要下滤的节点,n为当前堆的后边界 int temp; for (temp=a[i]; leftChild(i)<n; i=child) { //沿较大子节点的方向下滤,以保持堆序性 child = leftChild(i); if (child!=n-1 && a[child]<a[child + 1]) { child++; } if (temp < a[child]) { a[i] = a[child]; } else { break; //a[i]比它的两个子节点都大,则a[i]的堆序性正确,不用改变 } } a[i] = temp; //此时的i即为合适的插入位置 } /* -------- 归并排序 -------- */ //原理:经典的分治策略,递归地将数组分为前后两个部分各自归并排序,然后使用合并算法将两个部分合并起来,最坏情形O(NlogN) //将两个以排序的数组合并所需时间是线性的,最多进行N-1次比较 //归并排序与其他排序相比一般进行更少的比较次数,但是它使用了一个额外的附加内存,且还进行了将数据从临时数组拷贝回来这样的附加操作 //然而,在对Java泛型对象进行排序时,比较对象的大小是费时的,而拷贝移动对象则很容易(因为仅仅是引用的变化),故使用归并排序很合适 //Java类库中的泛型排序算法就是使用归并排序。 public void mergeSort(int[] A) { int[] tmpArray = new int[A.length]; mergeSort(A, tmpArray, 0, A.length-1); } private void mergeSort(int[] a, int[] temp, int left, int right){ if(left < right){ int center = (left + right) / 2; mergeSort(a, temp, left, center); //分成前后两部分递归 mergeSort(a, temp, center+1, right); merge(a, temp, left, center, center+1, right); //合并已排序的数组 } } private void merge(int[] a, int[] temp, int leftPos, int leftEnd, int rightPos, int rightEnd){ int tmpPos = leftPos; int numElements = rightEnd - leftPos + 1; while(leftPos <= leftEnd && rightPos <= rightEnd){ if(a[leftPos] <= a[rightPos]) temp[tmpPos++] = a[leftPos++]; else temp[tmpPos++] = a[rightPos++]; } while(leftPos <= leftEnd) temp[tmpPos++] = a[leftPos++]; while(rightPos <= rightEnd) temp[tmpPos++] = a[rightPos++]; //**** 多注意下面的拷贝的形式,是在递归中动态的不断的拷贝交换数据的 ****// for(int i=0; i<numElements; i++,rightEnd--) //将数组拷贝回去,注意此处的拷贝方法,切不能写成: a[rightEnd] = temp[rightEnd]; // for(i=0;i<N;i++) a[i]=temp[i]; } /* -------- 快速排序 -------- */ //原理:也是一种分治的递归算法,取一元素为枢纽元,大于枢纽元的和小于枢纽元的分成两个子集,对两个子集排序再合并起来 //其对C++或Java的基本类型排序特别有用,平均运行时间O(NlogN),最坏为O(N2) //枢纽元的选取:随机取三选中值 //对于小数组来说(N《20),快速排序不如插入排序的 public void quickSort(int[] A) { quickSort(A, 0, A.length-1); } public void quickSort(int[] a, int left, int right) { if (left < right) { int pivot = a[(left + right) / 2]; //取枢纽元 int i = left - 1; int j = right + 1; while (true) { while (a[++i] < pivot); // 向右找 while (a[--j] > pivot); // 向左找 if (i >= j) break; int temp = a[i]; a[i] = a[j]; //if(i<j) swap(a[i], a[j]); a[j] = temp; } quickSort(a, left, i - 1); // 对左边进行递回 quickSort(a, j + 1, right); // 对右边进行递回 } } }
测试main函数:
public class Main { public static void main(String[] args) { SortMethods mySort = new SortMethods(); final int MAX = 25; int[] myArray = new int[MAX]; { System.out.print("生成的随机数组是:"); for (int i = 0; i < MAX; i++) { myArray[i] = (int) (Math.random() * 100); System.out.print(myArray[i] + " "); } System.out.println(); } long start=System.nanoTime(); // mySort.insertSort(myArray); // mySort.selectSort(myArray); // mySort.bubbleSort(myArray); // mySort.shellSort(myArray); // mySort.heapSort(myArray); // mySort.mergeSort(myArray); mySort.quickSort(myArray); long end=System.nanoTime(); System.out.print("排序之后的数组是:"); for (int i = 0; i < MAX; i++) { System.out.print(myArray[i] + " "); } System.out.println(); System.out.println("排序使用时间:" + (end - start) + " ns"); } }
方法对比:
排序法 | 平均时间复杂度 | 最差情形复杂度 | 稳定度 | 额外空间 | 备注 |
冒泡 | O(n2) | O(n2) | 稳定 | O(1) | n小时较好 |
交换 | O(n2) | O(n2) | 不稳定 | O(1) | n小时较好 |
选择 | O(n2) | O(n2) | 不稳定 | O(1) | n小时较好 |
插入 | O(n2) | O(n2) | 稳定 | O(1) | 大部分已排序时较好 |
基数 | O(logRB) | O(logRB) | 稳定 | O(n) |
B是真数(0-9), R是基数(个十百) |
Shell | O(nlogn) | O(ns) 1 | 不稳定 | O(1) | s是所选分组 |
快速 | O(nlogn) | O(n2) | 不稳定 | O(nlogn) | n大时较好 |
归并 | O(nlogn) | O(nlogn) | 稳定 | O(1) | n大时较好 |
堆 | O(nlogn) | O(nlogn) | 不稳定 | O(1) |
n大时较好 |