Problem 83 : Path sum: four ways
NOTE: This problem is a significantly more challenging version of Problem 81.
In the 5 by 5 matrix below, the minimal path sum from the top left to the bottom right, by moving left, right, up, and down, is indicated in bold red and is equal to 2297.
Find the minimal path sum, in matrix.txt (right click and “Save Link/Target As…”), a 31K text file containing a 80 by 80 matrix, from the top left to the bottom right by moving left, right, up, and down.
1. 欧拉项目第83道题 路径之和:四个方向
注意: 这道题是第81道题的更有挑战性的版本。
下面5×5矩阵中,从左上角开始,到右下角结束,并且可以向上、向下、向左和向右移动,用红色和粗体表示,最小的路径之和等于2297。
在文件matrix.txt(右键单击保存)中,这是一个包含80x80矩阵的31K大小的文本文件,从最上角到右下角,可以向上、向下、向左和向右移动,找到最小路径之和。
2. 求解分析
这道题跟18, 67, 81 和82关联。这道题是求两点之间的最短路径,很明显我们需要使用Dijkstra算法。关于详细的算法介绍可以参考 迪杰斯特拉算法 。
要使用Dijkstra算法,我们需要知道有向图的顶点,边,权重等概念,构造一个有向图。
如何有效地构造有向图呢?最经典的是提供一个graph.dat文件:
6480 25360
2 1 2697
81 1 1096
3 2 5115
1 2 4445
82 2 20
4 3 718
2 3 2697
…
第一行有两个数:第一个是顶点Vertex总数,第二个是边Edge的总数。
从第二行到最后一行每一行都是构成图的一条边的三个值:终点VertexOut,起点VertexIn,权重Weight。
这道题因为顶点较多,边也很多,所以,我们就不采用临时文件graph.dat保存图的数据,我们采用了一个内部结构来保存:我们从文件读取数据后,会存放到一个二维数组Matrix[N][N]里,我们把(row, col) 作为顶点Vertex,向上、向下、向左和向右移动后的有效新位置(new_row, new_col),那么顶点(row, col)到顶点(new_row, new_col)的边的权重就是 Matrix[new_row][new_col]的值,这样,我们就可以建立起一个有向图了。值得注意的是,一般顶点在图的内部都是连续编号的,从1开始,一直到N*N,所以我们需要把(row, col)对应一个唯一的顶点编号。
最后,我们要求从左上角到右下角的最短路径,还需要加上Matrix[0][0]的值,因为它是原始的起点。
3. C++ 代码实现
Dijkstra算法有很多实现方法,我们实现了一种。求解分析简单介绍了我们实现的方法。
Dijkstra算法主要由类Graph_DG实现,程序由类PE0083实现。完整的类图如下:
C++ 代码
#include <iostream>
#include <fstream>
#include <string> // to_string(), stoi(), C++11
#include <sstream>
#include <vector>
#include <set>
#include <iterator>
#include <algorithm> // min()
#include <climits> // INT_MAX
#include <cassert> // assert()
#include <ctime> // clock()
// #define UNIT_TEST
// #define DETAILED_PATH_INFO
using namespace std;
typedef struct _EdgeData EdgeData;
struct _EdgeData
{
int vertexOut;
int vertexIn;
int weight;
};
typedef struct _GraphData GraphData;
struct _GraphData
{
int numOfVertexs;
int numOfEdges;
EdgeData *edgesData;
};
// save the shortest Distance information from starting vertex to each vertex
struct Distance
{
#ifdef DETAILED_PATH_INFO
string pathInfo;
#endif
vector<int> pathVec;
int value;
bool visited;
Distance()
{
visited = false;
value = 0;
#ifdef DETAILED_PATH_INFO
pathInfo = "";
#endif
}
};
class Graph_DG
{
private:
int numOfVertexs;
int numOfEdges;
int **adjaMatrix; // adjacent matrix
Distance *dist; // distance
set<int> vertexInSet; // set of vertexIn
set<int> vertexOutSet; // set of vertexOut
bool checkValuesScope(int vertexIn, int vertexOut, int weight);
void addEdge(int vertexIn, int vertexOut, int weight)
{
if (true == checkValuesScope(vertexIn, vertexOut, weight))
{
adjaMatrix[vertexIn - 1][vertexOut - 1] = weight;
}
}
public:
~Graph_DG();
void createGraph(GraphData& graphData);
void Dijkstra(int vertex);
int getMinimumPath(int vertexIn, int vertexOut);
#ifdef DETAILED_PATH_INFO
void printPathInfo(int vertex);
#endif
};
Graph_DG::~Graph_DG()
{
delete [] dist;
for (int i = 0; i < this->numOfVertexs; i++)
{
delete this->adjaMatrix[i];
}
delete adjaMatrix;
}
bool Graph_DG::checkValuesScope(int vertexIn, int vertexOut, int weight)
{
if (vertexIn < 1 || vertexOut < 1 || vertexIn > numOfVertexs ||
vertexOut > numOfVertexs || weight < 0)
{
return false;
}
return true;
}
void Graph_DG::createGraph(GraphData& graphData)
{
this->numOfVertexs = graphData.numOfVertexs;
this->numOfEdges = graphData.numOfEdges;
// allocate space for adjaMatrix and dist
adjaMatrix = new int*[this->numOfVertexs];
dist = new Distance [this->numOfVertexs];
for (int i = 0; i < this->numOfVertexs; i++)
{
adjaMatrix[i] = new int [this->numOfVertexs];
for (int k = 0; k < this->numOfVertexs; k++)
{
// initialize each element of adjacent matrix
adjaMatrix[i][k] = INT_MAX;
}
}
for (int i = 0; i < this->numOfVertexs; i++)
{
adjaMatrix[i][i] = 0;
dist[i].value = 0;
}
int vertexIn, vertexOut, weight;
for (int i = 0; i < this->numOfEdges; i++)
{
vertexOut = graphData.edgesData[i].vertexOut;
vertexIn = graphData.edgesData[i].vertexIn;
weight = graphData.edgesData[i].weight;
vertexInSet.insert(vertexIn);
vertexOutSet.insert(vertexOut);
#ifdef UNIT_TEST
cout << "V" << vertexIn << " -- " << weight << " --> V" << vertexOut << endl;
#endif
// add one edge from vertexIn to vertexOut for directed graph
addEdge(vertexIn, vertexOut, weight);
}
}
void Graph_DG::Dijkstra(int vertex)
{
// Firstly, initialize dist array
for (int vertexIdx = 0; vertexIdx < this->numOfVertexs; vertexIdx++)
{
// set the current path
#ifdef DETAILED_PATH_INFO
dist[vertexIdx].pathInfo="V"+to_string(vertex)+" --> V"+to_string(vertexIdx+1);
#endif
dist[vertexIdx].value = adjaMatrix[vertex - 1][vertexIdx];
dist[vertexIdx].pathVec.push_back(vertex);
dist[vertexIdx].pathVec.push_back(vertexIdx + 1);
}
// calculate the shortest dist from vertex to other vertexs
for (int numOfVertexs = 1; numOfVertexs < this->numOfVertexs; numOfVertexs++)
{
int tmpVertex = 0; // save the minimum vertex index in array dist[]
int min_value = INT_MAX; // save the minimum value
for (int vertexIdx = 0; vertexIdx < this->numOfVertexs; vertexIdx++)
{
if (!dist[vertexIdx].visited && dist[vertexIdx].value < min_value)
{
min_value = dist[vertexIdx].value;
tmpVertex = vertexIdx;
}
}
// add tmpVertex to shortest distance path information
dist[tmpVertex].visited = true;
for (int vertexIdx = 0; vertexIdx < this->numOfVertexs; vertexIdx++)
{
if (!dist[vertexIdx].visited && adjaMatrix[tmpVertex][vertexIdx] != INT_MAX &&
(dist[tmpVertex].value + adjaMatrix[tmpVertex][vertexIdx]) < dist[vertexIdx].value)
{
dist[vertexIdx].value = dist[tmpVertex].value + adjaMatrix[tmpVertex][vertexIdx];
#ifdef DETAILED_PATH_INFO
dist[vertexIdx].pathInfo=dist[tmpVertex].pathInfo+" --> V"+to_string(vertexIdx+1);
#endif
dist[vertexIdx].pathVec = dist[tmpVertex].pathVec;
dist[vertexIdx].pathVec.push_back(vertexIdx + 1);
}
}
}
}
int Graph_DG::getMinimumPath(int vertexIn, int vertexOut)
{
int minimal_path_sum = INT_MAX;
for (int i = 0; i != this->numOfVertexs; i++)
{
vector<int>::reverse_iterator iter = dist[i].pathVec.rbegin();
if (*iter == vertexOut && dist[i].value > 0 && dist[i].value != INT_MAX)
{
minimal_path_sum = min(minimal_path_sum, dist[i].value);
}
}
return minimal_path_sum;
}
#ifdef DETAILED_PATH_INFO
void Graph_DG::printPathInfo(int vertex)
{
string str = "V" + to_string(vertex);
cout << "The shortest distance from Vertex "<<str<<" to other Vertex :"<<endl;
for (int i = 0; i != this->numOfVertexs; i++)
{
if (dist[i].value > 0 && dist[i].value != INT_MAX)
{
cout << dist[i].pathInfo << ", the shortest distance = "\
<< dist[i].value << endl;
}
else
{
cout << dist[i].pathInfo << " has no path" << endl;
}
}
}
#endif
class PE0083
{
private:
static const int max_rows_or_cols = 80;
static const int max_edges = 30000;
int Matrix[max_rows_or_cols][max_rows_or_cols] = { 0, };
void handle_unit_test_data();
void readDataFromFile(const char *filename);
int getVertexNum(int row, int col, int numOfCols);
void generateGraphData(int numOfRows, int numOfOfCols);
public:
GraphData graphData;
PE0083() { graphData.edgesData = new EdgeData [max_edges]; };
~PE0083() { delete [] graphData.edgesData; };
int getMinimalPathSumOfMatrix(int numOfRowsOrCols);
};
int PE0083::getVertexNum(int row, int col, int numOfCols)
{
int vertexNum = row * numOfCols + col + 1;
return vertexNum; // vertex must be more than 1
}
void PE0083::generateGraphData(int numOfRows, int numOfCols)
{
struct _Step
{
int dx;
int dy;
} Steps[4] = { {-1,0}, {0,-1}, {1,0}, {0,1} };
graphData.numOfVertexs = numOfRows * numOfCols;
int numOfEdges = 0;
int new_row, new_col;
for (int row = 0; row < numOfCols; row++)
{
for (int col = 0; col < numOfCols; col++)
{
for (int k = 0; k < sizeof(Steps) / sizeof(_Step); k++)
{
new_row = row + Steps[k].dx;
new_col = col + Steps[k].dy;
if (0 <= new_row && new_row < numOfRows &&
0 <= new_col && new_col < numOfCols)
{
graphData.edgesData[numOfEdges].vertexOut = \
getVertexNum(row, col, numOfCols);
graphData.edgesData[numOfEdges].vertexIn = \
getVertexNum(new_row, new_col, numOfCols);
graphData.edgesData[numOfEdges].weight = \
Matrix[new_row][new_col];
numOfEdges++;
}
}
}
}
graphData.numOfEdges = numOfEdges;
}
void PE0083::handle_unit_test_data()
{
int Matrix5x5[5][5] = {
{131, 673, 234, 103, 18},
{201, 96, 342, 965, 150},
{630, 803, 746, 422, 111},
{537, 699, 497, 121, 956},
{805, 732, 524, 37, 331}
};
for (int i = 0; i < 5; i++)
{
for (int j = 0; j < 5; j++)
{
Matrix[i][j] = Matrix5x5[i][j];
}
}
}
void PE0083::readDataFromFile(const char *filename)
{
ifstream in(filename);
if (!in) return;
int row = 0, col;
string str;
char szBuf[1024];
while (in.getline(szBuf, 512))
{
col = 0;
istringstream istr(szBuf);
while (!istr.eof())
{
getline(istr, str, ',');
Matrix[row][col++] = stoi(str); // C++11
}
row++;
}
in.close();
}
int PE0083::getMinimalPathSumOfMatrix(int numOfRowsOrCols)
{
if (80 == numOfRowsOrCols)
{
readDataFromFile("p083_matrix.txt");
}
else
{
handle_unit_test_data();
}
generateGraphData(numOfRowsOrCols, numOfRowsOrCols);
Graph_DG graph;
graph.createGraph(graphData);
graph.Dijkstra(graphData.numOfVertexs);
int minimal_path_sum = Matrix[0][0] + graph.getMinimumPath(\
getVertexNum(numOfRowsOrCols-1,numOfRowsOrCols-1,numOfRowsOrCols), \
getVertexNum(0, 0, numOfRowsOrCols));
#ifdef DETAILED_PATH_INFO
graph.printPathInfo(getVertexNum(\
numOfRowsOrCols-1, numOfRowsOrCols-1, numOfRowsOrCols));
#endif
return minimal_path_sum;
}
int main()
{
clock_t start = clock();
PE0083 pe0083;
int minimal_path_sum = pe0083.getMinimalPathSumOfMatrix(5);
assert(2297 == minimal_path_sum);
minimal_path_sum = pe0083.getMinimalPathSumOfMatrix(80);
cout << "The minimal path sum (four ways moving) in the 80 by 80 matrix is ";
cout << minimal_path_sum << "." << endl;
clock_t finish = clock();
double duration = (double)(finish - start) / CLOCKS_PER_SEC;
cout << "C/C++ running time: " << duration << " seconds" << endl;
return 0;
}
4. Python 代码实现
Python我们没有自己实现Dijkstra算法,使用了非常棒的第三方的库networkx。但我们同样需要计算构建有向图的边(Edge)和权重(Weight)信息,然后调用下面语句
G.add_edge((i, j), (ix, jy), weight = matrix[ix][jy])
建立顶点(i, j)到顶点(ix, jy)且权重为matrix[ix][jy]的一条边。
最后调用下面语句得到顶点(0, 0)到顶点(n_rows-1, m_cols-1)的最短路径:
shortest_path_length = nx.dijkstra_path_length(G, (0, 0), (n_rows-1, m_cols-1))
当然,基于和C++代码类似的原因,我们还需要加上matrix[0][0]的值,才是我们需要的左上角到右下角的最小路径之和。
Python 代码
import networkx as nx
def handle_unit_test_data():
matrix = \
[[131, 673, 234, 103, 18],
[201, 96, 342, 965, 150],
[630, 803, 746, 422, 111],
[537, 699, 497, 121, 956],
[805, 732, 524, 37, 331]]
return matrix
def readDataFromFile(filename):
matrix = [list(map(int, row.split(','))) \
for row in open(filename).readlines()]
return matrix
def getMinimalPathSumOfMatrix(matrix):
n_rows, m_cols = len(matrix), len(matrix[0])
G = nx.DiGraph()
for i in range(n_rows):
for j in range(m_cols):
adjacent_nodes = [(i+x, j+y) for (x,y) in [(-1,0),(0,-1),(1,0),(0,1)]
if 0 <= i+x < n_rows and 0 <= j+y < m_cols ]
for (ix, jy) in adjacent_nodes:
# Add an edge between node(i,j) and node(ix,iy), including weight
G.add_edge((i, j), (ix, jy), weight = matrix[ix][jy])
shortest_path_length = nx.dijkstra_path_length(G, (0, 0), (n_rows-1, m_cols-1))
return shortest_path_length+matrix[0][0]
def main():
matrix = handle_unit_test_data()
assert 2297 == getMinimalPathSumOfMatrix(matrix)
matrix = readDataFromFile('p083_matrix.txt')
print("The minimal path sum (four ways moving) in 80 by 80 matrix is %d." \
% getMinimalPathSumOfMatrix(matrix))
if __name__ == '__main__':
main()