快速排序-三种实现方式

概念

      快速排序的基本思想是分治法,选取一个“基准数”作为比较参照,经过排序运算,将数据分为两个规模更小的数据源,其中一个所有的数据都比另一部分的小,然后递归进行操作从而使数据达到有序的状态。

分治法的基本思想是:将原问题分解为若干个规模更小但结构与原问题相似的子问题。递归地解这些子问题,然后将这些子问题的解组合为原问题的解。

快速排序的时间复杂度在最坏情况下是O(N2),平均的时间复杂度是O(N*logN)。快速排序是不稳定的算法。

算法稳定性:假设在数列中存在 a[i] = a[j],若在排序之前,a[i] 在a[j] 前面;并且排序之后,a[i] 仍然在a[j] 前面。则这个排序算法是稳定的!


实现方式

      记录一下三种简单的实现方式,总结以代码+日志的方式展示,通过日志可以清晰的展示出每轮每次的数据操作。前提假设最终的目标排序方式都是升序,可以为了清晰展示出每次具体操作了哪些数据,日志[]中括号内展示的为数组下标,()小括号内展示的为本轮的数据源,""双引号内展示的为本次需要交换的两个值,''单引号内展示的为pre(前后指针)的值

左右指针法

  • 选取基准值,开始本轮循环;
  • 从数组右侧开始递减比较,发现有值小于基准值,停下记录该下标;
  • 从数组左侧开始递增比较,发现有值大于基准值,停下记录该下标;
  • 将两个下标的值进行交换;
  • 接着循环重复第2~4步,直到左右两指针相遇,表示这一轮的内循环比较结束;
  • 此时左、右指针指向相同的值,将左、右指针指向的值与基准值交换(基准值归位),就完成了本轮的排序;
  • 每轮排序完成后,基准值左边的都是小于它的,而右边都是大于等于它的,接着以基准值为分割点,将两个子集数据源从第1步进行递归操作;

      通俗点说,就是一个指针从右侧依次递减,一个指针从左侧依次递增,当发现符合条件的情况时,交换两个指针指向的值,然后以基准值为分割点,将本次数据源分为两个小的数组,依次递归循环该操作。

      这里需要注意的就是内部循环初始先从右侧开始还是左侧开始,这里先分析下本例当中,要求升序排列,从左侧(最终肯定是小于基准值的值)取默认基准值,当内部循环优先从右侧开始时,每次循环完,右侧指针指向的永远是小于基准值的值(排除已经是升序的情况),这时假如本次没有交换,在循环外右侧指针(等于左侧指针)就可以与基准值的进行交换,最终基准值归位,小于基准值的数都在一侧。
      相应的,我们也可以在内循环时优先从左侧判断,这时在选基准值时就选择右侧的;或者尝试下降序时的初始方式,这里就不赘述了……

public static void quickSort(int left, int right) {
    if (left >= right) {
        return;
    }
        
    int l = left;
    int r = right;
    int target = arrs[l];
        
    while (l < r) {
        while (arrs[r] >= target && l < r) {
            r--;
        }
        while (arrs[l] <= target && l < r) {
            l++;
        }
            
        if (l < r) {
            swap(arrs[l], arrs[r]);
        }
    }
        
    if (l != left) {
        swap(arrs[l], arrs[left]):
    }
        
    quickSort(left, l - 1);
    quickSort(l + 1, right);
}
随机初始数组: [29, 13, 86, 2, 55, 7, 22, 33, ] 
################

第1轮,基准值是29:
交换[2]和[6]位置的值
交换前的数据: [(29, 13, "86", 2, 55, 7, "22", 33), ] 
交换后的数据: [(29, 13, "22", 2, 55, 7, "86", 33), ] 
交换[4]和[5]位置的值
交换前的数据: [(29, 13, 22, 2, "55", "7", 86, 33), ] 
交换后的数据: [(29, 13, 22, 2, "7", "55", 86, 33), ] 
内循环结束,当前l=r=[4],与基准值(下标[0])值交换
交换前的数据: [("29", 13, 22, 2, "7", 55, 86, 33), ] 
交换后的数据: [("7", 13, 22, 2, "29", 55, 86, 33), ] 
第1轮结束

第2轮,基准值是7:
交换[1]和[3]位置的值
交换前的数据: [(7, "13", 22, "2"), 29, 55, 86, 33, ] 
交换后的数据: [(7, "2", 22, "13"), 29, 55, 86, 33, ] 
内循环结束,当前l=r=[1],与基准值(下标[0])值交换
交换前的数据: [("7", "2", 22, 13), 29, 55, 86, 33, ] 
交换后的数据: [("2", "7", 22, 13), 29, 55, 86, 33, ] 
第2轮结束

第3轮,基准值是22:
循环结束,当前l=r=[3],与基准值(下标[2])值交换
交换前的数据: [2, 7, ("22", "13"), 29, 55, 86, 33, ] 
交换后的数据: [2, 7, ("13", "22"), 29, 55, 86, 33, ] 
第3轮结束

第4轮,基准值是55:
交换[6]和[7]位置的值
交换前的数据: [2, 7, 13, 22, 29, (55, "86", "33"), ] 
交换后的数据: [2, 7, 13, 22, 29, (55, "33", "86"), ] 
内循环结束,当前l=r=[6],与基准值(下标[5])值交换
交换前的数据: [2, 7, 13, 22, 29, ("55", "33", 86), ] 
交换后的数据: [2, 7, 13, 22, 29, ("33", "55", 86), ] 
第4轮结束

################
排序最终数组: [2, 7, 13, 22, 29, 33, 55, 86, ] 

填坑法

  • 选取基准值,该下标即为第一个坑位A,开始本轮循环;
  • 从数组右侧开始递减比较,当发现有值小于基准值时,将当前值填到坑位A中,同时当前下标替换为坑位B(注意:初始基准值被覆盖掉了,本轮操作最后,需要将该值填到坑位中);
  • 从数组左侧开始递增比较,当发现有值大于基准值,将值填到坑位B中,同时当前下标替换为坑位A;
  • 不停的重复第2~3步的操作,直到左右两指针相遇,表示这一轮的内循环比较结束;
  • 将基准值填到左右指针指向的坑位中,就完成了本轮的排序;
  • 以基准值为分割点,将本次数据源分为两个小的数组,依次从第1步递归循环操作;
public static void quickSort(int left, int right) {
    if (left >= right) {
        return;
    }
        
    int l = left;
    int r = right;
    int target = arrs[l];
        
    while (l < r) {
        while (arrs[r] >= target && l < r) {
            r--;
        }
        if (l < r) {
            arrs[l] = arrs[r];
        }
            
        while (arrs[l] <= target && l < r) {
            l++;
        }
        if (l < r) {        
            arrs[r] = arrs[l];
        }
    }
    arrs[l] = target;
    
    quickSort(left, l - 1);
    quickSort(l + 1, right);
}
随机初始数组: [14, 18, 1, 28, 30, 11, 78, 55, ] 
################

第1轮,基准值是14,当前坑位[0]:
交换前的数据: [("14", 18, 1, 28, 30, "11", 78, 55), ] 
交换后的数据: [("11", 18, 1, 28, 30, "11", 78, 55), ] 当前坑位[5],值是11
交换前的数据: [(11, "18", 1, 28, 30, "11", 78, 55), ] 
交换后的数据: [(11, "18", 1, 28, 30, "18", 78, 55), ] 当前坑位[1],值是18
交换前的数据: [(11, "18", "1", 28, 30, 18, 78, 55), ] 
交换后的数据: [(11, "1", "1", 28, 30, 18, 78, 55), ] 当前坑位[2],值是1
内循环结束,准备将基准值补上最后一个坑位 [(11, 1, "1", 28, 30, 18, 78, 55), ] 
交换后的数据: [(11, 1, "14", 28, 30, 18, 78, 55), ] 
第1轮结束

第2轮,基准值是11,当前坑位[0]:
交换前的数据: [("11", "1"), 14, 28, 30, 18, 78, 55, ] 
交换后的数据: [("1", "1"), 14, 28, 30, 18, 78, 55, ] 当前坑位[1],值是1
内循环结束,准备将基准值补上最后一个坑位 [(1, "1"), 14, 28, 30, 18, 78, 55, ] 
交换后的数据: [(1, "11"), 14, 28, 30, 18, 78, 55, ] 
第2轮结束

第3轮,基准值是28,当前坑位[3]:
交换前的数据: [1, 11, 14, ("28", 30, "18", 78, 55), ] 
交换后的数据: [1, 11, 14, ("18", 30, "18", 78, 55), ] 当前坑位[5],值是18
交换前的数据: [1, 11, 14, (18, "30", "18", 78, 55), ] 
交换后的数据: [1, 11, 14, (18, "30", "30", 78, 55), ] 当前坑位[4],值是30
内循环结束,准备将基准值补上最后一个坑位 [1, 11, 14, (18, "30", 30, 78, 55), ] 
交换后的数据: [1, 11, 14, (18, "28", 30, 78, 55), ] 
第3轮结束

第4轮,基准值是30,当前坑位[5]:
内循环结束,准备将基准值补上最后一个坑位 [1, 11, 14, 18, 28, ("30", 78, 55), ] 
交换后的数据: [1, 11, 14, 18, 28, ("30", 78, 55), ] 
第4轮结束

第5轮,基准值是78,当前坑位[6]:
交换前的数据: [1, 11, 14, 18, 28, 30, ("78", "55"), ] 
交换后的数据: [1, 11, 14, 18, 28, 30, ("55", "55"), ] 当前坑位[7],值是55
内循环结束,准备将基准值补上最后一个坑位 [1, 11, 14, 18, 28, 30, (55, "55"), ] 
交换后的数据: [1, 11, 14, 18, 28, 30, (55, "78"), ] 
第5轮结束
################
排序最终数组: [1, 11, 14, 18, 28, 30, 55, 78, ] 

前后指针法

  • 选取基准值,定义两个指针,cur 表示当前指针指向,pre 默认表示cur 指针的前一位;
  • cur 和 pre 指针依次++ 进行递增比较,当cur 发现大于基准值时,pre 暂停递增(即[pre+1] 指向了一个大于基准值的值);
  • cur 接着++ 进行递增比较,当发现比基准值小时,对数组[pre+1] 和 数组[cur] 的值进行交换;
  • 不停的重复第2~3步操作,直到一轮循环结束(即cur 从当前数据源从左移到了右);
  • 内循环结束后,将数组[pre+1] 的值与基准值进行交换;
  • 以基准值为分割点,将本次数据源分为两个小的数组,依次从第1步递归循环操作;
public static void quickSort(int left, int right) {
    if (left >= right) {
        return;
    }
        
    int cur = left;
    int pre = cur - 1;
    int target = arrs[right];
        
    while (cur < right) {
        while (arrs[cur] < target && ++pre != cur) {
            swap(arrs[cur], arrs[pre];
        }
        cur++;
    }
        
    swap(arrs[++pre], arrs[right]);
        
    quickSort(left, pre -1);
    quickSort(pre + 1, right);
}
随机初始数组: [86, 59, 63, 53, 68, 99, 58, 1, ] 
################

第1轮,基准值是1,'pre':[-1],"cur":[0]:
"cur"指针++了: [(86, "59", 63, 53, 68, 99, 58, 1), ] ,'pre':[-1], "cur":[1]
"cur"指针++了: [(86, 59, "63", 53, 68, 99, 58, 1), ] ,'pre':[-1], "cur":[2]
"cur"指针++了: [(86, 59, 63, "53", 68, 99, 58, 1), ] ,'pre':[-1], "cur":[3]
"cur"指针++了: [(86, 59, 63, 53, "68", 99, 58, 1), ] ,'pre':[-1], "cur":[4]
"cur"指针++了: [(86, 59, 63, 53, 68, "99", 58, 1), ] ,'pre':[-1], "cur":[5]
"cur"指针++了: [(86, 59, 63, 53, 68, 99, "58", 1), ] ,'pre':[-1], "cur":[6]
"cur"指针++了: [(86, 59, 63, 53, 68, 99, 58, "1"), ] ,'pre':[-1], "cur":[7]
内循环结束,准备将'++pre'(大于基准值的值)与基准值交换: [("86", 59, 63, 53, 68, 99, 58, "1"), ] 
交换后的数据: [("1", 59, 63, 53, 68, 99, 58, "86"), ] 
第1轮结束

第2轮,基准值是86,'pre':[0],"cur":[1]:
"cur"指针++了: [1, ('59', "63", 53, 68, 99, 58, 86), ] ,'pre':[1], "cur":[2]
"cur"指针++了: [1, (59, '63', "53", 68, 99, 58, 86), ] ,'pre':[2], "cur":[3]
"cur"指针++了: [1, (59, 63, '53', "68", 99, 58, 86), ] ,'pre':[3], "cur":[4]
"cur"指针++了: [1, (59, 63, 53, '68', "99", 58, 86), ] ,'pre':[4], "cur":[5]
"cur"指针++了: [1, (59, 63, 53, '68', 99, "58", 86), ] ,'pre':[4], "cur":[6]
交换前的数据: [1, (59, 63, 53, 68, "99", "58", 86), ] 
交换后的数据: [1, (59, 63, 53, 68, "58", "99", 86), ] 
"cur"指针++了: [1, (59, 63, 53, 68, '58', 99, "86"), ] ,'pre':[5], "cur":[7]
内循环结束,准备将'++pre'(大于基准值的值)与基准值交换: [1, (59, 63, 53, 68, 58, "99", "86"), ] 
交换后的数据: [1, (59, 63, 53, 68, 58, "86", "99"), ] 
第2轮结束

第3轮,基准值是58,'pre':[0],"cur":[1]:
"cur"指针++了: ['1', (59, "63", 53, 68, 58), 86, 99, ] ,'pre':[0], "cur":[2]
"cur"指针++了: ['1', (59, 63, "53", 68, 58), 86, 99, ] ,'pre':[0], "cur":[3]
交换前的数据: [1, ("59", 63, "53", 68, 58), 86, 99, ] 
交换后的数据: [1, ("53", 63, "59", 68, 58), 86, 99, ] 
"cur"指针++了: [1, ('53', 63, 59, "68", 58), 86, 99, ] ,'pre':[1], "cur":[4]
"cur"指针++了: [1, ('53', 63, 59, 68, "58"), 86, 99, ] ,'pre':[1], "cur":[5]
内循环结束,准备将'++pre'(大于基准值的值)与基准值交换: [1, (53, "63", 59, 68, "58"), 86, 99, ] 
交换后的数据: [1, (53, "58", 59, 68, "63"), 86, 99, ] 
第3轮结束

第4轮,基准值是63,'pre':[2],"cur":[3]:
"cur"指针++了: [1, 53, 58, ('59', "68", 63), 86, 99, ] ,'pre':[3], "cur":[4]
"cur"指针++了: [1, 53, 58, ('59', 68, "63"), 86, 99, ] ,'pre':[3], "cur":[5]
内循环结束,准备将'++pre'(大于基准值的值)与基准值交换: [1, 53, 58, (59, "68", "63"), 86, 99, ] 
交换后的数据: [1, 53, 58, (59, "63", "68"), 86, 99, ] 
第4轮结束
################
排序最终数组: [1, 53, 58, 59, 63, 68, 86, 99, ] 

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 216,402评论 6 499
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,377评论 3 392
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 162,483评论 0 353
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,165评论 1 292
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,176评论 6 388
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,146评论 1 297
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,032评论 3 417
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,896评论 0 274
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,311评论 1 310
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,536评论 2 332
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,696评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,413评论 5 343
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,008评论 3 325
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,659评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,815评论 1 269
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,698评论 2 368
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,592评论 2 353

推荐阅读更多精彩内容