继三种简单的排序方式之后,我们继续介绍高级的排序方式,所谓的高级排序方式,就是时间复杂度比简单排序方式要小的多的方式,即高级排序方式的时间复杂度一般不超过O(N*logN),因为在进行数据量很大的排序操作的时候,简单的排序方式的时间复杂度往往难以满足要求,所以高级排序方式就显得尤为重要,接下来我们介绍经典的快速排序,在之后的很多数据结构的排序当中,快排可谓为基础中的基础。
常见的快速排序是一种基于递归的排序方式,它的核心思路是:
1.首先从要排序的数组当中,选取一个数字作为 “中间点”。
2.然后定义两个“指针”(java当中没有指针的概念,在此处所谓的指针指的是指向数组下表的两个标示),一个从左至右,另一个从右至左对数组进行扫描。
3.从左至右的指针 left 扫描数组当中的元素,如找到比“中间点”大的数字,则该指针锁定;
4.从右至左的指针 right 扫描数组当中的元素,如找到比“中间点”小的数字,则该指针锁定;
5.交换两个指针所指向的数组位置的元素的值
6.重复过程2-5,直到left>=right,跳出
当跳出的时候,left所指向的位置即为“中间点”所应该在的数组的位置,将中心点放入。之后就是外层的递归整个数组的过程了,即将中心点左边的数组和中心点右边的数组进行同样上述的1-6的操作,核心思路如图所示:
理解了快排的核心之后,我们开始实现快速排序,代码如下:
class QuickSort { private int[]input; private int left; private int right; public QuickSort(int[]input) { this.input=input; left=0; right=input.length-1; } //封装快排入口 public void Sort() { reQuickSort(left, right); } //外层递归 private void reQuickSort(int left,int right) { //注意递归出口 if(left>=right)return; int partition=partition(left, right); reQuickSort(left,partition-1); reQuickSort(partition+1,right); } private void swap(int a,int b) { int temp=input[a]; input[a]=input[b]; input[b]=temp; } //快排核心方法,注意两个while的边界理解 private int partition(int start,int end) { int pivot=input[end]; int left=start; int right=end-1; while(true) { while(input[left]<pivot)left++; while(right>0&&input[right]>=pivot)right--; if(left<right) { swap(left, right); } else break; } swap(left,end); return left; } }
快排之后,我们还要介绍一个很重要的排序方式,就是归并排序,这种排序方式也是基于递归的排序方式,但其与快排的思路不同的是,归并排序的核心思路是,将两个已经排序好的数组进行有序的合并,这样合并之后的数组也一定是有序的,然后再利用递归分割的思路,将数组分割成最小单位,即只有一个数字的情况下,对两个数字进行有序归并,之后层层递归出来,最后得到的结果就一定是有序的,也许看文字讲解有些绕,那么我们可以看下图:
思路就一目了然,之后我们来考虑java怎么去实现归并排序,代码如下:
class MergeSort { private int[] input; private int length; int first; int last; public MergeSort(int[] input) { this.input=input; length=input.length; first=0; last=length-1; } //入口封装 public void Sort() { ReSplitTheArray(first,last); } //递归 private void ReSplitTheArray(int first,int last) { if(first<last) { int middle=(first+last)/2; ReSplitTheArray(first,middle); ReSplitTheArray(middle+1,last); mergearray(first, middle, last); } } //核心方法,合并数组 private void mergearray(int first, int middle, int last) { int[] temp=new int[last-first+1]; int i=first; int j=middle+1; int k=0; while(i<=middle&&j<=last) { if(input[i]>input[j])//左边的数组比右边的大 { temp[k++]=input[j++]; } else { temp[k++]=input[i++]; } } // 把左边边剩余的数移入数组 while (i <= middle) { temp[k++] = input[i++]; } // 把右边边剩余的数移入数组 while (j <= last) { temp[k++] = input[j++]; } // 把新数组中的数覆盖input数组 for (int k2 = 0; k2 < temp.length; k2++) { input[k2 + first] = temp[k2]; } } }
最后介绍希尔排序,希尔排序实际上是插入排序的一种改进方案,插入排序当需要把小数据移动到左边的正确位置上时,所有的中间数据项都必须向右移动一位,接近于N次数量级的移动,总共是N方/2次复制所以效率是O(N2),改进:在希尔排序当中,当步进值很大的时候,数据项每一趟排序需要移动的元素个数很少,但数据项移动的距离很长。这非常的有效率。当步进值减小的时候,每一趟要排序移动的元素增多,但是此时数据项已经接近于它们排序的最终位置,这对于插入排序可以更有效率。其基本的实现思路如下:
1.根据所传入的数组,设定一个步进值,假定步进值iincrementNum设定为数组长度的一半。
2.在原数组当中,去选曲这个步进值所在的位置的数字,如:数组{82 ,31 ,29 ,71, 72, 42, 64, 5,110} 第一次取增量设置为array.length/2 = 4 先从82开始以4为增量遍历直到末尾,得到(82,42) 排序得到{42 ,31 ,29 ,71, 72, 82, 64, 5,110}。 然后从第二个数31开始重复上一个步骤,得到(31,64) 排序得到{42 ,31 ,29 ,71, 72, 82, 64, 5,110}....... 以4为增量的遍历完数组之后,得到的结果是{42 ,31,5,71,72,82,64,29,110}。
3.步进值需要递减,即incrementNum=incrementNum/2.
4.重复过程 2-3 ,注意这个步进值的变化,最后一定要为1,要么不能保证最后得出的结果是有序的。当步进值是1的时候,相当于是最普通的冒泡排序
接下来考虑如何实现希尔排序
class ShellSort { private int[] input; private int length; public ShellSort(int[] input) { this.input=input; length=input.length; } private void swap(int a,int b) { int temp=input[a]; input[a]=input[b]; input[b]=temp; } public void Sort() { int incrementNum=length/2; while(incrementNum>=1) { for(int i=0;i<length;i++) { for(int j=i;j<length-incrementNum;j=j+incrementNum) { if(input[j]>input[j+incrementNum])swap(j, j+incrementNum); } } incrementNum = incrementNum/2; System.out.println(incrementNum); } } }
至此,三种高级的排序方式也讲解完毕。