数学建模-商人过河问题

商人过河问题

1.问题重述

三名商人各带一个随从乘船渡河,一只小船只能容纳二人,由他们自己划行。随从们密约,在河的任一岸,一旦随从的人数比商人多,就杀人越货。但是如何乘船渡河的大权掌握在商人们手中,商人们怎样才能安全渡河呢?

2.问题分析

该问题是一个多步决策的问题,商人的下一步决定方案依赖于此时的状态,同时商人可以选择的方案有多种可能,因此,要想顺利将商人送达到对岸,需要对每一步进行决策和假设,通过一步步推理,分析出最后的结果

3.模型假设

  • 将商人和随从的数目抽象成具体数值
  • 将河的此岸和彼岸抽象成两种不同的状态,比如说是0和1

4.符号说明

基于递归的方法

假设商人的人数是m,随从的人数是n,船的容量是k

5.模型的建立和求解

基于递归的方法

记第k次渡河前彼岸(和数学模型(第四版)姜启源 谢金星 叶俊的不一样)的商人数为x,随从数为y k = 1 , 2 , … k=1,2,\dots k=1,2, x = 0 , 1 , 2 , … , m x=0,1,2,\dots,m x=0,1,2,,m y = 0 , 1 , 2 , … , n y=0,1,2,\dots,n y=0,1,2,,n,将二维向量 s = ( x , y ) s=(x,y) s=(x,y)定义为人员状态,安全渡河条件下的状态集合称为允许人员状态集合,记作 S S S
S = { ( x , y ) ∣ x > y ∧ m − x > n − y } S=\{(x,y)|x>y \land m-x>n-y\} S={(x,y)x>ymx>ny}
首先需要注意到,此岸的决策恰好与彼岸决策相反,因此,针对此岸和彼岸的状态,需要不同的决策策略。不妨令船在此岸的状态抽象成0,船在彼岸的状态抽象成1。记三元组x, y, d表示彼岸的商人人数随从人数以及此事船所在的河岸。于是,该事件的起始状态是(0, 0, 0),即彼岸没有商人和随从,所有人都在此岸,且船在此岸;事件的结束状态是(m, n, 1),即次岸没有商人和随从,所有人都在彼岸,且船在彼岸。

假设第k次的状态是(x, y, d),那么他的下一次的状态只能是在 ( S , d ′ ) (S, d') (S,d)中,所以需要遍历所有可以到达的状态点即可。

6.附录

基于递归的方法的C++代码

#include <iostream>
#include <fstream>
#include <vector>
#include <queue>
#include <stack>
#include <cstring>
#include <memory>

using namespace std;

typedef struct node
{
    int x, y, d;
    shared_ptr<struct node> prev;// 记录父节点的信息
    node(shared_ptr<node> n = nullptr):prev(n) {}   
}node;

const int N = 100, M = N * N, mask = 0x1;

int feasible_state[N][N];// 所有可行的位置点
int state[2][N][N];// 所有可行的状态点 判定状态是否遍历过 (0, 0, 0) 和 (0, 0, 1)是不同的状态
pair<int, int> action[2][N * N];// 所有可行的决策策略
int m, n, k;// 商人、随从的数量和船的容量
int total_action = 0, all_cnt = 0, ans_cnt = 0, min_lens = 0x3f3f3f3f;// 所有的决策的数目,所有可行方案的数量,最少决策方案的数量,最少方案的决策步数
vector<stack<node>> answer_tmp;
vector<vector<node>> answer;// 所有解的存放
node head_state;// 起点

void init();
void run(shared_ptr<node> now);
bool same(shared_ptr<node> now);
void show_ans();

int main()
{
    printf("请输入商人、随从、船容量的信息:<m n k>\n");
    scanf("%d%d%d", &m, &n, &k);
    init();
    run(make_shared<node>(head_state));
    show_ans();
    system("pause");
    return 0;
}

/**
 * 初始化所有合法的行动
 * 初始化所有合法的状态
 * */
void init()
{
    // action[0][]是去向的行动
    // action[1][]是回向的行动
    // 使用pair存二维的方向
    int method_cnt = 0;
    for(int r = 1; r <= k; r++)
        for(int j = 0; j <= r; j++)
            action[0][method_cnt] = {j, r - j}, action[1][method_cnt++] = {-j, j - r};
    total_action = max(total_action, method_cnt);

    // 所有可达点都是1
    for(int j = 0; j <= n; j++)
    {
        feasible_state[0][j] = feasible_state[m][j] = feasible_state[0][j] = feasible_state[m][j] = 1;
        for(int i = 0; i <= m - n; i++)
            feasible_state[i + j][j] = feasible_state[i + j][j] = 1;
    }

    // 初试状态是 (0, 0, 0)
    // 终点状态是 (m, n, 1)
    head_state.prev = nullptr, head_state.x = head_state.y = head_state.d = 0; 
}

/**
 * 核心代码
 * 根据所有可行的行动中找到所有到达的合法点进行递归
 * */
void run(shared_ptr<node> now)
{
    shared_ptr<node> last = make_shared<node>(now->prev), to(new node);

    for(int i = 0; i < total_action; i++)
    {
        to->x = now->x + action[now->d][i].first;
        to->y = now->y + action[now->d][i].second;
        to->d = ~now->d & mask;
        to->prev = now;

        if(to->x < 0 || to->x > m || to->y < 0 || to->y > n) continue;// 出界
        if(to->x == m && to->y == n)// 到达终点需要保存这个方案
        {
            shared_ptr<node> tmp(to);
            stack<node> s;
            while(tmp != nullptr)
            {
                s.push(*tmp);
                tmp = tmp->prev;
            }
            answer_tmp.push_back(s);
            all_cnt++;
            continue;
        }
        if(feasible_state[to->x][to->y] == 1 && (last == nullptr || !same(to)))// 到达可行点且没有绕回去
        {
            run(to);
        }
    }
}

/**
 * 判定是否出现重路
 * */
bool same(shared_ptr<node> now)
{
    memset(state, 0, sizeof(state));
    for(; now != nullptr; now = now->prev)
        if(state[now->d][now->x][now->y] == false)
            state[now->d][now->x][now->y] = true;
        else
            return true;
    return false;
}

/**
 * 展示所有点
 * */
void show_ans()
{
    ofstream answer_file;
    answer_file.open("./answer_file.txt", ios::out | ios::trunc);
    for(auto stk : answer_tmp)
    {
        vector<node> vec;
        while(!stk.empty())
        {
            vec.push_back(stk.top());
            stk.pop();
        }
        answer.push_back(vec);
    }
    for(auto vec : answer)
        min_lens = min(min_lens, (int)vec.size());
    for(auto vec : answer)
        if(vec.size() == min_lens)
            ans_cnt++;
    
    if(answer_file.is_open())
    {
        for(auto vec : answer)
        {
            if(vec.size() == min_lens)
            {
                for(node n : vec)
                    answer_file << "(" << n.x << "," << n.y << "," << n.d << ")->";
                answer_file << "\n";
            }
        }
        answer_file.close();  
    }
    else
    {
        printf("一共有 %d 中最短解法\n", ans_cnt);
        for(auto vec : answer)
        {
            if(vec.size() == min_lens)
            {
                for(node n : vec)
                    printf("(%d,%d,%d)->", n.x, n.y, n.d);
                printf("\n");
            }
        }
    }
}

参考 商人过河问题

  • 15
    点赞
  • 96
    收藏
    觉得还不错? 一键收藏
  • 3
    评论
商人过河问题可以使用数学建模来解决,而MATLAB是一个非常适合进行数学建模和计算的工具。下面是一个使用MATLAB进行商人过河问题数学建模的示例: 首先,我们可以使用二进制向量来表示商人、野人和船的位置状态,其中0表示左岸,1表示右岸。假设商人和野人的数量分别为M和N。 下面是MATLAB代码示例: % 商人和野人的数量 M = 3; N = 3; % 初始状态(左岸) initial_state = [ones(1, M+N), 0]; % 目标状态(右岸) target_state = [zeros(1, M+N), 1]; % 状态转移规则函数 state_transition = @(state, action) state + action; % 判断状态是否合法 is_valid_state = @(state) ... all(state(1:M) >= state(M+1:end) | state(1:M) == 0) && ... all(state(M+1:end) >= state(1:M) | state(M+1:end) == 0); % 判断是否达到目标状态 is_goal_state = @(state) all(state == target_state); % 使用递归函数解决问题 solution = recursive_solve(initial_state, []); % 递归函数 function solution = recursive_solve(state, path) % 如果已经达到目标状态,则返回路径 if is_goal_state(state) solution = path; return; end % 遍历所有可能的动作 actions = generate_actions(state); for i = 1:size(actions, 1) action = actions(i, :); % 计算新状态 new_state = state_transition(state, action); % 如果新状态合法,则继续递归求解 if is_valid_state(new_state) solution = recursive_solve(new_state, [path; action]); % 如果找到解,则返回 if ~isempty(solution) return; end end end % 如果找不到解,则返回空 solution = []; end % 生成所有可能的动作 function actions = generate_actions(state) M = sum(state(1:end-1) == 1); N = sum(state(1:end-1) == 0); % 动作格式:[商人移动数 野人移动数 船移动方向] % 商人独自移动 actions = [-1 0 -1; -2 0 -1; 1 0 1; 2 0 1]; % 野人独自移动 actions = [actions; 0 -1 -1; 0 -2 -1; 0 1 1; 0 2 1]; % 商人和野人一起移动 for i = 1:M for j = 1:N if i + j <= 2 actions = [actions; -i -j -1; i j 1]; end end end end 这个示例代码使用了递归求解的方法来找到商人过河问题的解。代码中的state_transition函数定义了状态转移规则,is_valid_state函数判断状态是否合法,is_goal_state函数判断是否达到目标状态。generate_actions函数生成所有可能的动作。 注意:这只是商人过河问题的一个简单数学建模示例,实际问题可能需要更多的约束和复杂的规则。你可以根据具体需求进行修改和扩展。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值