快速排序
介绍
快速排序也叫快排,讲究的是一个快
字。它是分治思想的一个应用,即:将一个多数据的数组排序问题转换为一个一个数据的排序,每个数据排序后得到的数组即是排序后的数组。
原理
假设有一个数组
a
[
n
]
a[n]
a[n],数组中元素为
a
[
0
]
a
[
1
]
.
.
.
.
.
.
a
[
n
−
1
]
a[0]a[1]......a[n-1]
a[0]a[1]......a[n−1]
快速排序就是在这个数组中随机找到一个“哨兵”值
a
[
k
]
a[k]
a[k]
然后将
a
[
0
]
∼
a
[
k
−
1
]
a[0] \sim a[k-1]
a[0]∼a[k−1]存入比
a
[
k
]
a[k]
a[k]小的数,而
a
[
k
+
1
]
∼
a
[
n
−
1
]
a[k+1]\sim a[n-1]
a[k+1]∼a[n−1]则存入比
a
[
k
]
a[k]
a[k]大的数。
具体实现方法为:
使用两个指针i
和j
,将i
指向
a
[
0
]
a[0]
a[0],j
指向
a
[
n
−
1
]
a[n-1]
a[n−1],将i
从前往后遍历,j
从后往前遍历,在i<j
的前提下,若a[i] >= a[k]
,指针i
停住,等待。若a[j] <= a[k]
,指针j
停住,等待,若两指针都进入了等待状态,则将a[i]
与a[j]
的数值对换,指针继续往前走。
这番操作做完后
a
[
0
]
∼
a
[
k
−
1
]
a[0]\sim a[k-1]
a[0]∼a[k−1]的数都比
a
[
k
]
a[k]
a[k]小,而
a
[
k
+
1
]
∼
a
[
n
−
1
]
a[k+1]\sim a[n-1]
a[k+1]∼a[n−1]中的数都比
a
[
k
]
a[k]
a[k]大,此时
a
[
0
]
∼
a
[
k
−
1
]
a[0]\sim a[k-1]
a[0]∼a[k−1],
a
[
k
]
a[k]
a[k],
a
[
k
+
1
]
∼
a
[
n
−
1
]
a[k+1]\sim a[n-1]
a[k+1]∼a[n−1]已经是从小到大排序了。
因此只需要再将
a
[
0
]
∼
a
[
k
−
1
]
a[0]\sim a[k-1]
a[0]∼a[k−1]和
a
[
k
+
1
]
∼
a
[
n
−
1
]
a[k+1]\sim a[n-1]
a[k+1]∼a[n−1]分别作为单独数组进行同样的操作即可。
以此类推,当最后分割成单个数据排序后整个数组也就从小到大排序完成了。
时间复杂度( O ( n l o g n ) O(nlogn) O(nlogn))
假设一个数组长度为n
,平均每次分割都是分割成长度相等的2个数组,一直分割下去,假设分割
k
k
k次后每个数组长度为1
(代表排序完成),则此时
2
k
=
n
2^k = n
2k=n,解得
k
=
l
o
g
2
n
k = log_2n
k=log2n,在每次的循环中数组都要遍历一遍,因此时间复杂度为
O
(
n
l
o
g
n
)
O(nlogn)
O(nlogn)
空间复杂度( O ( 1 ) O(1) O(1))
因为只用了i
,j
,l
,r
,t
等有限个额外空间,因此空间复杂度为
O
(
1
)
O(1)
O(1)
快排模拟
我们以一个长度 n = 5 n=5 n=5数组为例:
3 1 2 4 5
我们取它的中间数mid = 2
,t = a[2] = 2
此时i = 0
,j = 4
- 判断
a[0]
是否小于t
:∵3 > 2,指针i
停住。 - 判断
a[4]
是否大于t
:∵5 > 2,j--
- 判断
a[3]
是否大于t
:∵4 > 2,j--
- 判断
a[2]
是否大于t
:∵2 = 2,指针j
停住 - 此时
i
与j
都在等待状态,将a[i]
和a[j]
的数值进行交换,i++
,j--
- 此时
i = 1
,j = 1
,i == j
,跳出循环 - 此时的数组为
2 1 3 4 5
- 从
mid
处分割成两个数组,第一个为 a [ 0 ] ∼ a [ m i d ] a[0]\sim a[mid] a[0]∼a[mid],第二个为 a [ m i d + 1 ] ∼ a [ n − 1 ] a[mid+1]\sim a[n-1] a[mid+1]∼a[n−1],再分别进行相同操作。后面可以自行模拟。
代码
void quick_sort(int q[],int l,int r){
if(l >= r) return;//l == r 也可以,因为l == r时一定会结束
int t = q[(r+l)/2], i = l - 1, j = r + 1;
//因为do-while循环会先执行一次i++和j--,所以i先减1,j先加1
while(i < j){
do i++;while(q[i] < t);
do j--;while(q[j] > t);
if(i < j) swap(q[i],q[j]);
}
quick_sort(q,l,j);
quick_sort(q,j + 1,r);
}
边界问题
快排边界问题和整数二分边界问题本质是一样的,都是容易引起死循环,这里就不说了,但是这个模板是没有问题的,将分割处记住即可。