排序算法之快速排序

(参考百度百科,程序员求职宝典)

快速排序(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为底

一趟快速排序的算法是:
1)设置两个变量i、j, 排序开始的时候:i=0,j=N-1;
2)以第一个数组元素作为关键数据,赋值给 key,即 key=A[0];
3)从j开始向前搜索,即由后开始向前搜索(j--),找到第一个小于 key的值A[j],将A[j]和A[i]互换;
4)从i开始向后搜索,即由前开始向后搜索(i++),找到第一个大于 key的A[i],将A[i]和A[j]互换;
5)重复第3、4步,直到i=j; (3,4步中,没找到符合条件的值,即3中A[j]不小于 key,4中A[i]不大于 key的时候改变j、i的值,使得j=j-1,i=i+1,直至找到为止。找到符合条件的值,进行交换的时候i, j指针位置不变。另外,i==j这一过程一定正好是i+或j-完成的时候,此时令循环结束)。

示例

假设用户输入了如下数组:
下标
0
1
2
3
4
5
数据
6
2
7
3
8
9
创建变量i=0(指向第一个数据), j=5(指向最后一个数据), k=6( 赋值为第一个数据的值)。
我们要把所有比k小的数移动到k的左面,所以我们可以开始寻找比6小的数,从j开始,从右往左找,不断递减变量j的值,我们找到第一个下标3的数据比6小,于是把数据3移到下标0的位置,把下标0的数据6移到下标3,完成第一次比较:
下标
0
1
2
3
4
5
数据
3
2
7
6
8
9
i=0 j=3 k=6
接着,开始第二次比较,这次要变成找比k大的了,而且要从前往后找了。递加变量i,发现下标2的数据是第一个比k大的,于是用下标2的数据7和j指向的下标3的数据的6做交换,数据状态变成下表:
下标
0
1
2
3
4
5
数据
3
2
6
7
8
9
i=2 j=3 k=6
称上面两次比较为一个循环。
接着,再递减变量j,不断重复进行上面的循环比较。
在本例中,我们进行一次循环,就发现i和j“碰头”了:他们都指向了下标2。于是,第一遍比较结束。得到结果如下,凡是k(=6)左边的数都比它小,凡是k右边的数都比它大:
下标
0
1
2
3
4
5
数据
3
2
6
7
8
9
如果i和j没有碰头的话,就递加i找大的,还没有,就再递减j找小的,如此反复,不断循环。注意判断和寻找是同时进行的。
然后,对k两边的数据,再分组分别进行上述的过程,直到不能再分组为止。

在c++中可以用函数qsort()可以直接为 数组 进行排序
void qsort(void *base, int nelem, int width, int (*fcmp)(const void *,const void *));
参数:
  1 待 排序数组首地址
  2 数组中待排序元素数量
  3 各元素的占用空间大小
  4 指向函数的 指针,用于确定排序的顺序

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;
		}
	}
}


  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值