西北工业大学 算法设计与分析 复习提纲
(是博主当时考前搜刮网上资料+自己整理的一些考点,可供参考
(博主是蠢蛋 有些备注写得很糟糕是为了自己能尽量理解,碎碎念也很多 dbqdbq
理论题
1.算法的基本概念,性质及其与程序的联系与区别
算法:算法是指解决问题的一系列计算步骤,是解决方案准确而完整的描述。
算法的基本性质:
输入性:有零个或多个外部量作为算法的输入。
输出性:算法产生至少一个量作为输出。
确定性:组成算法的每条指令必须是清晰的,无二义性的。
有限性:算法中指令的执行次数有限和执行的时间有限。
可行性:算法中每一条运算都必须足够基本,能够精确的执行。
程序(算法与程序的区别、联系):程序是算法用某种设计语言的具体实现,程序可以不满足算法的有限性。
2.算法和数据结构
联系:数据结构是算法设计的基础。算法的操作对象是数据结构,在设计算法时,通常要构建适合这种算法的数据结构。数据结构设计主要是选择数据的存储方式,如确定求解问题中的数据采用数组存储还是采用链表存储等。算法设计就是在选定的存储结构上设计一个满足要求的好算法。
区别:数据结构关注的是数据的逻辑结构、存储结构以及基本操作,而算法更多的是关注如何在数据结构的基础上解决实际问题。算法是编程思想,数据结构则是这些思想的逻辑基础。
3.算法的复杂性,如何度量,算法的渐进复杂度和阶
算法分析是分析算法占用计算机资源的情况。
一个算法的评价主要从时间复杂度和空间复杂度来考虑。
时间复杂度是指执行算法所需要的时间。一个算法是由控制结构(顺序、分支和循环3种)和原操作(指固有数据类型的操作)构成的,算法的运行时间取决于两者的综合效果。 设n为算法中的问题规模,通常用大O、大Ω和大Θ等三种渐进符号表示算法的执行时间与n之间的一种增长关系。
渐进符号(O 、Ω和Θ):
定义一(大O符号):f(n)=O(g(n))(读作“f(n)是g(n)的大O”)当且仅当存在正常量c和n0,使当n≥n0时,f(n)≤cg(n),即g(n)为f(n)的上界。
大O符号用来描述增长率的上界,表示f(n)的增长最多像g(n) 增长的那样快,也就是说,当输入规模为n时,算法消耗时间的最大值。这个上界的阶越低,结果就越有价值。
一个算法的时间用大O符号表示时,总是采用最有价值的g(n)表示,称之为“紧凑上界”或“紧确上界”。
一般地,如果f(n)多项式的最高次为m次,则有f(n)=O(n^m)。
定义二(大Ω符号):f(n)= Ω(g(n))(读作“f(n)是g(n)的大Ω”)当且仅当存在正常量c和n0,使当n≥n0时,f(n)≥cg(n),即g(n)为f(n)的下界。
一般地,如果f(n)多项式的最高次为m次,则有f(n)=Ω(n^m)。
定义三(大Θ符号):f(n)= Θ(g(n))(读作“f(n)是g(n)的大Θ”)当且仅当存在正常量c1、c2和n0,使当n≥n0时,有c1g(n)≤f(n)≤c2g(n),即g(n)与f(n)的同阶。
If f(n) is in O(kg(n)) for any constant k > 0, then f(n) is in O(g(n)).
If f1(n) is in O(g1(n)) and f2(n) is in O(g2(n)), then (f1 + f2)(n) is in O(max(g1(n), g2(n))).
If f1(n) is in O(g1(n)) and f2(n) is in O(g2(n)) then f1(n)f2(n) is in O(g1(n)g2(n)).
examples:
sum = 0;
for (j=1; j<=n; j++)
for (i=1; i<=j; i++)
sum++;
for (k=0; k<n; k++)
A[k] = k;
O(n^2)
sum1 = 0;
for (k=1; k<=n; k*=2) //k=k*2且k上限为n→k经历了logn次运算
for (j=1; j<=n; j++) //j经历了n次
sum1++;
sum2 = 0;
for (k=1; k<=n; k*=2)
for (j=1; j<=k; j++)
sum2++;
O(nlogn)
void fun(int n)
{ int s=0,i,j,k;
for (i=0;i<=n;i++)
for (j=0;j<=i;j++)
for (k=0;k<j;k++)
s++;
}
O(n^3)
最好、最坏、平均情况:
设一个算法的输入规模为n,Dn是所有输入的集合,任一输入I∈Dn,P(I)是I出现的概率,T(I)是算法在输入I下所执行的基本语句次数,则该算法的平均执行时间为:A(n)=ΣP(I)*T(I)
也就是说算法的平均情况是指用各种特定输入下的基本语句执行次数的带权平均值。
算法的最好情况为:G(n)=min(T(I)),是指算法在所有输入I下所执行基本语句的最少次数。
算法的最坏情况为:W(n)=max(T(I)),是指算法在所有输入I下所执行基本语句的最大次数。
递归算法的时间复杂度分析
关键:建立递归关系式
example:
void mergesort(int a[],int i,int j)
{ int m;
if (i!=j)
{ m=(i+j)/2;
mergesort(a,i,m);
mergesort(a,m+1,j);
merge(a,i,j,m);
}
}
//设调用mergesort(a,0,n-1)的执行时间为T(n)
//T(n)=O(1) 当n=1
//T(n)=2T(n/2)+O(n) 当n>1
O(nlogn)
主方法求递归式的时间复杂度
递归能划分成a个能n/b的问题和一个f(x)的问题,就去比较nlogba和f(x)的复杂度,谁大就取哪个,相等就是n^logba*logn
空间复杂度是指算法所消耗的内存空间。
一般来说,计算机算法的复杂性是问题规模n 的函数f(n),当问题规模n充分大时,我们只需考虑算法复杂性在渐近意义下的阶。
5.几大算法的基本思想
分治法:将问题规模为n的问题分解成k个规模较小的子问题,这些子问题互相独立且与原问题相同,递归地解这些子问题,然后将子问题的解合并得到原问题的解。
回溯算法:在包含原问题所有解的解空间树中,用深度优先搜索的策略,从根节点出发深度搜索解空间树。当搜索到某一节点时,先判断该节点是否包含原问题的解,若包含则继续搜索下去,否则逐层向其祖先节点回溯。
基本思想:
1.针对所给问题,定义问题的解空间
2.确定易于搜索的解空间结构
3.以深度优先方式搜索解空间,并在搜索过程中用剪枝函数避免无效搜索。
分支限界法:以广度优先或最小耗费优先搜索的方法搜索解空间树。每个活结点只有一次机会成为扩展节点,当成为扩展节点后一次性生成所有儿子节点,在这些儿子节点中,导致不可行解或非最优解的儿子节点被抛弃,其余的儿子节点加入到活结点表的队尾。此后从活结点表的队头取下一个节点成为扩展节点,重复上述步骤,直到找到最优解或活结点表为空时结束。
分支限界法分为队列式(FIFO)和优先队列式(优先级最高的先扩展)
动态规划:将原问题分解成若干个规模较小的子问题,先求解这些子问题,然后通过子问题的解得到原问题的解。
基本步骤:
找出最优解的性质,并刻画其结构特征;
递归地定义最优值;
以自底向上的方式计算出最优值;
根据计算最优解时得到的信息,构造最优解。
基本要素:最优子结构性质,无后效性和子问题重叠性质。
最优子结构性质是指问题的最优解所包含的子问题的解也是最优的;
无后效性是指某个状态以前的过程不会影响以后状态的决策,而只与当前状态有关;
子问题重叠性质是指一个子问题在下一阶段的决策中可能会被多次使用,动态规划法正是利用了这一特性,对每一个子问题只计算一次然后将结果保存在一个表格中,当需要时只需要从表格中取出结果即可。
贪心算法:贪心算法总是做出在当前看来是最好的选择,即贪心算法不是从整体最优上加以考虑,所做的选择只是在某种意义上的局部最优选择。
贪心选择性质:问题的整体最优解可以通过一系列局部最优的选择(即贪心选择)来达到。是贪心和动规的主要区别。
最优子结构性质:当一个问题的最优解包含其子问题的最优解时,称为有最优子结构性质。贪心和动规都有。
基本步骤:
从问题的某个初始解出发;
采用循环语句,当可以向求解目标前进一步时,就根据局部最优策略,得到一个局部最优解,缩小问题的规模;
将所有局部最优解综合起来,得到原问题的解。
基本要素:贪心选择性质,最优子结构性质,无后效性。
贪心选择性质是指所求问题的整体最优解可以通过一系列局部最优的选择来达到。
备忘录法:用表格保存已解决的子问题答案,通过自顶向下的递归方式求解,当递归到已经求解过的子问题时,直接从表格中返回子问题的解。
6.几大算法的联系与区别
分治算法vs动态规划(重复计算与否)
联系:基本思想相同,都是将原问题分解成若干个子问题,先求解子问题,然后通过子问题的解得到原问题的解。
区别:适用于动态规划法求解的问题,经分解得到的子问题往往不是相互独立的,分治法的子问题被重复多次计算,而动态规划法将子问题的解存在一个表格中,从而避免了子问题重复计算的问题。
动态规划vs备忘录法(记忆化搜索)(自顶向下&自底向上)
联系:都是用表格保存已解决的子问题答案,避免子问题重复计算
区别:动态规划法的递归方式是自底向上的,而备忘录法的递归方式是自顶向下的。
分支限界法vs回溯法(dfs和bfs的区别)
区别:对扩展节点采用的扩展方式不同,分支限界法采用广度优先搜索,当搜索到最优解或活结点表为空时结束,每个结点只有一次成为活结点的机会。而回溯法采用深度优先搜索,只有当活结点的所有可行子节点都被遍历之后才结束。
贪心算法vs动态规划(都要最优子结构 自底向上&自顶向下 局部最优&整体最优)
联系:都要求问题具有最优子结构性质。
区别:在动态规划法中,每一步所做的选择往往依赖于相关子问题的解,因此只有在求出相关子问题后才能做出选择,所采用的递归方式是自底向上的;而在贪心算法中,仅在当前状态下做出最好选择,即局部最优选择,然后再去解做出这个选择之后产生的相应子问题,所采用的递归方式是自顶向下的。
7.一些算法的一般模式
回溯算法的一般模式
int n,a[N]; //n为考察对象的个数
void backtrack (int m)
{
if (m>n)
output(); //叶子节点,输出可行解
else
for(int i=0;i<=k;i++) //当前节点的所有子节点
{
a[m]=value(i); //每个子节点的值赋值给a[m]
if (constraint(m)&&bound(m)) //满足约束条件和限界条件
backtrack(m+1); //递归下一层
}
}
回溯法搜索子集树
int n,a[N]; //n为考察对象的个数
void search(int m) //m为当前考察对象
{
if(m>n) //递归结束条件
output(); //相应的输出
else
for(int i=0;i<=1;i++) //控制分支数目,此处只有两个分支,0、1表示是否装入背包
{
a[m]=i;
if(constraint(m)&&bound(m)) //剪枝函数:约束函数+限界函数 ——> 递归
search(m+1);
}
}
//*************************//
int n,a[N]; //n为考察对象的个数
void search(int m) //m为当前考察对象
{
if(m>n) //递归结束条件
output(); //相应的输出
else
{
a[m]=0;
search(m+1);//0表示不要该物品
a[m]=1;
search(m+1);//1:要该物品
}
}
回溯法搜索排列树
int n,a[N]; //n为考察对象的个数,a[N]初始化为排列的一种
void search(int m) //m为当前考察对象
{
if(m>n) //递归结束条件
output(); //相应的输出
else
for (int i=m;i<=n;i++)
{
swap(a[m],a[i]); //调换位置
if (canplace(m)) //若不满足限制函数则不继续深入
search(m+1);
swap(a[m],a[i]); //调回原位
}
}
分支限界法的一般模式
struct node
{
int x,y;
}e1,e2;
vector <node> q; //open表中可以放结点,用vector存放队列这种数据类型
int flag[N][N]; //标记节点是否已经到达过,初始化为0
void search()
{
cin>>e1.x>>e1.y;
q.push(e1); //q.push意为将e1结点放入队列末端
flag[e1.x][e1.y]=1; //标记e1到达过
while(!q.empty()) //一定要判断一下队列是否为非空!!!
{
e2=q.front(); //q.front为取队列的队头,让e2=队头以产生e2的扩展结点
q.pop(); //让队头滚出去
for(对扩展节点e2得到的每个新节点e1)
{
if(e1是目标节点)
{
output();
return; //输出结果并返回
}
if(flag[e1.x][e1.y]==0 && bound(e1)) //满足限界条件且未到达过 不满足的点就直接舍弃
{
q.push(e1);
flag[e1.x][e1.y]=1; //标记e1到达过
}
}
}
}
8.手写代码会考的题
八皇后主要代码结构(回溯)
bool place(int i, int j) //用i,q[i]记录已经放下了的点,j来试探最新一行能放的列
{
if(i==1) return true;
int k=1;
while(k<i)
{
if(q[k]=j||abs(q[k]-j)==abs(i-k))
{
return false;//不能放
k++;//判断下一列
}
return true;
}
}
void queen(int i, int n)//dfs主要部分
{
if(i>n) output(); break;
else
{
for(int j=1; j<=n; j++)
if(place(i, j))
{
q[i]=j; //能放的话就记录下j
queen(i+1, n);//然后放下一行
}
}
}
m着色问题(回溯)
//二维数组a[i][j]为邻接矩阵,相邻则为1 一维数组x[i]为存放解的数组
void backtrack(int t)
{
if(t>n) sum++;
else
{
for(int j=1; j<=n; j++)
x[t]=j;//试探每一种颜色是不是可以
if(checkok(t)) backtrack(t+1);
x[t]=0;
}
}
boolean checkok(int k)//判断与k相邻的点是否可以上色
{
for(int j=1; j<=n; j++)//j为与之相邻的点
if(a[k][j]==1 && x[j]==x[k]) return false;
return true;
}
最大装载问题(回溯)
int search(int m) //数组a[]存储选不选择这个物品
{
int x,y;
if(m>n)
checkmax ();
else
{
a[m]=0; //不选这个物品
y=search(m+1);
if(cw+w[m]<=c1)
{
a[m]=1;
cw=cw+w[m]; //更新重量
x=search(m+1);
}
}
堡垒问题(回溯)
//4*4的地图
//全局变量定义
int main()
{
input(); //读入二维数组map[i][j]表示该位置是空白(0)或者有墙(2)
search(0);
printresult();
}
void search(int m)
{
if(m>=16)
checkmax(); //计算这个棋盘最多能放几个堡垒
else
{
a[m]=0; //这个位置不放
search(m+1);
if(canplace(m))
{
a[m]=1;
search(m+1);
}
}
}
void checkmax() //计算这个棋盘最多能放几个堡垒
{
int i,value=0;
for(i=0;i<16;i++)
{
if(a[i]==1) //a[i]=1意思是a[i]这个位置放置了堡垒,计数+1
{
value = value + 1;
}
}
if(value>max)
max=value; //替换max
}
int canplace(int m) //判断这个位置是否可以放置堡垒
{
int i,j,row,col;
row=m/4;
col=m%4;
if(map[row][col]!=0)
return 0; //0:不能放置在这里
else
{
for(i=row;i>=0;i--)
{ //从当前行沿着同一列返回检查
if(map[i][col]==2)
return 0;
else if(map[i][col]==1)
break;
}
for(j=col;j>=0;j--)
{
if(map[row][j]==2)
return 0;
else if(map[row][j]==1)
break;
}
return 1;
}
}
迷宫问题(回溯)
//row=s/20; col=s%20;
int canplace(int row, int col)
{
if(row>=0 && row<20 && col>=0 && col<20 && a[row][col]==0 ) //a[row][col]==0则为能走
return 1;
else
return 0; //判断这个点走过没有,若走过就是1
}
void search(int row, int col)
{
if (row*20+col==t)
printresult();
int r,c;
a[row][col]=1; //标记当前位置
r=row; //左
c=col-1;
if(canplace(r,c)) //判断(r,c)位置是否已经走过
search(r,c); //递归搜索(r,c)
r=row+1; //下
c=col;
if(canplace(r,c)) //判断(r,c)位置是否已经走过
search(r,c); //递归搜索(r,c)
r=row; //右
c=col+1;
if(canplace(r,c)) //判断(r,c)位置是否已经走过
search(r,c); //递归搜索(r,c)
r=row-1; //上
c=col;
if(canplace(r,c)) //判断(r,c)位置是否已经走过
search(r,c); //递归搜索(r,c)
}
素数环(回溯)
int isprime(int num)
{
int i,k;
k=sqrt(num); //大于根号num的数就不考虑了,减少循环次数
for(i=2;i<=k;i++)
if(num%i==0)
return(0); //不是素数
return(1);
}
void init()
{
int i;
for(i=0;i<21;i++)
a[i]=i; //一开始都先按顺序给1到20,后面swap调整次序
}
void search(int m)
{
int i;
if(m>20) //当已经搜索到叶结点时
{
if(isprime(a[1]+a[20])) //如果a[1]+a[20]也是素数
printresult(); //输出当前解
return;
}
else
{
for(i=m;i<=20;i++) //(排列树)
{
swap(m,i); //交换a[m]和a[i]
if(isprime(a[m-1]+a[m])) //判断a[m-1]+a[m]是否是素数
search(m+1); //递归搜索下一个位置
swap(m,i); //把a[m]和a[i]换回来
}
}
}
活动安排问题(贪心)
//每次选择具有最小结束时间的活动加入集合中
int greedySelector(int [] s, int [] f, boolean a[])//s[i]为第i个活动的开始时间,f[j]为第j个活动的结束时间
//a[i]为是否选择该活动,选择则为true
{
int n=s.length-1;
a[1]=true;
int j=1;
int count=1;
for (int i=2;i<=n;i++) {
if (s[i]>=f[j]) {
a[i]=true;
j=i; //可以选第i个活动的话把j替换成i继续寻找下一个
count++;
}
else a[i]=false;
}
return count;
}
//******完整代码
#include <iostream>
using namespace std;
//贪心算法
int n;
int b[1001];//开始时间
int e[1001];//结束时间
int endcur; //当前已参加活动的结束时间,简称当前结束时间
int cnt; //当前已参加活动的数目
int main()
{
//输入数据
cin>>n;
for(int i=0; i<n; i++)
{
cin>>b[i]>>e[i];
}
//按结束时间递增排序
for(int i=0; i<n; i++)
{
for(int j=i; j<n; j++)
{
if(e[j]<e[i]) //结束时间比i早的话就换前面来
{
swap(e[i],e[j]);
swap(b[i],b[j]);//交换
}
}
}
//贪心算法,按结束时间递增顺序遍历所有活动
//只要能参加的都参加
endcur=e[0]; //先参加第一个活动
cnt=1; //计数记上
for(int i=1; i<n; i++)
{
if(b[i]>=endcur) //如果第i个活动开始时间,比当前结束时间晚/相同
{
cnt++; //这个活动可以参加
endcur=e[i]; //更新一下当前结束时间
}
}
cout<<cnt<<endl;
return 0;
}
最优装载问题(贪心)装尽可能多的集装箱
float loading(float c, float [] w, int [] x)
{int n=w.length;
Element [] d = new Element [n];
for (int i = 0; i < n; i++)
d[i] = new Element(w[i],i);
MergeSort.mergeSort(d);
float opt=0;
for (int i = 0; i < n; i++) x[i] = 0;
for (int i = 0; i < n && d[i].w <= c; i++) {
x[d[i].i] = 1;
opt+=d[i].w;
c -= d[i].w;
}
return opt;
}
fibonacci数列问题(分治&动态规划)
//******递归**********
int fib(int n)
{
if(n<=1) return 1;
return fib(n-1)+fib(n-2);
}
//******动态规划*******
int dp[MAX]; //所有元素初始化为0
int count=1; //累计调用的步骤
int Fib1(int n) //算法1
{ dp[1]=dp[2]=1;
for (int i=3;i<=n;i++)
dp[i]=dp[i-1]+dp[i-2];
return dp[n];
}
快速排序(分治)
//******划分算法,每一次划分让前半部分的值小于基准temp
int partition(int a[], int s, int t)
{
int i=s, j=t; //i,j存放首尾
int temp=a[s]; //首个元素作为基准
while(i!=j)
{
while(j>i && a[j]>=temp) //选出一个后半段比基准小的放前面
j--;
a[i]=a[j];
while(j>i && a[i]<=temp) //同理
i++;
a[j]=a[i];
}
a[i]=temp; //中间元素为temp
return i;
}
//******递归部分
void quicksort(int a[], int s, int t)//对数列a部分快排,起点为s,终点为t
{
if(s<t)
int i=partition(a,s,t); //中间元素为i。每次partition的作用就是让a[s]到中间 并把数列分成前后两个无序区域
quicksort(a,s,i-1);
quicksort(a,i+1,t);
}
hanoi塔(分治)
void move(int count, int start, int finish, int temp) //从start挪到finish
{
if (count > 0)
{
move(count - 1, start, temp, finish);
//cout << "Move disk " << count << " from " << start << " to " << finish << "." << endl;
move(count - 1, temp, finish, start);
//每次move有三步,一次是把n-1个盘子从start挪到temp,(第二次把第n个从start挪到finish),第三次是把n-1个从temp挪到finish
}
}
归并排序(分治)
void Merge(int l,int mid,int r) //对两个分开的序列进行合并,合并成有序的序列tmp
{
int tmp[r-l+1]; //定义tmp数组
int cnt=0,i,j;
for(i=l,j=mid+1;i<=mid&&j<=r;) //i,j循环变量分别控制前半段和后半段,哪个小哪个就加入tmp表里
{
if(a[i]<a[j])
tmp[cnt++]=a[i++];
else
tmp[cnt++]=a[j++]; //往tmp里按照顺序加入元素
}
//↓然后如果在循环完了之后 两个子表里元素有剩余,就把剩下的全部放进tmp
while(i<=mid)
tmp[cnt++]=a[i++];
while(j<=r)
tmp[cnt++]=a[j++];
for(int k=0;k<cnt;k++)
a[l+k]=tmp[k]; //有序序列tmp写回数组a
}
void mergesort(int l,int r)
{
if(l<r)
{
int mid=r+l>>1; //mid等于(r+l)/2
mergesort(l,mid);
mergesort(mid+1,r); //分成左右
Merge(l,mid,r); //合并
}
}
int main()
{
int n;
cin>>n;
for(int i=0;i<n;i++)
cin>>a[i];
mergesort(0,n-1);
for(int i=0;i<n;i++)
cout<<a[i]<<endl;
return 0;
}
查找最大/次大元素(分治)
//a[low.high]中只有一个元素:则max1=a[low],max2=-INF(-∞)(要求它们是不同的元素)。
//a[low.high]中只有两个元素:则max1=MAX{a[low],a[high]},max2=MIN{a[low],a[high]}。
//a[low.high]中有两个以上元素:按中间位置mid=(low+high)/2划分为a[low..mid]和a[mid+1..high]左右两个区间(注意左区间包含a[mid]元素)。
//求出左区间最大元素lmax1和次大元素lmax2,求出右区间最大元素rmax1和次大元素rmax2。
//合并:若lmax1>rmax1,则max1=lmax1,max2=MAX{lmax2,rmax1};否则max1=rmax1,max2=MAX{lmax1,rmax2}。
void solve(int a[],int low,int high,int &max1,int &max2)//int &是引用,可以将形参的值传给实参
{
if (low==high) //区间只有一个元素
{ max1=a[low]; max2=-INF; }
else if (low==high-1) //区间只有两个元素
{ max1=max(a[low],a[high]); max2=min(a[low],a[high]); }
else //区间有两个以上元素
{
int mid=(low+high)/2;
int lmax1,lmax2;
solve(a,low,mid,lmax1,lmax2); //左区间求lmax1和lmax2
int rmax1,rmax2;
solve(a,mid+1,high,rmax1,rmax2); //右区间求lmax1和lmax2
if (lmax1>rmax1) //分类:哪一侧的最大元素更大
{ max1=lmax1;
max2=max(lmax2,rmax1); //lmax2,rmax1中求次大元素
}
else
{ max1=rmax1;
max2=max(lmax1,rmax2); //lmax1,rmax2中求次大元素
}
}
}
二分查找(分治)
//首先确定该区间的中点位置mid=(low+high)/2;然后将待查的k值与a[mid].key比较,再更改查找区间
int BinSearch(int a[],int low,int high,int k) //k为待查值
{
int mid;
if (low<=high) //当前区间存在元素时
{
mid=(low+high)/2; //求查找区间的中间位置
if (a[mid]==k) //找到后返回其物理下标mid
return mid;
if (a[mid]>k) //当a[mid]>k时
return BinSearch(a,low,mid-1,k);
else //当a[mid]<k时
return BinSearch(a,mid+1,high,k);
}
else return -1; //若当前查找区间没有元素时返回-1
}
寻找等长有序序列的中位数(分治)
//分别求出a、b的中位数a[m1]和b[m2]:
//若a[m1]=b[m2],则a[m1]或b[m2]即为所求中位数,算法结束。
//若a[m1]<b[m2],则舍弃序列a中前半部分(较小的一半),同时舍弃序列b中后半部分(较大的一半)要求舍弃的长度相等。
void prepart (int &s, int&t)
{
int m=(s+t)/2;
t=m;
}
Void postpart(int &s, int&t)
{
int m=(s+t)/2;
if((s+t)%2==0) //序列中有奇数个元素
s=m;
else s=m+1;//序列中有偶数个元素
}
int midnum(int a[],int s1,int t1,int b[],int s2,int t2)
{ //求两个有序序列a[s1..t1]和b[s2..t2]的中位数
int m1,m2;
if (s1==t1 && s2==t2) //两序列只有一个元素时返回较小者
return a[s1]<b[s2]?a[s1]:b[s2];
else
{ m1=(s1+t1)/2; //求a的中位数
m2=(s2+t2)/2; //求b的中位数
if (a[m1]==b[m2]) //两中位数相等时返回该中位数
return a[m1];
if (a[m1]<b[m2]) //当a[m1]<b[m2]时
{ postpart(s1,t1); //a取后半部分
prepart(s2,t2); //b取前半部分
return midnum(a,s1,t1,b,s2,t2);
}
else //当a[m1]>b[m2]时
{ prepart(s1,t1); //a取前半部分
postpart(s2,t2); //b取后半部分
return midnum(a,s1,t1,b,s2,t2);
}
}
}
最大连续子序列和问题(分治)
//将数列分为左半和右半,直到其中只剩下一个元素,若其大于0则返回这个数,若小于0则返回0
long maxSubSum(int a[],int left,int right)
//求a[left..right]序列中最大连续子序列和
{
int i,j;
long maxLeftSum,maxRightSum;
long maxLeftBorderSum,leftBorderSum;
long maxRightBorderSum,rightBorderSum;//maxbordersum记录最大和,bordersum记录当前和
if (left==right) //子序列只有一个元素时
{ if (a[left]>0) //该元素大于0时返回它
return a[left];
else //该元素小于或等于0时返回0
return 0;
}
int mid=(left+right)/2; //求中间位置
maxLeftSum=maxSubSum(a,left,mid); //求左边
maxRightSum=maxSubSum(a,mid+1,right); //求右边
maxLeftBorderSum=0,leftBorderSum=0;
for (i=mid;i>=left;i--) //求出以左边加上a[mid]元素
{
leftBorderSum+=a[i]; //构成的序列的最大和
if (leftBorderSum>maxLeftBorderSum)
maxLeftBorderSum=leftBorderSum;
}
maxRightBorderSum=0,rightBorderSum=0;
for (j=mid+1;j<=right;j++) //求出a[mid]右边元素
{
rightBorderSum+=a[j]; //构成的序列的最大和
if (rightBorderSum>maxRightBorderSum)
maxRightBorderSum=rightBorderSum;
}
return max3(maxLeftSum,maxRightSum,
maxLeftBorderSum+maxRightBorderSum); //记得define一个max3
}
找最短路径走迷宫(分支限界)
int search() //数组a[row][col]记录到r,c为止走过的步数 一个数t记录该点坐标 t=row*n+col;
{
int u, row, col, r, c, i, num;
while(!q.empty()) //当队列非空
{
u=takeoutofopen(); //从队列中取出一个元素,并把该元素从队列中删除
//也可以写成u=q.front(); q.pop();两句
row=u/n; //计算该点的坐标
col=u%n;
num=a[row][col]; //取得该点的步数
for(i=0;i<4;i++) //i为4个方向(direction)
{
if(canmoveto(row,col,&r,&c,i)) //判能否移动到该方向,并带回坐标(r,c)
{
if(isaim(r,c)) //如果是目标结点
return(num+1); //返回最小步数
else
{
a[r][c]=num+1; //记录该点的最小步数
addtoopen(r,c); //把该点加入到open表
}
}//end if
}//end for
}//end while
}//end search
int canmoveto(int row, int col, int *p, int *q, int direction) //判断能不能走,r,c需回传参数
{
int r,c;
r=row;
c=col;
switch(direction)
{ case 0: c--; //左
break;
case 1: r++; //下
break;
case 2: c++; //右
break;
case 3: r--; //上
}
*p=r;
*q=c; //回传
if(r<0||r>=n||c<0||c>=n) //如果越界返回0
return(0);
if(a[r][c]==0) //如果是空格返回1
return(1);
return(0); //其余情况返回0
}
跳马(分支限界)
//其他都和走迷宫一样,有区别的是跳马是八个方向,在search的for循环中需要让i循环八次
八数码问题(分支限界)
//移数字相当于移动空格!0为空格。
//移动以后与现在空格相邻的几个结点为活结点,若已经有过这个状态则舍弃
//用一串数来记录状态,所以在输入的时候需要用循环,s=0;for(int i=0;i<9;i++) s=s*10+num; 然后用init()函数向队列push进s,并初始化让smap(步数)=0。之后就可以开始bfs了
int canmoveto(int u, int dire)//在canmoveto函数里构造地图,但是在bfs函数中用一串数记录当前状态
{
int i, j;
int b[3][3];//构造9宫格
int row, col;
int r, c;
int v;
v = u;
for(i = 2; i >= 0; i--)
{
for(j = 2; j >= 0; j--) //从2到0 因为后面%10再/10的过程是倒序的
{
b[i][j] = v % 10; //将表示数码的数串的各位数从右往左逐个放在宫格里
v = v / 10;
if(b[i][j] == 0) //等于0是空格!
{
row = i; //记录空格的位置
col = j;
}
}
}
r = row + dr[dire];//根据dire的值来移动空格
c = col + dc[dire];
//这个算法好像没有记录是否出现过当前状态 所以可能时间复杂度会高一点(?
if(r >= 0 && r < 3 && c >= 0 && c < 3)//约束空格移动的范围
{
return(1);
}
else
{
return(0);
}
}
int bfs()
{
int i;
int u, v;
while(!q1.empty())
{
u = q1.front();
q1.pop(); //q1出队
for(i = 0; i < 4; i++)
{
if(canmoveto(u, i))
{
v = moveto(u, i);
if(v == 123456780) //正确的顺序
{
return(smap[u] + 1);
}
if(smap.count(v) == 0)//也就是说在map/set中不存在等价的两个(以上)元素,
//因此某个元素在map/set中出现的次数最多只能为1,用count得到的结果不是0就是
{
q1.push(v);
smap[v] = smap[u] + 1;//记录该条路径下的步数
}
}
}
}
return(-1);
}
int moveto(int u, int dire)//解码
{
int i, j;
int b[3][3];
int row, col;
int r, c;
int v;
v = u;
for(i = 2; i >= 0; i--)
{
for(j = 2; j >= 0; j--)
{
b[i][j] = v % 10;
v = v / 10;
if(b[i][j] == 0)
{
row = i;
col = j; //这段一样 也是由一串数→一个地图
}
}
}
r = row + dr[dire];
c = col + dc[dire]; //空格位置从row,col->r,c
//*******从这开始是正式的moveto函数功能!!*****
b[row][col] = b[r][c];//交换 重新记录0在9宫格中的位置
b[r][c] = 0;//把该位置的数值置为0
//将以9宫格状态存放的8数码****复现成数串*****
v = 0;
for(i = 0; i < 3; i++)
{
for(j = 0; j < 3; j++)
{
v = v * 10 + b[i][j];
}
}
return(v);//返回变换后的8数码
}
图的单源最短路径(分支限界)
//图的邻接矩阵a[MAXN][MAXN] 有权值 define INF=0x3f3f3f;dist[]存放路径长度 prev[]存放前面结点的编号
struct NodeType //队列结点类型
{ int vno; //顶点编号
int length; //路径长度
};
void bfs(int v) //求解算法
{
NodeType e,e1;
queue<NodeType> pqu;
//*****这道题如果用优先队列的话 前面还得加个函数,,然后定义队列就要用priority_queue<NodeType> pqu;然后后面pqu.front()改成pqu.top()*****
e.vno=v; //建立源点结点e(根结点)
e.length=0;
pqu.push(e); //源点结点e进队
dist[v]=0;
while(!pqu.empty()) //队列不空循环
{
e=pqu.front();
pqu.pop(); //出队列结点e
for (int j=0; j<n; j++)
{
if(a[e.vno][j]<INF && e.length+a[e.vno][j]<dist[j])
{ //剪枝:e.vno到顶点j有边并且路径长度更短
dist[j]=e.length+a[e.vno][j];
prev[j]=e.vno; //更短的话就更新这个结点
e1.vno=j; //建立相邻顶点j的结点e1
e1.length=dist[j];
pqu.push(e1); //结点e1进队
}
}
}
}
矩阵连乘问题(动态规划)
//其实就是一个画断点的问题,,t=min{matrixchain(i,k)+matrixchain(k+1,j)+p[i-1]*p[k]*p[j]}
int matrixchain(int i,int j)
{
if(i==j) return 0;
int u=matrixchain(i+1,j)+p[i-1]*p[i]*p[j];
s[i][j]=i; //k为断点位置,k在i,j之间,初始化断点位置为i
for(int k=i+1;k<j;k++)
{
int t;
t=matrixchain(i,k)+matrixchain(k+1,j)+p[i-1]*p[k]*p[j];
if(t<u)
{
u=t;
s[i][j]=k;
}
}
return u;
}
最长公共子序列(动态规划)
void dps()
{
for (int i = 0; i <= m; i++)
dp[i][1] = 0;
for (int j = 0; j <= n; j++)
dp[1][j] = 0; //初始化 这段好像有没有都无所谓(?
for (int i = 1; i <= m; i++)
for (int j = 1; j <= n; j++)
{
if (a[i-1] == b[j-1])
dp[i][j] = dp[i - 1][j - 1] + 1;
else
{
dp[i][j] = max(dp[i - 1][j], dp[i][j - 1]);
} //就是很普通的动态规划格式
}
}
int main()
{
cin >> a >> b; //string可以直接这么输入 不定长也没问题 cpp我的超人啊啊啊啊
m = a.length();
n = b.length(); //length记录两个string的长度作为循环的界限
dps();
cout << dp[m][n]<<endl;//记得换行!!!!!!!!!!不换行会说格式错误
return 0;
}