参考资料

《算法(第4版)》          — — Robert Sedgewick, Kevin Wayne
《啊哈! 算法》              — — 啊哈磊
《数据结构(教材)》     — — 严蔚敏,吴伟民
 
 

快速排序算法的编码描述

 

快排的基本思路

 
 
快速排序的基本思路是:
  1. 先通过第一趟排序,将数组原地划分为两部分,其中一部分的所有数据都小于另一部分的所有数据。原数组被划分为2
  2. 通过递归的处理, 再对原数组分割的两部分分别划分为两部分,同样是使得其中一部分的所有数据都小于另一部分的所有数据。 这个时候原数组被划分为了4
  3. 就1,2被划分后的最小单元子数组来看,它们仍然是无序的,但是! 它们所组成的原数组却逐渐向有序的方向前进。
  4. 到最后, 数组被划分为多个由一个元素或多个相同元素组成的单元, 这时候整个数组就有序了
 
总结: 通过第一趟排序,将原数组A分为B和C两部分, 整体上B<C, 第二躺排序时候将B划分为B1,B2两部分, 使得B1<B2, 同理C1<C2。那么通过两趟排序, 从B1/B2/C1/C2的长度的单元看待整个数组, 从左至右 B1<B2<C1<C2, 数组是“有序”的, 并且随着排序的深入,原数组有序性越来越强
 
整体的排序过程如下图所示(暂且不管实现的具体细节)
如上图所示, 数组
3 1 4 1 5 9 2 6 5 3

通过第一趟排序被分成了2 1 1 和4 5 9 3 6 5 3两个子数组,且对任意元素,左子数组总小于右子数组
通过不断递归处理,最终得到
1 1 2 3 3 4 5 5 6

这个有序的数组
 

快排的实现步骤

 
快排具体的实现步骤如下图所示:

图中的步骤3,4不难理解,这里就不多赘述,因为步骤3中的递归思想是大家比较熟悉的, 步骤4中的“组合”其实就只是个概念上的词,因为所有的子数组本来就连接在一起,只要所有的递归结束了,整个数组就是有序的。
 

下面我就只讲解1和2步骤, 而在1,2中,关键在于如何实现“划分”

切分的关键点: 基准元素, 左游标和右游标

 

划分的过程有三个关键点:“基准元素”, “左游标” 和“右游标”。

 
  • 基准元素:它是将数组划分为两个子数组的过程中, 用于界定大小的值, 以它为判断标准, 将小于它的数组元素“划分”到一个“小数值数组”里, 而将大于它的数组元素“划分”到一个“大数值数组”里面。这样,我们就将数组分割为两个子数组, 而其中一个子数组里的元素恒小于另一个子数组里的元素
  • 左游标: 它一开始指向待分割数组最左侧的数组元素。在排序过程中,它将向右移动
  • 右游标: 它一开始指向待分割数组最右侧的数组元素。在排序过程中,它将向左移动
 
【注意】
1.上面描述的基准元素/右游标/左游标都是针对单趟排序过程的, 也就是说,在整体排序过程的多趟排序中,各趟排序取得的基准元素/右游标/左游标一般都是不同的
2. 在不同的教材里,基准元素也叫“枢轴”,“关键字”, “划分”也叫“切分”
 
那这基准元素-右游标-左游标三个关键点是如何融会贯通,搞定一趟切分(划分)的呢?

一趟切分的具体过程

 
切分的具体过程如图所示。在下图中,基准元素是v,   左游标是i, 右游标是j
i一开始指向数组头部元素的位置lo, 切分时向右移动, j一开始指向数组末端元素hi,随后向左移动, 当左右游标相遇的时候,一趟切分就完成了。
 
当然, 看到这里你可能很懵懂,你可能会问:
  • “基准元素v是怎么选的?”
  • 游标i,j的移动的过程中发生了什么事情(比如元素交换)?,
  • 为什么左右游标相遇时一趟切分就完成了?
让我们继续往下看:
 
基准元素的选取
 
首先,在原则上,基准元素的选取是任意的
但我们一般选取数组的第一个元素为基准元素(假设数组是随机分布的
 
下面以啊哈磊老师的图示为例:
 
假设下面的是我们的待排序的数组的话, 根据我们的头元素作为基准元素的原则,士兵i下面的数组元素 “6” 就是我们选定的第一趟排序的基准元素
 
 
(作为入门,啊哈磊老师的《啊哈,算法》里的图示还是很有趣的! 这里向大家安利一下)
 
【注意】下面在优化中会讲关于基准元素的选取的诀窍, 但在快排的基础编码里,我们只要记住把头部元素当作基准元素就够了(假设数组元素是随机分布的)
 
左右游标扫描和元素交换
 
在选取了基准元素之后, 切分就正式开始了。这时候,左右游标开始分别向右/左移动,它们遵循的规则分别是:
 
  • 左游标扫描, 跨过所有小于基准元素的数组元素, 直到遇到一个大于或等于基准元素的数组元素, 在那个位置停下
  • 右游标扫描, 跨过所有大于基准元素的数组元素, 直到遇到一个大于或等于基准元素的数组元素,在那个位置停下
 
当左右游标扫描分两种情况(或者说是两个先后阶段...)
  1. 左右游标没有相遇
  2. 左右游标相遇了
 
在下图中, 左游标就是士兵i, 而右游标是士兵j啦。
 
 
1.首先,右游标j会向左跨过所有大于基准元素的元素, 所以士兵j向左跨过了板砖8和10, 然后当他遇到了“小于等于”基准元素6的元素5时候, “哎呀, 不能再前进了,在这里打住吧!”, 于是右游标就在5处停了下来,
2.然后, 士兵i(左游标)跨过了小于基准元素6的1和2,然后遇到了“大于等于”6的7,在7处停了下来。
3.  停下来之后, 左右游标所指的数组元素交换了它们的值(两个士兵交换了他们脚下的板砖)
 
下图同上:
游标扫描和元素交换的意义
 
很明显, 两个游标士兵的“工作” 就是不断靠近,并检查有没有小于(大于)规定要求(即基准元素6)的板砖(元素),一旦发现, 就“丢”到对面去, 而当他们相遇的时候, 大小关系严格的两块子数组也就分割出来了
 
【注意】
1.要注意一点: 我们选取的基准元素和左游标最初指定的元素是相同的! 那么就我们就会发现一个问题: 当左游标向右扫描的时候,第一个遇到的“大于或等于”的元素就是它本身, 那么问题来了: 需不需要停下来呢? 当然根据逻辑思考可以得出这是不必要的,所以下面我会结合算法指出这一细节: 左游标向右扫描的时候其实忽略了它最初所指的位置——头元素的比较
2. 必须等一个“士兵”(游标)先走完, 另一个“士兵”(游标)才能走,不能每人轮流走一步...
 
左右游标相遇
 
承接上文, 这次眼看士兵i和士兵j就要相遇了! 首先士兵j先走,当它遇到3的位置的时候,因为3“小于等于”6,所以士兵j就停下来了。再然后士兵i向右走,但因为他和士兵j“碰头”了,所以士兵i只能无奈地“提前”在3停住了(如果没和j碰面士兵i是能走到9的!)
 
所以这就是左右游标扫描相遇时候遵循的原则: 只相遇, 不交叉

【注意】这里你可能会问: 在我们制定的规则里, 左游标先扫描和右游标先扫描有区别吗?  (如果你这样想的话就和我想到一块去了...嘿嘿),因为就上图而言,两种情况下一趟排序中两个游标相遇的位置是不同的(一般而言,除非相遇位置的下方的元素刚好和基准元素相同):

  • 如果右游标先扫描,左右游标相遇的位置应该是3上方(图示)
  • 但如果左游标先扫描, 左右游标相遇的位置却是9上方
通过编码验证和翻阅书籍,我得出的结论是:对排序的划分过程有影响,但对最终结果是没有具体的影响的。特别的,在《数据结构》这本书中采取的是右游标先扫描,而在《算法(第四版)》书中,则采取左游标先扫描的策略
 
基准元素归位
 
当到达了我上面所说的“左右游标相遇”这个阶段后, 我们发现, 左右两个子数组已经基本有序了,即分成了 1 2 5 4 3和9 7 10 8 这两段元素,其中前一段元素都小于后一段元素
 
等等! 好像有两个数字违和感很强地打破了这个大小关系, 那就是6! (基准元素)
如下所示:
6 1 2 5 4 3 9 7 10 8

这时候我们发现整个数组的组成是这样的: 大小居中的基准元素 + 小数值数组 + 大数值数组
所以我们只要把基准元素6和游标相遇元素3换一下, 不就可以变成: 小数值数组 + 大小居中的基准元素 +   大数值数组 了吗?
1 2 5 4 3 6 9 7 10 8

 
如图所示
 
 
至此, 一趟排序结束, 回到中间的6已经处于有序状态,只要再对左右两边的元素进行递归处理就可以了

总结一趟排序的过程

 
OK,这里让我们总结下一趟快速排序的四个过程:
 
 
一趟排序全过程图示
 
(A - Z 字母排序, A最小, Z最大)
 

快速排序代码展示

 

具体的代码

这是我们的辅助函数exchange: 用于交换任意两个数组元素的位置:
// 交换两个数组元素
private static void exchange(int [] a , int i, int j) {int temp = a[i];a[i] = a[j];a[j] = temp;
}

 
这是切分函数partition, 它完成了一轮排序的主要工作,使得待分割数组以基准元素为界,分成了一个小数值数组和一个大数值数组
private static int partition (int[] a, int low, int high) {int i = low, j = high+1; // i, j为左右扫描指针 PS: 思考下为什么j比i 多加一个1呢?int pivotkey = a[low];  // pivotkey 为选取的基准元素(头元素)while(true) { while (a[--j]>pivotkey) {   if(j == low) break; }  // 右游标左移while(a[++i]<pivotkey) {   if(i == high) break;  }  // 左游标右移if(i>=j) break;    // 左右游标相遇时候停止, 所以跳出外部while循环else exchange(a,i, j) ;  // 左右游标未相遇时停止, 交换各自所指元素,循环继续
  }exchange(a, low, j); // 基准元素和游标相遇时所指元素交换,为最后一次交换return j;  // 一趟排序完成, 返回基准元素位置
}

 
这是主体函数sort, 将partition递归处理
private static void sort (int [] a,  int low, int high) {if(high<= low) { return; } // 终止递归int j = partition(a, low, high);  // 调用partition进行切分sort(a,  low,  j-1);   // 对上一轮排序(切分)时,基准元素左边的子数组进行递归sort(a,  j+1,  high); // 对上一轮排序(切分)时,基准元素右边的子数组进行递归
}

 

对切分函数partition的解读

 
1. 直观上看, partition由两部分组成: 外部while循环和两个并列的内部while循环。
 
2. 内部While循环的作用是使得左右游标相互靠近
例如对:
while (a[--j]>pivotkey) {  ...   }

先将右游标左移一位,然后判断指向的数组元素和基准元素pivotkey比较大小, 如果该元素大于基准元素, 那么循环继续,j再次减1,右游标再次左移一位...... (循环体可以看作是空的)

 
3.外部While循环的作用是不断通过exchange使得“逆序”元素的互相交换, 不断向左子数组小于右子数组的趋势靠近, 
if(i>=j) break; 

从i < j到 i == j 代表了“游标未相遇”到“游标相遇”的过度过程,此时跳出外部循环, 切分已接近完成,紧接着通过 exchange(a, low, j) 交换基准元素和相遇游标所指元素的位置, low是基准元素的位置(头部元素), j是当前两个游标相遇的位置

 
4. 第一个内部while循环体里面的的  if(j == low) break;判断其实是多余的,可以去除。
因为在
while (a[--j]>pivotkey) {   if(j == low) break; }  // 右游标左移

中,当随着右游标左移,到j = low + 1的时候,有 a[--j] == pivotkey为true(两者都是基准元素),自动跳出了while循环,所以就不需要在循环体里再判断 j == low 了

 
5. 注意一个细节: j 比 i 多加了一个1,为什么? 如下
int i = low, j = high+1

结合下面两个While循环中的判断条件:

while (a[--j]>pivotkey) {  ...   }
while (a[++i]<pivotkey) {  ...   } 

可知道, 左游标 i 第一次自增的时候, 跳过了对基准元素 a[low] 所执行的 a[low] < pivotkey判断, 这是因为在我们当前的算法方案里,基准元素和左游标初始所指的元素是同一个,所以没有执行a[low]>pivotke这个判断的必要。所以跳过( 一开始a[low] == pivotkey,如果执行判断那么一开始就会跳出内While循环,这显然不是我们希望看到的)

 
而相比之下,右游标却必须要对它初始位置所指的元素执行a[++i]<pivotkey , 所以 j 比 i 多加了一个
 
 
 

对主体函数sort的解读

 
1. high<= low是判断递归结束的条件
2. int j = partition(a, low, high);  有两种作用: 一是进行一轮切分二是取得上一轮的基准元素的最终位置j, 传递给另外两个sort函数,通过另外两个sort函数的调用
sort(a,  low,  j-1);
sort(a,  j+1,  high);

进行下一轮递归,设置j -1 和j + 1 是因为上一轮基准元素的位置已经是有序的了,不要再纳入下一轮递归里

 
快速排序QuickSort类的全部代码:
public class QuickSort {// 交换两个数组元素private static void exchange(int [] a , int i, int j) {int temp = a[i];a[i] = a[j];a[j] = temp;}private static int partition (int[] a, int low, int high) {int i = low, j = high+1;      // i, j为左右扫描指针 PS: 思考下为什么j比i 多加一个1呢?int pivotkey = a[low];  // pivotkey 为选取的基准元素(头元素)while(true) { while (a[--j]>pivotkey) {   if(j == low) break; }  // 右游标左移while(a[++i]<pivotkey) {   if(i == high) break;  }  // 左游标右移if(i>=j) break;    // 左右游标相遇时候停止, 所以跳出外部while循环else exchange(a,i, j) ;  // 左右游标未相遇时停止, 交换各自所指元素,循环继续
    }exchange(a, low, j); // 基准元素和游标相遇时所指元素交换,为最后一次交换return j;  // 一趟排序完成, 返回基准元素位置
  }private static void sort (int [] a,  int low, int high) {if(high<= low) { return; } // 当high == low, 此时已是单元素子数组,自然有序, 故终止递归int j = partition(a, low, high);  // 调用partition进行切分sort(a,  low,  j-1);   // 对上一轮排序(切分)时,基准元素左边的子数组进行递归sort(a,  j+1,  high); // 对上一轮排序(切分)时,基准元素右边的子数组进行递归
  }public static void sort (int [] a){ //sort函数重载, 只向外暴露一个数组参数sort(a, 0, a.length - 1);}
}

 
测试代码
public class Test {public static void main (String [] args) {int [] array = {4,1,5,9,2,6,5,6,1,8,0,7 };QuickSort.sort(array);for (int i = 0; i < array.length; i++) {System.out.print(array[i]);}}
}

结果:
01124556789

 

优化点一 —— 切换到插入排序

 
对于小数组而言, 快速排序比插入排序要慢, 所以在排序小数组时应该切换到插入排序。
只要把sort函数中的
if(high<= low) { return; }

改成:
if(high<= low + M) {  Insertion.sort(a,low, high) return; } // Insertion表示一个插入排序类

就可以了,这样的话,这条语句就具有了两个功能:
1. 在适当时候终止递归
2. 当数组长度小于M的时候(high-low <= M), 不进行快排,而进行插排
 
转换参数M的最佳值和系统是相关的,一般来说, 5到15间的任意值在多数情况下都能令人满意
 
例如, 将sort函数改成:
  private static void sort (int [] a,  int low, int high) {if(high<= low + 10) {  Insertion.sort(a,low, high) return; } // Insertion表示一个插入排序类int j = partition(a, low, high);  // 调用partition进行切分sort(a,  low,  j-1);   // 对上一轮排序(切分)时,基准元素左边的子数组进行递归sort(a,  j+1,  high); // 对上一轮排序(切分)时,基准元素右边的子数组进行递归}

 

优化点二 —— 基准元素选取的随机化

 
上面说过,基准元素的选取是任意的,但是不同的选取方式对排序性能的影响很大。
 
在上面所有的快速排序的例子中,我们都是固定选取基准元素,这种操作做了一个假设性的前提:数组元素的分布是随机的。而如果数组不是随机的,而是有一定顺序的,甚至在最坏的情况下:完全正序或完全逆序, 这个时候麻烦就来了: 快排所消耗的时间大大延长,完全达不到快排应有的效果。
 
所以为了保证快排算法的随机化,我们必须进行一些优化。
 
下面介绍的方法有三种:
  1. 排序前打乱数组的顺序
  2. 通过随机数保证取得的基准元素的随机性
  3. 三数取中法取得基准元素(推荐)
 
1. 排序前打乱数组的顺序
public static void sort (int [] a){StdRandom.shuffle(a)  // 外部导入的乱序算法,打乱数组的分布sort(a, 0, a.length - 1);
}

当然来了,因为乱序函数的运行,这会增加一部分耗时,但这可能是值得的
2.通过随机数保证取得的基准元素的随机性
  private static int getRandom (int []a, int low, int high) {int RdIndex = (int) (low + Math.random()* (high - low)); // 随机取出其中一个数组元素的下标exchange(a, RdIndex, low);  // 将其和最左边的元素互换return a[low];
  }private static int partition (int[] a, int low, int high) {int i = low, j = high+1;      // i, j为左右扫描指针 PS: 思考下为什么j比i 多加一个1呢?int pivotkey = getRandom (a, low, high); // 基准元素随机化while(true) { while (a[--j]>pivotkey) {   if(j == low) break; }  // 右游标左移while(a[++i]<pivotkey) {   if(i == high) break;  }  // 左游标右移if(i>=j) break;    // 左右游标相遇时候停止, 所以跳出外部while循环else exchange(a,i, j) ;  // 左右游标未相遇时停止, 交换各自所指元素,循环继续
    }exchange(a, low, j); // 基准元素和游标相遇时所指元素交换,为最后一次交换return j;  // 一趟排序完成, 返回基准元素位置}

 
3.  三数取中法(推荐)
一般认为, 当取得的基准元素是数组元素的中位数的时候,排序效果是最好的,但是要筛选出待排序数组的中位数的成本太高, 所以只能从待排序数组中选取一部分元素出来再取中位数, 经大量实验显示: 当筛选数组的长度为3时候,排序效果是比较好的, 所以由此发展出了三数取中法:
 
三数取中法: 分别取出数组的最左端元素,最右端元素和中间元素, 在这三个数中取出中位数,作为基准元素。
 
package mypackage;public class QuickSort {// 交换两个数组元素private static void exchange(int [] a , int i, int j) {int temp = a[i];a[i] = a[j];a[j] = temp;}// 选取左中右三个元素,求出中位数, 放入数组最左边的a[low]中private static int selectMiddleOfThree(int[] a, int low, int high) {int middle = low + (high -  low)/2;  // 取得位于数组中间的元素middle if(a[low]>a[high])    {  exchange(a, low, high);  //此时有 a[low] < a[high]
    }if(a[middle]>a[high]){ exchange(a, middle, high); //此时有 a[low], a[middle] < a[high]
    }if(a[middle]>a[low]) {exchange(a, middle, low); //此时有a[middle]< a[low] < a[high]
    }return a[low];  // a[low]的值已经被换成三数中的中位数, 将其返回
  }private static int partition (int[] a, int low, int high) {int i = low, j = high+1;      // i, j为左右扫描指针 PS: 思考下为什么j比i 多加一个1呢?int pivotkey = selectMiddleOfThree( a, low, high);while(true) { while (a[--j]>pivotkey) {   if(j == low) break; }  // 右游标左移while(a[++i]<pivotkey) {   if(i == high) break;  }  // 左游标右移if(i>=j) break;    // 左右游标相遇时候停止, 所以跳出外部while循环else exchange(a,i, j) ;  // 左右游标未相遇时停止, 交换各自所指元素,循环继续
    }exchange(a, low, j); // 基准元素和游标相遇时所指元素交换,为最后一次交换return j;  // 一趟排序完成, 返回基准元素位置
  }private static void sort (int [] a,  int low, int high) {if(high<= low) { return; } // 当high == low, 此时已是单元素子数组,自然有序, 故终止递归int j = partition(a, low, high);  // 调用partition进行切分sort(a,  low,  j-1);   // 对上一轮排序(切分)时,基准元素左边的子数组进行递归sort(a,  j+1,  high); // 对上一轮排序(切分)时,基准元素右边的子数组进行递归
  }public static void sort (int [] a){ //sort函数重载, 只向外暴露一个数组参数sort(a, 0, a.length - 1);}
}

 
 

优化点三 —— 去除不必要的边界检查

 
我在上面说过:“ 第一个内部while循环体里面的的  if(j == low) break;判断其实是多余的,可以去除” 
(请把文章往上翻到标题—“对切分函数partition的解读”中的第4点)
 
那么, 能不能把另外一个边界检查  if(i == high) break; 也去除呢? 当然是不能直接去除的,但是我们可以通过一些技巧使得我们能够去除它
 
首先要理解的是 if(i == high) break;的作用: 防止 i 增加到超过数组的上界, 造成数组越界的错误。
那么按照同样的思考方式,对于
while(a[++i]<pivotkey) {   if(i == high) break;  }

我们只要尝试把这一作用交给a[++i]<pivotkey去完成, 不就可以把 if(i == high) break; 给去掉了吗?

 
这里的技巧就是: 在排序前先把整个数组中最大的元素移到数组的最右边,这样的话, 就算左游标i增加(右移)到数组的最右端,a[++i]<pivotkey也会判定为false(数组最大值当然是大于或等于基准元素的), 从而无法越界。
 
代码:
public class QuickSort {// 交换两个数组元素private static void exchange(int [] a , int i, int j) {int temp = a[i];a[i] = a[j];a[j] = temp;}//将原数组里最大的元素移到最右边, 构造“哨兵”private static void Max(int [] a) {int max = 0; for(int i = 1; i<a.length;i++) {if(a[i]>a[max]) {max = i;}}exchange(a, max, a.length -1);}private static int partition (int[] a, int low, int high) {int i = low, j = high+1;      // i, j为左右扫描指针 PS: 思考下为什么j比i 多加一个1呢?int pivotkey = a[low];  // pivotkey 为选取的基准元素(头元素)while(true) { while (a[--j]>pivotkey) {   }  // 空的循环体while(a[++i]<pivotkey) {   }  // 空的循环体if(i>=j) break;    // 左右游标相遇时候停止, 所以跳出外部while循环else exchange(a,i, j) ;  // 左右游标未相遇时停止, 交换各自所指元素,循环继续 }exchange(a, low, j); // 基准元素和游标相遇时所指元素交换,为最后一次交换return j;  // 一趟排序完成, 返回基准元素位置
  }private static void sort (int [] a,  int low, int high) {if(high<= low) { return; } // 当high == low, 此时已是单元素子数组,自然有序, 故终止递归int j = partition(a, low, high);  // 调用partition进行切分sort(a,  low,  j-1);   // 对上一轮排序(切分)时,基准元素左边的子数组进行递归sort(a,  j+1,  high); // 对上一轮排序(切分)时,基准元素右边的子数组进行递归
  }public static void sort (int [] a){ //sort函数重载, 只向外暴露一个数组参数Max(a); // 将原数组里最大元素移到最右边, 构造“哨兵”sort(a, 0, a.length - 1);}
}

 
如果看到这里对“哨兵”这个概念还不是很清楚的话,看看下面这张图示:
《三种哨兵》
 
关于哨兵三再说几句: 在处理内部子数组的时候,右子数组中最左侧的元素可以作为左子数组右边界的哨兵(可能有点绕)
 

优化点四 —— 三切分快排(针对大量重复元素)

 
普通的快速排序还有一个缺点, 那就是会交换一些相同的元素
 
回忆一下我在前面提到的快排中对左右游标指定的规则:
  • 左游标向右扫描, 跨过所有小于基准元素的数组元素, 直到遇到一个大于或等于基准元素的数组元素, 在那个位置停下。
  • 右游标向左扫描, 跨过所有大于基准元素的数组元素,直到遇到一个大于或等于基准元素的数组元素,在那个位置挺停下
 
特别的, 当左右游标都指向和基准元素相同的元素时候, 不必要的交换就发生了
如图:
(下图中基准元素是6)
 
所以由此人们研究出了三切分快排(三路划分) , 在左右游标的基础上,再增加了一个游标,用于处理和基准元素相同的元素
 
 
代码如下:
package mypackage;public class Quick3way {public static void exchange(int [] a , int i, int j) {int temp = a[i];a[i] = a[j];a[j] = temp;}public static void sort (int [] a, int low, int high) {if(low>=high)  { return; }int lt = low, gt = high, i =low+1;int v = a[low];while(i<=gt) {int aValue = a[i];if(aValue>v) { exchange(a, i, gt--);  }else if(aValue<v) { exchange(a, i++, lt++); }else{ i++; }}sort(a, low, lt-1);sort(a, gt+1, high);}public static void sort (int [] a) {sort(a, 0, a.length - 1);}
}

 
 
切分轨迹:
 
(A - Z 字母排序, A最小, Z最大)
(不好意思,pdf书里的截图实在太模糊了,所以我自己用手机照了一张)

其实啊,我只是把你们喝咖啡的时间,都用来喝啤酒而已

【算法】快速排序算法的编码和优化相关推荐

  1. 十大经典排序算法-快速排序算法详解

    十大经典排序算法 十大经典排序算法-冒泡排序算法详解 十大经典排序算法-选择排序算法详解 十大经典排序算法-插入排序算法详解 十大经典排序算法-希尔排序算法详解 十大经典排序算法-快速排序算法详解 十 ...

  2. 用openAI写个js的排序算法(快速排序算法)

    文章目录 一.生成JS快速排序算法 二.openai还会给出调用方法 一.生成JS快速排序算法 这是一个通用的 JavaScript 排序算法.它使用的是快速排序算法. function sort(a ...

  3. 排序算法 | 快速排序算法原理及实现和优化(一)

    快速排序是对冒泡排序的一种改进,由 C.A.R.Hoare(Charles Antony Richard Hoare,东尼·霍尔)在 1962 年提出. 快速排序的基本思想是:通过一趟排序将要排序的数 ...

  4. 排序算法——快速排序算法

    快速排序的基本思想: 通过一趟排序将待排记录分隔成独立的两部分,一部分记录的关键字比基准值小,一部分记录的关键字比基准值大,然后再对这两部分进行同样操作. 1.快速排序递归算法 //快速排序,递归 i ...

  5. 快速排序 算法 详解 及 深度优化

    下面简介经典算法  快速排序算法 实现及优化. (欢迎大家指点,继续提出优化的方法,共同提高) 基本思想:(以按从小到大排序为例说明)通过多次的排序,每次的排序均将要排序的数组分为两部分,前一部分均比 ...

  6. 快速排序算法(基于Java实现)

    title: 快速排序算法(基于Java实现) tags: 快速排序算法 快速排序算法的原理与代码实现: 一.快速排序算法的原理 快排算法的思想是: 如果需要排序数组中下标从p到r之间的一组数据,我们 ...

  7. python实现快排算法_Python实现快速排序算法

    Python实现快速排序算法 快速排序算法是一种基于交换的高效的排序算法,由C.R.A.Hoare于1962年提出,是一种划分交换排序.它采用了一种分治的策略,通常称其为分治法(Divide and ...

  8. python快速排序代码_Python实现快速排序算法

    原标题:Python实现快速排序算法 Python实现快速排序算法 快速排序算法是一种基于交换的高效的排序算法,由C.R.A.Hoare于1962年提出,是一种划分交换排序.它采用了一种分治的策略,通 ...

  9. Go语言编程:Go语言实现快速排序算法

    前言 今天用Go语言实现下经典排序算法--快速排序算法.主要是学习了Go语言,得用它来干点事情嘛,就用快速排序来练手.在Go语言语法方面,主要用到 切片数组,for循环(Go语言没有while循环), ...

  10. 排序算法:快速排序算法实现及分析(递归形式和非递归形式)

    快速排序算法介绍 从名字上就可以看出快速排序算法很嚣张,直接以快速命名.确实快速排序 的确很快速,被列为20世纪十大算法之一.程序员难道不应该掌握么.快速排序(Quick Sort)的基本思想是:通过 ...

最新文章

  1. OC学习篇之---对象的拷贝
  2. boost::make_tuple用法的测试程序
  3. python islower函数_python字符串是否是小写-python 字符串小写-python islower函数-python islower函数未定义-嗨客网...
  4. 0004-Median of Two Sorted Arrays(寻找两个正序数组的中位数)
  5. 服务器(Windows镜像)自建git服务器超详细教程
  6. mysql+修改字段长度语句,mysql修改字段长度的sql语句分享
  7. 利用PYTHON计算偏相关系数(Partial correlation coefficient)
  8. U盘数据丢失后怎样恢复
  9. LFI获取WENSHELL
  10. 【论文解读】MacBERT: 中文自然语言预训练模型
  11. Design Compiler工具学习笔记(7)
  12. linux 获取当前工作路径
  13. wps批量将文档括号内的字符串修改颜色
  14. 餐饮app开发市场前景如何?行业竞争激烈吗?
  15. 力扣904(滑动窗口、哈希)
  16. 1024凑数篇之程序员职业生涯问答
  17. %在c语言中起什么作用
  18. 跨平台框架这么多, 老夫无可奈何!
  19. jquery 同源下载图片到本地
  20. Jetson-Xavier-NX使用教程(这里说插上烧好镜像的SD卡上电就可以直接用了)

热门文章

  1. java:多态的实现机制
  2. python二十七:从最内部的循环直接跳出来
  3. 利用反射机制获取未知类型的枚举的信息
  4. 广播的最佳实践——实现强制下线功能(Android_Broadcast)...
  5. UVA 11578 - Situp Benches(dp)
  6. [Linux] Linux smaps接口文件结构
  7. java基础学习(一)方法
  8. selenium如何在同一个窗口页面中返回上一个页面窗口(Python)。
  9. 【全文搜索引擎】Elasticsearch之分词器
  10. 【坑爹微信】总有一款接口能坑你到吐血 --- 微信开发经验录