快排

快排

分治:
  • 分解:将数组A[p,r]划分成两个(可能为空)的子数组A[p,q-1]和A[q+1,r],使得A[p,q-1]
    中的每一个元素都小于等于A[q],A[q]也小于等于A[p+1,r]中的每个元素
  • 解决:递归调用快排,对两个子数组进行排序
  • 合并:因为是基于地址的排序,所以不需要进行合并操作,原数组已经有序
partition函数:

先贴出代码

int partition(int *a, int p, int r) {
	int x = a[r];
	int i = p - 1;
	for (int j = p; j < r; j++)
		if (a[j] <= x) {
			++i;
			swap(a[i], a[j]);
		}
	swap(a[i + 1], a[r]);
	return i + 1;
}

在for循环中,每个部分都满足如下性质:
对任意数组下标k,有:

  1. 若p <= k <= i, 则A[k] <= x
  2. 若i+1 <= k <= j-1, 则A[k] > x
  3. 若k = r, 则A[k] = x

重点理解一下partition函数的作用:
在partition中完成一趟划分,选取一个基准元素(pivot element),将小于它的放到它的左边,将大于它的放到他的右边,最后返回基准的位置。
数组被划分成四个部分(如下图)

  • 浅阴影部分的元素都在划分的第一部分,其值都不大于x
  • 深阴影部分的元素都在划分的第二部分,其值都大于x
  • 无阴影的部分则是还未划分入这两个部分中的任意一个
  • 最后一个元素为基准(主元)
这里写图片描述

过程:以图中的数组为例

  • 初始化变量,基准元素x为最右边的4,i=p-1,j=p,数组中的元素均为被放到前两个部分中的任意一个(i为较小部分的最后一个位置,j为较大部分的最后一个位置的下一个位置,j也表示当前待加入到前两个部分的元素下标)
  • 对于第一个元素2,2<4,则i++,将2与他本身进行交换,2放入到元素值较小的部分
  • 对于第二个元素8,8>4,则直接j++。7同理。8与7放入到元素较大的部分
  • 对于第四个元素1,1<4,则将A[j] = 1与第一个大于基准的元素A[i+1] = 8进行交换(即swap(A[j], A[++i])),数值较小的部分规模增加。1进入较小的部分,8还在较大的部分,只是i与j的值都加了一,表示两部分的规模都变大了
  • 对于第五个元素3,3<4将3与第一个大于基准的元素交换,即交换7与3的位置(swap(A[j], A[++i])),3进入较小部分
  • 剩余元素都大于4,直接j++,将其放入较大的部分即可
  • 最后一个基准元素4,将它与第一个大于基准的元素交换,即交换A[i+1]与A[r],这样基准元素就放到了两个部分之间

综上:如果A[j]>x,需要做的只是将j的值加1。如果A[j]<=x,则将下标 i 加1,并交换A[i]与A[j],再将 j 的值加1。最后将主元A[r]与最左边的大于x的元素A[i+1]进行交换,即可将基准元素移动到正确的位置上。函数最终返回基准元素的下标
partition在子数组A[p,r]上的时间复杂度为O(n), n=r-p+1

快排
void quicksort(int *a, int p, int r) {   
	if (p < r) {
		int q = partition(a, p, r);
		quicksort(a, p, q - 1);
		quicksort(a, q + 1, r);
	}
}

每次递归,先进行一趟划分,然后选出基准元素,再分别对基准左右两侧的子数组递归调用快排即可

时间复杂度分析:

最好情况划分:partition得到的两个子数组的规模都(约)等于n/2,在这种情况下,算法的时间复杂度为:T(n) = 2T(n/2) + O(n),最终求得T(n) = O(nlogn)
最坏情况划分:每次划分得到的两个子问题都分别包含了n-1个元素和0个元素,则
T(n) = T(n-1) + T(0) + O(n) = T(n-1) + O(n)
最终求得T(n) = O(n^2)
平均情况:利用递归树求得平均时间复杂度为O(nlogn)

快排的随机化版本

基准元素的选取直接影响了整个算法的时间性能。可以在算法中引入随机性,从而使得算法对于所有的输入都能获得较好的期望性能
在A[p,r]中随机选取一个元素作为主元,再将A[r]与随机选取出的元素进行交换
通过对序列p-r的随机抽样,可以保证基准元素x=A[r]是等概率地从子数组的r-p+1个元素中选取的。因为主元素是随机选取的,所以期望的平均情况下,对输入数组的划分也是比较均衡的

int partition(int *a, int p, int r) {
	int x = a[r];
	int i = p - 1;
	for (int j = p; j < r; j++)
		if (a[j] <= x) {
			++i;
			swap(a[i], a[j]);
		}
	swap(a[i + 1], a[r]);
	return i + 1;
}

int randomPartition(int *a, int p, int r) {
	int i = p + rand() % (r - p + 1);
	swap(a[r], a[i]);
	return partition(a, p, r);
}

void randoomQuicksort(int *a, int p, int r) {
	if (p < r) {
		int q = randomPartition(a, p, r);
		randoomQuicksort(a, p, q - 1);
		randoomQuicksort(a, q + 1, r);
	}
}

期望运行时间T(n) = O(nlgn)

方法二:

双向扫描

void quickSort(int a[], int l, int r) {
	if (l < r) {
		int p = partition(a, l, r);
		quickSort(a, l, p - 1);
		quickSort(a, p + 1, r);
	}
}

int partition(int a[], int p, int r) {
	int i = p, j = r + 1;
	int t = a[p];
	while (1) {
		/*从左边开始找第一个比t大的,从右边开始找第一个比t小的,然后交换两个元素*/
		while (a[++i] < t && i < r);
		while (a[--j] > t && j);
		if (i >= j)	//如果i>j,则表示已经有序,不需要交换
					//如果i == j,也无交换的必要
			break;
		swap(a[i], a[j]);
	}
	//交换基准
	a[p] = a[j];
	a[j] = t;

	return j;
}

void quickSort(int s[], int left, int right) {
	if (left< right) {
		int begin = left, end = right, x = s[left];
		while (begin < end) {
			while (begin < end && s[end] >= x)		// 从右向左找第一个小于x的数  
				end--;
			if (begin < end)
				s[begin++] = s[end];
			while (begin < end && s[begin]< x)		// 从左向右找第一个大于等于x的数  
				begin++;
			if (begin < end)
				s[end--] = s[begin];
		}
		s[begin] = x;
		quickSort(s, left, begin - 1);				// 递归调用  
		quickSort(s, begin + 1, right);
	}
}

此外还有“三数取中划分”,留作以后进行补充

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值