CSharp: Floyd-Warshall Algorithms

 

/*
 # encoding: utf-8 
# 版权所有  2026 ©涂聚文有限公司™ ®
# 许可信息查看:言語成了邀功盡責的功臣,還需要行爲每日來值班嗎
# 描述: Floyd-Warshall Algorithms 弗洛伊德-沃沙尔算法(Floyd-Warshall Algorithm)是一种用于解决多源最短路径问题的算法,即计算图中任意两点之间的最短路径。该算法以其创始人之一罗伯特·弗洛伊德命名。
# Author    : geovindu,Geovin Du 涂聚文.
# IDE       : vs2026 c# .net 10
# os        : windows 10
# database  : mysql 9.0 sql server 2019, postgreSQL 17.0  Oracle 21c Neo4j
# Datetime  : 2026/07/04 22:16 
# User      :  geovindu
# Product   : Visual Studio 2026
# Project   : CSharpAlgorithms
# File      : City.cs

 */

using System;
using System.Collections.Generic;
using System.Text;

namespace CSharpAlgorithms.FloydWarshal
{
    /// <summary>
    /// 城市坐标实体
    /// </summary>
    public class City
    {
        public string Name { get; set; }
        public float X { get; set; }
        public float Y { get; set; }

        public City(string name, float x, float y)
        {
            Name = name;
            X = x;
            Y = y;
        }
    }
}



/*
 # encoding: utf-8 
# 版权所有  2026 ©涂聚文有限公司™ ®
# 许可信息查看:言語成了邀功盡責的功臣,還需要行爲每日來值班嗎
# 描述: Floyd-Warshall Algorithms
# Author    : geovindu,Geovin Du 涂聚文.
# IDE       : vs2026 c# .net 10
# os        : windows 10
# database  : mysql 9.0 sql server 2019, postgreSQL 17.0  Oracle 21c Neo4j
# Datetime  : 2026/07/04 22:16 
# User      :  geovindu
# Product   : Visual Studio 2026
# Project   : CSharpAlgorithms
# File      : FloydRoadGraph.cs

 */

using System;
using System.Collections.Generic;
using System.Text;

namespace CSharpAlgorithms.FloydWarshal
{
    /// <summary>
    /// Floyd-Warshall 路网图(距离矩阵+前驱矩阵)
    /// </summary>
    public class FloydRoadGraph
    {
        private const double INF = double.MaxValue / 2;
        public List<string> CityNames { get; }
        public Dictionary<string, int> NameToIdx { get; }
        private int _n;

        public double[,] DistMatrix { get; private set; }
        public int[,] PrevMatrix { get; private set; }
        /// <summary>
        /// 
        /// </summary>
        /// <param name="cityList"></param>
        public FloydRoadGraph(List<string> cityList)
        {
            CityNames = cityList;
            _n = cityList.Count;
            NameToIdx = new Dictionary<string, int>();
            for (int i = 0; i < _n; i++)
                NameToIdx[cityList[i]] = i;

            // 初始化矩阵
            DistMatrix = new double[_n, _n];
            PrevMatrix = new int[_n, _n];
            for (int i = 0; i < _n; i++)
            {
                for (int j = 0; j < _n; j++)
                {
                    DistMatrix[i, j] = INF;
                    PrevMatrix[i, j] = -1;
                }
                DistMatrix[i, i] = 0;
            }
        }

        /// <summary>
        /// 添加双向边
        /// </summary>
        /// <param name="from"></param>
        /// <param name="to"></param>
        /// <param name="weight"></param>
        public void AddEdge(string from, string to, double weight)
        {
            int u = NameToIdx[from];
            int v = NameToIdx[to];
            DistMatrix[u, v] = weight;
            DistMatrix[v, u] = weight;
            PrevMatrix[u, v] = u;
            PrevMatrix[v, u] = v;
        }

        /// <summary>
        /// 执行Floyd-Warshall 全源最短路
        /// </summary>
        public void RunFloyd()
        {
            int n = _n;
            double[,] d = (double[,])DistMatrix.Clone();
            int[,] p = (int[,])PrevMatrix.Clone();

            for (int k = 0; k < n; k++)
            {
                for (int i = 0; i < n; i++)
                {
                    for (int j = 0; j < n; j++)
                    {
                        if (d[i, k] + d[k, j] < d[i, j])
                        {
                            d[i, j] = d[i, k] + d[k, j];
                            p[i, j] = p[k, j];
                        }
                    }
                }
            }
            DistMatrix = d;
            PrevMatrix = p;
        }

        /// <summary>
        /// 回溯路径,过滤禁行城市
        /// </summary>
        /// <param name="start"></param>
        /// <param name="end"></param>
        /// <param name="banCities"></param>
        /// <returns></returns>
        public (double total, List<string> path) GetPath(string start, string end, HashSet<string> banCities)
        {
            if (!NameToIdx.ContainsKey(start) || !NameToIdx.ContainsKey(end))
                return (INF, new List<string>());

            int u = NameToIdx[start];
            int v = NameToIdx[end];
            if (DistMatrix[u, v] >= INF)
                return (INF, new List<string>());

            List<int> idxPath = new List<int>();
            int curr = v;
            while (curr != -1)
            {
                idxPath.Add(curr);
                curr = PrevMatrix[u, curr];
            }
            idxPath.Reverse();

            List<string> namePath = new List<string>();
            foreach (int idx in idxPath)
            {
                string city = CityNames[idx];
                if (banCities.Contains(city))
                    return (INF, new List<string>());
                namePath.Add(city);
            }
            return (DistMatrix[u, v], namePath);
        }
    }
}



using System;
using System.Collections.Generic;
using System.Drawing;
using System.Drawing.Drawing2D;
using System.Drawing.Imaging;
using System.Text;

namespace CSharpAlgorithms.FloydWarshal
{
    /// <summary>
    /// 路径规划器:约束 + 绘图
    /// </summary>
    public class FloydPathPlanner
    {
        private readonly FloydRoadGraph _graph;
        private readonly Dictionary<string, City> _cityMap;

        public List<string> MustPassCities = new();
        public HashSet<string> BanCities = new();
        /// <summary>
        /// 
        /// </summary>
        /// <param name="graph"></param>
        /// <param name="cityMap"></param>
        public FloydPathPlanner(FloydRoadGraph graph, Dictionary<string, City> cityMap)
        {
            _graph = graph;
            _cityMap = cityMap;
        }
        /// <summary>
        /// 
        /// </summary>
        /// <param name="start"></param>
        /// <param name="end"></param>
        /// <returns></returns>
        public (double total, List<string> path) CalculateRoute(string start, string end)
        {
            var res = _graph.GetPath(start, end, BanCities);
            double total = res.total;
            List<string> path = res.path;

            if (path.Count == 0)
                return (double.MaxValue, new List<string>());

            // 校验必须途经城市
            foreach (string req in MustPassCities)
            {
                if (!path.Contains(req))
                    return (double.MaxValue, new List<string>());
            }
            return (total, path);
        }

        /// <summary>
        /// 绘制PNG,偏移充足,负X城市全部可见
        /// </summary>
        /// <param name="savePath"></param>
        /// <param name="highlightRoute"></param>
        public void DrawMap(string savePath, List<string> highlightRoute)
        {
            int width = 2800;
            int height = 1300;
            float offsetX = 400f;
            float offsetY = 20f;
            float scale = 70f;
            float nodeRadius = 8f;

            using Bitmap bmp = new Bitmap(width, height);
            using Graphics g = Graphics.FromImage(bmp);
            g.SmoothingMode = SmoothingMode.AntiAlias;
            g.Clear(Color.White);

            // 修复1:FontStyle.Regular 替代不存在的 PLAIN
            using Font font = new Font("Microsoft YaHei", 11, FontStyle.Regular);
            using Pen penNormal = new Pen(Color.Gray, 1);
            using Pen penHighlight = new Pen(Color.Red, 4);
            using SolidBrush brushNode = new SolidBrush(Color.Blue);
            using SolidBrush brushText = new SolidBrush(Color.Black);

            // 绘制普通道路,去重双向边
            HashSet<string> drawnEdge = new HashSet<string>();
            foreach (string frm in _graph.CityNames)
            {
                City cFrom = _cityMap[frm];
                float fx = cFrom.X * scale + offsetX;
                float fy = height - (cFrom.Y * scale + offsetY);

                foreach (string to in _graph.CityNames)
                {
                    string k1 = frm + "|" + to;
                    string k2 = to + "|" + frm;
                    if (drawnEdge.Contains(k1) || drawnEdge.Contains(k2)) continue;

                    int u = _graph.NameToIdx[frm];
                    int v = _graph.NameToIdx[to];
                    if (_graph.DistMatrix[u, v] >= double.MaxValue / 2) continue;

                    drawnEdge.Add(k1);
                    City cTo = _cityMap[to];
                    float tx = cTo.X * scale + offsetX;
                    float ty = height - (cTo.Y * scale + offsetY);
                    g.DrawLine(penNormal, fx, fy, tx, ty);
                }
            }

            // 绘制红色高亮路径
            for (int i = 0; i < highlightRoute.Count - 1; i++)
            {
                string a = highlightRoute[i];
                string b = highlightRoute[i + 1];
                City ca = _cityMap[a];
                City cb = _cityMap[b];
                float ax = ca.X * scale + offsetX;
                float ay = height - (ca.Y * scale + offsetY);
                float bx = cb.X * scale + offsetX;
                float by = height - (cb.Y * scale + offsetY);
                g.DrawLine(penHighlight, ax, ay, bx, by);
            }

            // 绘制城市圆点与文字
            foreach (City city in _cityMap.Values)
            {
                float x = city.X * scale + offsetX;
                float y = height - (city.Y * scale + offsetY);
                g.FillEllipse(brushNode, x - nodeRadius, y - nodeRadius, nodeRadius * 2, nodeRadius * 2);
                g.DrawString(city.Name, font, brushText, x + 4, y - nodeRadius - 14);
            }

            // 修复2:原生Image.Save 替代不存在的ImageIO
            using FileStream fs = new FileStream(savePath, FileMode.Create);
            bmp.Save(fs, ImageFormat.Png);
            Console.WriteLine($"✅ 路网图片已保存:{Path.GetFullPath(savePath)}");
        }
    }
}

  

调用:

/*
 # encoding: utf-8 
# 版权所有  2026 ©涂聚文有限公司™ ®
# 许可信息查看:言語成了邀功盡責的功臣,還需要行爲每日來值班嗎
# 描述: Floyd-Warshall Algorithms
# Author    : geovindu,Geovin Du 涂聚文.
# IDE       : vs2026 c# .net 10
# os        : windows 10
# database  : mysql 9.0 sql server 2019, postgreSQL 17.0  Oracle 21c Neo4j
# Datetime  : 2026/07/04 22:16 
# User      :  geovindu
# Product   : Visual Studio 2026
# Project   : CSharpAlgorithms
# File      : FloydWarshallBll.cs

 */


using CSharpAlgorithms.FloydWarshal;
using System;
using System.Collections.Generic;
using System.Text;

namespace CSharpAlgorithms.Bll
{

    /// <summary>
    /// 
    /// </summary>
    public class FloydWarshallBll
    {

        /// <summary>
        /// 工具:拼接路径文本
        /// </summary>
        /// <param name="path"></param>
        /// <returns></returns>
        public static string JoinPath(List<string> path)
        {
            return path.Count == 0 ? "无路线" : string.Join(" → ", path);
        }

        /// <summary>
        /// 工具:解析顿号分隔城市输入
        /// </summary>
        /// <param name="input"></param>
        /// <returns></returns>
        public static List<string> ParseCityInput(string input)
        {
            if (string.IsNullOrWhiteSpace(input))
                return new List<string>();
            return input.Split("、", StringSplitOptions.RemoveEmptyEntries)
                        .Select(s => s.Trim())
                        .Where(s => !string.IsNullOrWhiteSpace(s))
                        .ToList();
        }
        /// <summary>
        /// 
        /// </summary>
        public void Demo()
        {

            // 城市坐标数据
            Dictionary<string, City> cityData = new()
        {
            {"深圳", new City("深圳", 5, 0)},
            {"惠州", new City("惠州", 6, 1)},
            {"东莞", new City("东莞", 4, 0)},
            {"广州", new City("广州", 3, 0)},
            {"佛山", new City("佛山", 2, 0)},
            {"肇庆", new City("肇庆", 1, 1)},
            {"梧州", new City("梧州", -2, 2)},
            {"桂林", new City("桂林", -3, 4)},
            {"柳州", new City("柳州", -4, 3)},
            {"南宁", new City("南宁", -5, 1)},
            {"韶关", new City("韶关", 2, 4)},
            {"河源", new City("河源", 7, 3)},
            {"赣州", new City("赣州", 8, 6)},
            {"吉安", new City("吉安", 9, 9)},
            {"南昌", new City("南昌", 10, 8)},
            {"萍乡", new City("萍乡", 7, 8)},
            {"长沙", new City("长沙", 6, 9)},
            {"株洲", new City("株洲", 6, 8)},
            {"衡阳", new City("衡阳", 6, 6)},
            {"郴州", new City("郴州", 6, 4)},
            {"九江", new City("九江", 11, 9)},
            {"武汉", new City("武汉", 9, 11)},
            {"郑州", new City("郑州", 10, 14)},
            {"西安", new City("西安", 7, 15)},
            {"福州", new City("福州", 13, 7)},
            {"厦门", new City("厦门", 14, 4)},
        };
            List<string> cityList = cityData.Keys.ToList();

            #region 1. 里程路网 KM
            FloydRoadGraph graphKm = new FloydRoadGraph(cityList);
            graphKm.AddEdge("深圳", "惠州", 75);
            graphKm.AddEdge("深圳", "广州", 140);
            graphKm.AddEdge("深圳", "东莞", 65);
            graphKm.AddEdge("惠州", "河源", 90);
            graphKm.AddEdge("东莞", "广州", 50);
            graphKm.AddEdge("广州", "韶关", 190);
            graphKm.AddEdge("广州", "佛山", 25);
            graphKm.AddEdge("佛山", "肇庆", 70);
            graphKm.AddEdge("肇庆", "梧州", 210);
            graphKm.AddEdge("梧州", "桂林", 260);
            graphKm.AddEdge("桂林", "柳州", 170);
            graphKm.AddEdge("柳州", "南宁", 220);
            graphKm.AddEdge("韶关", "赣州", 230);
            graphKm.AddEdge("河源", "赣州", 180);
            graphKm.AddEdge("赣州", "吉安", 240);
            graphKm.AddEdge("赣州", "南昌", 390);
            graphKm.AddEdge("吉安", "南昌", 215);
            graphKm.AddEdge("吉安", "萍乡", 280);
            graphKm.AddEdge("萍乡", "长沙", 150);
            graphKm.AddEdge("长沙", "武汉", 280);
            graphKm.AddEdge("长沙", "株洲", 60);
            graphKm.AddEdge("株洲", "衡阳", 130);
            graphKm.AddEdge("衡阳", "郴州", 180);
            graphKm.AddEdge("郴州", "韶关", 150);
            graphKm.AddEdge("南昌", "九江", 130);
            graphKm.AddEdge("九江", "武汉", 200);
            graphKm.AddEdge("武汉", "郑州", 470);
            graphKm.AddEdge("郑州", "西安", 450);
            graphKm.AddEdge("南昌", "福州", 380);
            graphKm.AddEdge("福州", "厦门", 230);
            graphKm.RunFloyd();
            #endregion

            #region 2. 耗时路网 Hour
            FloydRoadGraph graphHour = new FloydRoadGraph(cityList);
            graphHour.AddEdge("深圳", "惠州", 1.0);
            graphHour.AddEdge("深圳", "广州", 1.8);
            graphHour.AddEdge("深圳", "东莞", 0.8);
            graphHour.AddEdge("惠州", "河源", 1.3);
            graphHour.AddEdge("东莞", "广州", 0.7);
            graphHour.AddEdge("广州", "韶关", 2.2);
            graphHour.AddEdge("广州", "佛山", 0.4);
            graphHour.AddEdge("佛山", "肇庆", 0.9);
            graphHour.AddEdge("肇庆", "梧州", 2.5);
            graphHour.AddEdge("梧州", "桂林", 3.0);
            graphHour.AddEdge("桂林", "柳州", 2.0);
            graphHour.AddEdge("柳州", "南宁", 2.5);
            graphHour.AddEdge("韶关", "赣州", 2.7);
            graphHour.AddEdge("河源", "赣州", 2.0);
            graphHour.AddEdge("赣州", "吉安", 2.6);
            graphHour.AddEdge("赣州", "南昌", 4.2);
            graphHour.AddEdge("吉安", "南昌", 2.3);
            graphHour.AddEdge("吉安", "萍乡", 3.0);
            graphHour.AddEdge("萍乡", "长沙", 1.6);
            graphHour.AddEdge("长沙", "武汉", 3.0);
            graphHour.AddEdge("长沙", "株洲", 0.8);
            graphHour.AddEdge("株洲", "衡阳", 1.4);
            graphHour.AddEdge("衡阳", "郴州", 2.0);
            graphHour.AddEdge("郴州", "韶关", 1.7);
            graphHour.AddEdge("南昌", "九江", 1.4);
            graphHour.AddEdge("九江", "武汉", 2.1);
            graphHour.AddEdge("武汉", "郑州", 4.8);
            graphHour.AddEdge("郑州", "西安", 4.3);
            graphHour.AddEdge("南昌", "福州", 4.0);
            graphHour.AddEdge("福州", "厦门", 2.4);
            graphHour.RunFloyd();
            #endregion

            #region 3. 路费路网 Cost
            FloydRoadGraph graphCost = new FloydRoadGraph(cityList);
            graphCost.AddEdge("深圳", "惠州", 35);
            graphCost.AddEdge("深圳", "广州", 65);
            graphCost.AddEdge("深圳", "东莞", 30);
            graphCost.AddEdge("惠州", "河源", 42);
            graphCost.AddEdge("东莞", "广州", 25);
            graphCost.AddEdge("广州", "韶关", 85);
            graphCost.AddEdge("广州", "佛山", 15);
            graphCost.AddEdge("佛山", "肇庆", 35);
            graphCost.AddEdge("肇庆", "梧州", 100);
            graphCost.AddEdge("梧州", "桂林", 120);
            graphCost.AddEdge("桂林", "柳州", 70);
            graphCost.AddEdge("柳州", "南宁", 95);
            graphCost.AddEdge("韶关", "赣州", 105);
            graphCost.AddEdge("河源", "赣州", 80);
            graphCost.AddEdge("赣州", "吉安", 110);
            graphCost.AddEdge("赣州", "南昌", 180);
            graphCost.AddEdge("吉安", "南昌", 95);
            graphCost.AddEdge("吉安", "萍乡", 125);
            graphCost.AddEdge("萍乡", "长沙", 65);
            graphCost.AddEdge("长沙", "武汉", 130);
            graphCost.AddEdge("长沙", "株洲", 25);
            graphCost.AddEdge("株洲", "衡阳", 55);
            graphCost.AddEdge("衡阳", "郴州", 80);
            graphCost.AddEdge("郴州", "韶关", 65);
            graphCost.AddEdge("南昌", "九江", 55);
            graphCost.AddEdge("九江", "武汉", 85);
            graphCost.AddEdge("武汉", "郑州", 210);
            graphCost.AddEdge("郑州", "西安", 190);
            graphCost.AddEdge("南昌", "福州", 170);
            graphCost.AddEdge("福州", "厦门", 105);
            graphCost.RunFloyd();
            #endregion

            // 初始化规划器
            FloydPathPlanner planKm = new FloydPathPlanner(graphKm, cityData);
            FloydPathPlanner planHour = new FloydPathPlanner(graphHour, cityData);
            FloydPathPlanner planCost = new FloydPathPlanner(graphCost, cityData);

            Console.WriteLine("===== C# .NET10 Floyd-Warshall 全源路径规划系统 =====");
            Console.WriteLine($"可用城市:{string.Join("、", cityList)}");
            Console.WriteLine("----------------------------------------");

            // 控制台输入
            Console.Write("输入起点城市:");
            string start = Console.ReadLine().Trim();
            Console.Write("输入终点城市:");
            string end = Console.ReadLine().Trim();
            Console.Write("必须途经城市(多城顿号分隔,无直接回车):");
            string mustInput = Console.ReadLine().Trim();
            Console.Write("禁止绕行城市(多城顿号分隔,无直接回车):");
            string banInput = Console.ReadLine().Trim();

            List<string> mustCities = ParseCityInput(mustInput);
            List<string> banCities = ParseCityInput(banInput);

            // 统一绑定约束
            void SetConstraint(FloydPathPlanner p)
            {
                p.MustPassCities = new List<string>(mustCities);
                p.BanCities = new HashSet<string>(banCities);
            }
            SetConstraint(planKm);
            SetConstraint(planHour);
            SetConstraint(planCost);

            // 计算三条路线
            var (kmTotal, kmPath) = planKm.CalculateRoute(start, end);
            var (hourTotal, hourPath) = planHour.CalculateRoute(start, end);
            var (costTotal, costPath) = planCost.CalculateRoute(start, end);

            // 控制台输出
            Console.WriteLine("\n==================================================");
            Console.WriteLine($"【{start} → {end} 规划结果】");
            Console.WriteLine("==================================================");
            Console.WriteLine(kmPath.Count > 0
                ? $"📏 最短里程:{kmTotal:F0} km | 路线:{JoinPath(kmPath)}"
                : "📏 最短里程:无可行路线(约束过滤)");

            Console.WriteLine(hourPath.Count > 0
                ? $"⏰ 最短耗时:{hourTotal:F1} h | 路线:{JoinPath(hourPath)}"
                : "⏰ 最短耗时:无可行路线(约束过滤)");

            Console.WriteLine(costPath.Count > 0
                ? $"💰 最低路费:{costTotal:F0} 元 | 路线:{JoinPath(costPath)}"
                : "💰 最低路费:无可行路线(约束过滤)");
            Console.WriteLine("==================================================");

            // 生成PNG图片
            if (kmPath.Count > 0)
            {
                planKm.DrawMap("floyd_route_map.png", kmPath);
            }

        }
    }
}

  

输出:

d981919b-6bd8-4e83-bf68-4adeab93b07a

 

floyd_route_map

 

posted @ 2026-07-12 12:10  ®Geovin Du Dream Park™  阅读(7)  评论(0)    收藏  举报