Dijkstra算法
用途: 最短路径
前提: 顶点与顶点的距离大于0
贪心策略: 当前步数最小值, 即为由起点出发到达此点的最小步数. 因为点与点间的距离都为正数, 不可能通过第三节点进一步减小最小步数.
核心步骤: 找出最小点, 访问最小点, 计算经过此最小点能否缩小其他未访问点步数
二面题:
对于给定数组vector v中下标为i的点, 在不越界的前提下, 有以下3种跳跃方式:
值相同的两个点可相互跳跃, i<->j (i≠j且num[i]=num[j])
i->i+1
i->i-1
求从下标为0跳跃到下标为nums.size()-1需要的最少步数
解题思路: 化为无向图, 相邻坐标间有边, 值相同的点有边. 即: 将邻接的条件dis[u][v]!=INT_MAX
(其中INT_MAX
指u, v
两点间没有边) 改为index == mini + 1 || index == mini - 1 || v[index] == v[mini]
. 此时问题就转化为无向图的最短路径问题, 利用Dijkstra算法解决
int vectorGraphShortestSteps(vector<int> v)
{
bool* visited = new bool[v.size()];// 记录是否访问过
memset(visited, 0, sizeof(bool) * v.size());
int* totalSteps = new int[v.size()];// 记录步数
visited[0] = true;
totalSteps[0] = 0;
// 初始化起点到各点的步数
for (int i = 1; i < v.size(); i++)
{
totalSteps[i] = (v[i] == v[0]) ? 1 : i;
}
for (int i = 1; i < v.size(); i++)
{
// 寻找步数最小的点
int mini = -1;
int min = INT_MAX;
for (int index = 0; index < v.size(); index++)
{
if (!visited[index] && totalSteps[index] < min)
{
mini = index;
min = totalSteps[index];
}
}
if (mini == v.size() - 1) break;// 数组某尾为当前最小点,计算结束
if (mini < 0)
{
cout << "没有距离小于INT_MAX的点!" << endl;
return -1;
}
visited[mini] = true;
// 经过此点步数更小的更新
for (int index = 0; index < v.size(); index++)
{
if (!visited[index]
&& (index == mini + 1 || index == mini - 1 || v[index] == v[mini])
&& totalSteps[mini] + 1 < totalSteps[index])
{
totalSteps[index] = totalSteps[mini] + 1;
}
}
}
return totalSteps[v.size() - 1];
}
一面题:
一个二维数组迷宫, 一次走一步, 不能斜着走, 找出最短路径中得到金子最多的路径.
输入迷宫元素的含义如下:
# 墙
S 起点, 只有一个
E 终点, 只有一个
0-9 金子数量
解题思路: 化为无向图, 二维数组元素与紧邻最多4个非墙元素之间有边. 即: 将邻接的条件dis[u][v]!=INT_MAX
(其中INT_MAX
指u, v
两点间没有边) 改为map[r][c] != '#' && ((r == minR - 1 && c == minC) || (r == minR + 1 && c == minC) || (r == minR && c == minC - 1) || (r == minR && c == minC + 1))
. 此时问题就转化为无向图的最短路径问题, 利用Dijkstra算法解决.
注意此处要求出多个最短路径, 利用pre保存前驱节点. 由前驱节点dfs找到全部最短路径. 再在全部最短路径中找到取得金子最多的路径.
即: D i j k s t r a ( p r e ) → d f s ( p a t h ) → m o s t G o l d ( p a t h [ m a x i ] ) Dijkstra( pre )\to dfs( path )\to mostGold( path[maxi]) Dijkstra(pre)→dfs(path)→mostGold(path[maxi])
vector<vector<int>> pre;// 前驱节点(因寻找多个最短路径,前驱节点可能不止一个,用数组存前驱节点)
vector<int> r;// 一种路径
vector<vector<int>> path;// 全部路径
// 深度优先搜索:由终点开始,寻找前驱数组中可达起点的全部路径
void dfs(int cur, int start)
{
if (cur == start)// 终止条件
{
path.push_back(r);
return;
}
for (auto it : pre[cur])// 遍历当前点的前驱数组
{
r.push_back(it);
dfs(it, start);
r.pop_back();
}
}
vector<int> mazeMostGoldOfShortestPath(vector<vector<char>> map)
{
int C = map[0].size();// 二维迷宫一维存储,行r列c对应一维下标为r*C+c,C为一共的列数
int R = map.size();
pre.resize(R * C);// 不resize会越界
vector<bool> visited(R * C, 0);
vector<int> totalSteps(R * C, INT_MAX);// 不可到达点之间的距离是INT_MAX
visited[0] = true;
totalSteps[0] = 0;
// 找到起点与终点
int startR, startC, endR, endC;// 起点与终点行(row)列(column)坐标,r表示行,c表示列
startC = startR = endR = endC = -1;
for (int r = 0; r < R; r++)
{
for (int c = 0; c < C; c++)
{
if (map[r][c] == 'S')
{
startC = c;
startR = r;
}
else if (map[r][c] == 'E')
{
endR = r;
endC = c;
}
}
}
if (startR < 0 || startC < 0 || endR < 0 || endC < 0)
{
cout << "地图中没有起点或终点!" << endl;
return { -1 };
}
// 访问起点
totalSteps[startR * C + startC] = 0;
visited[startR * C + startC] = true;
// 起点可一步到达(有相邻边)的点距离置为1
if (startC+1>=0&&startC+1<C&&map[startR][startC+1]!='#')
{
totalSteps[startR * C + startC + 1] = 1;
pre[startR * C + startC + 1].push_back(0);
}
if (startC - 1 >= 0 && startC - 1 < C && map[startR][startC - 1] != '#')
{
totalSteps[startR * C + startC - 1] = 1;
pre[startR * C + startC - 1].push_back(0);
}
if (startR + 1 >= 0 && startR + 1 < R && map[startR+1][startC] != '#')
{
totalSteps[(startR+1) * C + startC] = 1;
pre[(startR + 1) * C + startC].push_back(0);
}
if (startR - 1 >= 0 && startR - 1 < R && map[startR - 1][startC] != '#')
{
totalSteps[(startR - 1) * C + startC] = 1;
pre[(startR - 1) * C + startC].push_back(0);
}
for (int i = 1; i < R * C; i++)// 最多再访问R*C-1次(因已访问过起点,次数-1)
{
int minR = -1;
int minC = -1;
int min = INT_MAX;
// 寻找未访问过的距离最短的点,注意不是之前点能达到的最小的点
for (int r = 0; r < R; r++)
{
for (int c = 0; c < C; c++)
{
if (!visited[r * C + c] && totalSteps[r * C + c] < min)
{
minR = r;
minC = c;
min = totalSteps[r * C + c];
}
}
}
if (minR == endR && minC == endC) break;// 已访问起点最短路径
if (minR < 0 || minC < 0)// 到达起点前已无路可走(没有小于INT_MAX的路径,即余下点全为INT_MAX不可到达)
{
cout << "迷宫无解!" << endl;
return { -1 };
}
visited[minR * C + minC] = true;
// 更新经过此最小点距离变短的点
for (int r = minR - 1; r <= minR + 1; r++)
{
for (int c = minC - 1; c <= minC + 1; c++)
{
if (r >= 0 && r < R && c >= 0 && c < C// 不越界
&& !visited[r * C + c]// 未访问
&& map[r][c] != '#'// 不是墙
&& ((r == minR - 1 && c == minC) || (r == minR + 1 && c == minC) || (r == minR && c == minC - 1) || (r == minR && c == minC + 1))// 4个紧接节点
&& totalSteps[minR * C + minC] + 1 <= totalSteps[r * C + c])
// 关键:距离相等的点也要加入前驱数组!
{
if (totalSteps[minR * C + minC] + 1 < totalSteps[r * C + c])// 更新最小点,清空前驱数组
{
totalSteps[r * C + c] = totalSteps[minR * C + minC] + 1;
pre[r * C + c].clear();
}
pre[r * C + c].push_back(minR * C + minC);// 补充最小点
}
}
}
}
// 若要求求出最小步数,加上此行代码
//int minSteps = totalSteps[endR * C + endC];
// 打印前驱节点
int cnt = 0;
for (auto it:pre)
{
cout << "[" << cnt++ << "] ";
for (auto single:it)
{
cout << single << " ";
}
cout << endl;
}
// dfs找出所有可能最短路径存入path
dfs(endR * C + endC, startR * C + startC);
// 所有最短路径中,捡到金子最多的路径
int maxi = -1;
int max = 0;
for (int i = 0; i < path.size() - 1; i++)// 从终点前驱dfs,不计起点数字
{
int sum = 0;
for (auto it : path[i])
{
int r = it / C;
int c = it % C;
sum += (map[r][c] - '0');
}
if (sum > max)
{
max = sum;
maxi = i;
}
}
if (maxi < 0)
{
cout << "没有路线可以拾到金子!" << endl;
return { -2 };
}
return path[maxi];
}
测试:
int main() {
// 地图构建
vector<vector<char>> map;
map.push_back({ 'S','0','1','2','3','4' });
map.push_back({ '0','1','2','3','5','E' });
int C = map[0].size();
cout << "无墙时: ";
for (auto it : mazeMostGoldOfShortestPath(map))
{
cout << "(" << it / C << "," << it % C << ") ";
}
cout << endl;
cout << "有墙时: ";
map[0][3] = '#';
for (auto it : mazeMostGoldOfShortestPath(map))
{
cout << "(" << it / C << "," << it % C << ") ";
}
cout << endl;
cout << "无法到达终点时:";
map[1][3] = '#';
for (auto it : mazeMostGoldOfShortestPath(map))
{
cout << "(" << it / C << "," << it % C << ") ";
}
return 0;
}
控制台输出效果:
![image-20200507231229748](https://gitee.com/guo_zi_yi_412/picture_bed/raw/master/Typora-notes/image-20200507231229748.png)
其实还有更简便的算法, 之前没有想到, 我果然还是太菜…
解题思路: bfs, 同一步数的点存在一轮栈中. 比如第1步为开始点, 将第一点推入栈作初始化. 记录此时栈的大小, 这次只弹出上层栈大小数量的节点, 寻找它周边的可到达的点, 压入数组, 此时数组中所有的点都是步数为1的点, 记录所有有终点的点的路径到shortestPath. 如果这些点中有终点, 就停止计算. 计算shortestPath中取得金子最多的点.
struct point {
int x;
int y;
};
// 栈中的点
struct node {
int x;
int y;
vector <point> path;
};
vector <point> bfs(vector<vector<char>> map)
{
queue<node> que;
int startx=0, starty=0, endx=1, endy=5;
int next[4][2] = { {0,1},{0,-1},{-1,0},{1,0} };
bool flag = false;
vector<vector<bool>> book(map.size(), vector<bool>(map[0].size(),0));
node tmp;
tmp.x = startx;
tmp.y = starty;
tmp.path.push_back({ startx,starty });
que.push(tmp);
vector<vector <point>> shortestPath;
while (que.size())
{
int tx, ty;
int n = que.size();
for (size_t i = 0; i < n; i++)
{
for (size_t k = 0; k < 4; k++)
{
tx = que.front().x + next[k][0];
ty = que.front().y + next[k][1];
if (tx<0||tx>= map.size() ||ty<0||ty>=map[0].size())
continue;
if (map[tx][ty]=='E')
{
flag = true;
shortestPath.push_back(que.front().path);
}
if (map[tx][ty]!='#'&& !book[tx][ty])
{
book[tx][ty] = true;
tmp.x = tx;
tmp.y = ty;
tmp.path = que.front().path;
tmp.path.push_back({ tx,ty });
que.push(tmp);
}
}
que.pop();
}
if (flag) break;
}
if (shortestPath.empty())
{
cout << " 无路可走!";
return {};
}
// 遍历寻找最短路径中金子最少的点
int maxGold = 0;
int maxi = 0;
for (size_t i = 0; i < shortestPath.size(); i++)
{
int sum = 0;
for (size_t k = 0; k < shortestPath[i].size(); k++)
{
sum += map[shortestPath[i][k].x][shortestPath[i][k].y] - '0';
}
if (sum>maxGold)
{
maxi = i;
maxGold = sum;
}
}
if(maxGold == 0)
{
cout << "没有最短路径可以拾到金子!" <<endl;
return {};
}
return shortestPath[maxi];
}
附参考此博客学习的Dijkstra算法实现程序:
Graph_Di.h ( Di在此处指Dijkstra )
#pragma once
#include <iostream>
#include <vector>
using namespace std;
struct Discript
{
vector<int> path;// 路径
int totalWeight;// 路径权值
bool visited;// 是否访问过
Discript()
{
totalWeight = 0;
visited = false;
}
};
class Graph_Di
{
private:
int** adjMatrix;// 邻接矩阵
int vexNum;// 顶点数
int edgeNum;// 边数
Discript* dis;// 每个点的路径及路径权值及是否访问信息
bool isValidEdge(int start, int end, int weight);
public:
Graph_Di(int vexNum, int edgeNum);
~Graph_Di();
void createGraph();
void printAdjacentMatrix();
void Dijkstra(int start);
void printPath();
};
Graph_Di.cpp
#include "Graph_Di.h"
string line(100, '-');
bool Graph_Di::isValidEdge(int start, int end, int weight)
{
return start > 0 && start <= vexNum
&& end > 0 && end <= vexNum
&& weight > 0;
}
Graph_Di::Graph_Di(int vexNum, int edgeNum) :vexNum(vexNum), edgeNum(edgeNum)
{
if (vexNum < 0 || edgeNum < 0 || vexNum * (vexNum - 1) / 2 < edgeNum)// 两两组合,第一个有vexNum个选择,第二个有vexNum-1个选择,二者顺序无关/2,则两两组合最多vexNum*(vexNum-1)/2个
{
cout << "顶点数及边数必须大于零,且边数不得超过vexNum * (vexNum - 1) / 2!" << endl;
return;
}
adjMatrix = new int* [vexNum];
dis = new Discript[vexNum];
for (size_t i = 0; i < vexNum; i++)
{
adjMatrix[i] = new int[vexNum];
for (size_t j = 0; j < vexNum; j++)
{
adjMatrix[i][j] = INT_MAX;// 距离初始化为INT_MAX,意为不可到达
}
}
}
Graph_Di::~Graph_Di()
{
if (adjMatrix)
{
for (size_t i = 0; i < vexNum; i++)
{
delete[] adjMatrix[i];
}
delete[] adjMatrix;
adjMatrix = nullptr;
}
if (dis)
{
delete[] dis;
dis = nullptr;
}
}
void Graph_Di::createGraph()
{
cout << "请依次输入起点,终点,以及它们之间的距离:" << endl;
int start, end, weight;
for (size_t i = 0; i < edgeNum; i++)
{
cin >> start >> end >> weight;
while (!isValidEdge(start, end, weight))
{
cout << "输入点不合法,请重新输入:" << endl;
cin >> start >> end >> weight;
}
// start,end从1开始,其对应下标-1
adjMatrix[start - 1][end - 1] = weight;
// 无向图添加此行代码
//adjMatrix[end - 1][start - 1] = weight;
}
}
void Graph_Di::printAdjacentMatrix()
{
cout << line << endl;
cout << "邻接矩阵:" << endl;
for (size_t i = 0; i < vexNum; i++)
{
for (size_t j = 0; j < vexNum; j++)
{
if (adjMatrix[i][j] == INT_MAX)
{
cout << "∞\t";
}
else
{
cout << adjMatrix[i][j] << "\t";
}
}
cout << endl;
}
cout << line << endl;
}
void Graph_Di::Dijkstra(int start)
{
// 初始化开始点
for (size_t i = 0; i < vexNum; i++)
{
dis[i].totalWeight = adjMatrix[start - 1][i];
dis[i].path.push_back(start);
}
dis[start - 1].totalWeight = 0;
dis[start - 1].visited = true;
for (size_t i = 1; i < vexNum; i++)
{
int mini = -1;
int min = INT_MAX;// 最小值初始化为INT_MAX
for (size_t vex = 0; vex < vexNum; vex++)
{
if (!dis[vex].visited && dis[vex].totalWeight < min)
{
mini = vex;
min = dis[vex].totalWeight;
}
}
if (mini < 0)//有无法到达的点则终止计算
{
cout << "有无法到达的点!" << endl;
return;
}
dis[mini].path.push_back(mini + 1);
dis[mini].visited = true;
// 经过这点去往其他未访问点的距离会不会更小
for (size_t vex = 0; vex < vexNum; vex++)
{
if (!dis[vex].visited && adjMatrix[mini][vex] != INT_MAX //此点未访问,可通过出度点到达此点
&& dis[vex].totalWeight > dis[mini].totalWeight + adjMatrix[mini][vex])
{
dis[vex].path.clear();
dis[vex].path = dis[mini].path;
dis[vex].totalWeight = dis[mini].totalWeight + adjMatrix[mini][vex];
}
}
}
}
void Graph_Di::printPath()
{
cout << line << endl;
cout << "最短路径:" << endl;
for (size_t i = 0; i < vexNum; i++)
{
cout << "[V" << i + 1 << "] ";
if (dis[i].totalWeight == INT_MAX)
{
cout << "∞: ";
}
else
{
cout << dis[i].totalWeight << ": ";
}
for (auto it : dis[i].path)
{
cout << it << " ";
}
cout << endl;
}
cout << line << endl;
}
main.cpp (测试文件)
#include "Graph_Di.h"
int main()
{
Graph_Di graph(6, 8);
graph.createGraph();
graph.printAdjacentMatrix();
graph.Dijkstra(1);
graph.printPath();
}
控制台输入测试内容:
1 3 10
1 5 30
1 6 100
2 3 5
3 4 50
4 6 10
5 6 60
5 4 20
控制台输出效果:
![image-20200507173604898](https://gitee.com/guo_zi_yi_412/picture_bed/raw/master/Typora-notes/image-20200507173604898.png)