《算法导论》学习笔记

第4章入门篇(2)——算法初步(7)

4.7 其他高效技巧与算法

4.7.1 打表
打表是一种典型的用空间换时间的技巧,一般指将所有可能需要用到的结果事先计算出来,这样后面需要用到时就可以直接查表获得。
打表常见的几种方法:

  1. 在程序中一次性计算出所有需要用到的结果,之后的查询直接取这些结果
  2. 在程序B中分一次或多次计算出所有需要用到的结果,手工把结果写在程序A的组中,然后在程序A中就可以直接使用这些结果。
  3. 对一些感觉不会做的题目,先用暴力程序计算小范围数据的结果,然后找规律,或许就能发现一些“蛛丝马迹”

4.7.2 活用递推

PAT B1040/A1093
在这里插入图片描述
在这里插入图片描述

#include <iostream>
#include <string>
using namespace std;
int main() {
	string s;
	cin >> s;
	int len = s.length(), result = 0, countp = 0, countt = 0;
	for (int i = 0; i < len; i++) {
		if (s[i] == 'T')
			countt++;
	}
	for (int i = 0; i < len; i++) {
		if (s[i] == 'P') countp++;
		if (s[i] == 'T') countt--;
		if (s[i] == 'A') result = (result + (countp * countt) % 1000000007) %1000000007;
	}
	cout << result;
	return 0;
}

4.7.3 随机选择算法

第一个问题:如何从一个无序的数组中求出第K大的数
直接想法:对数组排序,然后直接取出第K个元素即可。但是这样做法需要0(nlogn)的时间复杂度。
随机选择算法:对任何输入都可以达到0(n)的期望时间复杂度。随机选择算法的原理类似于 4.6.3中介绍的随机快速排序算法.当対A[left, righ]执行一次randParition函数之后,主元左侧的元素个数就是确定的,且它们都小主元。假没此主元是 A[p],那么A[p]就是A[left, righ]中的第p-left+1大的数。不妨令M表示p-left+1,那么如果K==M成立, 说明第K大的数就是主元A[p]:如果K<M成立,就说明第K大的数在主元左侧,即A[left…(p-1)]中的第K大,往左侧递归即可;如果K>M成立,则说明第K大的数在主元右侧即A[(p+1)… right]中的第K-M大,往右侧递归即可。

int randSelect(int A[],int left,int right,int K){
	if(left==right) return A[left];
	int p=randPartition(A,left,right);
	int M=p-left+1;
	if(K==M) return A[p];
	if(K<M){
		return randSelect(A,left,p-1,K);
	}else{
		return randSelect(A,p-1,right,K-M);
	}
}

第二个问题:给定一个由整数组成的集合,集合中的整数各不相同,现在要将它分为两个子集合,使得这两个子集合的并为原集合、交为空集,同时在两个子集合的元素个数n1与n2之差的绝对值|n1-n2|尽可能小的前提下,要求它们各自的元素之和S1与S2之差的绝对值|S1-S2|大
直接思路:将原集合中的元素从小到大排序,取排序后的前n/2个元素作为其中一个子集合,剩下的元素作为另一个子集合即可,时间复杂度为0(nlogn)。
随机选择算法:求元素集合中的第n/2大,同时根据这个数把集合分为两部分,使得其中一个子集合中的元素都不小于这个数,而另一个子集合中的元素都大于这个数,至于两个子集合内部元素的顺序不需要关心。

#include <iostream>
#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));
	swap(A[p],A[left]);
	int temp=A[left];
	while(left<right){
		while(left<right&&A[right]>temp) right--;
		A[left]=A[right];
		while(left<right&&A[left]<=temp) left++;
		A[right]=A[left];
	}
	A[left]=temp;
	return left;
}
void randSelect(int A[],int left,int right,int K){
	if(left==right) return;
	int p=randPartition(A,left,right);
	int M=p-left+1;
	if(K==M) return;
	if(K<M){
		randSelect(A,left,p-1,K);
	}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;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值