1. Dijkstra
1.1 原理与步骤
步骤:
- 选取距离源点最近且未被访问过的节点
- 标记该节点为已访问
- 更新未访问节点到源点的距离
1.2 代码实现
以KamaCoder47题为例
题目:47. 参加科学大会(第六期模拟笔试) (kamacoder.com)
class Program
{
public static void Main(string[] args)
{
//处理输入
string[] dimensions = Console.ReadLine().Split();
int n = int.Parse(dimensions[0]);
int m = int.Parse(dimensions[1]);
// visited & minDist & graph
bool[] visited = new bool[n + 1];
int[] minDist = new int[n + 1];
int[][] graph = new int[n + 1][];
for (int i = 0; i <= n; i++)
{
graph[i] = new int[n + 1];
for (int j = 0; j <= n; j++)
{
graph[i][j] = int.MaxValue;
}
}
// 填充
for (int i = 0; i <= n; i++)
{
visited[i] = false;
if (i == 1) minDist[i] = 0;
else minDist[i] = int.MaxValue;
}
for (int i = 0; i < m; i++)
{
string[] edges = Console.ReadLine().Split();
int start = int.Parse(edges[0]);
int end = int.Parse(edges[1]);
int weight = int.Parse(edges[2]);
graph[start][end] = weight;
}
//
int result = Dj(graph, n, visited, minDist);
Console.WriteLine(result);
}
public static int Dj(int[][] graph, int n, bool[] visited, int[] minDist)
{
for (int count = 1; count < n + 1; count++)
{
//find min node
int mindist = int.MaxValue;
int minnode = 0;
for (int i = 1; i < n + 1; i++)
{
if (visited[i] == false)
{
if (minDist[i] < mindist)
{
minnode = i;
mindist = minDist[i];
}
}
}
//update visited
visited[minnode] = true;
//update minDist
for (int i = 1; i < n + 1; i++)
{
if (graph[minnode][i] != int.MaxValue)
{
minDist[i] = Math.Min(graph[minnode][i] + mindist, minDist[i]);
}
}
Console.WriteLine(string.Join(" ", minDist));
}
return minDist[n] == int.MaxValue ? -1 : minDist[n];
}
}
1.3 堆优化
1、使用 List<Edge>[] 表示图
Edge结构体
public struct Edge
{
public int To; // 邻接顶点
public int Val; // 边的权重
public Edge(int to, int val)
{
To = to;
Val = val;
}
}
图的表示
List<Edge>[] graph = new List<Edge>[V + 1];
for (int i = 1; i <= V; i++)
{
graph[i] = new List<Edge>();
}
2、输入处理
3、初始化
int[] minDist = new int[V + 1];
for (int i = 0; i <= V; i++)
{
minDist[i] = int.MaxValue;
}
minDist[start] = 0;
bool[] visited = new bool[V + 1];
PriorityQueue<int, int> pq = new PriorityQueue<int, int>();
pq.Enqueue(start, 0); // 起点入队,距离为0
4、算法主循环
while (pq.Count > 0)
{
// 从优先队列中取出距离最小的节点
pq.TryDequeue(out int currentNode, out int currentDist);
// 如果已经访问过,跳过
if (visited[currentNode])
continue;
// 标记为已访问
visited[currentNode] = true;
// 遍历所有邻接点
foreach (var edge in graph[currentNode])
{
int neighbor = edge.To;
int weight = edge.Val;
// 如果未访问且可以通过当前节点更新最短距离
if (!visited[neighbor] && currentDist + weight < minDist[neighbor])
{
minDist[neighbor] = currentDist + weight;
pq.Enqueue(neighbor, minDist[neighbor]);
}
}
}
5、输出
完整代码:
namespace DijkstraAlgorithm
{
// 定义一个结构体来表示带权重的边
public struct Edge
{
public int To; // 邻接顶点
public int Val; // 边的权重
public Edge(int to, int val)
{
To = to;
Val = val;
}
}
class Program
{
static void Main(string[] args)
{
// 读取顶点数V和边数E
string[] firstLine = Console.ReadLine().Split();
int V = int.Parse(firstLine[0]);
int E = int.Parse(firstLine[1]);
// 初始化邻接表
List<Edge>[] graph = new List<Edge>[V + 1];
for (int i = 1; i <= V; i++)
{
graph[i] = new List<Edge>();
}
// 读取所有边的信息
for (int i = 0; i < E; i++)
{
string[] edgeInput = Console.ReadLine().Split();
int p1 = int.Parse(edgeInput[0]);
int p2 = int.Parse(edgeInput[1]);
int val = int.Parse(edgeInput[2]);
// 添加边到邻接表(无向图需要双向添加)
graph[p1].Add(new Edge(p2, val));
graph[p2].Add(new Edge(p1, val));
}
int start = 1; // 起点
int end = V; // 终点
// 存储从源点到每个节点的最短距离
int[] minDist = new int[V + 1];
for (int i = 0; i <= V; i++)
{
minDist[i] = int.MaxValue;
}
minDist[start] = 0;
// 记录顶点是否被访问过
bool[] visited = new bool[V + 1];
// 优先队列中存放节点,优先级是源点到该节点的距离
// 使用C#内置的PriorityQueue,参数为元素类型和优先级类型
PriorityQueue<int, int> pq = new PriorityQueue<int, int>();
pq.Enqueue(start, 0); // 起点入队,距离为0
while (pq.Count > 0)
{
// 从优先队列中取出距离最小的节点
pq.TryDequeue(out int currentNode, out int currentDist);
// 如果已经访问过,跳过
if (visited[currentNode])
continue;
// 标记为已访问
visited[currentNode] = true;
// 遍历所有邻接点
foreach (var edge in graph[currentNode])
{
int neighbor = edge.To;
int weight = edge.Val;
// 如果未访问且可以通过当前节点更新最短距离
if (!visited[neighbor] && currentDist + weight < minDist[neighbor])
{
minDist[neighbor] = currentDist + weight;
pq.Enqueue(neighbor, minDist[neighbor]);
}
}
}
// 输出结果
if (minDist[end] == int.MaxValue)
Console.WriteLine(-1); // 不能到达终点
else
Console.WriteLine(minDist[end]); // 到达终点的最短路径
}
}
}
2. Bellman_ford
2.1 原理与步骤
2.2 代码实现
以KamaCoder 94题为例
题目:94. 城市间货物运输 I (kamacoder.com)
class Program
{
public static void Main(string[] args)
{
//处理输入
string[] dimensions = Console.ReadLine().Split();
int n = int.Parse(dimensions[0]);
int m = int.Parse(dimensions[1]);
//minDist
int[] minDist = new int[n + 1];
for (int i = 0; i <= n; i++)
{
minDist[i] = i == 1 ? 0 : int.MaxValue;
}
//edges
int[][] edges = new int[m][];
for (int i = 0; i < m; i++)
{
edges[i] = new int[3];
string[] edge = Console.ReadLine().Split();
edges[i][0] = int.Parse(edge[0]);
edges[i][1] = int.Parse(edge[1]);
edges[i][2] = int.Parse(edge[2]);
}
//BF
if (BF(edges, minDist, n - 1))
{
Console.WriteLine(minDist[n]);
}
else
{
Console.WriteLine("unconnected");
}
}
public static bool BF(int[][] edges, int[] minDist, int n)
{
bool isUpdate = true;
int count = 1;
while (count <= n && isUpdate == true)
{
count++;
isUpdate = false;
for (int i = 0; i < edges.Length; i++)
{
int start = edges[i][0];
int end = edges[i][1];
int weight = edges[i][2];
if (minDist[start] != int.MaxValue)
{
int dist = minDist[start] + weight;
if (dist < minDist[end])
{
minDist[end] = dist;
isUpdate = true;
}
}
}
}
return !isUpdate;
}
}
2.3 队列优化
class Edge
{
public int To { get; set; }
public int Weight { get; set; }
public Edge(int to, int weight)
{
To = to;
Weight = weight;
}
}
class Program
{
public static void Main(string[] args)
{
string[] input = Console.ReadLine().Split();
int n = int.Parse(input[0]);
int m = int.Parse(input[1]);
List<Edge>[] graph = new List<Edge>[n + 1];
for (int i = 1; i <= n; i++)
{
graph[i] = new List<Edge>();
}
bool[] isInQueue = new bool[n + 1];
for (int i = 0; i < m; i++)
{
string[] edgeInput = Console.ReadLine().Split();
int p1 = int.Parse(edgeInput[0]);
int p2 = int.Parse(edgeInput[1]);
int val = int.Parse(edgeInput[2]);
graph[p1].Add(new Edge(p2, val));
}
int start = 1; // 起点
int end = n; // 终点
int[] minDist = new int[n + 1];
for (int i = 1; i <= n; i++)
{
minDist[i] = int.MaxValue;
}
minDist[start] = 0;
Queue<int> queue = new Queue<int>();
queue.Enqueue(start);
isInQueue[start] = true;
while (queue.Count > 0)
{
int node = queue.Dequeue();
isInQueue[node] = false;
foreach (var edge in graph[node])
{
int from = node;
int to = edge.To;
int weight = edge.Weight;
if (minDist[to] > minDist[from] + weight)
{
minDist[to] = minDist[from] + weight;
if (!isInQueue[to])
{
queue.Enqueue(to);
isInQueue[to] = true;
}
}
}
}
if (minDist[end] == int.MaxValue)
{
Console.WriteLine("unconnected");
}
else
{
Console.WriteLine(minDist[end]);
}
}
}
2.4 应用场景
3. Floyd
3.1 原理与步骤
动态规划
1、动规数组
grid[ i ][ j ][ k ]:节点 i 到节点 j ,以[ 1 ... k]为中间节点的最短距离
2、递推公式
case1:经过节点 k:grid[ i ][ j ][ k ] = grid[ i ][ k ][ k-1] + grid[ k ][ j ][ k-1 ]
case2:不经过节点 k:grid[ i ][ j ][ k ] = grid[ i ][ j ][ k-1]
取最小值
3、初始化
对于图中存在的每一条双向边,其距离为边的两节点无中间节点的最短距离,即grid[ i ][ j ][ 0 ] = m
对于图中不存在边的两个节点,其距离被初始化为整数最大值,即grid[ i ][ j ][ 0 ] = int.MaxValue
4、遍历顺序
由遍历公式可以看出,遍历顺序应该为 k 值从小到大,而对 i 和 j 无固定要求
3.2 代码实现
以KamaCoder 97题为例
题目:97. 小明逛公园 (kamacoder.com)
class Program
{
public static void Main(string[] args)
{
//处理输入&初始化
string[] dimensions = Console.ReadLine().Split();
int v = int.Parse(dimensions[0]);
int e = int.Parse(dimensions[1]);
int[,,] grid = new int[v + 1, v + 1, v + 1];
for (int i = 0; i <= v; i++)
{
for (int j = 0; j <= v; j++)
{
for (int k = 0; k <= v; k++)
{
grid[i, j, k] = int.MaxValue;
}
}
}
for (int i = 0; i < e; i++)
{
string[] edge = Console.ReadLine().Split();
int n1 = int.Parse(edge[0]);
int n2 = int.Parse(edge[1]);
int w = int.Parse(edge[2]);
grid[n1, n2, 0] = w;
grid[n2, n1, 0] = w;
}
//输出列表
int count = int.Parse(Console.ReadLine());
int[][] outresult = new int[count][];
for (int i = 0; i < count; i++)
{
outresult[i] = new int[2];
string[] nodes = Console.ReadLine().Split();
outresult[i][0] = int.Parse(nodes[0]);
outresult[i][1] = int.Parse(nodes[1]);
}
//递推
for (int k = 1; k <= v; k++)
{
for (int i = 1; i <= v; i++)
{
for (int j = 1; j <= v; j++)
{
int c1, c2;
if (grid[i, k, k - 1] != int.MaxValue && grid[k, j, k - 1] != int.MaxValue) c1 = grid[i, k, k - 1] + grid[k, j, k - 1];
else c1 = int.MaxValue;
c2 = grid[i, j, k - 1];
grid[i, j, k] = Math.Min(c1, c2);
}
}
}
//输出
for (int i = 0; i < count; i++)
{
int result = grid[outresult[i][0], outresult[i][1], v];
Console.WriteLine(result == int.MaxValue ? -1 : result);
}
}
}