算法笔记-快速排序衍生

two pointers

快速排序

问题:将一个无序数组排序,要求时间复杂度优于 O ( n 2 ) O(n^2) O(n2)
思路:先定位主元位置,主元左侧元素全部小于主元,右侧元素全部大于主元

//实质上是递归,只不过要先找到主元,根据它把数组分成两部分
int Partition(int A[], int left, int right){
	int temp=A[left];
	while(left<right)//left,right两步走
	{
		while(left<right && A[right]>temp)--right;//注意每次还是要判断left<right,因为内循环里left,right会改变
		A[left]=A[right];
		while(left<right && A[left]<=temp)++left;
		A[right]=A[left];
}
	A[left]=temp;
	return left;
}
void quickSort(int A[], int left, int right){
	if(left<right){
		int pos=Partition(A,left,right);
		quickSort(A,left,pos-1);//注意是pos-1,pos已经固定好位置了
		quickSort(A,pos+1,right);
}
}

随机选择算法

问题一:从一个无序数组中求出第K大的数,要求对任何输入都达到时间复杂度O(n)
思路:因为时间复杂度O(n)<O(nlogn),所以先排序再取出第K的元素不可行。理解快速排序后,随机选择算法原理类似,也需要先定位主元的位置p;这时主元已经是它左侧元素中的第M =(p-left+1)大,如果我们要找的是第K大,而K<M,则只需要往左递归,找A[left,…,(p-1)]中的第K大;如果K>M,则只需要往右递归,找A[(p+1),…,right]中的第(K-M)大

int randSelect(int A[], int left, int right, int K){
	int(left==right)return A[left];
	int p=Partition(A,left,right);//这一步如果使用随机数而不是A[left]作为初始temp,可以加速,因此可以采用下面说的randPartition()
	int M=p-left+1;
	if(K<M) return randSelect(A,left,p-1,K);
	else if(K=M)return A[P];
	else return randSelect(A,p+1,right,K-M);
}

问题二:给定一个由整数组成的集合,元素各不相同,把它分成两个子集合,要求两个子集合的并为原交集,交为空集,同时两个集合的元素个数 ∣ n 1 − n 2 ∣ |n_1-n_2| n1n2尽可能小的前提下,使它们各自的元素之和 ∣ S 1 − S 2 ∣ |S_1-S_2| S1S2尽可能大,求这个 ∣ S 1 − S 2 ∣ |S_1-S_2| S1S2
思路:如果时间复杂度没有特别要求,一个较简便的方法是先对数组排序,取前、后半部分分别为两个子集,这样 ∣ S 1 − S 2 ∣ |S_1-S_2| S1S2即最大。更优的做法是使用随机选择算法,这个问题实际上是求原集合中元素的第 n 2 \frac{n}{2} 2n大,其中一个子集元素全部小于该元素,另一个子集元素全部大于该元素,而两个分得集合内部顺序不作要求

#include <cstdio>
#include <cstdlib>
#include <ctime>
#include <algorithm>
using namespace std;
const int maxn=100010;
int A[maxn],n;

int randPartition(int A[], int left, int right){
	//int p=(round(1.0*rand()/RAND_MAX*(right-left)+left));
	int p=rand()%(right-left+1)+left;//[left,right]
	swap(A[p],A[left]);//algorithm头文件
	//以下是原Partition()
	int temp=A[left];
	while(left<right)//left,right两步走
	{
		while(left<right && A[right]>temp)--right;//注意每次还是要判断left<right,因为内循环里left,right会改变
		A[left]=A[right];
		while(left<right && A[left]<=temp)++left;
		A[right]=A[left];
}
	A[left]=temp;
	return left;
}
//现在的randSelect()不需要返回值,因为最终是计算$|S_1-S_2|$
void randSelect(int A[], int left, int right, int K){
	int(left==right)return A[left];
	int p=randPartition(A,left,right);
	int M=p-left+1;
	if(K<M) randSelect(A,left,p-1,K);
	else if(K=M)return;
	else randSelect(A,p+1,right,K-M);
}
int main(){
	srand((unsigned)time(NULL));//很重要,初始化随机数种子
	int sum=0,sum1=0;
	scanf("%d",&n);
	for(int i=0;i<n;++i){
		scanf("%d",&A[i]);
		sum+=A[i];//为了后半部分直接减
}
	randSelect(A,0,n-1,n/2);
	for(int i=0;i<n/2;++i){
		sum1+=A[i];
}
	printf("%d\n",(sum-sum1)-sum1);
	return 0;
}

以前踩过的低级坑:scanf每次读入一个%d变量,用while控制读入多个;或者用几个连在一起写的%d一次性读入多个变量,键盘输入的时候都是用空格隔开就好(如果变量类型不是%c就不会接受空格,会自动跳过);重新写scanf读入的变量,从键盘输入的时候用回车键分开。

其他的随机数的范围通式

产生一定范围随机数的通用表示公式是:
要取得[0,n) 就是rand()%n 表示 从0到n-1的数
要取得[a,b)的随机整数,使用(rand() % (b-a))+ a;
要取得[a,b]的随机整数,使用(rand() %(b-a+1))+ a;
要取得(a,b]的随机整数,使用(rand() % (b-a))+ a + 1;

通用公式:a +rand() % n;其中的a是起始值,n是整数的范围
要取得a到b之间的随机整数,另一种表示:a + (int)b * rand() / (RAND_MAX + 1)
要取得0~1之间的浮点数,可以使用rand() / double(RAND_MAX)


参考

c++随机数产生
算法笔记-胡凡

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值