快速排序思想步骤:
1.找到一个基准值key
2.设置2个元素下标i=0和j=len-1
3.从后往前找到比key小的数num[j],从前往后找到比key大的数num[i](这里有个先后顺序)
4.交换这两个数:num[i],num[j]
5.继续重复该过程,直到i==j,完成一趟排序(循环结束条件)
6.以num[j]为界限,左右2边分别进行快速排序
图片来源于百度:
总结:
确定一个数字的准确位置,再递归它分出来的两个数组,直到递归规模缩小为1停止,最终整个数组会趋于有序。
注意:
i和j先后顺序不重要,但是每次都必须按照第一次的顺序来。如果第一次是i先走,后面的子分组快速排序也得i先走。
举例
num=[1,3,4,2,6,5]
1.找到基准值(首尾都行),key=1
2.num[i]=1,num[j]=5
3.j--找到比1小的第一个数
4.此时j=i(满足j<=i找不到),第一趟快速排序结束,数字1为有序。
此时顺序为:[1,3,4,2,6,5]
5.数字1左边的分组数据<=1左边分组已经有序,右边的分组继续进行快速排序
6.选择基准值key=3,num[i]=3,num[j]=5
7.j--找到比3小的第一个数,num[j]=2
8.i++找到比3大的第一个数,num[i]=4
9.交换num[i]和num[j]
此时顺序为:[1,3,2,4,6,5]
10.j--找比3小的第一个数,num[i]=2,此时j==i
11.交换key和num[j],发现3为分界线的左边 只有1个数字:2。(子分组,1为有序了)所以只需要对3右边进行快速排序。
此时顺序为:[1,2,3,4,6,5]
12.找基准值4,num[i]=4,num[j]=5
13.j--找到比4小的第一个数,找不到,此时i=j,num[i]==num[j]==key,4为有序
此时顺序为:[1,2,3,4,6,5]
14.找到基准值6,num[i]=6,num[j]=5
15.j--找到比6小的第一个数,5
16.i++找到比6大的第一个数,找不到,此时i=j,num[i]==num[j]==5
17.交换key和num[j]
此时顺序为:[1,2,3,4,5,6]
第4步和第17步体现了i和j顺序的作用。
由于我这里是j先走,所以,num[j]往前走到i==j还是没找到比key小的数的话,说明key是有序,不用交换。---对应第4步
当j--找到了比key小的第一个数(i不等于j时)之后,i++,直到i==j都没找到比key大的数,此时交换key和num[j]。----对应第17步
代码实现:
第一版代码(存在重复元素会陷入死循环)
1.设置基准值变量key,游标i,j。i是起始索引,j是结束索引
2.通过一个partition函数去完成num[i],num[j]的交换,函数返回索引mid,mid为划分子分组的界限
3.快速排序函数:参数为列表,开始索引,结束索引(用来传给partition函数获取mid索引)
4.以mid索引为界限,对左右两边子分组分别递归调用快速排序函数。
def partition(num,i,j):
key=num[i]
while i<j:
while i<j and num[j]>key:
j=j-1
while i<j and num[i]<key:
i=i+1
num[i],num[j]=num[j],num[i]
return j
def quick_sort(num,i,j):
if i<j:
mid = partition(num, i, j)
quick_sort(num,i,mid-1)
quick_sort(num, mid + 1, j)
# num=[1,3,4,2,6,5]
num = [5, 7, 4, 6, 3, 1, 2, 9, 8]
i=0
j=len(num)-1
quick_sort(num,i,j)
print(num)
partition函数:
1.当i小于j的时候,判断num[j]>key就一直执行j=j-1,直到找到一个num[j]<=key
2.再判断i<j并且num[i]>key,一直执行i=i+1,直到找到一个num[i]>=key
3.交换num[i]和num[j]的值
当列表内没有重复元素时,初版代码完美实现。
第二版代码(重复元素测试通过)
但是当列表中存在重复元素,会跳过partition中两个内循环,所以并不会有i和j的更改。
直接在外面的循环中一直交换2个元素,就陷入了死循环。于是,我在第一版代码的基础上又做了一些改进得到第二版代码。
思路沿用第一版,具体实现区别在于partition函数。
partition函数:
1.设置基准值key=num[i](我设置的左边的值为基准值)
2.循环比较num[j]和key的大小,如果num[j]>=key,执行j=j-1。当num[j]<key的时候,停止循环,然后把num[j]赋值给num[i](此时比key大的数放到左边去了)
3.循环比较num[i]和key的大小,如果num[i]<=key,执行i=i+1。当num[i]>key的时候,停止循环,把num[i]赋值给num[j](此时比key小的数放到右边了)
4.把key的值赋值给num[i],相当于key为中间值,第一轮结束,小的数已分到了左边,大的数分到了右边。
def partition(num,i,j):
key=num[i]
while i<j:
while i<j and num[j]>=key:
j=j-1;
num[i]=num[j]
while i<j and num[i]<=key:
i=i+1
num[j]=num[i]
num[i]=key
return i
def quick_sort(num,i,j):
if i<j:
mid = partition(num, i, j)
quick_sort(num,i,mid-1)
quick_sort(num, mid + 1, j)
# num=[1,3,4,2,6,5]
num=[1,0,2,2,1,0]
# num = [5, 7, 4, 6, 3, 1, 2, 9, 8]
i=0
j=len(num)-1
quick_sort(num,i,j)
print(num)
在没有重复元素的时候,2-3步相当于第一版代码的外循环里面的交换。
当有重复元素的时候,也会交换,但是不会陷入上一版代码的死循环了。
上一版代码由于没有重复元素,所以内循环里,>=和<=的效果都一样。这版代码是有重复元素的,所以得加上=。
因为如果j=j-1找到小于key的元素,就要进行交换。如果是找到相等元素,可以不进行交换。