[分支限界]给定一个矩阵m*n,从左上角开始每次只能向右或者向下走,最后到右下角的位置共有多少种路径

问题描述:

给定一个m行n列的矩阵,从左上角开始每次只能向右或向下移动,最后到达右下角的位置,路径上的所有数字累加起来作为这条路径的路径和。编写程序求所有路径和中的最小路径和。例如,一下矩阵中的路径1->3->1->0->6->1->0是所有路径中路径和最小的,结果为12.

1 3 5 9

8 1 3 4

5 0 6 1

8 8 4 0

要求:

1)使用随机数算法生成一个行数和列数均大于10000的矩阵,其中随机数范围为[0,100],相邻数字差距大于10,每个数字出现频率相近(考察报告中部分截图显示即可);

2)基于搜索算法(蛮力法、回溯法、分支限界法)设计一个程序解决该问题;

随机数算法

使用rand和srand生成随机数,由于rand() 函数产生的随机数是伪随机数,是根据一个数值按照某个公式推算出来的,这个数值称之为“种子”。种子和随机数之间的关系是一种正态分布,通过 srand() 函数来重新“播种”,这样种子就会发生改变,使用时间作为参数,只要每次播种的时间不同,那么生成的种子就不同,最终的随机数也就不同。使用rand() % (b - a + 1) + a生成[a,b]的随机数,传入0和100可生成[0,100]的随机数,使用last存放上一次的随机数的结果,即矩阵前一个数的结果,使用now存放当前随机数,使用while循环判断直到前一个和当前值大于10,给now赋予新值并添加到矩阵中。这样就可以使矩阵相邻的数差距大于10.

在这里插入图片描述

分支限界算法

首先想无论是使用蛮力法还是其他的方法都是先将题目做出来,由于输入是一个矩阵,使用数组存放,假设有M行N列,路径要从左上角走到右下角,而且每次只能向右走或者向下走,仔细观察发现无论用哪种方法向下走或向右走的步数永远分别是M,N减一,一共要走的步数就是M+N-2。那么可以想象他的解空间树,他是一个二叉树,只有向右走或者向下走,假设左子树是向下走,右子树是向右走,解空间树的高度就是M+N-2。解空间树中每个结点存放当前所在行与当前所在列,当前结点的总路径长度和解向量,用FIFO表示队列(实际上对应层次遍历),初始时,FIFO=[ ],每遍历一层将其入队列。

采用STL的queue容器qu作为队列,队列中的结点类型声明如下:
在这里插入图片描述

现在设计限界函数,为了简便,设根结点为第0层,然后各层依次递增,当结点所在行数和列数相加等于M+N-2时表示是叶子结点层。解空间树如下

在这里插入图片描述

由于该问题是求路径和最小,属求最小值问题,采用下界设计方式,假设整体的下界为smallest,初始的smallest设置的初始路径是矩阵最上面一行和最右边一列。对于第i层的某个结点e,用(e.row,e.column)表示结点e的位置,用e.length表示走到当前结点的路径和,那么当前结点的路径和为:

e1.length = e.length + Arr[e.column][e.row + 1],那么此时价值的下界为e1.length,即后面的结点都不选可达到的路径和,如果此时的下界超过了smallest,则进行减枝。如果路径向右或者向下走的步数超过了行数或者列数,同样进行减枝。

路径向下走的剪枝
在这里插入图片描述

路径向右走进行剪枝

在这里插入图片描述

核心代码,使用广度优先搜索求最短路径:

在这里插入图片描述
在这里插入图片描述

结点进队并判断是否为叶子结点,如果是叶子结点,判断此时的路径是否为最短路径,如果比最短路径还要短则将此时的路径赋值给最短路径,并将当前结点的解向量赋值给最优解向量,用于输出。

在这里插入图片描述

实验结果与分析:

测试数据由(1)随机生成的,矩阵大小为18*18,其中随机数范围为[0,100],相邻数字差距大于10,每个数字出现频率相近,一共运行三分钟,此程序的时间复杂度为logM+N。

本机配置,CPU:i7-8750,内存:16G,当程序运行时候打开任务管理器发现,内存几乎跑满,但是CPU只有一小部分的消耗,说明此程序调用内存,消耗内存更多,但是运算更少,现在对老师说过的递归调用函数消耗内存更大有了更深刻的理解。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-LOMg0ffh-1640431445896)(%E5%88%86%E6%94%AF%E9%99%90%E7%95%8C%E8%A7%A3%E6%9C%80%E7%9F%AD%E8%B7%AF%E5%BE%84.assets/wps8D91.tmp.jpg)]

在这里插入图片描述

再次测试10*10的矩阵观察输出,当输入矩阵,输出路径和最短路径。

在这里插入图片描述

程序源码

#include <iostream>
#include <cstdlib>
#include <ctime>
#include <time.h>
#include <queue>
#define M 18
#define N 18
int Arr[M][N] = {{0}};
int x[M + N - 1] = {0}; //最优解向量
int smallest = 0;       //最短路径和

using namespace std;

//队列结点类型
struct NodeType //队列结点类型
{
    int row;                    //所在行号
    int column;                 //所在列号
    int length;                 //当前结点总路径长度
    int thisx[M + N - 1] = {0}; //当前结点包含的解向量
};

//生成随机数
void random(int a, int b)
{
    int now = 0, last = 0;
    srand((int)time(0)); //
    for (int i = 0; i < M; i++)
    {
        for (int j = 0; j < N; j++)
        {
            while (-11 < last - now && last - now < 11)
            {
                now = rand() % (b - a + 1) + a;
            }
            Arr[i][j] = now;
            last = now;
        }
    }
}

void EnQueue(NodeType e, queue<NodeType> &qu)
//结点e进队qu
{
    if (e.row + e.column == M + N - 2) //到达叶子结点
    {
        if (e.length < smallest) //找到路径最短的解
        {
            smallest = e.length;
            for (int j = 0; j < M + N - 1; j++)
                x[j] = e.thisx[j];
        }
    }
    else
        qu.push(e); //非叶子结点进队
}

void bfs()
{
    //给最短路径设置初始值,假设他一直向右走再一直向下走
    for (int i = 0; i < M; i++)
    {
        smallest += Arr[0][i];
    }
    for (int i = 1; i < N; i++)
    {
        smallest += Arr[i][M - 1];
    }
    NodeType e, e1, e2;
    queue<NodeType> qu;
    e.row = 0;
    e.column = 0; //建立源点结点e(根结点)
    e.length = Arr[e.row][e.column];
    e.thisx[0] = e.length;
    qu.push(e);         //源点结点e进队
    while (!qu.empty()) //队列不空循环
    {
        e = qu.front();
        //cout<<e.column<<" "<<e.row<<" "<<e.length<<endl;
        qu.pop();                                                            //出队列结点e
        if (e.length + Arr[e.column][e.row + 1] < smallest && e.row < M - 1) //向下走,检查下界并进行剪枝
        {
            e1.row = e.row + 1;
            e1.column = e.column;
            e1.length = e.length + Arr[e1.column][e1.row];

            for (int i = 0; i < e1.row + e1.column; i++) //复制解向量
            {
                e1.thisx[i] = e.thisx[i];
            }
            e1.thisx[e1.row + e1.column] = Arr[e1.column][e1.row];
            EnQueue(e1, qu); //向右走的结点进队操作
        }

        if (e.length + Arr[e.column + 1][e.row] < smallest && e.column < N - 1) //向右走,检查下界并进行剪枝
        {
            e2.row = e.row;
            e2.column = e.column + 1;
            e2.length = e.length + Arr[e2.column][e2.row];
            for (int i = 0; i < e2.row + e2.column; i++)
                e2.thisx[i] = e.thisx[i];
            e2.thisx[e2.row + e2.column] = Arr[e2.column][e2.row];
            EnQueue(e2, qu); //向右走的结点进队操作
        }
    }
}

int main()
{
    random(0, 100);

    for (int i = 0; i < M; i++)
    {
        for (int j = 0; j < N; j++)
            cout << Arr[i][j] << " ";
        cout << endl;
    }
    clock_t start, finish; //clock_t为CPU时钟计时单元数
    start = clock();//clock()函数返回此时CPU时钟计时单元数
    bfs();

    cout << "解向量为";
    for (int i = 0; i < M + N - 1; i++)
    {
        cout << x[i] << "->";
    }

    cout << "最短路径和为" << smallest << endl;
    finish = clock();                                                     //clock()函数返回此时CPU时钟计时单元数
    cout << "共耗时:" << double(finish - start) / CLOCKS_PER_SEC << endl; //finish与start的差值即为程序运行花费的CPU时钟单元数量,再除每秒CPU有多少个时钟单元,即为程序耗时
    return 0;
}




  • 2
    点赞
  • 18
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值