go: Floyd-Warshall Algorithms

Go 复制代码
/*
# 版权所有  2026 ©涂聚文有限公司™ ®
# 许可信息查看:言語成了邀功盡責的功臣,還需要行爲每日來值班嗎
# 描述:Floyd-Warshall Algorithms
# Author    : geovindu,Geovin Du 涂聚文.
# IDE       : goLang 2024.3.6 go 26.2
# os        : windows 10
# database  : mysql 9.0 sql server 2019, postgreSQL 17.0  Oracle 21c Neo4j
# Datetime  : 2026/7/11 22:36
# User      :  geovindu
# Product   : GoLand
# Project   : goalgorithms
# File      : floydwarshall.go
*/
package floydwarshall
 
import (
    "fmt"
    "os"
    "sort"
    "strings"
 
    "golang.org/x/image/font/opentype"
    "gonum.org/v1/plot"
    "gonum.org/v1/plot/font"
    "gonum.org/v1/plot/plotter"
    "gonum.org/v1/plot/plotutil"
    "gonum.org/v1/plot/text"
    "gonum.org/v1/plot/vg"
)
 
const INF = 1e18
 
// City 城市坐标实体
type City struct {
    Name string
    X    float64
    Y    float64
}
 
// RoadGraph Floyd全源图,距离矩阵+前驱矩阵
type RoadGraph struct {
    cityList []string
    nameIdx  map[string]int
    n        int
    dist     [][]float64 // 距离矩阵
    prev     [][]int     // 前驱矩阵,用于回溯路径
}
 
func NewRoadGraph(cityNames []string) *RoadGraph {
    n := len(cityNames)
    nameIdx := make(map[string]int, n)
    for i, name := range cityNames {
        nameIdx[name] = i
    }
 
    // 初始化距离矩阵
    dist := make([][]float64, n)
    prev := make([][]int, n)
    for i := 0; i < n; i++ {
        dist[i] = make([]float64, n)
        prev[i] = make([]int, n)
        for j := 0; j < n; j++ {
            dist[i][j] = INF
            prev[i][j] = -1
        }
        dist[i][i] = 0
    }
 
    return &RoadGraph{
        cityList: cityNames,
        nameIdx:  nameIdx,
        n:        n,
        dist:     dist,
        prev:     prev,
    }
}
 
// AddEdge 添加双向道路
func (g *RoadGraph) AddEdge(from, to string, weight float64) {
    u := g.nameIdx[from]
    v := g.nameIdx[to]
    g.dist[u][v] = weight
    g.dist[v][u] = weight
    g.prev[u][v] = u
    g.prev[v][u] = v
}
 
// FloydRun 执行Floyd-Warshall 全源最短路,更新dist与prev
func (g *RoadGraph) FloydRun() {
    n := g.n
    for k := 0; k < n; k++ {
        for i := 0; i < n; i++ {
            for j := 0; j < n; j++ {
                if g.dist[i][k]+g.dist[k][j] < g.dist[i][j] {
                    g.dist[i][j] = g.dist[i][k] + g.dist[k][j]
                    g.prev[i][j] = g.prev[k][j]
                }
            }
        }
    }
}
 
// GetPath 回溯路径,同时过滤禁行城市
func (g *RoadGraph) GetPath(start, end string, banSet map[string]bool) (float64, []string) {
    u, okU := g.nameIdx[start]
    v, okV := g.nameIdx[end]
    if !okU || !okV || g.dist[u][v] >= INF {
        return INF, nil
    }
 
    // 回溯索引路径
    var idxPath []int
    cur := v
    for cur != -1 {
        idxPath = append(idxPath, cur)
        cur = g.prev[u][cur]
    }
    sort.Slice(idxPath, func(i, j int) bool { return i > j })
 
    // 转城市名称
    var path []string
    for _, idx := range idxPath {
        city := g.cityList[idx]
        if banSet[city] {
            return INF, nil
        }
        path = append(path, city)
    }
    return g.dist[u][v], path
}
 
// PathPlanner 规划器:约束 + 绘图
type PathPlanner struct {
    graph   *RoadGraph
    cityMap map[string]*City
 
    MustPass  []string
    BanCities map[string]bool
}
 
func NewPathPlanner(g *RoadGraph, cityMap map[string]*City) *PathPlanner {
    return &PathPlanner{
        graph:     g,
        cityMap:   cityMap,
        BanCities: make(map[string]bool),
    }
}
 
func (p *PathPlanner) CalcRoute(start, end string) (float64, []string) {
    total, path := p.graph.GetPath(start, end, p.BanCities)
    if path == nil || len(path) == 0 {
        return INF, nil
    }
    // 校验必经城市
    for _, req := range p.MustPass {
        has := false
        for _, c := range path {
            if c == req {
                has = true
                break
            }
        }
        if !has {
            return INF, nil
        }
    }
    return total, path
}
 
// DrawMap 生成PNG路网图,偏移充足显示负坐标城市
func (p *PathPlanner) DrawMap(savePath string, highlight []string) error {
    plt := plot.New()
    plt.Title.Text = "China City Highway Network | Shortest Path Highlighted"
    plt.X.Label.Text = "X Coord"
    plt.Y.Label.Text = "Y Coord"
 
    cjkFont := loadCJKFont()
 
    // 1. 全部灰色路网
    var allRoadXY plotter.XYs
    drawn := make(map[string]bool)
    for _, frm := range p.graph.cityList {
        cF := p.cityMap[frm]
        for _, to := range p.graph.cityList {
            key1 := frm + "|" + to
            key2 := to + "|" + frm
            if drawn[key1] || drawn[key2] {
                continue
            }
            u := p.graph.nameIdx[frm]
            v := p.graph.nameIdx[to]
            if p.graph.dist[u][v] >= INF {
                continue
            }
            drawn[key1] = true
 
            cT := p.cityMap[to]
            allRoadXY = append(allRoadXY, plotter.XY{X: cF.X, Y: cF.Y})
            allRoadXY = append(allRoadXY, plotter.XY{X: cT.X, Y: cT.Y})
        }
    }
    allRoadLine, err := plotter.NewLine(allRoadXY)
    if err != nil {
        return err
    }
    allRoadLine.LineStyle.Color = plotutil.Color(11)
    allRoadLine.LineStyle.Width = vg.Length(1)
    plt.Add(allRoadLine)
 
    // 2. 最优路径红色粗线
    var pathXY plotter.XYs
    for _, cityName := range highlight {
        city := p.cityMap[cityName]
        pathXY = append(pathXY, plotter.XY{X: city.X, Y: city.Y})
    }
    pathLine, err := plotter.NewLine(pathXY)
    if err != nil {
        return err
    }
    pathLine.LineStyle.Color = plotutil.Color(51)
    pathLine.LineStyle.Width = vg.Length(4)
    plt.Add(pathLine)
 
    // 3. 城市蓝色散点节点
    var pointXY plotter.XYs
    for _, city := range p.cityMap {
        pointXY = append(pointXY, plotter.XY{X: city.X, Y: city.Y})
    }
    scatter, err := plotter.NewScatter(pointXY)
    if err != nil {
        return err
    }
    scatter.GlyphStyle.Color = plotutil.Color(31)
    scatter.GlyphStyle.Radius = vg.Length(8)
    plt.Add(scatter)
 
    // 4. 城市中文名称标签
    var cityLabels plotter.XYLabels
    var labelStyles []text.Style
    cjkHandler := text.Plain{
        Fonts: font.DefaultCache,
    }
    for _, city := range p.cityMap {
        cityLabels.XYs = append(cityLabels.XYs, plotter.XY{X: city.X + 0.2, Y: city.Y + 0.15})
        cityLabels.Labels = append(cityLabels.Labels, city.Name)
        labelStyles = append(labelStyles, text.Style{
            Font:    cjkFont,
            Handler: cjkHandler,
        })
    }
    labelPlotter, err := plotter.NewLabels(cityLabels)
    if err != nil {
        return err
    }
    labelPlotter.TextStyle = labelStyles
    plt.Add(labelPlotter)
 
    // 保存图片
    err = plt.Save(12*vg.Inch, 9*vg.Inch, savePath)
    if err != nil {
        return err
    }
    fmt.Printf("✅ 路网图片已保存:%s\n", savePath)
    return nil
}
 
func loadCJKFont() font.Font {
    fontPaths := []string{
        "C:\\Windows\\Fonts\\STXIHEI.TTF",
        "C:\\Windows\\Fonts\\AdobeHeitiStd-Regular.otf",
        "C:\\Windows\\Fonts\\msyh.ttc",
        "C:\\Windows\\Fonts\\simsun.ttc",
    }
    for _, path := range fontPaths {
        fontData, err := os.ReadFile(path)
        if err != nil {
            continue
        }
        var ttf *opentype.Font
        if strings.HasSuffix(strings.ToLower(path), ".ttc") {
            col, err := opentype.ParseCollection(fontData)
            if err != nil {
                continue
            }
            if col.NumFonts() > 0 {
                ttf, err = col.Font(0)
                if err != nil {
                    continue
                }
            } else {
                continue
            }
        } else {
            ttf, err = opentype.Parse(fontData)
            if err != nil {
                continue
            }
        }
        cjkFace := font.Face{
            Font: font.Font{Typeface: "CJK"},
            Face: ttf,
        }
        font.DefaultCache.Add(font.Collection{cjkFace})
        plot.DefaultTextHandler = text.Plain{
            Fonts: font.DefaultCache,
        }
        return font.Font{Typeface: "CJK", Size: vg.Points(8)}
    }
    return font.Font{}
}
 
// 工具函数
func joinPath(path []string) string {
    if len(path) == 0 {
        return "无路线"
    }
    return strings.Join(path, " → ")
}
 
func parseCityInput(s string) []string {
    s = strings.TrimSpace(s)
    if s == "" {
        return nil
    }
    parts := strings.Split(s, "、")
    var res []string
    for _, p := range parts {
        p = strings.TrimSpace(p)
        if p != "" {
            res = append(res, p)
        }
    }
    return res
}
 
func setConstraint(planner *PathPlanner, must []string, ban []string) {
    planner.MustPass = must
    planner.BanCities = make(map[string]bool)
    for _, c := range ban {
        planner.BanCities[c] = true
    }
}
 
 
 
/*
# 版权所有  2026 ©涂聚文有限公司™ ®
# 许可信息查看:言語成了邀功盡責的功臣,還需要行爲每日來值班嗎
# 描述:Floyd-Warshall Algorithms
# Author    : geovindu,Geovin Du 涂聚文.
# IDE       : goLang 2024.3.6 go 26.2
# os        : windows 10
# database  : mysql 9.0 sql server 2019, postgreSQL 17.0  Oracle 21c Neo4j
# Datetime  : 2026/7/11 22:41
# User      :  geovindu
# Product   : GoLand
# Project   : goalgorithms
# File      : floydwarshallbll.go
*/
package bll
 
import (
    "fmt"
    "goalgorithms/floydwarshall"
    "strings"
)
 
func FloydWarshallMain() {
    // 城市坐标数据
    cityData := map[string]*floydwarshall.City{
        "深圳": {Name: "深圳", X: 5, Y: 0},
        "惠州": {Name: "惠州", X: 6, Y: 1},
        "东莞": {Name: "东莞", X: 4, Y: 0},
        "广州": {Name: "广州", X: 3, Y: 0},
        "佛山": {Name: "佛山", X: 2, Y: 0},
        "肇庆": {Name: "肇庆", X: 1, Y: 1},
        "梧州": {Name: "梧州", X: -2, Y: 2},
        "桂林": {Name: "桂林", X: -3, Y: 4},
        "柳州": {Name: "柳州", X: -4, Y: 3},
        "南宁": {Name: "南宁", X: -5, Y: 1},
        "韶关": {Name: "韶关", X: 2, Y: 4},
        "河源": {Name: "河源", X: 7, Y: 3},
        "赣州": {Name: "赣州", X: 8, Y: 6},
        "吉安": {Name: "吉安", X: 9, Y: 9},
        "南昌": {Name: "南昌", X: 10, Y: 8},
        "萍乡": {Name: "萍乡", X: 7, Y: 8},
        "长沙": {Name: "长沙", X: 6, Y: 9},
        "株洲": {Name: "株洲", X: 6, Y: 8},
        "衡阳": {Name: "衡阳", X: 6, Y: 6},
        "郴州": {Name: "郴州", X: 6, Y: 4},
        "九江": {Name: "九江", X: 11, Y: 9},
        "武汉": {Name: "武汉", X: 9, Y: 11},
        "郑州": {Name: "郑州", X: 10, Y: 14},
        "西安": {Name: "西安", X: 7, Y: 15},
        "福州": {Name: "福州", X: 13, Y: 7},
        "厦门": {Name: "厦门", X: 14, Y: 4},
    }
 
    // 提取城市名称列表
    var cityList []string
    for k := range cityData {
        cityList = append(cityList, k)
    }
 
    // ========== 1. 里程路网 KM ==========
    graphKm := floydwarshall.NewRoadGraph(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.FloydRun()
 
    // ========== 2. 耗时路网 Hour ==========
    graphHour := floydwarshall.NewRoadGraph(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.FloydRun()
 
    // ========== 3. 路费路网 Cost ==========
    graphCost := floydwarshall.NewRoadGraph(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.FloydRun()
 
    // 初始化规划器
    planKm := floydwarshall.NewPathPlanner(graphKm, cityData)
    planHour := floydwarshall.NewPathPlanner(graphHour, cityData)
    planCost := floydwarshall.NewPathPlanner(graphCost, cityData)
 
    // 打印城市列表
    fmt.Println("===== Go Floyd-Warshall 全源高速路径规划系统 =====")
    fmt.Printf("可用城市:%s\n", strings.Join(cityList, "、"))
    fmt.Println("----------------------------------------")
 
    // 控制台输入
    var start, end, mustInput, banInput string
    fmt.Print("输入起点城市:")
    fmt.Scanln(&start)
    fmt.Print("输入终点城市:")
    fmt.Scanln(&end)
    fmt.Print("必须途经城市(多城顿号分隔,无直接回车):")
    fmt.Scanln(&mustInput)
    fmt.Print("禁止绕行城市(多城顿号分隔,无直接回车):")
    fmt.Scanln(&banInput)
 
    mustList := parseCityInput(mustInput)
    banList := parseCityInput(banInput)
 
    // 绑定约束
    setConstraint(planKm, mustList, banList)
    setConstraint(planHour, mustList, banList)
    setConstraint(planCost, mustList, banList)
 
    // 计算路线
    kmTotal, kmPath := planKm.CalcRoute(start, end)
    hourTotal, hourPath := planHour.CalcRoute(start, end)
    costTotal, costPath := planCost.CalcRoute(start, end)
 
    // 输出结果
    fmt.Println("\n==================================================")
    fmt.Printf("【%s → %s 规划结果】\n", start, end)
    fmt.Println("==================================================")
    if len(kmPath) > 0 {
        fmt.Printf("📏 最短里程:%.0f km | 路线:%s\n", kmTotal, dujoinPath(kmPath))
    } else {
        fmt.Println("📏 最短里程:无可行路线(约束过滤)")
    }
    if len(hourPath) > 0 {
        fmt.Printf("⏰ 最短耗时:%.1f h | 路线:%s\n", hourTotal, dujoinPath(hourPath))
    } else {
        fmt.Println("⏰ 最短耗时:无可行路线(约束过滤)")
    }
    if len(costPath) > 0 {
        fmt.Printf("💰 最低路费:%.0f 元 | 路线:%s\n", costTotal, dujoinPath(costPath))
    } else {
        fmt.Println("💰 最低路费:无可行路线(约束过滤)")
    }
    fmt.Println("==================================================")
 
    // 生成PNG图片
    if len(kmPath) > 0 {
        err := planKm.DrawMap("floyd_route_map.png", kmPath)
        if err != nil {
            fmt.Printf("绘图失败:%v\n", err)
        }
    }
}
 
// 工具函数
 
func dujoinPath(path []string) string {
    if len(path) == 0 {
        return "无路线"
    }
    return strings.Join(path, " → ")
}
 
func parseCityInput(s string) []string {
    s = strings.TrimSpace(s)
    if s == "" {
        return nil
    }
    parts := strings.Split(s, "、")
    var res []string
    for _, p := range parts {
        p = strings.TrimSpace(p)
        if p != "" {
            res = append(res, p)
        }
    }
    return res
}
 
func setConstraint(planner *floydwarshall.PathPlanner, must []string, ban []string) {
    planner.MustPass = must
    planner.BanCities = make(map[string]bool)
    for _, c := range ban {
        planner.BanCities[c] = true
    }
}

输出:

相关推荐
KaMeidebaby1 小时前
卡梅德生物技术快报|噬菌体筛选:噬菌体筛选工程化优化方案:基于侵染全链条参数调控与水环境应用验证
java·开发语言·人工智能·机器学习·spark
AC赳赳老秦1 小时前
Excel 动态仪表盘制作:用 OpenClaw 自动处理数据、生成交互式图表并实时更新仪表盘
大数据·服务器·后端·测试用例·excel·deepseek·openclaw
CoderYanger1 小时前
视频裁剪+缩放+自动添加水印脚本(Python版)
开发语言·后端·python·程序人生·职场和发展·音视频·学习方法
db_murphy1 小时前
机器学习决策树的基尼系数是个啥?
学习·算法
酷酷的身影1 小时前
Managers/APConfigManager.cs
开发语言·ui·c#
拳里剑气1 小时前
C++算法:优先级队列
开发语言·c++·算法·优先级队列
小七在进步1 小时前
数据结构:用队列实现栈
开发语言·数据结构
Turbo正则2 小时前
机器学习入门笔记 | 基础算法及其应用场景
笔记·算法·机器学习
东华万里2 小时前
第37篇 手撕二叉树与堆的底层逻辑
数据结构·算法