快速排序 (通俗易懂的讲解)
关于快排,查了很多网上的资料,大抵都是抽象难懂的表述,有些大牛也许不屑于讲清楚这件事,而大部分是相互抄袭。今天希望能讲这个常用的算法让所有人看明白。
冒泡排序
确定位置,找数字的排序
要说快排,不得不先说冒泡这种排序。仔细回想下冒泡的过程,事实上可以总结成确定位置,找数字的排序。
设排序前的串叫 目标串,排序后的串叫 结束串,从目标串 —->结束串 一共会经历n趟排序。
进行第1趟排序后, 目标串 的第1个位置数,一定就是 结束串 第1个位置的数。这一趟一共要比较n-1次,才能结束。
进行第x趟排序后, 目标串 的第x个位置数,一定就是 结束串 第x个位置的数。这一趟一共要比较n-x次,才能结束。
排序完一共经历了 n-1 + n-2 +…+ n-x+…+1 大于等于 n^2/2,可以确定是O(n^2)数量级;
这里不作过多解释,相信聪明的你,仔细想想就明白了。
快速排序
确定数字,找位置的排序
仔细看,快排 是在目标串 里拿到第一个数,在一趟排序结束后,这个数字在 目标串里的位置就会被调整成 它最后在 结束串 里的位置。例子:
目标串 8 3 1 9 7 19 6
结束串 1 3 6 7 8 9 19
目标串 经历一次排序,变成:
中间串 6 3 1 7 8 19 9
中间串 8的位置和 结束串 8的位置已经达成一致
然后8 就好像一根分割线 把中间串分成
串 6 3 1 7
串 19 9
这两个串继续作为 目标串 进行排序。
好了那么问题就剩下
目标串 8 3 1 9 7 19 6
是如何变成
中间串 6 3 1 7 8 19 9
在这个变化过程中 我们遵循 一个逻辑原则 我希望把8一口气移动到它最后该在的位置上, 如何确定8的位置呢, 我只要保证 8的左边的所有数字都比8小,8右边的数字都比8大,那么8 就在他该在的位置上了。
说明一下上图,短指针就是为了确定8以外的数字,和8之间的位置关系。
如果短指针在长指针的右边,那么我们期望短指针的指的数比8大,
如果成立,那么说明这个数相对于8 的位置没错,那么短指针向长指针靠近来寻找下一个数是不是在正确的相对位置。
如果不成立,交换2个位置的数,让她们相对位置正确。
然后我们可以把问题抽象一下变成:
小 区域表示 确定了比target小得数
大 区域表示 确定了比target大得数
随着 靠近和交换 两种动作的进行,小区域 和 大区域 的面积 不断变大,不确定区域不断变小,直到消失,就完成了这次排序。并且把目标串 分成了两个子目标串(递归对子目标串进行 和目标串 相同的操作)
总结下快排的排序想法:
把选中的数字放到他最后该在的位置,把串分成两部分
最后再来看看时间复杂度:
目标串 会被切成一颗二叉树。
我们先考虑最好的情况,每次切分都是切在中点。这样树的深度最浅(层数最少)
对于第一层来说 ,n个数,最后两个指针能相遇 说明比较了n次,O(n)
对于第二层俩说,左右两个节点相加也是n个数(可能少一两个数),说明也是比较了n次,O(n)
对于每一层我们都大约考虑为O(n),一共logN层,复杂度就是O(n*logN)
再考虑最差情况,每次都砍再边缘 而不是中间,这样树的深度就变成n,相当于每次排序都是为了找最边缘位置上应该有的值(和冒泡的逻辑策略完全一致),自然时间复杂度也退化成了O(n^2)