快速排序 及应用

快速排序因其排序效率高, 成为 二十世纪最伟大的10大算法之一。 本文根据Introduction of algorithm (IOA)和 数据结构 李春葆版,研究快速排序算法。
快速排序算法最重要在Partition 过程,即将数组A[p, ..., r]分治排序,使得 A[p, ..., q-1]<=A[q]<=A[q+1, ..., r]。实现上述功能后,递归的排序A[q]之前及之后的数组。

首先给出IOA上的 算法流程

QUICKSORT(A, p, r)
1 if p < r
2    then q ← PARTITION(A, p, r)   //关键
3         QUICKSORT(A, p, q - 1)
4         QUICKSORT(A, q + 1, r)

PARTITION(A, p, r)
1  x ← A[r]
2  i ← p - 1
3  for j ← p to r - 1
4       do if A[j] ≤ x
5             then i ← i + 1
6                  exchange A[i] <-> A[j]
7  exchange A[i + 1] <-> A[r]
8  return i + 1


这个Partition算法设计的十分美, 其中有两个指针i,j,分别使得

IF p<= k <= i, then A[k] <= x

IF i+1 <= k <= j -1, then A[k] > x

IF k = r, then A[k] = x

x 称作是pivot (主元)。  交换过程如下:

当A[j] < x 时候(即找到一个小于pivot的元素时候),交换A[i], A[j].

最后在交换A[i+1] 和A[r](主元)。 返回值是pivot的位置。 这个算法十分的帅!!

 

递归过程比较好理解。代码如下。

void QuickSort_LastPivot(int* A, int left, int right)
{
	if(left < right)
	{
		int PartitionPos = QuickSort_Partion_LastPivot(A, left, right);
		QuickSort_LastPivot(A, left, PartitionPos);
		QuickSort_LastPivot(A, PartitionPos + 2, right);
	}
}



int QuickSort_Partion_LastPivot(
				int* A, int left, int right)
{
	int i = left - 1, j;
	int pivot = A[right];

	for(j = left; j < right; j++)
	{
		if(A[j] <= pivot)
		{
			i++;
			Swap(&A[i], &A[j]);
		}
	}
	Swap(&A[i+1], &A[right]); 
	return i;
}



 快速算法是一种不稳定算法,平均时间复杂度是 O(nlogn). 当最坏的情况下如果数组已经是排序好的,每次partition的大小都是一样的时候这样算法的复杂会reduce 到O(n^2). 影响算法性能其实在partition的 pivot的选择上, 上述选取最后一个节点为pivot。为了增加随机性,可以随机的选择一个pivot。 代码如下

 

int QuickSort_Random_Partion_LastPivot(
				int* A, int left, int right)
{
	srand(time(NULL));
	Swap(&A[right], &A[(rand()%(right - left + 1)) + left]);
	return QuickSort_Partion_LastPivot(A, left, right);
}


在李春葆的数据结构Partition是另外一种算法,这个partition过程其实是HOARE-PARTITION 过程

 

void Partition_HOARE(int* list, int left, int right)
{
	int pivot = list[right];
	int i = left, j = right;
	while(left < right)
	{
		while(i < j && list[i] <= pivot)
		{
			i++;
		}
		list[j] = list[i];
	
		while(j > i && list[j] >= pivot)
		{
			j--;
		}
		list[i] = list[j];		
	}
	list[i] = pivot;

	return i;
}


李春葆第二章练习题2.4的第二个解法。(第一个解法有误,当存在相等的两个元素时候第一个解法死循环)

效率上来说还是IOA上的partition方法更加高效一些。

 

 之前面试被XJ问道如何根据快速排序找到中位数的算法,当时好久不看这些东西 真是没有想出来。其实在快排上可以很快找到。

当partition的位置大于中间的位置,只在左面去Partition;

当partition的位置小于中间的位置,只在右面去Partition;

等于时候直接返回。

其实该方法可以快速的找到任意一组数中任意rank位置的数,保证左面的都小于他,右面的都大于他。

程序如下

void FindNumberAtRank(int* A, int left, int right, int rank)
{
	if (rank < left && rank > right)
		return;
	if(left < right)
	{
		int PartitionPos = QuickSort_Partion_LastPivot(A, left, right);
		if ((PartitionPos + 1) > rank)
			FindNumberAtRank(A, left, PartitionPos, rank);
		else if ((PartitionPos + 1) < rank)
			FindNumberAtRank(A, PartitionPos, right, rank);
		else
			return ;
	}
}

// 测试
int main(int argc, _TCHAR* argv[])
{
	clock_t begin = clock();

	int A[11] = {43,3,2,4,1,5,6,4,9,78,8};
	int rank = 4;
	qs.FindNumberAtRank(A, 0, num-1, rank);
	printf("Then ranked %d is %d\n", rank, A[rank]);
	for(int i = 0; i < num; i++)
 	{
 		printf("%d ", A[i]);
 	}
	printf("\n");	
	clock_t end = clock();
	printf("Running time: %f s\n", float(end-begin)/1000l );
	system("pause");
	return 0;
}


程序保证在Rank 左面的数都小于 A[rank], 在 rank右面的数都大于 A[rank]。

这个算法的复杂度是O(n*lgn) - n, 每次平均少排一半的元素,总共下来(1/2 + 1/4 + 1/8 + ....)*n = n 次 排序。所以复杂度是 O(n*lgn) - n。

因为面的不好,心中一直抑郁,这回终于解开我心中的疙瘩。

 

其实这个Partition过程还有好多应用,比如将 元素划分section,使得每个section在一个范围内。即保留多个指针。

 

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值