说在前面
个人读书笔记
快速排序
分治策略
与归并排序算法一样,快速排序(quicksort)算法也是分治策略的典型应用,但二者之间也有本质区别。
归并排序的计算量主要消耗于有序子向量的归并操作,而子向量的划分却几乎不费时间。快速排序恰好相反,它可以在 O ( 1 ) O(1) O(1)时间内,由子问题的解直接得到原问题的解;但为了将原问题划分为两个子问题,却需要O(n)时间。
快速排序算法虽然能够确保,划分出来的子任务彼此独立,并且其规模总和保持渐进不变,却不能保证两个子任务的规模大体相当——实际上,甚至有可能极不平衡。
因此,该算法并不能保证最坏情况下的O(nlogn)时间复杂度。
尽管如此,它仍然受到人们的青睐,并在实际应用中往往成为首选的排序算法。究其原因在于,快速排序算法易于实现,代码结构紧凑简练,而且对于按通常规律随机分布的输入序列,快速排序算法实际的平均运行时间较之同类算法更少。
快速排序算法
如上图所示,考查任一向量区间 S [ l o , h i ) S[lo, hi) S[lo,hi)。对于任何 l o < = m i < h i lo <= mi < hi lo<=mi<hi,以元素 S [ m i ] S[mi] S[mi]为界,都可分割出前、后两个子向量 S [ l o , m i ) S[lo, mi) S[lo,mi)和 S ( m i , h i ) S(mi, hi) S(mi,hi)。若 S [ l o , m i ) S[lo, mi) S[lo,mi)中的元素均不大于 S [ m i ] S[mi] S[mi],且 S ( m i , h i ) S(mi, hi) S(mi,hi)中的元素均不小于 S [ m i ] S[mi] S[mi],则元素 S [ m i ] S[mi] S[mi]称作向量S的一个轴点(pivot)。
设向量 S S S经排序可转化为有序向量 S ′ S' S′。不难看出,轴点位置 m i mi mi必然满足如下充要条件:
- S [ m i ] = S ′ [ m i ] S[mi] = S'[mi] S[mi]=S′[mi]
- S [ l o , m i ) S[lo, mi) S[lo,mi)和 S ′ [ l o , m i ) S'[lo, mi) S′[lo,mi)的成员完全相同
- S ( m i , h i ) S(mi, hi) S(mi,hi)和 S ′ ( m i , h i ) S'(mi, hi) S′(mi,hi)的成员完全相同
因此,不仅以轴点 S [ m i ] S[mi] S[mi]为界,前、后子向量的排序可各自独立地进行,而且更重要的是,一旦前、后子向量各自完成排序,即可立即(在 O ( 1 ) O(1) O(1)时间内)得到整个向量的排序结果。
采用分治策略,递归地利用轴点的以上特性,便可完成原向量的整体排序。
快速排序算法:
可见,轴点的位置一旦确定,则只需以轴点为界,分别递归地对前、后子向量实施快速排序;子向量的排序结果就地返回之后,原向量的整体排序即告完成。
算法的核心与关键在于:
轴点构造算法partition()应如何实现?可以达到多高的效率?
轴点构造——快速划分算法
不妨取首元素 m = S [ l o ] m = S[lo] m=S[lo]作为候选,将其从向量中取出并做备份,腾出的空闲单元便于其它元素的位置调整。
然后不断试图移动 l o lo lo和 h i hi hi,使之相互靠拢。
当然,整个移动过程中,需始终保证 l o ( h i ) lo(hi) lo(hi)左侧(右侧)的元素均不大于(不小于) m m m。
最后,当 l o lo lo与 h i hi hi彼此重合时,只需将原备份的 m m m回填至这一位置,则 S [ l o = h i ] = m S[lo = hi]= m S[lo=hi]=m便成为一个名副其实的轴点。
以上过程在构造出轴点的同时,也按照相对于轴点的大小关系,将原向量划分为左、右两个子向量,故亦称作快速划分(quick partitioning)算法。
c++实现数组的快速排序:
时间复杂度
O
(
n
l
o
g
n
)
O(nlogn)
O(nlogn)
#include <iostream>
using namespace std;
template <typename T>
int __partition(T arr[], int l, int r)
{
// 第一个值拿出来, 假设为轴点
T v = arr[l];
int j = l;
// 从第二个开始向后遍历
for (int i = l + 1; i <= r; i++)
{
if (arr[i] < v)
{
swap(arr[j + 1], arr[i]);
j++;
}
}
swap(arr[l], arr[j]);
return j;
}
template <typename T>
void __quickSort(T arr[], int l, int r)
{
if (l>=r)
{
return;
}
int p = __partition(arr, l, r);
__quickSort(arr, l, p-1);
__quickSort(arr, p+1, r);
}
template <typename T>
void quickSort(T arr[], int n)
{
__quickSort(arr, 0, n-1);
}
int main()
{
int a[10] = {10, 9, 8, 6, 11, 5, 4, 1, 0, 3};
quickSort(a, 10);
for (int i = 0; i< 10; i++)
{
cout << a[i] << " ";
}
cout << endl;
return 0;
}
当快速排序轴点两侧数量极度不平衡的时候,算法将退化。
如上,每次都选取第一个数值为轴点时。这样当数组完全有序时,算法时间复杂度退化到
O
(
n
2
)
O(n^2)
O(n2),解决方法是在进行快速划分算法中,每次都随机选取一个点和第一个点交换,再将这个点作为轴点。
但是当重复的值比较多时,由于划分的两部分,其中一部分是小于轴点,另一部分是大于或等于轴点,划分的两部分仍然可能极不平衡,改进的算法如下:
#include <iostream>
#include <stdlib.h>
using namespace std;
template <typename T>
int __partition(T arr[], int l, int r)
{
swap(arr[l], arr[rand() % (r-l+1) + l]);
// 第一个值拿出来, 假设为轴点
T v = arr[l];
int i = l + 1, j = r;
while (true)
{
while (i <= r && arr[i] < v)
{
i++;
}
while (j >= l+1 && arr[j] > v)
{
j--;
}
if (i>j)
{
break;
}
swap(arr[i], arr[j]);
i++;
j--;
}
swap(arr[l], arr[j]);
return j;
}
template <typename T>
void __quickSort(T arr[], int l, int r)
{
if (l>=r)
{
return;
}
int p = __partition(arr, l, r);
__quickSort(arr, l, p-1);
__quickSort(arr, p+1, r);
}
template <typename T>
void quickSort(T arr[], int n)
{
srand(time(NULL));
__quickSort(arr, 0, n-1);
}
int main()
{
int a[10] = {10, 9, 8, 6, 11, 5, 4, 1, 0, 3};
quickSort(a, 10);
for (int i = 0; i< 10; i++)
{
cout << a[i] << " ";
}
cout << endl;
return 0;
}
python实现的快速排序:
# -*- coding:utf-8 -*-
import numpy as np
import time
def swap(list0, index1, index2):
b = list0[index1]
list0[index1] = list0[index2]
list0[index2] = b
def partition(list0, l, r):
swap(list0, l, np.random.randint(0, len(list0)) % (r-l+1) + l)
v = list0[l]
p = l+1
q = r
while True:
while (p <= r) and (list0[p] < v):
p = p + 1
while (q >= l+1) and (list0[q] > v):
q = q - 1
if p >= q:
break
else:
swap(list0, p, q)
p = p + 1
q = q - 1
swap(list0, l, q)
return q
def quick_sort(list0, l, r):
if l >= r:
return
p = partition(list0, l, r)
quick_sort(list0, l, p-1)
quick_sort(list0, p+1, r)
def main(list0, n):
np.random.seed(int(str(time.time()).split(".")[1]))
quick_sort(list0, 0, n-1)
print(list0)
if __name__ == "__main__":
a = [10, 9, 8, 6, 11, 5, 4, 1, 0, 3]
main(a, len(a))
三路快速排序算法
c++实现数组的三路快速排序:
#include <iostream>
#include <stdlib.h>
using namespace std;
template <typename T>
void __quickSort3Ways(T arr[], int l, int r)
{
if (l>=r)
{
return;
}
swap(arr[l], arr[rand() % (r-l+1) + l]);
T v = arr[l];
int lt = l;
int gt = r + 1;
int i = l + 1;
while (i < gt)
{
if (arr[i] < v)
{
swap(arr[i], arr[lt+1]);
i++;
lt++;
}
else if (arr[i]>v)
{
swap(arr[i], arr[gt-1]);
gt--;
}
else
{
i++;
}
}
swap(arr[l], arr[lt]);
__quickSort3Ways(arr, l, lt-1);
__quickSort3Ways(arr, gt, r);
}
template <typename T>
void quickSort3Ways(T arr[], int n)
{
srand(time(NULL));
__quickSort3Ways(arr, 0, n-1);
}
int main()
{
int a[10] = {10, 9, 8, 6, 11, 5, 4, 1, 0, 3};
quickSort3Ways(a, 10);
for (int i = 0; i< 10; i++)
{
cout << a[i] << " ";
}
cout << endl;
return 0;
}
python实现的三路快速排序:
# -*- coding:utf-8 -*-
import numpy as np
import time
def swap(list0, index1, index2):
b = list0[index1]
list0[index1] = list0[index2]
list0[index2] = b
def quick_sort(list0, l, r):
if l >= r:
return
swap(list0, l, np.random.randint(0, len(list0)) % (r - l + 1) + l)
v = list0[l]
p = l
q = r + 1
i = l + 1
while i < q:
if list0[i] < v:
swap(list0, i, p+1)
p = p + 1
i = i + 1
elif list0[i] > v:
swap(list0, i, q-1)
q = q - 1
else:
i = i + 1
swap(list0, l, p)
quick_sort(list0, l, p-1)
quick_sort(list0, q, r)
def main(list0, n):
np.random.seed(int(str(time.time()).split(".")[1]))
quick_sort(list0, 0, n-1)
print(list0)
if __name__ == "__main__":
a = [10, 9, 8, 6, 11, 5, 4, 1, 0, 3]
main(a, len(a))
求逆序对的数量——使用归并排序
# -*- coding:utf-8 -*-
class MergeSort:
def __init__(self, list0, n):
self.list0 = list0
self.n = n
@staticmethod
def _merge(list0, l, mid, r):
global count
list1 = list0[:]
p = l
q = mid + 1
for i in range(r-l+1):
if p > mid:
list0[i+l] = list1[q]
q = q + 1
elif q > r:
list0[i+l] = list1[p]
p = p + 1
elif list1[p] <= list1[q]:
list0[i+l] = list1[p]
p = p + 1
else:
list0[i+l] = list1[q]
count = count + mid + 1 - p
q = q + 1
def _merge_sort(self, list0, l, r):
if l >= r:
return
mid = int((l+r)/2)
self._merge_sort(list0, l, mid)
self._merge_sort(list0, mid+1, r)
self._merge(list0, l, mid, r)
def merge_sort(self):
self._merge_sort(self.list0, 0, self.n - 1)
def main():
global count
count = 0
# -------------------------------------
x = 0
a = [10, 9, 8, 6, 11, 5, 4, 1, 0, 3]
for i in range(len(a)-1):
for j in range(i+1, len(a)):
if a[j] < a[i]:
x = x + 1
# -------------------------------------
b = MergeSort(a, len(a))
b.merge_sort()
print(a)
print(count, x)
if __name__ == "__main__":
main()
求第n大的值——使用快速排序
# -*- coding:utf-8 -*-
import numpy as np
import time
import sys
def swap(list0, index1, index2):
b = list0[index1]
list0[index1] = list0[index2]
list0[index2] = b
def partition(list0, l, r, th):
swap(list0, l, np.random.randint(0, len(list0)) % (r-l+1) + l)
v = list0[l]
p = l+1
q = r
while True:
while (p <= r) and (list0[p] < v):
p = p + 1
while (q >= l+1) and (list0[q] > v):
q = q - 1
if p >= q:
break
else:
swap(list0, p, q)
p = p + 1
q = q - 1
swap(list0, l, q)
return q
def quick_sort(list0, l, r, th):
if l >= r:
return
p = partition(list0, l, r, th)
if p == len(list0) - th:
print(list0[p])
sys.exit(0)
elif p > len(list0) - th:
quick_sort(list0, l, p-1, th)
else:
quick_sort(list0, p+1, r, th)
def main(list0, n, th):
np.random.seed(int(str(time.time()).split(".")[1]))
quick_sort(list0, 0, n-1, th)
# print(list0)
if __name__ == "__main__":
a = [10, 9, 8, 6, 11, 5, 4, 1, 0, 3]
# 想找第th大的数的值, th小于等于len(a) + 1
th = 3
main(a, len(a), th)
结语
如果您有修改意见或问题,欢迎留言或者通过邮箱和我联系。
手打很辛苦,如果我的文章对您有帮助,转载请注明出处。