(参考百度百科,程序员求职宝典)
快速排序(Quicksort)是对冒泡排序的一种改进。
数组是A[0]……A[N-1]
任意选取一个数据(通常选用数组的第一个数)作为关键数据
所有比它小的数都放到它前面,所有比它大的数都放到它后面,这个过程称为一趟快速排序
稳定性:快速排序不是一种稳定的排序算法,也就是说,多个相同的值的相对位置也许会在算法结束时产生变动。
L={3,2,2}经过一趟排序,L={2,2,3},相对顺序发生改变
时间效率:与划分是否对称有关,最坏情况是两个区域分别包含n-1个元素和0个元素这种不对称若发生在每一层递归上,即对应初始排序表基本有序或基本逆序,就得到最坏情况下的时间复杂度O(n^2)
最理想划分,平衡划分,得到两个子问题的大小都不可能大于n/2,时间复杂度为O(nlog2n) (2在下面)
空间效率:快速排序是递归的,需借助一个递归工作栈来保存每一层递归调用的必要信息,其容量与递归调用的最大深度一致。最好log2(n+1)向上取整(2为底)最坏情况,进行n-1次递归,所以栈的深度为O(n);平均情况为O(log2n)2为底
示例
下标
|
0
|
1
|
2
|
3
|
4
|
5
|
数据
|
6
|
2
|
7
|
3
|
8
|
9
|
下标
|
0
|
1
|
2
| 3 |
4
|
5
|
数据
|
3
|
2
|
7
|
6
|
8
|
9
|
下标
|
0
|
1
|
2
|
3
|
4
|
5
|
数据
|
3
|
2
|
6
|
7
|
8
|
9
|
下标
|
0
|
1
|
2
|
3
|
4
|
5
|
数据
|
3
|
2
|
6
|
7
|
8
|
9
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
|
#include <iostream>
using
namespace
std;
void
Qsort(
int
a[],
int
low,
int
high)
{
if
(low >= high)
{
return
;
}
int
first = low;
int
last = high;
int
key = a[first];
/*用字表的第一个记录作为枢轴*/
while
(first < last)
{
while
(first < last && a[last] >= key)
{
--last;
}
a[first] = a[last];
/*将比第一个小的移到低端*/
while
(first < last && a[first] <= key)
{
++first;
}
a[last] = a[first];
/*将比第一个大的移到高端*/
}
a[first] = key;
/*枢轴记录到位*/
Qsort(a, low, first-1);
Qsort(a, first+1, high);
}
int
main()
{
int
a[] = {57, 68, 59, 52, 72, 28, 96, 33, 24};
Qsort(a, 0,
sizeof
(a) /
sizeof
(a[0]) - 1);
/*这里原文第三个参数要减1否则内存越界*/
for
(
int
i = 0; i <
sizeof
(a) /
sizeof
(a[0]); i++)
{
cout << a[i] <<
""
;
}
return
0;
}
/*参考数据结构p274(清华大学出版社,严蔚敏)*/
|
void QuickSort(ElemType A[],int low,int high)
{
if(low<high)
{
int pivotpos=Partition(A,low,high);
QuickSort(A,low,pivotpos-1);
QuickSort(A,pivotpos+1,high);
}
}
快速排序的分治partition过程有两种
1)
int Partition(ElemType A[],int low,int high)
{
ElemType pivot=A[low];
while(low<high)
{
while(low<high&&A[high]>=pivot)--high;
A[low]=A[high];
while(low<high&&A[low]<=pivot)++low;
A[high]=A[low];
}
A[low]=pivot;
return low;
}
2)两个指针索引一前一后逐步向后扫描
int Partition(ElemType A[],int p,int r)
{
ElemType x=A[r];
int i=p-1;
for(int j=p;i<=r-1;++j)
{
if(A[j]<=x)
{
++i;
exchange(A[i],A[j]);
}
}
exchange(A[i+1],A[r]);
return i+1;
}
这种方法保持相对顺序
快速排序效率分析
if l < r
s ← Partition( A[l..r] )
QuickSort( A[l..s-1] )
QuickSort( A[s+1..r] )
写出总体工作量表达式。
C(n)=Cpartition(n)+CQuickSort(s前面)+ CQuickSort(s后面)
上式依赖于s的位置。
考虑partition的基本操作:
比较
一次分区算法的比较次数是否和其他因素相关
对于一次长度为n的数组的分区算法,如果出现指针交叉,所执行的比较是n+1次,为什么?
相等,比较次数为n次
整个算法的最坏情况下:
在进行了n+1次比较后建立了分区,还会对数组进行排序,继续到最后一个子数组A[n-2..n-1]。总比较次数为:
Cworst(n)
=(n+1)+n+…+3
=(n+2)(n+1)/2-3
∈Θ(n2)
最好情况
每次分区执行n次
并且每次都是等分
Cbest(n)=2Cbest(n/2)+n
∈Θ(nlog2n)
平均情况
分裂点有可能在一次分区后出现在每个位置
设概率是1/n
一些排序算法的时间效率总结
合并排序最差Θ(nlog2n)
快速排序最优Θ(nlog2n)
最差Θ(n2)
平均Θ(1.38nlog2n)
选择排序 Θ(n2)
冒泡排序 Θ(n2)
快速排序用例:
排序后数组中间的数字即是所求
1)O(n)
int MoreThanHalfNum(int *numbers ,int length)
{
if(CheckInvalidArray(numbers,length))
return 0;
int middle=length>>1;
int start=0;
int end=length-1;
int index=Partition(numbers,length,start,end);
while(index!=middle)
{
if(index>middle)
{
end=index-1;
index=Partition(numbers,length,start,end);
}
else
{
start=index+1;
index=Partition(numbers,length,start,end);
}
}
int result=numbers[middle];
if(!CheckMoreThanHalf(numbers,length,result))
result=0;
return result;
}
bool g_bInputInvalid=false;
bool CheckInvalidArray(int *numbers,int length)//检查输入数组是否无效
{
g_bInputInvalid=false;
if(numbers==NULL&&length<=0)
g_bInputInvalid=true;
return g_bInputInvalid;
}
bool CheckMoreThanHalf(int *numbers,int length,int number)//检查数组出现频率最高的数字是否达到标准
{
int times=0;
for(int i=0;i<length;++i)
{
if(numbers[i]==number)
times++;
}
bool isMoreThanHalf=true;
if(times*2<=length)
{
g_bInputInvalid=true;
isMoreThanHalf=false;
}
return isMoreThanHalf;
}
2)O(n)
这段话个人理解:所求数比数组长度的一半还多,可以想象其它数字遇到它会消掉
int MoreThanHalf(int *numbers,int length)
{
if(CheckInvalidArray(numbers,length))
return 0;
int result=numbers[0];
int times=1;
for(int i=1;i<length;++i)
{
if(times==0)
{
result=numbers[i];
times=1;
}
else if(numbers[i]==result)
times++;
else
times--;
}
if(!CheckMoreThanHalf(numbers,length,result))
result=0;
return result;
}
2)荷兰国旗问题
将乱序的红蓝白三色小球排列成同颜色在一起的小球组(按红蓝白)
假设0为红球,2为篮球,1为白球
思路:三个指针,一前begin,一中current,一后end,begin和end都初始化为数组首部,end数组尾部
1.current遍历整个数组序列,current指1,不交换,current++;
2.指0时,与begin交换,而后current++,begin++;
3.指2时,与end交换,current不动,end--;
while(current<=end)
{
if(array[current]==0)
{
swap(array[current],array[begin]);
current++;
begin++;
}
else if(array[current]==1)
{
current++;
}
else
{
swap(array[current],array[end]);
end--;
}
3)让小写字母在所有大写字母的前面
void Partition(char A[],int low,int high)
{
while(low<high)
{
while(low<high&&isUpper(A[high]))--high;//isUpper()是自己写的判断大写字母的函数
while(low<high&&isLower(A[low]))++low;
char temp=A[high];
A[high]=A[low];
A[low]=A[high];
}
}
4)n个元素,包括0和非0元素,要求
1、排序后所有0在非0元素的前面,相对位置不发生改变
2、不使用额外存储空间
void Partition(int A[],int p,int r)
{
int i=r+1;
for(int j=r;j>=p;--j)
{
if(A[j]!=0)
{
--i;
int temp=A[i];
A[i]=A[j];
A[j]=temp;
}
}
}