《算法导论》学习笔记
第4章入门篇(2)——算法初步(7)
4.7 其他高效技巧与算法
4.7.1 打表
打表是一种典型的用空间换时间的技巧,一般指将所有可能需要用到的结果事先计算出来,这样后面需要用到时就可以直接查表获得。
打表常见的几种方法:
- 在程序中一次性计算出所有需要用到的结果,之后的查询直接取这些结果
- 在程序B中分一次或多次计算出所有需要用到的结果,手工把结果写在程序A的组中,然后在程序A中就可以直接使用这些结果。
- 对一些感觉不会做的题目,先用暴力程序计算小范围数据的结果,然后找规律,或许就能发现一些“蛛丝马迹”
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;
}