对DFS和BFS的理解。学习动态规划以及从该算法联想到的其他一些算法。

为了增强对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]);//输出最长长度

如果要输出最长子序列,用一个数组记录下标即可。

例题:

Problem Description
当今国际反恐形势很严峻,特别是美国“9.11事件”以后,国际恐怖势力更是有恃无恐,制造了多起骇人听闻的恐怖事件。基于此,各国都十分担心恐怖势力会对本国社会造成的不稳定,于是纷纷在本国的军队、警察队伍中开展了反恐训练。作为反恐立场坚定的大国,中国也十分重视在人民解放军、武装警察部队、人民警察队伍中反恐训练,还专门成立了反恐特警队。

炜炜是反恐特警队的一名新队员,现在正在接受培训。这几天刚好是射击训练第二阶段——实弹应变训练的日子,此前的第一阶段里,炜炜经过努力,已经将自己训练成为一个百发百中的神抢手了!这次,他将背着国产最新型12.7mm重型狙击枪进行训练比赛。

这次训练比赛的规则是这样的:

1、每个队员从出发点开始,沿着一条唯一的笔直道路跑直到终点,途中不允许往回跑,否则将被取消比赛资格。
2、出发前,每个队员的枪膛内都被装了顺序一样的、用小写英文字母标明类型的子弹序列,每位队员被告知这一序列的信息;同时,每位队员也被告知恐怖分子即将出现的序列和类型(同样用小写英文字母标明类型)。
3、在跑动的过程中,若发现“恐怖分子”,特警队员可以选择用枪击毙他,来得到写在“恐怖分子”胸前的得分,但是前提是他使用的子弹类型必须和“恐怖分子”类型相同,否则,即使击毙了“恐怖分子”,也得不到分数;当然选择不击毙他也是可以的,这样他不会从那个“恐怖分子”身上得到分数。
4、允许特警队员放空枪,这样可以消耗掉型号不对的子弹而不至于杀死“恐怖分子”(当然每个特警队员都不会愚蠢到不装消音装置就放空枪,以至于吓跑“恐怖分子”),等待枪口出现正确型号的子弹击毙他得分。

这里,我们假定:
1、对于每个队员,途中出现恐怖分子的地点、时间、类型也是完全一样的。
2、每颗子弹都是质量合格的,都可以发挥杀伤效力
3、由于队员各个都是神枪手,一旦他选择了正确的子弹,向目标射击,目标100%被爆头
4、每个队员的记忆力超强,能记住所有子弹序列信息和恐怖分子序列信息。
5、每个队员体力足够好,能跑完全程,并做他想要做的
6、“恐怖分子”是不动的,小范围内不存在多于一个的恐怖分子;

炜炜需要你的帮助,告诉他如何做,才能得到最高的分数。现在如果告诉你出发时枪膛内子弹的序号和型号、恐怖分子出现的序号和类型,你能告诉炜炜他最多能得到多少分数吗?
 

Input
输入数据的第一行有一个整数N表示子弹和恐怖分子的类型数。随后的一行是各种恐怖分子类型的一行字母,两个字母之间没有任何字符。接下来的一行是击毙上一行对应位置恐怖分子类型的得分数,每个分数之间恰有一个空格。第三第四行分别表示开始时枪膛内子弹的序列(左边的先打出)和恐怖分子出现的序列(左边的先出现),字母之间都没有任何字符。
每个测试数据之间没有空格和空行。你的程序必须通过全部测试数据,才能被判为AC。
 

Output
对于每一个测试数据,输出炜炜最多能得到的分数。
 

Sample Input
 
 
3abc1 1 1abcccc3abc1 1 1cccaba

Sample Output
 
 
10

代码如下:

#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:一个人的旅行

Problem Description
虽然草儿是个路痴(就是在杭电待了一年多,居然还会在校园里迷路的人,汗~),但是草儿仍然很喜欢旅行,因为在旅途中 会遇见很多人(白马王子,^0^),很多事,还能丰富自己的阅历,还可以看美丽的风景……草儿想去很多地方,她想要去东京铁塔看夜景,去威尼斯看电影,去阳明山上看海芋,去纽约纯粹看雪景,去巴黎喝咖啡写信,去北京探望孟姜女……眼看寒假就快到了,这么一大段时间,可不能浪费啊,一定要给自己好好的放个假,可是也不能荒废了训练啊,所以草儿决定在要在最短的时间去一个自己想去的地方!因为草儿的家在一个小镇上,没有火车经过,所以她只能去邻近的城市坐火车(好可怜啊~)。
 

Input
输入数据有多组,每组的第一行是三个整数T,S和D,表示有T条路,和草儿家相邻的城市的有S个,草儿想去的地方有D个;
接着有T行,每行有三个整数a,b,time,表示a,b城市之间的车程是time小时;(1=<(a,b)<=1000;a,b 之间可能有多条路)
接着的第T+1行有S个数,表示和草儿家相连的城市;
接着的第T+2行有D个数,表示草儿想去地方。
 

Output
输出草儿能去某个喜欢的城市的最短时间。
 

Sample Input
 
 
6 2 3 1 3 5 1 4 7 2 8 12 3 8 4 4 9 12 9 10 2 1 2 8 9 10
 

Sample Output
 
 
9
实现代码如下:
#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);
    }
}

  动态规划以及图的最短路径算法的关键都是状态更新方程,构建一个正确的状态更新方程才是写对这几个算法的关键。

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值