为了增强对DFS和BFS的理解,先看看在树里面他是如何遍历的。
一、用DFS和BFS对二叉树进行搜索。
首先我们先建立一个如下的二叉树

#include <iostream>
using namespace std;
typedef struct BinaryTreeNode{
int value;
struct BinaryTreeNode *left;
struct BinaryTreeNode *right;
}BinaryTreeNode,*ptr_BinaryTreeNode;
void CreateBinaryTree(ptr_BinaryTreeNode &T)
{
int data;
cin>>data;
if(data==-1)
T=NULL;
else
{
T=new BinaryTreeNode;
T->value=data;
CreateBinaryTree(T->left);
CreateBinaryTree(T->right);
}
}//创建以T为结点的二叉树
int main()
{
cout << "输入这个二叉树数组" << endl;
//输入 1 2 4 8 -1 -1 9 -1 -1 5 10 -1 -1 11 -1 -1 3 6 12 -1 -1 13 -1 -1 7 14 -1 -1 15 -1 -1
ptr_BinaryTreeNode T;
CreateBinaryTree(T);
return 0;
}
二、使用DFS和BFS算法分别对这个二叉树进行遍历。
深度优先搜索算法(Depth First Search)
DFS是搜索算法的一种,这种算法借助栈来实现。它沿着树的深度遍历树的节点,尽可能深的搜索树的分支。
当节点v的所有边都己被探寻过,搜索将回溯到发现节点v的那条边的起始节点。这一过程一直进行到已发现从源节点可达的所有节点为止。
如果还存在未被发现的节点,则选择其中一个作为源节点并重复以上过程,整个进程反复进行直到所有节点都被访问为止。
对于上图的搜索,输出结果应该为1 2 4 8 9 5 10 11 3 6 12 13 7 14 15
递归是使用系统栈来实现的,所以我们可以用递归算法来实现DFS算法。
DFS搜索算法代码实现如下
void DFS(ptr_BinaryTreeNode T)//DFS
{
if (T==NULL)
return;
cout<<T->value<<" ";
if (T->left!=NULL){
DFS(T->left);
}
if (T->right!=NULL)
{
DFS(T->right);
}
}
使用这个代码,输出结果为1 2 4 8 9 5 10 11 3 6 12 13 7 14 15,与预期相同。
广度优先搜索算法(Breadth First Search)
从根节点开始,逐层进行搜索,一层一层的进行搜索,对于上述二叉树(默认从左向右搜索),其输出结果为1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
BFS实现代码如下:
void BFS(ptr_BinaryTreeNode T)
{
if (T==NULL)
return;
queue<ptr_BinaryTreeNode> Q;//定义一个Q队列用来实现逐层搜索
Q.push(T);
while(!Q.empty())
{
ptr_BinaryTreeNode L= Q.front();//取队头元素
cout<<L->value<<" ";
if (L->left!=NULL)
{
Q.push(L->left);
}
if (L->right!=NULL)
{
Q.push(L->right);
}
Q.pop();//弹出队头元素
}
}
动态规划
算法原理:动态规划的思想:将待求解的问题分解为若干个子问题(阶段),按顺序求解子阶段,前一子问题的解,为后一子问题的求解提供了有用的信息。在求解任一子问题时,列出各种可能的局部解,通过决策保留那些有可能达到最优的局部解,丢弃其他局部解。依次解决各子问题,最后一个子问题就是初始问题的解。
动态规划解决的问题多数有重叠子问题这个特点,为减少重复计算,对每一个子问题只解一次,将其不同阶段的不同状态保存下来,即空间换时间。
问题特征(可以使用动态规划思想解决的问题)
最优子结构:当问题的最优解包含了其子问题的最优解时,称该问题具有最优子结构性质。
重叠子问题:在用递归算法自顶向下解问题时,每次产生的子问题并不总是新问题,有些子问题被反复计算多次。动态规划算法正是利用了这种子问题的重叠性质,对每一个子问题只解一次,而后将其解保存在一个表格中,在以后尽可能多地利用这些子问题的解。
1.背包问题。
一、01背包
1.使用二维数组解决问题。
背包的状态转移方程为f[i][v]=max{f[i-1][v],f[i-1][v-c[i]]+w[i]},可以理解为,消耗C[i]空间,换取w[i]的价值,如果比以前的大,那么就将原来的替换。
代码实现如下:
int main()
{
int m,n;
cin >> n >> m;// n件物品 M个资源
int a[2000],b[2000];//a[i]表示第i件物品消耗的资源,b[i]的第i件物品的价值。
int f[2000][2000];
for(int i=1;i <= n;i++)
cin>> a[i] >> b[i];
for(int j = m; j > 0;j--)
{
if(a[i] <= j)
f[i][j] = max(f[i-1][j],f[i-1][j-a[i]]+b[i]);
else f[i][j]=f[i-1][j];
}
return 0;
}
但是上述方法,数组并不能开太大,不能解决一些数太大大的问题。
由此,我们的算法可以优化为一维数组进行。
2.使用一维数组解决
#include <iostream>
#include <cstring>
using namespace std;
int main()
{
int m,n;
cin >> n >> m;// n件物品 M个资源
int a[50000],b[50000];
int f[50001];
memset(f,0,sizeof(a));
for(int i=1;i <= n;++i)
cin>> a[i] >> b[i];//a[i]表示第i件物品消耗的资源,b[i]的第i件物品的价值.
for(int i=1;i<=n;i++)
for(int j=a[i];j<=m;j++)
{
if(f[j-a[i]]+b[i]>f[j])
f[j]=f[j-a[i]]+b[i];
}
cout<<f[m]<<endl;//f[m]就是最优解
return 0;
}
二、完全背包
有N种物品(每种物品无限件)和一个容量为V的背包。放入第 i种物品耗费的空间是Ci,得到的价值是Wi。求解将哪些物品装入背包可使价值总和最大
实现的主要代码如下:
for(int i =1; i<=n;++i)
{
for(int j = w[i]; j<=m;++j)
dp[j] = max(dp[j],dp[j-w[i]]+v[i]);
}
对每一个物品,分别进行计算,保留最优解。最后得到的dp[m]就是最优解。
三、多重背包
有n件物品和容量为m的背包,给出i件物品的重量以及价值,还有数量,求解让装入背包的物品重量不超过背包容量,且价值最大 。
代码实现如下#include <iostream>
#include <cstring>
using namespace std;
int main()
{
int m,n;
cin>> m >> n;
int a[10000],b[10000],c[10000],f[10000];
//a[i]表示第i件物品消耗的资源,b[i]的第i件物品的价值,c[i]表示物品数量。
for(int i=1;i <= n;++i)
{
cin>>a[i]>>b[i]>>c[i];
}
for(int i=1;i<=n;i++)
for(int j=m;j>=0;j--)
for(int k=0;k<=c[i];k++)
{
if(j-k*a[i]<0)break;
f[j]=max(f[j],f[j-k*a[i]]+k*b[i]);
}
cout<<f[m]<<endl;//最优解
return 0;
}
背包问题大部分就是套用代码的问题举例了。
2.最长公共子序列(不连续)
子序列是有序的,但是不一定是连续的,例如<C,D,F>是<A,B,C,D,E,F>的子序列,不连续,它是<A,B,C,D,E,F>的子序列,子序列下标为<3,4,6>。
现在有两个串,分比为BCBDAB和BDCABA,请求解他们的最长公共子序列。
状态:dp[i][j] 表示以x[i],y[i]结尾的序列,当前的LCS长度
for(int i = 1; i<=n; ++i)
for(int j = 1; j<=m;++j){
if(a[i] == b[j]) dp[i][j] = dp[i-1][j-1] +1;//状态更新方程。
else dp[i][j] = max(dp[i-1][j],dp[i][j-1]);
}
printf("%d\n\n",dp[n][m]);//输出最长长度
如果要输出最长子序列,用一个数组记录下标即可。
例题:
炜炜是反恐特警队的一名新队员,现在正在接受培训。这几天刚好是射击训练第二阶段——实弹应变训练的日子,此前的第一阶段里,炜炜经过努力,已经将自己训练成为一个百发百中的神抢手了!这次,他将背着国产最新型12.7mm重型狙击枪进行训练比赛。
这次训练比赛的规则是这样的:
1、每个队员从出发点开始,沿着一条唯一的笔直道路跑直到终点,途中不允许往回跑,否则将被取消比赛资格。
2、出发前,每个队员的枪膛内都被装了顺序一样的、用小写英文字母标明类型的子弹序列,每位队员被告知这一序列的信息;同时,每位队员也被告知恐怖分子即将出现的序列和类型(同样用小写英文字母标明类型)。
3、在跑动的过程中,若发现“恐怖分子”,特警队员可以选择用枪击毙他,来得到写在“恐怖分子”胸前的得分,但是前提是他使用的子弹类型必须和“恐怖分子”类型相同,否则,即使击毙了“恐怖分子”,也得不到分数;当然选择不击毙他也是可以的,这样他不会从那个“恐怖分子”身上得到分数。
4、允许特警队员放空枪,这样可以消耗掉型号不对的子弹而不至于杀死“恐怖分子”(当然每个特警队员都不会愚蠢到不装消音装置就放空枪,以至于吓跑“恐怖分子”),等待枪口出现正确型号的子弹击毙他得分。
这里,我们假定:
1、对于每个队员,途中出现恐怖分子的地点、时间、类型也是完全一样的。
2、每颗子弹都是质量合格的,都可以发挥杀伤效力
3、由于队员各个都是神枪手,一旦他选择了正确的子弹,向目标射击,目标100%被爆头
4、每个队员的记忆力超强,能记住所有子弹序列信息和恐怖分子序列信息。
5、每个队员体力足够好,能跑完全程,并做他想要做的
6、“恐怖分子”是不动的,小范围内不存在多于一个的恐怖分子;
炜炜需要你的帮助,告诉他如何做,才能得到最高的分数。现在如果告诉你出发时枪膛内子弹的序号和型号、恐怖分子出现的序号和类型,你能告诉炜炜他最多能得到多少分数吗?
每个测试数据之间没有空格和空行。你的程序必须通过全部测试数据,才能被判为AC。
代码如下:
#include<iostream>
#include<cstdio>
#include<cstring>
using namespace std;
int dp[2005][2005];
char s[30],m[2005],k[2005];
int b[30];
int main()
{
int n;
while(~scanf("%d",&n))
{
scanf("%s",s);
for(int i=0;i<n;i++)
{
scanf("%d",&b[s[i]-'a']);
}
scanf("%s%s",m,k);
int n1=strlen(m);
int n2=strlen(k);
memset(dp,0,sizeof(int)*(n1+1));
for(int i=0;i<=n2;i++)
dp[0][i]=0;
for(int i=1;i<=n1;i++)
{
for(int j=1;j<=n2;j++)
{
if(m[i-1]==k[j-1])
dp[i][j]=dp[i-1][j-1]+b[m[i-1]-'a'];
else
dp[i][j]=max(dp[i-1][j],dp[i][j-1]);
}
}
printf("%d\n",dp[n1][n2]);
}
}
图的最短路径的搜索
图的最短路径的搜索目前有两种基本算法,一个是Dijkstra算法,一个是弗洛伊德算法。
Dijkstra算法
该算法用于解决一个点到剩余点的最短距离。该算法要求我们定义一个初始结点,为此我们需要定义一个点为他的初始点。
在学会如何使用这个算法前,我们先要学会使用一个一种方式储存表中各个点之间的关系,以及他们各点之间的权值。
在这里我们使用邻接矩阵法来储存图中各点之间的联系。
我们进行接下来将会对这个图进行储存
邻接矩阵代码实现如下:
typedef struct
{
int vexs[100];//顶点表,最大顶点数为100
int arcs[100][100];//邻接矩阵
int vexnum;//图当前的点数
int arcnum;//图当前的边数
}Graph,*ptr_Graph;
void CreateGraph(ptr_Graph &pGragh)
{
//该图的初始输入数据为下面一行
pGragh = new Graph;
if (NULL == pGragh)
return;//分配空间失败就退出
cin >> pGragh->vexnum >> pGragh->arcnum;//输入总顶点数和边数
for (int i = 0; i < pGragh->vexnum;++i)/*建立顶点表*/
(pGragh->vexs)[i] = i;
for (int i = 0; i < pGragh->vexnum; ++i)/*邻接矩阵初始化*/
{
for (int j = 0; j < pGragh->vexnum; ++j)
{
(pGragh->arcs)[i][j] = maxint;//两点权值为maxint,表示两点之间不连通
if (i == j)
(pGragh->arcs)[i][j] = 0;//为0表示这两点为同一点
}
}
for (int k = 0; k < pGragh->arcnum; ++k)
{
int i, j, w;
cin >> i >> j >> w;
(pGragh->arcs)[i][j] = w;//清楚上面输入的顺序,有向边的开始点和终点
}
}
在编写Dijkstra算法前,我们需要了解Dijkstra算法需要哪些额外的数组进行标志位的储存。
1.邻接矩阵G[N][N]。这个在上面定义有向图的时候已经使用了。
2.数组S[N]。记录相应顶点是否已经被确定为最短距离。
3.数组D[N]。记录与初始结点相应其他结点的路径长度。
4.数组path[N]。记录相应顶点的前驱顶点。(使用一些其他算法导出path数组,我们就可以得到初始结点与某个其他结点间的最短路径)。
具体代码实现如下
int * Dijkstra(ptr_Graph &G,int v0)//传输G表进去,定义V0为初始结点
{
int i,j,v;
int s[G->vexnum];//记录相应顶点是否被确定为最短距离 开始全部初始化为0
int *D = new int[G->vexnum];//记录起始点到其他点之间的距离
int path[G->vexnum];//记录相应节点的前驱结点
for(i=0;i<G->vexnum;i++)//对以上数组进行初始化
{
s[i]=0;
D[i]=G->arcs[v0][i];
if(D[i]<maxint) path[i]=v0;//和初始结点有通路,其上个点定为V0
else path[i]=-1;
}
s[v0]=1;D[v0]=0;
/*初始化完成,开始循环,每次球的V0到某个顶点V0的最短路径*/
for(i=1;i<G->vexnum;++i)//N个点 只需循环n-1次
{
int min_path=maxint;
for(j=0;j<G->vexnum;++j)if(s[j]==0&&D[j]<min_path){v=j;min_path=D[j];}//第一次循环找出除V0外与V0距离最短的一个点
s[v]=1;//第一次循环就代表找到了与V0最近的点
for(j=0;j<G->vexnum;++j)
{
if(s[j]==0&&(D[v]+G->arcs[v][j]<D[j]))//状态转移方程 小于当前最短 路径则进行更新
{
D[j]=D[v]+G->arcs[v][j];
path[j]=v;
}
}
}
return D;
}
使用int *定义的变量来接收该函数返回的值,结果储存在D数组中,需得到结果只需要输出D数组就可以了。
至于定义D数组为何采用new的定义方式,详情请见:https://www.cnblogs.com/walter-xh/p/6192800.html
如果想要得到最短路径,将返回值改为path即可,但此时也要注意要把定义path的方法修改,修改为new定义。
下面是贴出来的伪代码
a[0]=i;
while(d[i]!=V0)//n的初始值为1
{
i=d[i];
a[n]=i;
n++;
}
a[n]=v0;
其中i为末点,V0为初始点,将a[n]数组倒着输出也就可以得到最短路径了
我自认为Dijkstra算法和动态规划方程有些相似之处,其核心都在于一个更新方程。
Dijkstra的更新方程为
if(s[j]==0&&(D[v]+G->arcs[v][j]<D[j]))
{
D[j]=D[v]+G->arcs[v][j];
path[j]=v;
}
意思是初始点以v为跳板,到达j点,如果距离小于原来的初始点到j点的距离,则进行更新,并记录下跳板j。
Folyd算法
int **Floyd(ptr_Graph G)
{
int i,j,k;
int **D = new int *[G->vexnum];
for (i = 0; i <G->vexnum; i++)
{
D[i] = new int[G->vexnum];
for(j = 0; j <G->vexnum; j++)
D[i][j]=G->arcs[i][j];//D的初始化放在这里
}
int path[G->vexnum][G->vexnum];
for(i=0;i<G->vexnum;i++)
{
for(j=0;j<G->vexnum;j++)
{
if(D[i][j]<maxint&&i!=j)path[i][j]=i;
else path[i][j]=-1;
}
}
for(k=0;k<G->vexnum;++k)
{
for(i=0;i<G->vexnum;++i)//初始化
{
for(j=0;j<G->vexnum;++j)
{
if((D[i][k]+D[k][j])<D[i][j])
{
D[i][j]=D[i][k]+D[k][j];
path[i][j]=path[k][j];
}
}
}
}
return D;
}
使用int **类型来接收函数返回值,实验代码如下
int main()
{
ptr_Graph G;
int **d;
//对于该图 我们需要输入的数据为
//6 8 1 2 5 0 2 10 0 4 30 0 5 100 2 3 50 3 5 10 4 3 20 4 5 60 4 3 20
CreateGraph(G);
d=Floyd(G);
for(int i=0;i<G->vexnum;i++)
{
for(int j=0;j<G->vexnum;j++)
printf("%d ",d[i][j]);
printf("\n");
}
}
核心部分为下列代码,状态更新方程,可以这么理解,由i点到j点 ,使用K这个中间变量能否缩短i到j的距离,如果能,那么就对D[i][j]进行更新。三重循环,把每一个可能的结果都遍历到了,循环结束后,D[i][j]就是最优解。
for(k=0;k<G->vexnum;++k)
{
for(i=0;i<G->vexnum;++i)//初始化
{
for(j=0;j<G->vexnum;++j)
{
if((D[i][k]+D[k][j])<D[i][j])
{
D[i][j]=D[i][k]+D[k][j];
path[i][j]=path[k][j];
}
}
}
}
接下来我们进行思考,如何输出最短路径path呢?使用嵌套函数来完成下列操作,我们只需要使用pri1函数就可以获得i->j的最短路径。
void pri(int **path,int i,int j)
{
if(path[i][j]!=0)
{
pri(path,i,path[i][j]);
printf("%d ",path[i][j]);
}
}
void pri1(int **path,int i,int j)
{
printf("%d ",i);
pri(path,i,j);
printf("%d ",j);
}
比如语句pri1(path,0,5);运行的结果为
Dijkstra算法的具体应用
HDU2066:一个人的旅行
接着有T行,每行有三个整数a,b,time,表示a,b城市之间的车程是time小时;(1=<(a,b)<=1000;a,b 之间可能有多条路)
接着的第T+1行有S个数,表示和草儿家相连的城市;
接着的第T+2行有D个数,表示草儿想去地方。
#include<stdio.h>
#include<string.h>
#include<stdlib.h>
#include<queue>
#include<vector>
using namespace std;
#define INF 1000000000
int t,s,d;
int dis[1010];
int dd[1010];
int map[1010][1010];
int vis[1010];
int max11;
void dijkstra()
{
memset(vis,0,sizeof(vis));
for(int i=0;i<=max11;i++)
dis[i]=map[0][i];
vis[0]=1;
for(int k=1;k<=max11;k++)
{
int min=INF,u;
for(int i=1;i<=max11;i++)
{
if(!vis[i]&&dis[i]<min)
{
min=dis[i];
u=i;
}
}
vis[u]=1;
for(int i=1;i<=max11;i++)
{
if(!vis[i]&&dis[i]>dis[u]+map[u][i])
dis[i]=dis[u]+map[u][i];
}
}
}
int max1(int x,int y)
{
if(x>y) return x;
return y;
}
int main()
{
while(scanf("%d %d %d",&t,&s,&d)!=EOF)
{
for(int i=0;i<1001;i++)
for(int j=0;j<1001;j++)
{
map[i][j]=INF;
}
max11=0;
for(int i=1;i<=t;i++)
{
int x,y,w;
scanf("%d%d%d",&x,&y,&w);
if(map[x][y]>w)
{ map[x][y]=w;
map[y][x]=w;
}
max11=max1(max11,y);
max11=max1(max11,x);
}
for(int i=1;i<=s;i++)
{
int x;
scanf("%d",&x);
map[0][x]=0;
map[x][0]=0;
}
for(int i=1;i<=d;i++)
{
scanf("%d",&dd[i]);
}
dijkstra();
int min=999999999;
for(int i=1;i<=d;i++)
{
if(min>dis[dd[i]]) min=dis[dd[i]];
}
printf("%d\n",min);
}
}
动态规划以及图的最短路径算法的关键都是状态更新方程,构建一个正确的状态更新方程才是写对这几个算法的关键。