经典的排序算法--快速排序
2017-03-27 18:26
232 查看
快排的核心思想:确定基准,然后按照基准进行分割。递归
例如:ABCDEFGHIJK;
如果此序列的基准为F,那么我们最终通过一次partition要达到的效果就是:ABCDE都小于等于F,GHIJK都大于F。ABCDE和GHIJK不需要保证相互有序。
紧接着,我们分别对ABCDE和GHIJK进行partition过程。递归对其分割子序列进行partition。
在递归进行回退时,所有的子序列已经有序,所以最终的序列形成了整体有序。
总的来说,快速排序是一种分治思想。
到这,很多人大体应该明白快排的基本思路了,但是一个疑问来了:如果找到基准,而且partition过程是怎样的?
partition过程是快速排序中的关键点,它有多种变体实现:
Hoare版本
Lomuto版本(算法导论版)
等等…
那么我们怎样才能避免这种情况的发生呢?下面我们列举几种解决方法。
随机基准(每次基准的选择都是随机产生的,不进行人为指定)
三数取中法(选取数组首尾和中间位的值,找其中的中间值作为基准,较为靠谱,不会严重退化)
有了思路,解决起来就容易多了,只需要改变partition中基准的选择即可。我们下面给出三数取中的函数,So easy…
参考:
大神对快排的讲解
http://www.cnblogs.com/chengxiao/p/6262208.html
0.快速排序的框架
快速排序(升序)的思想就是先确定一个基准,然后通过partition函数去让数组保持小于等于和大于分别置于数组两端。然后通过递归不断对数组进行划分,最终达到有序。例如:ABCDEFGHIJK;
如果此序列的基准为F,那么我们最终通过一次partition要达到的效果就是:ABCDE都小于等于F,GHIJK都大于F。ABCDE和GHIJK不需要保证相互有序。
紧接着,我们分别对ABCDE和GHIJK进行partition过程。递归对其分割子序列进行partition。
在递归进行回退时,所有的子序列已经有序,所以最终的序列形成了整体有序。
总的来说,快速排序是一种分治思想。
到这,很多人大体应该明白快排的基本思路了,但是一个疑问来了:如果找到基准,而且partition过程是怎样的?
partition过程是快速排序中的关键点,它有多种变体实现:
Hoare版本
Lomuto版本(算法导论版)
等等…
public class QuickSort { public static void main(String[] args) { QuickSort qs = new QuickSort(); int[] s = new int[] { 3, 1, 5, 7, 4, 3, 2, 1, 6 }; qs.quickSort(s, 0, s.length-1); System.out.println(Arrays.toString(s)); } public void sort(int[] s, int low, int high) { if (low < high) { int mid = partition2(s, low, high); quickSort(s, low, mid - 1); quickSort(s, mid + 1, high); } } public void swap(int[] s, int a, int b) { int tmp = s[a]; s[a] = s; s[b] = tmp; } public int partition(int[] s, int low, int high) { //具体实现版本见下方 } }
[b]1.Hoare版本快速排序
Hoare版本的partition过程中可能最困惑你的是:为什么s[i] = s[j];和s[j] = s[i];可以直接赋值,那岂不是把值给覆盖了?NO,完全不会的。仔细分析你会发现,被覆盖的数其实都是有备份的,这个备份的初始动力是来源于对基准s[i]的备份,所以在每次覆盖前,我们都能找到被覆盖数的备份。此处要正确理解备份的含义。public int partition(int[] s, int low, int high) { int i = low, j = high;//定义两个指针,j指针指向从尾到首遍历中大于基准t的值,i指针指向从首到尾遍历中小于等于基准t的值 int t = s[i];//基准定义为t,实际选取了low位置上的值为基准 while (j > i) { //从尾部到首部遍历,发现小于等于基准t的值则进行对s[i]的覆盖 while (s[j] > t && j > i) j--; s[i] = s[j]; //从首部到尾部遍历,发现大于基准t的值则进行对s[j]的覆盖 while (s[i] <= t && j > i) i++; s[j] = s[i]; } s[j] = t;//此时i等于j,将基准覆盖该值 return j; }
2.Lomuto版本快速排序(算法导论版)
Lomuto版本的partition过程则没有了覆盖,取而代之的是swap互相替换。理解Lomuto版本的partition过程最重要就是要搞懂i和j的作用。public int partition(int[] s, int low, int high) { int i = low;//指向小于等于基准t的‘当前j范围’内的最高位,比较拗口,细细体会 int t = s[low]; for (int j = i + 1; j <= high; j++) {//j作用就是遍历指针 if (s[j] <= t) {//遇到小于等于基准t的数,与i+1位的值进行交换,实质是与大于基准t的最低位的数进行交换 i++; swap(s, i, j); } } swap(s, low, i);//交换基准与小于等于基准的最高位 return i; }
3.基准问题
到这,你会发现一个问题,也是我们一开始提到的问题之一,基准的选择,我们可以看到在上述的两种partition过程中,基准都是选取的数组最低位值。然而,数组可能会出现一种可怕的情况,对于升序,我们可能每次都选取到数组中的最大值,结果就是我们的快速排序退化了,退化为插入排序了。那么我们怎样才能避免这种情况的发生呢?下面我们列举几种解决方法。
随机基准(每次基准的选择都是随机产生的,不进行人为指定)
三数取中法(选取数组首尾和中间位的值,找其中的中间值作为基准,较为靠谱,不会严重退化)
有了思路,解决起来就容易多了,只需要改变partition中基准的选择即可。我们下面给出三数取中的函数,So easy…
//三数取中基准选择函数 public static void getMid(int[] s, int low, int high) { int mid = (low+ high) / 2; //三数排序 if (s[low] > s[mid]) { swap(s, low, mid); } if (s[low] > s[high]) { swap(s, low, high); } if (s[high] < s[mid]) { swap(s, high, mid); } //基准和首位值进行交换 swap(s, low, mid); }
参考:
大神对快排的讲解
http://www.cnblogs.com/chengxiao/p/6262208.html
相关文章推荐
- 经典排序算法 - 快速排序Quick sort
- C#实现所有经典排序算法(选择排序,冒泡排序,快速排序,插入排序,希尔排序)
- 经典排序算法(一)--冒泡排序、快速排序java实现
- 经典排序算法 -----冒泡排序,插入排序,快速排序,归并排序,堆排序
- 经典排序算法——快速排序
- 排序算法汇总(选择排序 ,直接插入排序,冒泡排序,希尔排序,快速排序...)
- Java 常用排序算法实现--快速排序、插入排序、选择、冒泡
- 排序算法之--从冒泡排序到快速排序
- 排序算法‘冒泡排序,选择排序,快速排序’
- 排序算法复习(Java实现)(一): 插入,冒泡,选择,Shell,快速排序
- 白话经典算法系列之六 快速排序 快速搞定
- 经典排序思想,并用C语言指针实现排序算法
- C语言实现基本排序算法----排序(直接插入排序,SHELL排序,冒泡排序,快速排序,简单选择排序,堆排序)
- 白话经典算法系列之六 快速排序 快速搞定
- 排序算法之三——快速排序
- 排序算法-------快速排序
- 排序算法复习(Java实现):插入,冒泡,选择,Shell,快速排序, 归并排序,堆排序,桶式排序,基数排序
- 排序算法复习(Java实现)(二): 插入,冒泡,选择,Shell,快速排序
- 常用排序工具类:标准【正序、倒序】排序算法‘冒泡排序,选择排序,快速排序’
- 排序算法复习(Java实现)(一): 插入,冒泡,选择,Shell,快速排序