排序方法小结

来源:互联网 发布:自学ui 软件 编辑:程序博客网 时间:2024/05/05 20:31

                                                        排序方法小结【java】

1:插入类排序

public static void insertionSort(int a[]){for(int j=1;j<a.length;j++) {int key =a[j];int i=j-1;while( i>=0&&a[i]>key){a[i+1]=a[i];i--;}a[i+1]=key;}}


        插入类排序是先假定前面的序列已经有序,然后把后面的元素依次插入到前面序列的合适位置。因此在排序过程中我们默认下标为0的元素是有序的,从下标为1的元素开始比较。从比较次数来看,最好的情况下,即序列已经有序时,我们只需要比较n次即可,在最差的情况下,即序列恰好是反序的,比较次数达到n^2量级;从移动次数来看,最好情况下不需要移动,最差的情况下从下标为1的元素开始每个元素都需要移动,需要移动的次数也达到了n^2级别,所以综合来看,插入排序的时间复杂度应该是O(n^2)的。从空间复杂度上看,插入排序只需要额外占用一个空间存储key。从稳定性上看,只有前面元素比它小,才可能插在前面元素的位置上,因此经典的插入排序是稳定的。但是要记住,稳定与不稳定的区别有时候可能就在一个>或者≥符号的区别上。

2:折半插入排序

public static void binInsertSort(int a[]){for(int i=1;i<a.length;i++){int temp=a[i];int high=i-1;int low=0;int mid=i/2;while(low<=high){mid=(low+high)/2;if(temp<a[mid])high=mid-1;elselow=mid+1;}//最终跳出while时一定满足low比high大1即,low-1=highfor(int j=i;j>low;j--) {a[j]=a[j-1];}a[low]=temp;}}


        折半插入排序是在插入排序的基础上改进而来的。插入排序在寻找元素在有序序列的插入位置时,是逐个比较的,现在采用折半查找来寻找插入位置,就把比较的次数减少了,但是移动次数没有变化,这样看来时间复杂度仍然是O(n^2)量级,空间复杂度不变,折半插入排序是稳定的。

3:希尔排序

public static void shellSort(int a[]){int shell[]={5,3,1};for(int i=0;i<shell.length;i++){shellInsert(a,shell[i]);}}public static void shellInsert(int a[],int shell){for(int j=1;j<a.length;j+=shell) {int key =a[j];int i=j-1;while( i>=0&&a[i]>key){a[i+1]=a[i];i--;}a[i+1]=key;}}


          希尔排序也是在插入排序的基础上来的。我们知道,在序列基本有序时,插入排序只需要比较n次,移动0次即可,相对来讲效率很高。因此希尔排序的目的就是按照不同步长运行几次插入排序,让序列大体有序,最后再运行一次步长为1的希尔排序(即插入排序)就可以保证序列有序了。希尔排序的关键在于选择步长序列,据说最好的情况下时间复杂度可能为O(n^3/2),希尔排序是稳定的。上面三种排序都是基于插入排序的,俗称“插入类排序”。

4:冒泡排序

public static void bubbleSort(int a[]){for(int i=0;i<a.length;i++){for(int j=a.length-1;j>i;j--){if(a[j-1]>a[j]){int temp=a[j];a[j]=a[j-1];a[j-1]=temp;}}}}


        冒泡排序是一类经典的交换排序。从直观上讲,冒一遍泡就找到了最小值,当然反过来可以找到最大值,这是相比之下插入类排序做不到的。冒泡排序的时间复杂度也是O(n^2)量级的,思路简单,就是比较慢,冒泡排序也是稳定的。

5:快速排序

public static void quickSort(int a[],int p,int r){int q=partition(a,p,r);if(q-1>p)quickSort(a,p,q-1);if(q+1<r)quickSort(a,q+1,r);}public static int partition(int a[],int p,int r){int x=a[r];int i=p-1;for(int j=p;j<r;j++){if(a[j]<=x){i=i+1;exchange(a,i,j);}}exchange(a,i+1,r);return i+1;}public static void exchange(int a[],int i,int j){int temp=a[i];a[i]=a[j];a[j]=temp;}


      快排是一类非常经典的排序方法,采用了分治的思想,先对整个序列做一次划分,然后把问题分解,递归对规模更小的序列做划分,当序列规模为2的时候,划分就代表有序了,此时问题解决。由于问题是在原址上解决的,所以就不用合并操作了。这些在《算法导论》上讲得很清楚明白。快排中最重要的是划分操作,划分的方法很巧妙,相当于把一个序列以某一个中间值分为了两部分,我们递归的想,如果这个序列规模只有2,那么划分不就代表着该序列有序了吗?也就是说,对于一个大序列,只要把问题拆解到规模为2的序列,那么就可以实现排序了。值得注意的是,归并排序也用了分治的思想,但是归并排序是先让小序列有序,然后向上返回才让大序列有序,而快排是先让大序列基本(似乎只能这么说)有序,最后让小序列有序,所以归并排序需要不断向上合并,而快排不需要。另外,快排是不稳定的,平均时间复杂度为O(nlgn)。上述两种排序都是交换类排序。

6:选择排序

public static void selectionSort(int a[]){for(int i=0;i<a.length-1;i++){int min=a[i],location=i;for(int j=i+1;j<a.length;j++) {if(a[j]<min) {min=a[j];location=j;}}a[location]=a[i];a[i]=min;}}


     选择排序是简单排序中唯一不稳定的。选择排序在第一次循环中也能找到最小值,时间复杂度为O(n^2)。

7:堆排序

//维护堆的方法public  void maxHeapFy(HeapSort heap, int i) {int l = 2 * i+1, r = l + 1;int largest;if (l <heap.heapSize && heap.a[l] > heap.a[i])largest = l;elselargest = i;if (r < heap.heapSize && heap.a[r] > heap.a[largest])largest = r;if (largest != i) {heap.exchange(heap.a, i, largest);// 选出左右子树的最大点与根结点替换后,被替换的子树可能又违反最大堆的性质了,故对该子树再递归// 调用维护方法heap.maxHeapFy(heap, largest);}}//建堆public  void buildMaxHeap(HeapSort heap){heap.heapSize=heap.a.length;for(int i=(int) (Math.ceil(heap.heapSize/2)-1);i>=0;i--){heap.maxHeapFy(heap, i);}}//排序public void heapSort(HeapSort heap){//先建堆,找出最大的结点buildMaxHeap(heap);for(int i=0;i<heap.a.length-1;i++){heap.heapSize--;heap.exchange(heap.a,0,heap.heapSize);maxHeapFy(heap,0);}}public  void exchange(int a[], int i, int j) {int temp = a[i];a[i] = a[j];a[j] = temp;}


      堆排序是不稳定的,时间复杂度为O(nlgn)。堆排序的思想非常牛,以最大堆为例,先构建一个最大堆,堆顶元素就是序列中最大的元素,把该元素提取出来和序列的最后一个元素相交换,我们就找到了序列中最大的元素并把它放到了应该放的位置上。接下来堆顶元素就不满足最大堆的性质了,递归调整,使得堆顶的元素为序列中第二大的元素,再把该元素与下标为n-2的元素交换,直到找到最小的元素。核心在于维护堆性质的方法,即maxHeapFy(),通过该方法可以保证从某个结点开始向下调整,维护最大堆的性质。
      

8:归并排序

public static void merge(int a[], int p, int q, int r) {int a0[] = new int[q - p + 1];int a1[] = new int[r - q];for (int i = 0; i < q - p + 1; i++) {a0[i] = a[p + i];}for (int i = 0; i < r - q; i++) {a1[i] = a[q + 1 + i];}int i = 0, j = 0, k = p;while (i < a0.length && j < a1.length) {if (a0[i] <= a1[j]) {a[k] = a0[i];i++;} else {a[k] = a1[j];j++;}k++;}while (i < a0.length) {a[k] = a0[i];k++;i++;}while (j < a1.length) {a[k] = a1[j];k++;j++;}}public static void mergeSort(int a[],int p,int r){if(p<r){int q=(int) Math.floor((p+r)/2);mergeSort(a,p,q);mergeSort(a,q+1,r);merge(a,p,q,r);}}


        归并排序是典型的运用了分治的思想,先把序列切割成一个个长度为1的子序列,然后以子序列为基础进行合并,层层合并就得到了有序的序列。归并排序是稳定的,时间复杂度为O(nlogn)。
        归并排序,堆排序,快速排序这三种排序的平均时间复杂度都比较低,但是对应思想非常巧妙,我是看着《算法导论》的伪代码来写的,我的理解还十分浅薄,这三种算法的优化也还没看,仔细重写一遍排序算法是补上大二上学期学《数据结构与算法》这门课时的漏洞,弥补一个遗憾吧。





0 0