CppCon 2022 学习: Graph Algorithms and Data Structures in C++20 Toward std::graph
<svg version="1.1" viewBox="0 0 1200 900" xmlns="http://www.w3.org/2000/svg">
<line x1="600" x2="300" y1="80" y2="250" stroke="black" stroke-width="2"/>
<line x1="599.73" x2="660.04" y1="79.562" y2="215.41" stroke="black" stroke-width="2"/>
<line x1="705.03" x2="765.35" y1="310.8" y2="446.64" stroke="black" stroke-width=".80161"/>
<line x1="300" x2="200" y1="250" y2="450" stroke="black" stroke-width="2"/>
<line x1="200" x2="350" y1="450" y2="650" stroke="black" stroke-width="2"/>
<line x1="350" x2="600" y1="650" y2="800" stroke="black" stroke-width="2"/>
<line x1="629.12" x2="498.96" y1="316.31" y2="451.26" stroke="black" stroke-width="2"/>
<line x1="761.13" x2="648.66" y1="534.55" y2="583.92" stroke="black" stroke-width=".5269"/>
<line x1="682.17" x2="781.08" y1="757.03" y2="526.34" stroke="black" stroke-width="2"/>
<line x1="1045.2" x2="689.14" y1="408.67" y2="792.75" stroke="black" stroke-width="2"/>
<line x1="1003.2" x2="720.59" y1="303.34" y2="76.433" stroke="black" stroke-width="2"/>
<text x="430" y="150" font-family="serif" font-size="20" fill="blue" text-anchor="middle">85 km</text>
<text x="598.17426" y="189.20297" font-family="serif" font-size="20" fill="blue" text-anchor="middle">217 km</text>
<text x="774.0871" y="381.63113" font-family="serif" font-size="20" fill="blue" text-anchor="middle">173 km</text>
<text x="240" y="340" font-family="serif" font-size="20" fill="blue" text-anchor="middle">80 km</text>
<text x="609.91656" y="377.53476" font-family="serif" font-size="20" fill="blue" text-anchor="middle">186 km</text>
<text x="774.88416" y="626.56165" font-family="serif" font-size="20" fill="blue" text-anchor="middle">167 km</text>
<text x="260" y="560" font-family="serif" font-size="20" fill="blue" text-anchor="middle">250 km</text>
<text x="460" y="740" font-family="serif" font-size="20" fill="blue" text-anchor="middle">84 km</text>
<text x="614.73596" y="557.49768" font-family="serif" font-size="20" fill="blue" text-anchor="middle">183 km</text>
<text x="987.81281" y="515.77393" font-family="serif" font-size="20" fill="blue" text-anchor="middle">502 km</text>
<ellipse cx="600" cy="80" rx="120" ry="50" fill="white" stroke="black" stroke-width="2"/>
<text x="600" y="95" font-family="serif" font-size="24" text-anchor="middle" fill="black">Frankfurt [0]</text>
<ellipse cx="300" cy="250" rx="120" ry="50" fill="white" stroke="black" stroke-width="2"/>
<text x="300" y="265" font-family="serif" font-size="24" text-anchor="middle" fill="black">Mannheim [1]</text>
<ellipse cx="664.78" cy="265.85" rx="120" ry="50" fill="white" stroke="black" stroke-width="2"/>
<text x="664.78" y="280.85" font-family="serif" font-size="24" text-anchor="middle" fill="black">Würzburg [2]</text>
<ellipse cx="1026.2" cy="356.49" rx="100" ry="50" fill="white" stroke="black" stroke-width="2"/>
<text x="1021.22" y="348.15" font-family="serif" font-size="24" text-anchor="middle" fill="black">Kassel [3]</text>
<ellipse cx="200" cy="450" rx="120" ry="50" fill="white" stroke="black" stroke-width="2"/>
<text x="200" y="465" font-family="serif" font-size="24" text-anchor="middle" fill="black">Karlsruhe [4]</text>
<ellipse cx="350" cy="650" rx="120" ry="50" fill="white" stroke="black" stroke-width="2"/>
<text x="350" y="665" font-family="serif" font-size="24" text-anchor="middle" fill="black">Augsburg [5]</text>
<ellipse cx="600" cy="800" rx="120" ry="50" fill="white" stroke="black" stroke-width="2"/>
<text x="600" y="815" font-family="serif" font-size="24" text-anchor="middle" fill="black">München [6]</text>
<ellipse cx="500" cy="450" rx="100" ry="50" fill="white" stroke="black" stroke-width="2"/>
<text x="500" y="465" font-family="serif" font-size="24" text-anchor="middle" fill="black">Erfurt [7]</text>
<ellipse cx="775.67" cy="484.2" rx="120" ry="50" fill="white" stroke="black" stroke-width="2"/>
<text x="789.85" y="495.03" font-family="serif" font-size="24" text-anchor="middle" fill="black">Nürnberg [8]</text>
<ellipse cx="594.76" cy="627.48" rx="110" ry="50" fill="white" stroke="black" stroke-width="2"/>
<text x="594.76" y="642.48" font-family="serif" font-size="24" text-anchor="middle" fill="black">Stuttgart [9]</text>
</svg>
这段内容是讲 图(Graph) 的定义和一个示例图的可视化(用 SVG 绘制的“城市距离图”)。我帮你分成两部分解释:
一、概念解释
What Is a Graph?
A graph ( G = (V, E) ) is a set of vertices ( V ) (points in a space), and edges ( E ) (links between these vertices).
Edges may or may not be oriented, that is, directed or undirected, respectively. Moreover, edges may be weighted, that is, assigned a value.
理解:
图 ( G = (V, E) ) 是由 顶点(Vertices) 和 边(Edges) 组成的集合:
- 顶点(V):图中的点,代表对象(比如城市、节点、任务等)。
- 边(E):顶点之间的连接,代表关系(比如道路、通信链路等)。
进一步地: - 有向边(directed edge):边有方向(例如从A指向B)。
- 无向边(undirected edge):边没有方向(A与B是相互连接的)。
- 加权边(weighted edge):每条边有一个“权值”(如距离、成本、时间等)。
- 加权顶点(weighted vertex):顶点本身也可以带权值(例如城市人口、节点负载)。
二、SVG 图示理解
SVG 画的是一个“城市距离图”,用来说明图的直观结构。
每个城市是一个 顶点(vertex),城市之间的道路(带距离的连线)是 边(edge)。
顶点(城市)
| 城市名称 | 索引 | 位置 | 说明 |
|---|---|---|---|
| Frankfurt | [0] | 顶部中央 | 图的中心城市 |
| Mannheim | [1] | 左上 | 与 Frankfurt 相连 |
| Würzburg | [2] | 右上 | 与 Frankfurt 相连 |
| Kassel | [3] | 右下远端 | 与 Frankfurt、München 等城市相连 |
| Karlsruhe | [4] | 左中 | 与 Mannheim、Augsburg 相连 |
| Augsburg | [5] | 左下 | 与 Karlsruhe、München 相连 |
| München | [6] | 底部中央 | 大终点,很多城市通向这里 |
| Erfurt | [7] | 中央偏左 | 中间枢纽节点 |
| Nürnberg | [8] | 右中 | 与 Würzburg、Stuttgart 等相连 |
| Stuttgart | [9] | 右下中 | 与 Nürnberg、München 等相连 |
边(道路)举例:
| 起点 | 终点 | 距离 |
|---|---|---|
| Frankfurt → Mannheim | 85 km | |
| Frankfurt → Würzburg | 217 km | |
| Würzburg → Nürnberg | 103 km | |
| Mannheim → Karlsruhe | 80 km | |
| Karlsruhe → Augsburg | 250 km | |
| Augsburg → München | 84 km | |
| München → Stuttgart | 167 km | |
| München → Kassel | 502 km | |
| 这其实是一个 加权无向图(weighted undirected graph),展示了城市之间的连接与距离。 |
三、总结
| 概念 | 含义 |
|---|---|
| 图(Graph) | 顶点 + 边 的集合 |
| 顶点(Vertex) | 图中的点(如城市) |
| 边(Edge) | 顶点间的连接(如道路) |
| 有向/无向 | 是否存在方向性 |
| 加权 | 是否有数值(距离、权重等) |
| 图示(SVG) | 展示城市与道路的关系,是图论的直观实例 |
| Adjacency List |
<?xml version="1.0" encoding="UTF-8"?>
<svg version="1.1" viewBox="0 0 1200 900" xmlns="http://www.w3.org/2000/svg">
<!-- 定义箭头 marker -->
<defs>
<marker id="arrow" markerHeight="10" markerWidth="10" orient="auto" refX="10" refY="3">
<path d="m0 0v6l9-3z"/>
</marker>
<marker id="arrow-9" markerHeight="10" markerWidth="10" orient="auto" refX="10" refY="3">
<path d="m0 0v6l9-3z"/>
</marker>
</defs>
<g stroke="#000">
<!-- Connections (lines) with arrow -->
<line x1="600.12" x2="368.28" y1="79.791" y2="207.67" marker-end="url(#arrow)" stroke-width="2"/>
<line x1="599.73" x2="660.04" y1="79.562" y2="215.41" marker-end="url(#arrow)" stroke-width="2"/>
<line x1="704.97" x2="752.9" y1="310.77" y2="431.66" marker-end="url(#arrow)" stroke-width=".80161"/>
<g stroke-width="2">
<line x1="300.13" x2="208.21" y1="249.94" y2="400.01" marker-end="url(#arrow)"/>
<line x1="199.77" x2="302.68" y1="449.83" y2="600.13" marker-end="url(#arrow)"/>
<line x1="349.84" x2="537.6" y1="649.74" y2="749.38" marker-end="url(#arrow)"/>
<line x1="629.36" x2="538.76" y1="316.08" y2="401.45" marker-end="url(#arrow)"/>
</g>
<line x1="753.66" x2="669.13" y1="539.06" y2="754.4" marker-end="url(#arrow)" stroke-width="1.8286"/>
<line x1="1036.9" x2="707.44" y1="411.13" y2="771.11" marker-end="url(#arrow)" stroke-width="1.9858"/>
<line x1="702.62" x2="949.43" y1="109.26" y2="319.72" marker-end="url(#arrow)" stroke-width="2.0955"/>
</g>
<g fill="blue" font-family="serif" font-size="20" text-anchor="middle">
<!-- Distance labels -->
<text x="430" y="150">85 km</text>
<text x="598.17426" y="189.20297">217 km</text>
<text x="774.0871" y="381.63113">173 km</text>
<text x="240" y="340">80 km</text>
<text x="609.91656" y="377.53476">186 km</text>
<text x="774.88416" y="626.56165">167 km</text>
<text x="260" y="560">250 km</text>
<text x="460" y="740">84 km</text>
<text x="614.73596" y="557.49768">183 km</text>
<text x="987.81281" y="515.77393">502 km</text>
<!-- Cities -->
</g>
<ellipse cx="600" cy="80" rx="120" ry="50" fill="#fff" stroke="#000" stroke-width="2"/>
<text x="600" y="95" fill="black" font-family="serif" font-size="24" text-anchor="middle">Frankfurt [0]</text>
<ellipse cx="300" cy="250" rx="120" ry="50" fill="#fff" stroke="#000" stroke-width="2"/>
<text x="300" y="265" fill="black" font-family="serif" font-size="24" text-anchor="middle">Mannheim [1]</text>
<ellipse cx="664.78" cy="265.85" rx="120" ry="50" fill="#fff" stroke="#000" stroke-width="2"/>
<text x="664.78" y="280.85" fill="black" font-family="serif" font-size="24" text-anchor="middle">Würzburg [2]</text>
<ellipse cx="1026.2" cy="356.49" rx="100" ry="50" fill="#fff" stroke="#000" stroke-width="2"/>
<text x="1021.22" y="348.15" fill="black" font-family="serif" font-size="24" text-anchor="middle">Kassel [3]</text>
<ellipse cx="200" cy="450" rx="120" ry="50" fill="#fff" stroke="#000" stroke-width="2"/>
<text x="200" y="465" fill="black" font-family="serif" font-size="24" text-anchor="middle">Karlsruhe [4]</text>
<ellipse cx="350" cy="650" rx="120" ry="50" fill="#fff" stroke="#000" stroke-width="2"/>
<text x="350" y="665" fill="black" font-family="serif" font-size="24" text-anchor="middle">Augsburg [5]</text>
<ellipse cx="600" cy="800" rx="120" ry="50" fill="#fff" stroke="#000" stroke-width="2"/>
<text x="600" y="815" fill="black" font-family="serif" font-size="24" text-anchor="middle">München [6]</text>
<ellipse cx="500" cy="450" rx="100" ry="50" fill="#fff" stroke="#000" stroke-width="2"/>
<text x="500" y="465" fill="black" font-family="serif" font-size="24" text-anchor="middle">Erfurt [7]</text>
<ellipse cx="775.67" cy="484.2" rx="120" ry="50" fill="#fff" stroke="#000" stroke-width="2"/>
<text x="789.85" y="495.03" fill="black" font-family="serif" font-size="24" text-anchor="middle">Nürnberg [8]</text>
<ellipse cx="594.76" cy="627.48" rx="110" ry="50" fill="#fff" stroke="#000" stroke-width="2"/>
<text x="594.76" y="642.48" fill="black" font-family="serif" font-size="24" text-anchor="middle">Stuttgart [9]</text>
<line x1="719.17" x2="650.39" y1="529.87" y2="583.29" marker-end="url(#arrow-9)" stroke="#000" stroke-width="1.3395"/>
</svg>
Outer Range - Vertices Inner Range - Edges
┌──────────┬──┐ ┌───┬──────┬───┐ ┌───┬──────┬───┐ ┌───┬──────┬───┐
0 │Frankfürt │ ─┼───────▶ 1 │ 85 │ ──┼───────▶│ 4 │ 217 │ ──┼───────▶│ 6 │173 │ / │
├──────────┼──┤ └───┴──────┴───┘ └───┴──────┴───┘ └───┴──────┴───┘
1 │Mannheim │ │
├──────────┼──┤
2 │Karlsruhe │ │
├──────────┼──┤
3 │Augsburg │ │
├──────────┼──┤
4 │Würzburg │ │
├──────────┼──┤
5 │Nürnberg │ │
├──────────┼──┤
6 │Kassel │ │
├──────────┼──┤
7 │Erfurt │ /│
├──────────┼──┤
8 │München │ /│
├──────────┼──┤
9 │Stuttgart │ /│
└──────────┴──┘
Outer Range - Vertices Inner Range - Edges
┌──────────┬──┐ ┌───┬──────┬───┐ ┌───┬──────┬───┐ ┌───┬──────┬───┐
0 │Frankfürt │ ─┼──────▶│ 1 │ 85 │ ──┼───────▶│ 4 │ 217 │ ──┼───────▶│ 6 │173 │ / │
├──────────┼──┤ ├───┼──────┼───┤ └───┴──────┴───┘ └───┴──────┴───┘
1 │Mannheim │ ─┼──────▶│ 2 │ 80 │ / │
├──────────┼──┤ ├───┼──────┼───┤
2 │Karlsruhe │ ─┼──────▶│ 3 │ 250 │ / │
├──────────┼──┤ ├───┼──────┼───┤
3 │Augsburg │ ─┼──────▶│ 8 │ 84 │ / │
├──────────┼──┤ ├───┼──────┼───┤ ┌───┬──────┬───┐
4 │Würzburg │ ─┼──────▶│ 5 │ 103 │ ──┼───────▶│ 7 │ 186 │ / │
├──────────┼──┤ ├───┼──────┼───┤ ├───┼──────┼───┤
5 │Nürnberg │ ─┼──────▶│ 8 │ 502 │ ──┼───────▶│ 9 │ 183 │ / │
├──────────┼──┤ ├───┼──────┼───┤ └───┴──────┴───┘
6 │Kassel │ ─┼──────▶│ 8 │ 85 │ / │
├──────────┼──┤ └───┴──────┴───┘
7 │Erfurt │ /│
├──────────┼──┤
8 │München │ /│
├──────────┼──┤
9 │Stuttgart │ /│
└──────────┴──┘
<svg version="1.1" viewBox="0 0 1200 900" xmlns="http://www.w3.org/2000/svg">
<!-- 定义箭头 marker -->
<defs>
<!-- 终点箭头 -->
<marker id="arrow-end" markerHeight="10" markerWidth="10" orient="auto" refX="10" refY="3">
<path d="m0 0v6l9-3z"/>
</marker>
<!-- 起点箭头(反向) -->
<marker id="arrow-start" markerHeight="10" markerWidth="10" orient="auto" refY="3">
<path d="m9 0v6l-9-3z"/>
</marker>
</defs>
<g stroke="#000">
<!-- Connections (lines) 双箭头 -->
<line x1="600.12" x2="368.28" y1="79.791" y2="207.67" marker-end="url(#arrow-end)" marker-start="url(#arrow-start)" stroke-width="2"/>
<line x1="599.73" x2="660.04" y1="79.562" y2="215.41" marker-end="url(#arrow-end)" marker-start="url(#arrow-start)" stroke-width="2"/>
<line x1="704.97" x2="752.9" y1="310.77" y2="431.66" marker-end="url(#arrow-end)" marker-start="url(#arrow-start)" stroke-width=".80161"/>
<g stroke-width="2">
<line x1="300.13" x2="208.21" y1="249.94" y2="400.01" marker-end="url(#arrow-end)" marker-start="url(#arrow-start)"/>
<line x1="199.77" x2="302.68" y1="449.83" y2="600.13" marker-end="url(#arrow-end)" marker-start="url(#arrow-start)"/>
<line x1="349.84" x2="537.6" y1="649.74" y2="749.38" marker-end="url(#arrow-end)" marker-start="url(#arrow-start)"/>
<line x1="629.36" x2="538.76" y1="316.08" y2="401.45" marker-end="url(#arrow-end)" marker-start="url(#arrow-start)"/>
</g>
<line x1="753.66" x2="669.13" y1="539.06" y2="754.4" marker-end="url(#arrow-end)" marker-start="url(#arrow-start)" stroke-width="1.8286"/>
<line x1="1036.9" x2="707.44" y1="411.13" y2="771.11" marker-end="url(#arrow-end)" marker-start="url(#arrow-start)" stroke-width="1.9858"/>
<line x1="702.62" x2="949.43" y1="109.26" y2="319.72" marker-end="url(#arrow-end)" marker-start="url(#arrow-start)" stroke-width="2.0955"/>
<line x1="719.17" x2="650.39" y1="529.87" y2="583.29" marker-end="url(#arrow-end)" marker-start="url(#arrow-start)" stroke-width="1.3395"/>
</g>
<g fill="blue" font-family="serif" font-size="20" text-anchor="middle">
<!-- Distance labels -->
<text x="430" y="150">85 km</text>
<text x="598.17426" y="189.20297">217 km</text>
<text x="774.0871" y="381.63113">173 km</text>
<text x="240" y="340">80 km</text>
<text x="609.91656" y="377.53476">186 km</text>
<text x="774.88416" y="626.56165">167 km</text>
<text x="260" y="560">250 km</text>
<text x="460" y="740">84 km</text>
<text x="614.73596" y="557.49768">183 km</text>
<text x="987.81281" y="515.77393">502 km</text>
</g>
<!-- Cities -->
<ellipse cx="600" cy="80" rx="120" ry="50" fill="#fff" stroke="#000" stroke-width="2"/>
<text x="600" y="95" fill="black" font-family="serif" font-size="24" text-anchor="middle">Frankfurt [0]</text>
<ellipse cx="240.78" cy="208.29" rx="120.07" ry="43.395" fill="#fff" stroke="#000" stroke-width="2"/>
<text x="246.61725" y="211.61723" fill="#000000" font-family="serif" font-size="24px" text-anchor="middle">Mannheim [1]</text>
<ellipse cx="664.78" cy="265.85" rx="120" ry="50" fill="#fff" stroke="#000" stroke-width="2"/>
<text x="664.78" y="280.85" fill="black" font-family="serif" font-size="24" text-anchor="middle">Würzburg [2]</text>
<ellipse cx="1026.2" cy="356.49" rx="100" ry="50" fill="#fff" stroke="#000" stroke-width="2"/>
<text x="1021.22" y="348.15" fill="black" font-family="serif" font-size="24" text-anchor="middle">Kassel [3]</text>
<ellipse cx="104.91" cy="424.98" rx="120" ry="50" fill="#fff" stroke="#000" stroke-width="2"/>
<text x="104.91196" y="439.97684" fill="#000000" font-family="serif" font-size="24px" text-anchor="middle">Karlsruhe [4]</text>
<ellipse cx="350" cy="650" rx="120" ry="50" fill="#fff" stroke="#000" stroke-width="2"/>
<text x="350" y="665" fill="black" font-family="serif" font-size="24" text-anchor="middle">Augsburg [5]</text>
<ellipse cx="600" cy="800" rx="120" ry="50" fill="#fff" stroke="#000" stroke-width="2"/>
<text x="600" y="815" fill="black" font-family="serif" font-size="24" text-anchor="middle">München [6]</text>
<ellipse cx="500" cy="450" rx="100" ry="50" fill="#fff" stroke="#000" stroke-width="2"/>
<text x="500" y="465" fill="black" font-family="serif" font-size="24" text-anchor="middle">Erfurt [7]</text>
<ellipse cx="775.67" cy="484.2" rx="120" ry="50" fill="#fff" stroke="#000" stroke-width="2"/>
<text x="789.85" y="495.03" fill="black" font-family="serif" font-size="24" text-anchor="middle">Nürnberg [8]</text>
<ellipse cx="594.76" cy="627.48" rx="110" ry="50" fill="#fff" stroke="#000" stroke-width="2"/>
<text x="594.76" y="642.48" fill="black" font-family="serif" font-size="24" text-anchor="middle">Stuttgart [9]</text>
</svg>
Outer Range - Vertices Inner Range - Edges
┌──────────┬──┐ ┌───┬──────┬───┐ ┌───┬──────┬───┐ ┌───┬──────┬───┐
0 │Frankfürt │ ─┼──────▶│ 1 │ 85 │ ──┼───────▶│ 4 │ 217 │ ──┼───────▶│ 6 │173 │ / │
├──────────┼──┤ ├───┼──────┼───┤ ┢━━━┿━━━━━━┿━━━┪ └───┴──────┴───┘
1 │Mannheim │ ─┼──────▶│ 2 │ 80 │ ━━┿━━━━━━━▶┃ 0 │ 85 │ / ┃
├──────────┼──┤ ├───┼──────┼───┤ ┣━━━┿━━━━━━┿━━━┫
2 │Karlsruhe │ ─┼──────▶│ 3 │ 250 │ ━━┿━━━━━━━▶┃ 1 │ 80 │ / ┃
├──────────┼──┤ ├───┼──────┼───┤ ┣━━━┿━━━━━━┿━━━┫
3 │Augsburg │ ─┼──────▶│ 8 │ 84 │ ━━┿━━━━━━━▶┃ 2 │ 250 │ / ┃
├──────────┼──┤ ├───┼──────┼───┤ ┡━━━┿━━━━━━┿━━━┩ ┏━━━┯━━━━━━┯━━━┓
4 │Würzburg │ ─┼──────▶│ 5 │ 103 │ ──┼───────▶│ 7 │ 186 │ ━━┿━━━━━━━▶┃ 0 │ 217 │ / ┃
├──────────┼──┤ ├───┼──────┼───┤ ├───┼──────┼───┤ ┣━━━┿━━━━━━┿━━━┫
5 │Nürnberg │ ─┼──────▶│ 8 │ 502 │ ──┼───────▶│ 9 │ 183 │ ━━┿━━━━━━━▶┃ 4 │ 103 │ / ┃
├──────────┼──┤ ├───┼──────┼───┤ ┢━━━┿━━━━━━┿━━━┪ ┗━━━┷━━━━━━┷━━━┛
6 │Kassel │ ─┼──────▶│ 8 │ 85 │ ━━┿━━━━━━━▶┃ 0 │ 173 │ / ┃
├──────────┼──┤ ┢━━━┿━━━━━━┿━━━┪ ┗━━━┷━━━━━━┷━━━┛
7 │Erfurt │ ━┿━━━━━━▶┃ 4 │ 186 │ / ┃
├──────────┼──┤ ┣━━━┿━━━━━━┿━━━┫ ┏━━━┯━━━━━━┯━━━┓ ┏━━━┯━━━━━━┯━━━┓
8 │München │ ━┿━━━━━━▶┃ 3 │ 84 │ ━━╋━━━━━━━▶┃ 5 │ 167 │ ━━╋━━━━━━━▶┃ 6 │ 502 │ / ┃
├──────────┼──┤ ┣━━━┿━━━━━━┿━━━┫ ┗━━━┷━━━━━━┷━━━┛ ┗━━━┷━━━━━━┷━━━┛
9 │Stuttgart │ ━┿━━━━━━▶┃ 5 │ 183 │ / ┃
└──────────┴──┘ ┗━━━┷━━━━━━┷━━━┛
一、Adjacency List(邻接表)
图示含义
邻接表是一种图的常见表示形式:
- 外层(Outer Range - Vertices):是顶点(城市,如 Frankfurt、Mannheim…)
- 内层(Inner Range - Edges):是该顶点连接到的边(目标城市和距离)
举例解释
0 │Frankfürt│ ─▶│ 1 │ 85 │ ─▶│ 4 │ 217 │ ─▶│ 6 │173 │ / │
意思是:
- 顶点 0 (Frankfurt) 有三条边:
- 到顶点 1(Mannheim),距离 85
- 到顶点 4(Würzburg),距离 217
- 到顶点 6(Kassel),距离 173
二、C++ 数据结构定义
route — 表示一条边
struct route {
int target_id; // 目标顶点的编号
double distance; // 边的权重(距离)
};
edges_type — 邻接链表
using edges_type = std::list<route>;
每个顶点的所有出边,用一个 std::list<route> 表示。
vertex — 表示一个顶点
struct vertex {
edges_type edges; // 该顶点的所有出边
std::string name; // 顶点的名称(如 “Frankfurt”)
};
vertices_type — 整个图的节点集合
using vertices_type = std::vector<vertex>;
即:整个图是一组顶点(每个顶点又包含一个边表)。
🗺 三、SVG 图形结构
SVG 文件是这个邻接表的可视化版本。
<ellipse class="city">:绘制城市节点(椭圆)<line class="line">:绘制边(带箭头的线)<text class="distance">:显示边上的距离数字<text class="city-text">:显示城市名与编号
每个城市的坐标(cx,cy)手动或程序生成,例如:
<ellipse class="city" cx="600" cy="80" rx="120" ry="50"/>
<text class="city-text" x="600" y="95">Frankfurt [0]</text>
四、Edge List(边列表)
另一种图表示方法:
直接列出所有的边,用表格表示。
| From | To | Distance |
|---|---|---|
| Frankfurt | Mannheim | 85 |
| Frankfurt | Würzburg | 217 |
| Frankfurt | Kassel | 173 |
| Mannheim | Karlsruhe | 80 |
| Karlsruhe | Augsburg | 250 |
| Augsburg | München | 84 |
| Würzburg | Erfurt | 186 |
| Würzburg | Nürnberg | 103 |
| Nürnberg | Stuttgart | 183 |
| Nürnberg | München | 167 |
| Kassel | München | 502 |
| 也可以用数字索引表示: | ||
| source_id | target_id | distance |
| --------- | --------- | -------- |
| 0 | 1 | 85 |
| 0 | 4 | 217 |
| 0 | 6 | 173 |
| 1 | 2 | 80 |
| 2 | 3 | 250 |
| 3 | 8 | 84 |
| 4 | 7 | 186 |
| 4 | 5 | 103 |
| 5 | 9 | 183 |
| 5 | 8 | 167 |
| 6 | 8 | 502 |
五、图的其他种类
| 类型 | 说明 |
|---|---|
| Bipartite Graph(二部图) | 顶点分成两组,只允许跨组连接 |
| n-partite Graph(多部图) | 多个独立组间的连接 |
| Hypergraph(超图) | 一条边可以连接两个以上顶点(本系统暂不支持) |
六、命名规范(Naming Conventions)
为了在算法模板中统一命名:
| 模板参数 | 示例变量 | 含义 |
|---|---|---|
G |
g |
图对象 |
V |
u, v, x, y |
顶点(引用) |
VId |
uid, vid |
顶点编号 |
VR |
ur, vr |
顶点范围 |
VI |
ui, vi |
顶点迭代器 |
VV |
val |
顶点的用户自定义值 |
VVF |
vvf |
顶点值函数 |
E |
uv, vw |
边引用 |
ER |
er |
边范围 |
EI |
uvi, vwi |
边迭代器 |
EV |
val |
边值 |
EVF |
evf |
边值函数 |
| 这套命名通常出现在泛型算法模板中(如 Dijkstra、BFS、DFS、Prim 等)。 |
七、总结
| 层级 | 内容 | 对应代码或图形 |
|---|---|---|
| 图结构定义 | 顶点与边的抽象 | route, vertex, vertices_type |
| 存储方式 | 邻接表(vector) | Adjacency List |
| 可视化表示 | SVG 文件绘图 | 各城市及边箭头 |
| 算法输入形式 | Edge List | source_id, target_id, distance |
| 命名规范 | 模板参数与变量命名约定 | 泛型算法模板接口 |
一个完整的 “城市路网图(Graph of Cities & Routes)” 的定义、遍历和最短路径算法示例。下面我给出逐步解析,帮助你全面理解每一部分的含义和设计思想。
一、原始数据(Raw Data – Cities & Routes)
using city_id_type = int32_t;
using city_name_type = string;
vector<city_name_type> city_names = {
"Frankfürt", "Mannheim", "Karlsruhe", "Augsburg", "Würzburg",
"Nürnberg", "Kassel", "Erfurt", "München", "Stuttgart"
};
含义:
- 城市编号
city_id_type(用作顶点ID)。 - 城市名称
city_name_type。 city_names是一个顶点表,共 10 个城市。
边数据(Edge List)
using route_data = copyable_edge_t<city_id_type, double>; // {source_id, target_id, value}
vector<route_data> routes_doubled = {
{0, 1, 85.0}, {0, 4, 217.0}, {0, 6, 173.0},
{1, 0, 85.0}, {1, 2, 80.0},
{2, 1, 80.0}, {2, 3, 250.0},
{3, 2, 250.0}, {3, 8, 84.0},
{4, 0, 217.0}, {4, 5, 103.0}, {4, 7, 186.0},
{5, 4, 103.0}, {5, 8, 167.0}, {5, 9, 183.0},
{6, 0, 173.0}, {6, 8, 502.0},
{7, 4, 186.0},
{8, 3, 84.0}, {8, 5, 167.0}, {8, 6, 502.0},
{9, 5, 183.0},
};
含义:
- 每条边
{source_id, target_id, distance}表示一条有向路。 - 因为是双向路网(Frankfurt↔Mannheim),所以成对出现(正反方向各一条)。
例如:
{0, 1, 85.0} 表示 Frankfürt → Mannheim 距离 85 km
{1, 0, 85.0} 表示 Mannheim → Frankfürt 距离 85 km
二、构建图结构(Graph Construction)
struct route {
city_id_type target_id = 0;
double distance = 0.0; // km
};
using AdjList = vector<list<route>>;
// G 是基于 city_names 与 routes 构建的图
using G = rr_adaptor<AdjList, city_names_type>;
G g(city_names, routes_doubled);
解析:
- 每个城市对应一个
list<route>(邻接表); - 整个图
G是一个邻接表的集合; rr_adaptor是一个“range of ranges”封装器,用于迭代访问图的顶点和边;routes_doubled提供连接关系和权重信息。
三、遍历图(Graph Traversal with Views)
cout << "Traverse the vertices & outgoing edges" << endl;
for (auto&& [uid, u] : vertexlist(g)) { // [id, vertex&]
cout << city_id(g, uid) << endl; // 打印城市名 [id]
for (auto&& [vid, uv] : incidence(g, uid)) { // [target_id, edge&]
cout << " --> " << city_id(g, vid) << endl; // 打印相邻城市
}
}
输出示例:
Traverse the vertices & outgoing edges
Frankfürt [0] --> Mannheim [1] --> Würzburg [4] --> Kassel [6]
Mannheim [1] --> Frankfürt [0] --> Karlsruhe [2]
Karlsruhe [2] --> Mannheim [1] --> Augsburg [3]
Augsburg [3] --> Karlsruhe [2] --> München [8]
Würzburg [4] --> Frankfürt [0] --> Nürnberg [5] --> Erfurt [7]
...
München [8] --> Augsburg [3] --> Nürnberg [5] --> Kassel [6]
Stuttgart [9] --> Nürnberg [5]
理解要点:
- 外层循环:遍历每个顶点。
- 内层循环:遍历该顶点的所有出边(
incidence)。 city_id(g, uid)显示城市编号;city(g, uid)则可取城市名。
四、Dijkstra 最短路径(Dijkstra’s Shortest Paths)
void dijkstra_clrs(
G&& g, vertex_id_t<G> seed,
Distance& distance, Predecessor& predecessor,
WF weight
);
解释:
g:图对象;seed:起点(顶点编号);distance[uid]:保存起点到各顶点的最短距离;predecessor[uid]:保存最短路径中上一个顶点;WF:权重函数(从边获取权重值)。
五、按“段数”计算最短路径(不考虑公里数)
auto weight_1 = [](edge_reference_t<G> uv) -> int {
return 1;
};
std::vector<int> distance(size(vertices(g)));
std::vector<vertex_id_t<G>> predecessor(size(vertices(g)));
dijkstra_clrs(g, frankfurt_id, distance, predecessor, weight_1);
cout << "Shortest distance (segments) from " << city(g, frankfurt_id) << endl;
for (vertex_id_t<G> uid = 0; uid < size(vertices(g)); ++uid)
if (distance[uid] > 0)
cout << " --> " << city_id(g, uid)
<< " - " << distance[uid] << " segments" << endl;
解释:
- 每条边权重恒为 1(
return 1),表示“经过一段路”; - 最终结果显示每个城市距离 Frankfurt 经过的“段数”。
输出示例:
Shortest distance (segments) from Frankfürt
--> Mannheim [1] - 1 segments
--> Karlsruhe [2] - 2 segments
--> Augsburg [3] - 3 segments
--> Würzburg [4] - 1 segments
--> Nürnberg [5] - 2 segments
--> Kassel [6] - 1 segments
--> Erfurt [7] - 2 segments
--> München [8] - 2 segments
--> Stuttgart [9] - 3 segments
六、整体结构总结
| 层级 | 含义 | 示例 |
|---|---|---|
| 顶点(vertex) | 城市(City) | Frankfurt, Mannheim… |
| 边(edge) | 城市间路线 | {source_id, target_id, distance} |
| 邻接表(AdjList) | 各城市出边集合 | vector<list<route>> |
| 图(Graph) | 城市网络 | G g(city_names, routes_doubled) |
| 遍历(Traversal) | 查看所有城市及连接 | vertexlist(g) + incidence(g, uid) |
| 最短路径算法 | Dijkstra | dijkstra_clrs() |
| 权重函数 | weight(uv) |
可返回距离或“段数” |
七、知识拓展
| 概念 | 说明 |
|---|---|
routes_doubled |
双向边结构,确保每条路两端互通 |
rr_adaptor |
“range of ranges” 工具,用于在 C++ ranges 框架中遍历图 |
copyable_edge_t |
边类型包装器,确保可拷贝性 |
vertexlist(g) |
获取所有顶点及引用 |
incidence(g, uid) |
获取指定顶点的所有出边 |
weight_1 |
常量权重函数(适用于 BFS 式最短路径) |
Dijkstra 算法(Dijkstra’s shortest path) 计算从法兰克福(Frankfürt)出发到所有其他城市的最短路径,并进一步找出“距离最远的城市”,以及“返回路径”的输出。下面是详细讲解
一、核心逻辑:按距离(公里)计算最短路径
auto weight = [&g](edge_reference_t<G> uv) {
return edge_value(g, uv); // 返回边的距离(公里)
};
这是权重函数,告诉 Dijkstra 算法每条边的代价(即距离)。
然后:
std::vector<double> distance(size(vertices(g)));
std::vector<vertex_id_t<G>> predecessor(size(vertices(g)));
distance[uid]:从法兰克福到城市uid的最短距离(公里)predecessor[uid]:最短路径上,uid的前驱城市 id(用来还原路径)
运行:
dijkstra_clrs(g, frankfurt_id, distance, predecessor, weight);
用法兰克福作为起点,计算所有最短路径。
二、输出最短路径(公里)
cout << "Shortest distance (km) from " << city(g, frankfurt_id) << endl;
for (vertex_id_t<G> uid = 0; uid < size(vertices(g)); ++uid)
if (distance[uid] > 0)
cout << " --> " << city_id(g, uid)
<< " - " << distance[uid] << "km" << endl;
输出示例:
Shortest distance (km) from Frankfürt
--> Mannheim [1] - 85km
--> Karlsruhe [2] - 165km
--> Augsburg [3] - 415km
--> Würzburg [4] - 217km
--> Nürnberg [5] - 320km
--> Kassel [6] - 173km
--> Erfurt [7] - 403km
--> München [8] - 487km
--> Stuttgart [9] - 503km
这表示:
- 从 法兰克福 出发,
- 到每个城市的最短路程(以公里计)都被算出来了。
三、找出“最远的城市”
vertex_id_t<G> farthest_id = frankfurt_id;
double farthest_dist = 0.0;
for (vertex_id_t<G> uid = 0; uid < size(vertices(g)); ++uid) {
if (distance[uid] > farthest_dist) {
farthest_dist = distance[uid];
farthest_id = uid;
}
}
这段代码遍历所有城市,找出:
distance[uid]最大的城市 → 就是最远城市。
输出:
The farthest city from Frankfürt is Stuttgart at 503km
也就是:
离法兰克福最远的城市是 斯图加特(Stuttgart),距离 503 公里。
四、输出最远路径的路线
cout << "The shortest path from " << city(g, farthest_id)
<< " to " << city(g, frankfurt_id) << " is: " << endl
<< " ";
for (vertex_id_t<G> uid = farthest_id; uid != frankfurt_id;
uid = predecessor[uid]) {
if (uid != farthest_id)
cout << " -- ";
cout << city_id(g, uid);
}
cout << " -- " << city_id(g, frankfurt_id) << endl;
这部分利用 predecessor 数组反向追踪最短路径。
示例输出:
The shortest path from Stuttgart to Frankfürt is:
Stuttgart [9] -- Nürnberg [5] -- Würzburg [4] -- Frankfürt [0]
意思是:
从斯图加特回到法兰克福的最短路径经过:
斯图加特 → 纽伦堡 → 维尔茨堡 → 法兰克福
总结(理解)
| 步骤 | 含义 |
|---|---|
| 1⃣ 定义权重函数 | 边的权重 = 公里距离 |
| 2⃣ 运行 Dijkstra | 从法兰克福出发,计算所有城市最短路径 |
| 3⃣ 输出结果 | 打印每个城市的最短距离(公里) |
| 4⃣ 找最远城市 | 找出最大 distance 的城市(斯图加特) |
| 5⃣ 还原路径 | 根据 predecessor 反向输出路径顺序 |
#include <iostream>
#include <vector>
#include <list>
#include <string>
#include <queue>
#include <limits>
#include <algorithm>
using namespace std;
// ============================
// 类型定义
// ============================
// 城市 ID 类型(用 int32_t 确保跨平台一致性)
using city_id_type = int32_t;
// 城市名称类型
using city_name_type = string;
// ============================
// 图的边结构(用于输入数据)
// ============================
// copyable_edge_t 表示一个“可复制的边”
// 其中包含源顶点 ID、目标顶点 ID 和权值(即距离)
template <typename VertexId, typename Value>
struct copyable_edge_t {
VertexId source_id;
VertexId target_id;
Value value;
};
// ============================
// 路径结构(用于邻接表)
// ============================
struct route {
city_id_type target_id = 0; // 目标城市 ID
double distance = 0.0; // 边的权重(单位:公里)
};
// ============================
// 邻接表定义
// ============================
using AdjList = vector<list<route>>; // 每个城市对应一条边的链表
// ============================
// 图适配器(rr_adaptor)
// ============================
// 封装邻接表和城市名列表,实现对图的访问接口
// 支持操作:获取邻接表、按索引访问边列表、获取名称等
// ============================
template <typename AdjListType, typename NamesType>
class rr_adaptor {
private:
AdjListType adj_list; // 邻接表(每个节点存储所有出边)
const NamesType& names; // 引用城市名称数组(避免复制)
public:
// 构造函数:从城市名和边表初始化图
rr_adaptor(const NamesType& city_names,
const vector<copyable_edge_t<city_id_type, double>>& edges)
: names(city_names) {
adj_list.resize(city_names.size());
for (const auto& edge : edges) {
route r;
r.target_id = edge.target_id;
r.distance = edge.value;
adj_list[edge.source_id].push_back(r);
}
}
size_t size() const { return adj_list.size(); } // 顶点数量
const AdjListType& get_adj_list() const { return adj_list; }
const NamesType& get_names() const { return names; }
// 下标访问符:访问指定城市的边表
list<route>& operator[](size_t idx) { return adj_list[idx]; }
const list<route>& operator[](size_t idx) const { return adj_list[idx]; }
};
// 定义图类型别名
using G = rr_adaptor<AdjList, vector<city_name_type>>;
// ============================
// 一些辅助类型定义与函数
// ============================
template <typename Graph>
using vertex_id_t = city_id_type; // 顶点 ID 类型简化
template <typename Graph>
using edge_reference_t = const route&; // 边引用类型简化
// 根据城市 ID 获取城市名
string city(const G& g, city_id_type id) { return g.get_names()[id]; }
// 获取带 ID 的城市信息字符串,例如 “Frankfurt [0]”
string city_id(const G& g, city_id_type id) {
return g.get_names()[id] + " [" + to_string(id) + "]";
}
// 获取边的权重值(距离)
double edge_value(const G& g, const route& r) { return r.distance; }
// ============================
// Dijkstra 算法实现(CLRS 版本)
// ============================
template <typename Graph, typename Distance, typename Predecessor, typename WeightFunc>
void dijkstra_clrs(Graph& g, vertex_id_t<Graph> seed, Distance& distance, Predecessor& predecessor,
WeightFunc weight) {
const auto INF = numeric_limits<typename Distance::value_type>::max();
// 初始化距离数组与前驱节点
fill(distance.begin(), distance.end(), INF);
fill(predecessor.begin(), predecessor.end(), -1);
distance[seed] = 0;
// 使用最小堆(优先队列)按最短距离排序
using pii = pair<typename Distance::value_type, vertex_id_t<Graph>>;
priority_queue<pii, vector<pii>, greater<pii>> pq;
pq.push({0, seed});
while (!pq.empty()) {
auto [dist, u] = pq.top();
pq.pop();
// 若弹出的节点已不是最短路径(过期),则跳过
if (dist > distance[u]) continue;
// 遍历所有相邻边
for (const auto& edge : g[u]) {
vertex_id_t<Graph> v = edge.target_id;
auto w = weight(edge); // 获取边的权值
// 松弛操作(Relaxation)
if (distance[u] + w < distance[v]) {
distance[v] = distance[u] + w;
predecessor[v] = u;
pq.push({distance[v], v});
}
}
}
}
// ============================
// 主程序入口
// ============================
int main() {
// ---------- 定义城市 ----------
vector<city_name_type> city_names = {"Frankfurt", "Mannheim", "Karlsruhe", "Augsburg",
"Würzburg", "Nürnberg", "Kassel", "Erfurt",
"München", "Stuttgart"};
// ---------- 定义城市间的道路(边表) ----------
using route_data = copyable_edge_t<city_id_type, double>;
vector<route_data> routes_doubled = {
{0, 1, 85.0}, {0, 4, 217.0}, {0, 6, 173.0}, {1, 0, 85.0}, {1, 2, 80.0}, {2, 1, 80.0},
{2, 3, 250.0}, {3, 2, 250.0}, {3, 8, 84.0}, {4, 0, 217.0}, {4, 5, 103.0}, {4, 7, 186.0},
{5, 4, 103.0}, {5, 8, 167.0}, {5, 9, 183.0}, {6, 0, 173.0}, {6, 8, 502.0}, {7, 4, 186.0},
{8, 3, 84.0}, {8, 5, 167.0}, {8, 6, 502.0}, {9, 5, 183.0}};
// ---------- 创建图 ----------
G g(city_names, routes_doubled);
city_id_type frankfurt_id = 0;
// ---------- 遍历所有城市与出边 ----------
cout << "=== Traverse the vertices & outgoing edges ===" << endl;
for (size_t uid = 0; uid < g.size(); ++uid) {
cout << city_id(g, uid) << endl;
for (const auto& edge : g[uid]) {
cout << " --> " << city_id(g, edge.target_id) << endl;
}
}
cout << endl;
// ---------- 最短路径(按“边数”计) ----------
cout << "=== Shortest Paths - Segments ===" << endl;
auto weight_1 = [](edge_reference_t<G> uv) -> int { return 1; }; // 每条边权重为 1
vector<int> distance_segments(g.size());
vector<vertex_id_t<G>> predecessor_segments(g.size());
dijkstra_clrs(g, frankfurt_id, distance_segments, predecessor_segments, weight_1);
cout << "Shortest distance (segments) from " << city(g, frankfurt_id) << endl;
for (vertex_id_t<G> uid = 0; uid < g.size(); ++uid) {
if (distance_segments[uid] > 0) {
cout << " --> " << city_id(g, uid) << " - " << distance_segments[uid] << " segments"
<< endl;
}
}
cout << endl;
// ---------- 最短路径(按“公里”计) ----------
cout << "=== Shortest Paths - Kilometers ===" << endl;
auto weight = [&g](edge_reference_t<G> uv) { return edge_value(g, uv); };
vector<double> distance(g.size());
vector<vertex_id_t<G>> predecessor(g.size());
dijkstra_clrs(g, frankfurt_id, distance, predecessor, weight);
cout << "Shortest distance (km) from " << city(g, frankfurt_id) << endl;
for (vertex_id_t<G> uid = 0; uid < g.size(); ++uid) {
if (distance[uid] > 0) {
cout << " --> " << city_id(g, uid) << " - " << distance[uid] << "km" << endl;
}
}
cout << endl;
// ---------- 查找最远城市 ----------
cout << "=== Farthest City ===" << endl;
vertex_id_t<G> farthest_id = frankfurt_id;
double farthest_dist = 0.0;
for (vertex_id_t<G> uid = 0; uid < g.size(); ++uid) {
if (distance[uid] > farthest_dist) {
farthest_dist = distance[uid];
farthest_id = uid;
}
}
cout << "The farthest city from " << city(g, frankfurt_id) << " is " << city(g, farthest_id)
<< " at " << distance[farthest_id] << "km" << endl;
// ---------- 输出从最远城市回到法兰克福的最短路径 ----------
cout << "The shortest path from " << city(g, farthest_id) << " to " << city(g, frankfurt_id)
<< " is: " << endl
<< " ";
// 沿前驱节点回溯路径
for (vertex_id_t<G> uid = farthest_id; uid != frankfurt_id; uid = predecessor[uid]) {
if (uid != farthest_id) cout << " -- ";
cout << city_id(g, uid);
}
cout << " -- " << city_id(g, frankfurt_id) << endl;
return 0;
}
非常好 你现在这份代码是一个完整的、面向对象封装的 Dijkstra 最短路径算法实现,并且使用了模板与泛型编程的技巧。下面我帮你逐层讲解这份程序的逻辑理解,让你能完全吃透它的结构与设计思想。
一、整体功能概览
这个程序模拟了一个“城市与道路”的图结构,并执行以下操作:
- 定义了若干城市(顶点);
- 定义了它们之间的道路(边,带距离);
- 构造了一个邻接表表示的图;
- 打印出图中所有城市及出边;
- 使用 Dijkstra 算法计算:
- 最短路径(按边数);
- 最短路径(按公里数);
- 找出距离法兰克福(Frankfurt)最远的城市;
- 打印该最远城市到法兰克福的最短路径。
二、主要数据结构
1⃣ copyable_edge_t
template <typename VertexId, typename Value>
struct copyable_edge_t {
VertexId source_id; // 源城市 ID
VertexId target_id; // 目标城市 ID
Value value; // 边的权重(例如距离)
};
这相当于是输入阶段的“边表”(edge list)。
每条边表示一条从城市 A 到城市 B 的道路。
2⃣ route
struct route {
city_id_type target_id = 0;
double distance = 0.0;
};
这是邻接表中的“边记录”类型。
每个城市的出边链表中保存若干 route,记录“我能到哪些城市”以及“距离多远”。
3⃣ AdjList
using AdjList = vector<list<route>>;
表示邻接表结构:
- 每个城市对应一个
list<route>; - 整个图是一个
vector,每个下标对应城市 ID; - 例如:
adj_list[0]是 Frankfurt 的出边链表。
4⃣ rr_adaptor
template <typename AdjListType, typename NamesType>
class rr_adaptor {
AdjListType adj_list;
const NamesType& names;
};
rr_adaptor 就是“图适配器”:
它把“输入的边表 + 城市名”转换为一个可遍历的邻接表图对象。
作用:
- 在构造时,根据
edges构建邻接表; - 提供接口让你能:
- 获取邻接表;
- 获取城市名;
- 通过
operator[]访问某城市的所有出边。
例如:
G g(city_names, routes_doubled);
for (auto& e : g[0]) { // 遍历 Frankfurt 的所有出边
cout << e.target_id << " " << e.distance;
}
三、辅助函数理解
| 函数名 | 功能 |
|---|---|
city(g, id) |
返回城市名称,如 "Frankfurt" |
city_id(g, id) |
返回 "Frankfurt [0]" 带编号的名称 |
edge_value(g, r) |
返回边的距离值 |
| 这些都是让输出更易读的工具函数。 |
四、Dijkstra 算法逻辑
模板函数:
template <typename Graph, typename Distance, typename Predecessor, typename WeightFunc>
void dijkstra_clrs(Graph& g, vertex_id_t<Graph> seed, Distance& distance, Predecessor& predecessor,
WeightFunc weight)
含义:
Graph:图类型;seed:起点(source);distance:保存起点到每个点的最短距离;predecessor:保存每个点的“上一个节点”;weight:计算边权值的函数(支持不同度量方式)。
算法步骤:
- 初始化
fill(distance.begin(), distance.end(), INF); distance[seed] = 0; predecessor[seed] = -1; - 优先队列(小根堆)
队列中保存priority_queue<pair<double,int>, vector<...>, greater<...>> pq; pq.push({0, seed});(当前距离, 顶点ID)。 - 主循环
- 弹出当前距离最小的顶点;
- 对它的每条出边
(u,v)执行“松弛”:if (distance[u] + w < distance[v]) { distance[v] = distance[u] + w; predecessor[v] = u; pq.push({distance[v], v}); } - 直到队列为空。
🌏 五、主函数逻辑
1⃣ 定义城市
vector<string> city_names = {"Frankfurt", "Mannheim", ...};
2⃣ 定义双向道路
vector<route_data> routes_doubled = {
{0, 1, 85.0}, {1, 0, 85.0}, // Frankfurt ↔ Mannheim
...
};
3⃣ 构造图
G g(city_names, routes_doubled);
4⃣ 打印城市和出边
输出:
Frankfurt [0]
--> Mannheim [1]
--> Würzburg [4]
--> Kassel [6]
...
5⃣ 按“段数”计算最短路径
auto weight_1 = [](auto) { return 1; }; // 每条边权值为1
dijkstra_clrs(g, frankfurt_id, distance_segments, predecessor_segments, weight_1);
意思是只看“经过几条边”,不管距离。
6⃣ 按“公里”计算最短路径
auto weight = [&g](edge_reference_t<G> uv) { return edge_value(g, uv); };
dijkstra_clrs(g, frankfurt_id, distance, predecessor, weight);
这才是真正的 Dijkstra 算法,按距离最短计算。
7⃣ 查找最远城市
for (uid in all vertices)
if (distance[uid] > farthest_dist)
farthest_id = uid;
找到最远的城市(从 Frankfurt 出发最远的节点)。
8⃣ 回溯路径
for (uid = farthest_id; uid != frankfurt_id; uid = predecessor[uid])
输出城市路径
通过 predecessor 反向追踪路径。
六、程序输出总结
输出大致如下:
=== Traverse the vertices & outgoing edges ===
Frankfurt [0]
--> Mannheim [1]
--> Würzburg [4]
--> Kassel [6]
...
=== Shortest Paths - Segments ===
--> Mannheim [1] - 1 segments
--> Karlsruhe [2] - 2 segments
--> München [8] - 3 segments
...
=== Shortest Paths - Kilometers ===
--> Mannheim [1] - 85 km
--> Karlsruhe [2] - 165 km
--> München [8] - 487 km
...
=== Farthest City ===
The farthest city from Frankfurt is München at 487 km
The shortest path from München to Frankfurt is:
München [8] -- Nürnberg [5] -- Würzburg [4] -- Frankfurt [0]
七、总结:代码设计思想
| 模块 | 思想 |
|---|---|
| 数据结构 | 使用模板实现灵活图结构(可复用) |
| 算法 | 经典 Dijkstra(优先队列 + 松弛) |
| 权重策略 | 支持“按边数”或“按距离”自定义 |
| 输出 | 清晰显示最短路径和路径构造 |
| 结构设计 | 解耦“图定义”与“算法逻辑” |
Dijkstra 最短路径接口设计与相关概念,并顺带解释其他算法的分类与候选项。内容较抽象,我会逐步拆解,尽量用理解方便掌握。
1⃣ Dijkstra Shortest Paths 接口
你提供的接口是一个 泛型模板函数:
template <
adjacency_list G,
ranges::random_access_range Distance,
ranges::random_access_range Predecessor,
class WF = std::function<ranges::range_value_t<Distance>(edge_reference_t<G>)>
>
requires ranges::random_access_range<vertex_range_t<G>> &&
integral<vertex_id_t<G>> &&
is_arithmetic_v<ranges::range_value_t<Distance>> &&
convertible_to<vertex_id_t<G>, ranges::range_value_t<Predecessor>> &&
edge_weight_function<G, WF>
void dijkstra_clrs(
G&& g, // 图对象
vertex_id_t<G> seed, // 起始顶点ID
Distance& distance, // 输出:从 seed 到各顶点的最短距离
Predecessor& predecessor,// 输出:前驱顶点 ID,用于构建最短路径
WF weight = [](edge_reference_t<G> uv) { return ranges::range_value_t<Distance>(1); }
);
解析:
- 模板参数
G:图类型,必须满足adjacency_list概念;Distance:存储距离的容器,要求随机访问;Predecessor:存储前驱节点的容器,要求随机访问;WF:边权重函数,默认每条边权值为1。
- 约束条件 (
requires)- 顶点集合可以随机访问;
- 顶点 ID 是整数;
- 距离类型是算术类型(int、float、double 等);
- 前驱数组可以存储顶点 ID;
- 权重函数返回值是数值类型。
- 函数参数
g:图对象;seed:起点顶点 ID;distance:输出数组,记录从seed到每个顶点的距离;predecessor:输出数组,记录最短路径上每个顶点的前驱;weight:获取边权重的函数。
核心思想:这是 CLRS 风格的 Dijkstra 泛型版本,可以用于任意满足概念的图结构,而不仅限于邻接表或 vector 形式。
2⃣ 边权函数 (edge_weight_function)
template <class G, class F>
concept edge_weight_function = is_arithmetic_v<invoke_result_t<F, edge_reference_t<G>>>;
理解:
- 边权函数是一个 callable(可调用对象);
- 输入是边引用,输出是一个数值类型;
- Dijkstra 算法会使用这个函数来获取每条边的权重;
- 默认是
weight(uv) -> 1,即每条边权值为 1(用于“段数最短”计算)。
3⃣ 顶点相关概念 (vertex_range)
template <class G>
using vertex_range_t = decltype(vertices(declval<G&&>()));
vertices(g)返回图的顶点集合;- 顶点类型可以遍历(range)且可以获取顶点 ID;
vertex_iterator_t和vertex_reference_t分别是迭代器类型和引用类型;- 约束:顶点集合必须可以正向遍历、已知大小。
4⃣ 边相关概念 (targeted_edge)
template <class G>
using vertex_edge_range_t = decltype(edges(declval<G&&>(), declval<vertex_reference_t<G>>()));
- 每个顶点可以访问它的出边集合;
- 每条边可以获取目标顶点 ID 或者目标顶点对象;
- 对于邻接表图来说,就是遍历
adj_list[u]。
5⃣ 邻接表概念 (adjacency_list)
template <class G>
concept adjacency_list = vertex_range<G> &&
targeted_edge<G, edge_t<G>> &&
requires(G&& g, vertex_reference_t<G> u, vertex_id_t<G> uid) {
{ edges(g, u) } -> ranges::forward_range;
{ edges(g, uid) } -> ranges::forward_range;
};
理解:
- 图必须满足:
- 顶点集合可遍历;
- 每个顶点的出边可遍历;
- 可以通过顶点对象或顶点 ID 获取出边集合。
6⃣ 源边邻接表概念 (sourced_adjacency_list)
source_id(g, uv)或source(g, uv)可以获取边的源顶点;edge_id(g, uv)可获取边编号;- 增强版邻接表,便于在算法中获取边的来源信息。
7⃣ 已确认与候选算法列表
已确认算法(Confirmed)
- Dijkstra 最短路径
- Bellman-Ford 最短路径
- Connected Components(连通分量)
- Strongly Connected Components(强连通分量)
- Biconnected Components(双连通分量)
- Articulation Points(割点)
- Minimum Spanning Tree(最小生成树)
候选算法(Candidates)
- Page Rank(网页排名)
- Betweenness Centrality(中介中心性)
- Triangle Count(三角形计数)
- Subgraph Isomorphism(子图同构)
- Kruskal 最小生成树
- Prim 最小生成树
- Louvain(社区发现)
- Label Propagation(社区发现)
理解:
- Confirmed 表示已经实现或设计确认的算法;
- Candidates 表示可能在后续加入或作为实验实现的算法。
总结
- 模板与概念化接口可以支持任意类型的邻接表图,灵活调用 Dijkstra。
- 顶点、边、权重函数、前驱数组、距离数组都通过泛型和约束实现类型安全。
- 默认权重为 1 支持“边数最短”模式。
- 概念化设计与 CLRS 算法结合,保证算法通用性。
- 已实现算法主要是图的基本分析和最短路径,候选算法偏向图分析和图挖掘。
#include <iostream>
#include <vector>
#include <list>
#include <string>
#include <queue>
#include <limits>
#include <algorithm>
#include <concepts>
#include <ranges>
#include <functional>
#include <type_traits>
using namespace std;
namespace ranges = std::ranges;
// ==================== 类型特征和概念 ====================
// 为图算法提供泛型接口所需的类型提取
// 顶点相关类型
template <typename G>
using vertex_range_t = decltype(vertices(declval<G&&>())); // 获取图的顶点范围类型
template <typename G>
using vertex_iterator_t = ranges::iterator_t<vertex_range_t<G&&>>; // 顶点迭代器类型
template <typename G>
using vertex_reference_t = ranges::range_reference_t<vertex_range_t<G>>; // 顶点引用类型
template <typename G>
using vertex_id_t = decltype(vertex_id(declval<G&&>(), declval<vertex_iterator_t<G>>())); // 顶点 ID 类型
// 边相关类型
template <typename G>
using vertex_edge_range_t = decltype(edges(declval<G&&>(), declval<vertex_reference_t<G>>())); // 顶点出边范围类型
template <typename G>
using edge_reference_t = ranges::range_reference_t<vertex_edge_range_t<G>>; // 边引用类型
template <typename G>
using edge_t = ranges::range_value_t<vertex_edge_range_t<G>>; // 边值类型
// 边权函数概念:返回数值类型
template <typename G, typename F>
concept edge_weight_function = is_arithmetic_v<invoke_result_t<F, edge_reference_t<G>>>;
// 顶点范围概念
template <typename G>
concept vertex_range =
ranges::forward_range<vertex_range_t<G>> && ranges::sized_range<vertex_range_t<G>> &&
requires(G&& g, vertex_iterator_t<G> ui) {
{ vertices(g) } -> ranges::forward_range;
vertex_id(g, ui);
};
// 目标边概念:可以获取目标顶点 ID 和引用
template <typename G>
concept targeted_edge = requires(G&& g, edge_reference_t<G> uv) {
target_id(g, uv);
target(g, uv);
};
// 邻接表概念
template <typename G>
concept adjacency_list = vertex_range<G> && targeted_edge<G> &&
requires(G&& g, vertex_reference_t<G> u, vertex_id_t<G> uid) {
{ edges(g, u) } -> ranges::forward_range;
{ edges(g, uid) } -> ranges::forward_range;
};
// 源边概念:可以获取源顶点 ID 和引用
template <typename G, typename E>
concept sourced_edge = requires(G&& g, E& uv) {
source_id(g, uv);
source(g, uv);
};
// 源邻接表概念
template <typename G>
concept sourced_adjacency_list = adjacency_list<G> && sourced_edge<G, edge_t<G>> &&
requires(G&& g, edge_reference_t<G> uv) { edge_id(g, uv); };
// ==================== 图的实现 ====================
using city_id_type = int32_t; // 顶点 ID 类型
using city_name_type = string; // 顶点名称类型
// 边结构体
struct route {
city_id_type target_id = 0; // 目标顶点 ID
double distance = 0.0; // 距离(权值)
};
// 顶点结构体
struct vertex {
city_id_type id; // 顶点 ID
list<route> edges; // 出边列表(邻接表)
};
// 图类
class Graph {
private:
vector<vertex> vertices_; // 顶点集合
vector<city_name_type> city_names_; // 顶点名称
public:
// 构造函数:传入城市名和边列表
Graph(const vector<city_name_type>& names,
const vector<tuple<city_id_type, city_id_type, double>>& edge_list)
: city_names_(names) {
vertices_.resize(names.size());
// 初始化顶点 ID
for (size_t i = 0; i < names.size(); ++i) {
vertices_[i].id = i;
}
// 初始化边
for (const auto& [src, tgt, dist] : edge_list) {
route r{tgt, dist};
vertices_[src].edges.push_back(r);
}
}
// 获取顶点集合(可修改/只读)
auto& get_vertices() { return vertices_; }
const auto& get_vertices() const { return vertices_; }
// 获取城市名称集合
const auto& get_city_names() const { return city_names_; }
// 支持索引访问顶点
vertex& operator[](size_t idx) { return vertices_[idx]; }
const vertex& operator[](size_t idx) const { return vertices_[idx]; }
};
// ==================== 图接口函数 ====================
// 返回顶点范围
auto vertices(Graph& g) {
return ranges::subrange(g.get_vertices().begin(), g.get_vertices().end());
}
auto vertices(const Graph& g) {
return ranges::subrange(g.get_vertices().begin(), g.get_vertices().end());
}
// 返回迭代器指向顶点的 ID
city_id_type vertex_id(const Graph& g, auto it) { return it->id; }
// 返回某顶点的出边范围
auto edges(Graph& g, vertex& v) { return ranges::subrange(v.edges.begin(), v.edges.end()); }
auto edges(const Graph& g, const vertex& v) {
return ranges::subrange(v.edges.begin(), v.edges.end());
}
auto edges(Graph& g, city_id_type uid) { return edges(g, g[uid]); }
auto edges(const Graph& g, city_id_type uid) { return edges(g, g[uid]); }
// 返回边的目标顶点 ID
city_id_type target_id(const Graph& g, const route& e) { return e.target_id; }
// 返回边的目标顶点引用
const vertex& target(const Graph& g, const route& e) { return g[e.target_id]; }
// 辅助函数:根据 ID 获取城市名称
string city(const Graph& g, city_id_type id) { return g.get_city_names()[id]; }
string city_id_str(const Graph& g, city_id_type id) {
return city(g, id) + " [" + to_string(id) + "]";
}
// 获取边权值
double edge_value(const Graph& g, const route& r) { return r.distance; }
// ==================== Dijkstra 算法 ====================
template <adjacency_list G, ranges::random_access_range Distance,
ranges::random_access_range Predecessor,
class WF = function<ranges::range_value_t<Distance>(edge_reference_t<G>)>>
requires ranges::random_access_range<vertex_range_t<G>> && integral<vertex_id_t<G>> &&
is_arithmetic_v<ranges::range_value_t<Distance>> &&
convertible_to<vertex_id_t<G>, ranges::range_value_t<Predecessor>> &&
edge_weight_function<G, WF>
void dijkstra_clrs(
G&& g, // 图对象
vertex_id_t<G> seed, // 起点 ID
Distance& distance, // 输出:从起点到各顶点距离
Predecessor& predecessor, // 输出:最短路径前驱顶点
WF weight = [](edge_reference_t<G> uv) { return ranges::range_value_t<Distance>(1); }) {
using dist_type = ranges::range_value_t<Distance>;
const auto INF = numeric_limits<dist_type>::max(); // 无穷大初始化
// 初始化距离和前驱
ranges::fill(distance, INF);
ranges::fill(predecessor, -1);
distance[seed] = 0;
// 优先队列存储 (distance, vertex_id),用于贪心选择
using pii = pair<dist_type, vertex_id_t<G>>;
priority_queue<pii, vector<pii>, greater<pii>> pq;
pq.push({0, seed});
// 主循环
while (!pq.empty()) {
auto [dist, u] = pq.top();
pq.pop();
if (dist > distance[u]) continue; // 已经有更短路径,跳过
// 松弛邻居边
for (const auto& edge : edges(g, u)) {
vertex_id_t<G> v = target_id(g, edge);
auto w = weight(edge);
if (distance[u] != INF && distance[u] + w < distance[v]) {
distance[v] = distance[u] + w;
predecessor[v] = u;
pq.push({distance[v], v});
}
}
}
}
// ==================== 主程序 ====================
int main() {
// 城市名称数据
vector<city_name_type> city_names = {"Frankfurt", "Mannheim", "Karlsruhe", "Augsburg",
"Würzburg", "Nürnberg", "Kassel", "Erfurt",
"München", "Stuttgart"};
// 边数据(双向)
vector<tuple<city_id_type, city_id_type, double>> routes = {
{0, 1, 85.0}, {0, 4, 217.0}, {0, 6, 173.0}, {1, 0, 85.0}, {1, 2, 80.0}, {2, 1, 80.0},
{2, 3, 250.0}, {3, 2, 250.0}, {3, 8, 84.0}, {4, 0, 217.0}, {4, 5, 103.0}, {4, 7, 186.0},
{5, 4, 103.0}, {5, 8, 167.0}, {5, 9, 183.0}, {6, 0, 173.0}, {6, 8, 502.0}, {7, 4, 186.0},
{8, 3, 84.0}, {8, 5, 167.0}, {8, 6, 502.0}, {9, 5, 183.0}};
// 构建图
Graph g(city_names, routes);
city_id_type frankfurt_id = 0;
// 遍历顶点及其出边
cout << "=== Traverse the vertices & outgoing edges ===" << endl;
for (const auto& v : vertices(g)) {
cout << city_id_str(g, v.id) << endl;
for (const auto& e : edges(g, v)) {
cout << " --> " << city_id_str(g, e.target_id) << endl;
}
}
cout << endl;
// ================== 最短路径 - 按段数 (unweighted) ==================
cout << "=== Shortest Paths - Segments ===" << endl;
auto weight_1 = [](const route& uv) -> int { return 1; }; // 每条边权值为1
vector<int> distance_seg(city_names.size());
vector<city_id_type> predecessor_seg(city_names.size());
dijkstra_clrs(g, frankfurt_id, distance_seg, predecessor_seg, weight_1);
cout << "Shortest distance (segments) from " << city(g, frankfurt_id) << endl;
for (city_id_type uid = 0; uid < city_names.size(); ++uid) {
if (distance_seg[uid] > 0) {
cout << " --> " << city_id_str(g, uid) << " - " << distance_seg[uid] << " segments"
<< endl;
}
}
cout << endl;
// ================== 最短路径 - 按公里数 (weighted) ==================
cout << "=== Shortest Paths - Kilometers ===" << endl;
auto weight_km = [&g](const route& uv) { return edge_value(g, uv); };
vector<double> distance(city_names.size());
vector<city_id_type> predecessor(city_names.size());
dijkstra_clrs(g, frankfurt_id, distance, predecessor, weight_km);
cout << "Shortest distance (km) from " << city(g, frankfurt_id) << endl;
for (city_id_type uid = 0; uid < city_names.size(); ++uid) {
if (distance[uid] > 0) {
cout << " --> " << city_id_str(g, uid) << " - " << distance[uid] << "km" << endl;
}
}
cout << endl;
// ================== 找最远城市 ==================
cout << "=== Farthest City ===" << endl;
city_id_type farthest_id = frankfurt_id;
double farthest_dist = 0.0;
for (city_id_type uid = 0; uid < city_names.size(); ++uid) {
if (distance[uid] < numeric_limits<double>::max() && distance[uid] > farthest_dist) {
farthest_dist = distance[uid];
farthest_id = uid;
}
}
cout << "The farthest city from " << city(g, frankfurt_id) << " is " << city(g, farthest_id)
<< " at " << distance[farthest_id] << "km" << endl;
// ================== 输出最短路径 ==================
cout << "The shortest path from " << city(g, farthest_id) << " to " << city(g, frankfurt_id)
<< " is:" << endl
<< " ";
vector<city_id_type> path;
for (city_id_type uid = farthest_id; uid != frankfurt_id; uid = predecessor[uid]) {
path.push_back(uid);
}
path.push_back(frankfurt_id);
for (size_t i = 0; i < path.size(); ++i) {
if (i > 0) cout << " -- ";
cout << city_id_str(g, path[i]);
}
cout << endl;
return 0;
}
VIEWS(视图接口) 和相关算法概念用逐步理解整理一下。这里主要是现代 C++ 图算法库的 “迭代器式接口” 和 视图封装 设计思想。
1⃣ 顶点视图 vertexlist 和 vertex_view
用法:
G g = ...;
for (auto&& uu : vertexlist(g)) {
vertex_id_t<G> uid = uu.id; // 顶点 ID
vertex_reference_t<G> u = uu.vertex; // 顶点引用
// 做一些操作
}
// C++17/20结构化绑定写法
for (auto&& [uid, u] : vertexlist(g)) {
// 做一些操作
}
视图结构:
template <class VId, class V, class VV>
struct vertex_view {
VId id; // 顶点 ID
V vertex; // 顶点引用
VV value; // 可选顶点值
};
- 可以不带值:
vertex_view<VId, V, void> { id, vertex }
- 可以只存 ID:
vertex_view<VId, void, void> { id }
- 可以带顶点值:
vertex_view<VId, V, VV> { id, vertex, value }
支持的重载:
vertexlist(g)→ 返回所有顶点的vertex_view<VId,V,void>vertexlist(g, vvf)→ 通过vvf(u)获取顶点值,返回vertex_view<VId,V,VV>
理解:
vertexlist提供遍历顶点的统一接口,支持额外顶点值访问(value function)。
2⃣ 出边视图 incidence 和 edge_view
用法:
vertex_reference_t<G> u = ...;
for (auto&& [vid, uv] : incidence(g, uid)) {
// vid: 目标顶点 ID
// uv: 边引用
}
- 可以加边权值函数:
auto edge_fn = [&g](edge_reference_t<G> uv) { return edge_value(g, uv); };
for (auto&& [vid, uv, val] : incidence(g, uid, edge_fn)) {
// val: 边权值
}
视图结构:
template <class VId, bool Sourced, class E, class EV>
struct edge_view {
VId source_id; // 源顶点(Sourced = true)
VId target_id; // 目标顶点
E edge; // 边引用
EV value; // 边值(可选)
};
- 不带值的情况:
edge_view<VId, false, E, void> { target_id, edge }
理解:
incidence是顶点的出边迭代器,统一提供目标 ID、边引用、可选权值。
3⃣ 邻居视图 neighbors 和 neighbor_view
用法:
for (auto&& [vid, v] : neighbors(g, uid)) {
// vid: 邻居顶点 ID
// v: 顶点引用
}
// 带顶点值
auto vvf = [&g](vertex_reference_t<G> v) { return vertex_value(g, v); };
for (auto&& [vid, v, val] : neighbors(g, uid, vvf)) {
// val: 顶点值
}
视图结构:
template <class VId, bool Sourced, class V, class VV>
struct neighbor_view {
VId source_id; // 源顶点
VId target_id; // 邻居顶点
V target; // 顶点引用
VV value; // 可选值
};
理解:
neighbors是出边的简化视图,只关心邻居顶点,适合访问顶点属性而不必关注边对象。
4⃣ 边集合视图 edgelist
用法:
for (auto&& [uid, vid, uv] : edgelist(g)) {
// uid: 源顶点 ID
// vid: 目标顶点 ID
// uv: 边引用
}
// 带边值
auto evf = [&g](edge_reference_t<G2> uv) { return edge_value(g, uv); };
for (auto&& [uid, vid, uv, val] : edgelist(g, evf)) {
// val: 边权值
}
edgelist提供全图边的统一遍历接口。
5⃣ 深度优先搜索 DFS (depth_first_search)
- 提供顶点和边遍历接口:
for(auto&& [vid,v] : vertices_depth_first_search(g,seed)) ...
for(auto&& [vid,uv] : edges_depth_first_search(g,seed)) ...
- 可以带顶点值/边值函数:
for(auto&& [vid,v,val] : vertices_depth_first_search(g,seed,vvf)) ...
for(auto&& [vid,uv,val] : edges_depth_first_search(g,seed,evf)) ...
- 支持
sourced_edges_depth_first_search获取源顶点信息。
理解:DFS 提供按访问顺序遍历顶点和边的迭代器接口,和
vertex_view/edge_view结合使用。
6⃣ 广度优先搜索 BFS (breadth_first_search)
- 接口设计与 DFS 一样,只是访问顺序不同(层序访问)。
- 顶点和边遍历都可用:
vertices_breadth_first_search(g, seed)
edges_breadth_first_search(g, seed)
sourced_edges_breadth_first_search(g, seed)
- 同样可选顶点值/边值。
7⃣ 拓扑排序 Topological Sort
- 顶点迭代器:
vertices_topological_sort(g, seed)
- 边迭代器:
edges_topological_sort(g, seed)
sourced_edges_topological_sort(g, seed)
- 可带顶点值/边值函数。
理解:和 DFS/BFS 一样,提供
vertex_view和edge_view,但遍历顺序为拓扑序。
总结理解
- 统一视图设计
vertex_view、edge_view、neighbor_view都是用 模板结构体封装顶点/边信息;- 支持 ID、引用、可选值(通过函数计算);
- 支持源边/目标边区分(
Sourced模板参数)。
- 遍历接口
vertexlist(g):顶点集合;neighbors(g,u):邻居集合;incidence(g,u):出边集合;edgelist(g):全图边集合。
- 算法遍历
- DFS、BFS、Topological Sort 都返回这些视图,统一访问顶点/边;
- 支持结构化绑定
[uid, vertex, value],可简化算法开发。
- 可扩展性
- 支持额外顶点值/边值函数;
- 支持泛型图结构(不仅限 vector/list)。
总体思路:通过视图接口,把图的结构和属性解耦,统一访问顶点、邻居、边,方便泛型算法编写。
#include <iostream>
#include <vector>
#include <list>
#include <string>
#include <queue>
#include <stack>
#include <limits>
#include <algorithm>
#include <functional>
#include <type_traits>
#include <unordered_set>
using namespace std;
// ==================== 类型定义 ====================
// 顶点 ID 类型
using city_id_type = int32_t;
// 顶点名称类型
using city_name_type = string;
// ==================== 边结构 ====================
// 表示城市间的路线
struct route {
city_id_type target_id = 0; // 目标城市 ID
double distance = 0.0; // 距离(单位 km)
};
// ==================== 顶点结构 ====================
// 表示图的顶点
struct vertex {
city_id_type id; // 顶点 ID
string name; // 顶点名称
list<route> edges; // 顶点的出边列表
};
// ==================== 视图结构 ====================
// 顶点视图(带可选值类型 VV)
template <class VId, class V, class VV>
struct vertex_view {
VId id; // 顶点 ID
V vertex; // 顶点对象
VV value; // 可选顶点值
};
// 仅顶点对象,不带 VV
template <class VId, class V>
struct vertex_view<VId, V, void> {
VId id;
V vertex;
};
// 仅顶点值,不带 V
template <class VId, class VV>
struct vertex_view<VId, void, VV> {
VId id;
VV value;
};
// 仅 ID
template <class VId>
struct vertex_view<VId, void, void> {
VId id;
};
// 边视图(可带 source_id / 是否来源 Sourced /边对象 E /边值 EV)
template <class VId, bool Sourced, class E, class EV>
struct edge_view {
VId source_id; // 源顶点 ID
VId target_id; // 目标顶点 ID
E edge; // 边对象
EV value; // 边值
};
// Sourced 为 true 时
template <class VId, class E, class EV>
struct edge_view<VId, true, E, EV> {
VId source_id;
VId target_id;
E edge;
EV value;
};
// Sourced 为 true,但没有 EV
template <class VId, class E>
struct edge_view<VId, true, E, void> {
VId source_id;
VId target_id;
E edge;
};
// Sourced 为 false
template <class VId, class E, class EV>
struct edge_view<VId, false, E, EV> {
VId target_id;
E edge;
EV value;
};
template <class VId, class E>
struct edge_view<VId, false, E, void> {
VId target_id;
E edge;
};
// 邻居视图
template <class VId, bool Sourced, class V, class VV>
struct neighbor_view {
VId source_id;
VId target_id;
V target;
VV value;
};
// Sourced 为 false
template <class VId, class V, class VV>
struct neighbor_view<VId, false, V, VV> {
VId target_id;
V target;
VV value;
};
template <class VId, class V>
struct neighbor_view<VId, false, V, void> {
VId target_id;
V target;
};
// ==================== 图类 ====================
class Graph {
private:
vector<vertex> vertices_; // 顶点数组
public:
// 构造函数:通过顶点名列表和边列表初始化图
Graph(const vector<city_name_type>& names,
const vector<tuple<city_id_type, city_id_type, double>>& edge_list) {
vertices_.resize(names.size());
for (size_t i = 0; i < names.size(); ++i) {
vertices_[i].id = i; // 顶点 ID 从 0 开始
vertices_[i].name = names[i];
}
// 初始化边
for (const auto& [src, tgt, dist] : edge_list) {
route r{tgt, dist};
vertices_[src].edges.push_back(r);
}
}
size_t size() const { return vertices_.size(); }
auto& get_vertices() { return vertices_; }
const auto& get_vertices() const { return vertices_; }
vertex& operator[](size_t idx) { return vertices_[idx]; }
const vertex& operator[](size_t idx) const { return vertices_[idx]; }
};
// ==================== 类型别名 ====================
template <typename G>
using vertex_id_t = city_id_type;
template <typename G>
using vertex_reference_t = vertex&;
template <typename G>
using edge_reference_t = const route&;
// ==================== 基本图接口 ====================
inline auto& vertices(Graph& g) { return g.get_vertices(); }
inline const auto& vertices(const Graph& g) { return g.get_vertices(); }
inline auto& edges(Graph& g, vertex& v) { return v.edges; }
inline const auto& edges(const Graph& g, const vertex& v) { return v.edges; }
inline auto& edges(Graph& g, city_id_type uid) { return g[uid].edges; }
inline const auto& edges(const Graph& g, city_id_type uid) { return g[uid].edges; }
inline city_id_type target_id(const Graph& g, const route& e) { return e.target_id; }
inline const vertex& target(const Graph& g, const route& e) { return g[e.target_id]; }
inline double edge_value(const Graph& g, const route& r) { return r.distance; }
inline string vertex_value(const Graph& g, const vertex& v) { return v.name; }
inline string city(const Graph& g, city_id_type id) { return g[id].name; }
inline string city_id_str(const Graph& g, city_id_type id) {
return city(g, id) + " [" + to_string(id) + "]"; // 显示城市名和 ID
}
// ==================== 视图生成器 ====================
// vertexlist:遍历所有顶点
class vertexlist_range {
Graph& g_;
public:
vertexlist_range(Graph& g) : g_(g) {}
struct iterator {
Graph& g;
size_t idx;
iterator(Graph& graph, size_t i) : g(graph), idx(i) {}
auto operator*() { return vertex_view<city_id_type, vertex&, void>{g[idx].id, g[idx]}; }
iterator& operator++() {
++idx;
return *this;
}
bool operator!=(const iterator& other) const { return idx != other.idx; }
};
iterator begin() { return iterator(g_, 0); }
iterator end() { return iterator(g_, g_.size()); }
};
inline auto vertexlist(Graph& g) { return vertexlist_range(g); }
// incidence:顶点的出边视图
class incidence_range {
Graph& g_;
city_id_type uid_;
public:
incidence_range(Graph& g, city_id_type uid) : g_(g), uid_(uid) {}
struct iterator {
Graph& g;
city_id_type source;
list<route>::iterator it;
iterator(Graph& graph, city_id_type src, list<route>::iterator i)
: g(graph), source(src), it(i) {}
auto operator*() {
return edge_view<city_id_type, false, const route&, void>{it->target_id, *it};
}
iterator& operator++() {
++it;
return *this;
}
bool operator!=(const iterator& other) const { return it != other.it; }
};
iterator begin() { return iterator(g_, uid_, g_[uid_].edges.begin()); }
iterator end() { return iterator(g_, uid_, g_[uid_].edges.end()); }
};
inline auto incidence(Graph& g, city_id_type uid) { return incidence_range(g, uid); }
// neighbors:顶点的邻居顶点视图
class neighbors_range {
Graph& g_;
city_id_type uid_;
public:
neighbors_range(Graph& g, city_id_type uid) : g_(g), uid_(uid) {}
struct iterator {
Graph& g;
list<route>::iterator it;
iterator(Graph& graph, list<route>::iterator i) : g(graph), it(i) {}
auto operator*() {
return neighbor_view<city_id_type, false, vertex&, void>{it->target_id,
g[it->target_id]};
}
iterator& operator++() {
++it;
return *this;
}
bool operator!=(const iterator& other) const { return it != other.it; }
};
iterator begin() { return iterator(g_, g_[uid_].edges.begin()); }
iterator end() { return iterator(g_, g_[uid_].edges.end()); }
};
inline auto neighbors(Graph& g, city_id_type uid) { return neighbors_range(g, uid); }
// edgelist:遍历所有边
class edgelist_range {
Graph& g_;
public:
edgelist_range(Graph& g) : g_(g) {}
struct iterator {
Graph& g;
size_t vid;
list<route>::iterator eit;
iterator(Graph& graph, size_t v, list<route>::iterator e) : g(graph), vid(v), eit(e) {
skip_empty();
}
void skip_empty() {
while (vid < g.size() && eit == g[vid].edges.end()) {
++vid;
if (vid < g.size()) eit = g[vid].edges.begin();
}
}
auto operator*() {
return edge_view<city_id_type, true, const route&, void>{static_cast<city_id_type>(vid),
eit->target_id, *eit};
}
iterator& operator++() {
++eit;
skip_empty();
return *this;
}
bool operator!=(const iterator& other) const {
return vid != other.vid || (vid < g.size() && eit != other.eit);
}
};
iterator begin() {
return iterator(g_, 0, g_.size() > 0 ? g_[0].edges.begin() : list<route>::iterator());
}
iterator end() { return iterator(g_, g_.size(), list<route>::iterator()); }
};
inline auto edgelist(Graph& g) { return edgelist_range(g); }
// ==================== 深度优先搜索 DFS ====================
class vertices_dfs_range {
Graph& g_;
city_id_type seed_;
public:
vertices_dfs_range(Graph& g, city_id_type seed) : g_(g), seed_(seed) {}
struct iterator {
Graph& g;
stack<city_id_type> stk; // DFS 栈
unordered_set<city_id_type> visited; // 已访问顶点
city_id_type current;
bool done;
iterator(Graph& graph, city_id_type start, bool is_end)
: g(graph), current(start), done(is_end) {
if (!done) {
stk.push(start);
advance();
}
}
void advance() {
while (!stk.empty()) {
current = stk.top();
stk.pop();
if (visited.find(current) == visited.end()) {
visited.insert(current);
for (auto& e : g[current].edges) {
if (visited.find(e.target_id) == visited.end()) {
stk.push(e.target_id);
}
}
return;
}
}
done = true;
}
auto operator*() { return vertex_view<city_id_type, vertex&, void>{current, g[current]}; }
iterator& operator++() {
advance();
return *this;
}
bool operator!=(const iterator& other) const { return done != other.done; }
};
iterator begin() { return iterator(g_, seed_, false); }
iterator end() { return iterator(g_, seed_, true); }
};
inline auto vertices_depth_first_search(Graph& g, city_id_type seed) {
return vertices_dfs_range(g, seed);
}
// ==================== 广度优先搜索 BFS ====================
class vertices_bfs_range {
Graph& g_;
city_id_type seed_;
public:
vertices_bfs_range(Graph& g, city_id_type seed) : g_(g), seed_(seed) {}
struct iterator {
Graph& g;
queue<city_id_type> q;
unordered_set<city_id_type> visited;
city_id_type current;
bool done;
iterator(Graph& graph, city_id_type start, bool is_end)
: g(graph), current(start), done(is_end) {
if (!done) {
q.push(start);
visited.insert(start);
advance();
}
}
void advance() {
if (q.empty()) {
done = true;
return;
}
current = q.front();
q.pop();
for (auto& e : g[current].edges) {
if (visited.find(e.target_id) == visited.end()) {
visited.insert(e.target_id);
q.push(e.target_id);
}
}
}
auto operator*() { return vertex_view<city_id_type, vertex&, void>{current, g[current]}; }
iterator& operator++() {
advance();
return *this;
}
bool operator!=(const iterator& other) const { return done != other.done; }
};
iterator begin() { return iterator(g_, seed_, false); }
iterator end() { return iterator(g_, seed_, true); }
};
inline auto vertices_breadth_first_search(Graph& g, city_id_type seed) {
return vertices_bfs_range(g, seed);
}
// ==================== Dijkstra 最短路径算法 ====================
template <typename G, typename Distance, typename Predecessor, typename WF>
void dijkstra_clrs(G& g, vertex_id_t<G> seed, Distance& distance, Predecessor& predecessor,
WF weight) {
using dist_type = typename Distance::value_type;
const auto INF = numeric_limits<dist_type>::max();
fill(distance.begin(), distance.end(), INF); // 初始化距离为无穷大
fill(predecessor.begin(), predecessor.end(), -1); // 初始化前驱顶点为 -1
distance[seed] = 0; // 起点到自身距离为 0
// 优先队列,用于选择当前距离最小的顶点
using pii = pair<dist_type, vertex_id_t<G>>;
priority_queue<pii, vector<pii>, greater<pii>> pq;
pq.push({0, seed});
while (!pq.empty()) {
auto [dist, u] = pq.top(); // 取出队列中距离最小的顶点
pq.pop();
// 如果当前距离已经大于记录的最短距离,则跳过
if (dist > distance[u]) continue;
// 遍历 u 的所有邻居
for (const auto& edge : edges(g, u)) {
vertex_id_t<G> v = target_id(g, edge); // 目标顶点
auto w = weight(edge); // 边权值
// 松弛操作
if (distance[u] != INF && distance[u] + w < distance[v]) {
distance[v] = distance[u] + w; // 更新最短距离
predecessor[v] = u; // 更新前驱节点
pq.push({distance[v], v}); // 将新距离压入队列
}
}
}
}
// ==================== 主程序 ====================
int main() {
// 顶点名称列表
vector<city_name_type> city_names = {"Frankfurt", "Mannheim", "Karlsruhe", "Augsburg",
"Würzburg", "Nürnberg", "Kassel", "Erfurt",
"München", "Stuttgart"};
// 边列表:源城市 ID,目标城市 ID,距离(km)
vector<tuple<city_id_type, city_id_type, double>> routes = {
{0, 1, 85.0}, {0, 4, 217.0}, {0, 6, 173.0}, {1, 0, 85.0}, {1, 2, 80.0}, {2, 1, 80.0},
{2, 3, 250.0}, {3, 2, 250.0}, {3, 8, 84.0}, {4, 0, 217.0}, {4, 5, 103.0}, {4, 7, 186.0},
{5, 4, 103.0}, {5, 8, 167.0}, {5, 9, 183.0}, {6, 0, 173.0}, {6, 8, 502.0}, {7, 4, 186.0},
{8, 3, 84.0}, {8, 5, 167.0}, {8, 6, 502.0}, {9, 5, 183.0}};
// 创建图对象
Graph g(city_names, routes);
city_id_type frankfurt_id = 0; // Frankfurt 的顶点 ID
// ==================== 演示:vertexlist ====================
cout << "=== vertexlist: 遍历所有顶点 ===" << endl;
for (auto&& [uid, u] : vertexlist(g)) {
cout << city_id_str(g, uid) << endl;
}
cout << endl;
// ==================== 演示:incidence ====================
cout << "=== incidence: Frankfurt 的出边 ===" << endl;
for (auto&& [vid, uv] : incidence(g, frankfurt_id)) {
cout << " --> " << city_id_str(g, vid) << " (" << uv.distance << "km)" << endl;
}
cout << endl;
// ==================== 演示:neighbors ====================
cout << "=== neighbors: Frankfurt 的邻居城市 ===" << endl;
for (auto&& [vid, v] : neighbors(g, frankfurt_id)) {
cout << " --> " << city_id_str(g, vid) << endl;
}
cout << endl;
// ==================== 演示:edgelist ====================
cout << "=== edgelist: 图中所有边 ===" << endl;
for (auto&& [uid, vid, uv] : edgelist(g)) {
cout << city_id_str(g, uid) << " --> " << city_id_str(g, vid) << " (" << uv.distance
<< "km)" << endl;
}
cout << endl;
// ==================== 演示:DFS ====================
cout << "=== Depth First Search (DFS) 从 Frankfurt ===" << endl;
for (auto&& [vid, v] : vertices_depth_first_search(g, frankfurt_id)) {
cout << city_id_str(g, vid) << endl;
}
cout << endl;
// ==================== 演示:BFS ====================
cout << "=== Breadth First Search (BFS) 从 Frankfurt ===" << endl;
for (auto&& [vid, v] : vertices_breadth_first_search(g, frankfurt_id)) {
cout << city_id_str(g, vid) << endl;
}
cout << endl;
// ==================== 演示:Dijkstra ====================
cout << "=== Dijkstra 最短路径 (km) 从 Frankfurt ===" << endl;
auto weight_km = [&g](const route& uv) { return edge_value(g, uv); };
vector<double> distance(city_names.size()); // 存储最短距离
vector<city_id_type> predecessor(city_names.size()); // 存储前驱节点
dijkstra_clrs(g, frankfurt_id, distance, predecessor, weight_km);
// 输出每个城市的最短路径距离
for (city_id_type uid = 0; uid < city_names.size(); ++uid) {
if (distance[uid] > 0 && distance[uid] < numeric_limits<double>::max()) {
cout << " --> " << city_id_str(g, uid) << " - " << distance[uid] << "km" << endl;
}
}
cout << endl;
// 找最远的城市
city_id_type farthest_id = frankfurt_id;
double farthest_dist = 0.0;
for (city_id_type uid = 0; uid < city_names.size(); ++uid) {
if (distance[uid] < numeric_limits<double>::max() && distance[uid] > farthest_dist) {
farthest_dist = distance[uid];
farthest_id = uid;
}
}
cout << "从 " << city(g, frankfurt_id) << " 最远的城市是 " << city(g, farthest_id) << ",距离 "
<< distance[farthest_id] << "km" << endl;
// 输出从 Frankfurt 到最远城市的最短路径
cout << "最短路径: ";
vector<city_id_type> path;
for (city_id_type uid = farthest_id; uid != frankfurt_id && uid >= 0; uid = predecessor[uid]) {
path.push_back(uid);
if (predecessor[uid] == uid) break; // 防止自环
}
path.push_back(frankfurt_id);
for (size_t i = 0; i < path.size(); ++i) {
if (i > 0) cout << " -- ";
cout << city_id_str(g, path[i]);
}
cout << endl;
return 0;
}
CSR 图、图容器以及相关接口、概念的内容整理成详细的理解说明,条理清晰并尽量保留原文信息。
一、图容器概述
1. 图容器的特点
- 独特性:每个容器都是唯一的,不能与其他图容器混淆。
- 范围嵌套:图是“范围的范围”(range of ranges),即顶点集合、每个顶点的边集合。
- 自由函数接口:所有函数都是自由函数(free functions),类似 STL 的
begin,end,size,empty。 - 自定义点:所有函数都是可定制的(customization points)。
- 可选用户值:顶点、边和图可以包含用户自定义的值,但不是必需的。
2. CSR 图(Compressed Sparse Row)
- 压缩稀疏行矩阵表示,高性能且内存紧凑。
- 静态结构:一旦构造完成,顶点和边的结构不能改变,但顶点/边的值可以修改。
- 值存储分离:结构(adjacency)和数据(权值)是分离的。
模板参数:
template <class EV, class VV, class GV = void, integral VId = uint32_t, class Alloc = allocator<uint32_t>>
class csr_graph;
EV:边的值类型VV:顶点的值类型GV:图的值类型(可选)VId:顶点 ID 类型Alloc:内部容器的分配器类型
使用示例:
using G = std::graph::csr_graph<double, std::string_view, std::string>;
3. CSR 图构造函数
- 仅边构造:
template <ranges::forward_range ERng, class EProj = identity>
csr_graph(const ERng& erng, EProj eproj = {}, const Alloc& alloc = Alloc());
ERng:边的范围EProj:边投影函数,将用户边对象转换为标准edge_view{source_id, target_id[, value]}
- 边 + 顶点构造:
template <ranges::forward_range ERng, class EProj = identity,
ranges::forward_range VRng, class VProj = identity>
csr_graph(const ERng& erng, const VRng& vrng, EProj eproj = {}, VProj vproj = {}, const Alloc& alloc = Alloc());
- 同时提供顶点值范围
VRng和顶点投影VProj。
二、图和顶点函数
| 函数 | 返回类型 | 默认复杂度 | 注释 |
|---|---|---|---|
graph_value(g) |
graph_value_t<G> |
常量 | 可选,返回图的用户值 |
vertices(g) |
vertex_range_t<G> |
常量 | 返回顶点范围 |
vertex_id(g, ui) |
vertex_id_t<G> |
常量 | 根据迭代器获取顶点 ID |
vertex_value(g,u) |
vertex_value_t<G> |
常量 | 可选,返回顶点值 |
degree(g,u) |
整数类型 | 常量 | 顶点出度 = size(edges(g,u)) |
find_vertex(g, uid) |
vertex_iterator_t<G> |
常量 | 返回顶点迭代器 |
三、边函数
| 函数 | 返回类型 | 默认复杂度 | 注释 |
|---|---|---|---|
edges(g,u) |
vertex_edge_range_t<G> |
常量 | 返回顶点 u 的边范围 |
edges(g,uid) |
vertex_edge_range_t<G> |
常量 | 默认调用 edges(g, *find_vertex(g, uid)) |
target_id(g, uv) |
vertex_id_t<G> |
常量 | 返回边的目标顶点 ID |
target(g, uv) |
vertex_t<G> |
常量 | 返回目标顶点对象引用 |
edge_value(g, uv) |
edge_value_t<G> |
常量 | 可选,返回边的值 |
find_vertex_edge(g,u,vid) |
vertex_edge_t<G> |
线性 | 根据目标顶点 ID 查找边 |
contains_edge(g, uid, vid) |
bool | 线性 | 检查是否存在某条边 |
四、源边函数(Sourced Edge)
| 函数 | 返回类型 | 默认实现 |
|---|---|---|
source_id(g, uv) |
vertex_id_t<G> |
常量,返回源顶点 ID |
source(g, uv) |
vertex_t<G> |
默认 *(begin(vertices(g)) + source_id(g, uv)) |
edge_id(g, uv) |
edge_id_t<G> |
返回 (source_id, target_id) 对 |
五、类型别名与 Traits
顶点相关类型
graph_reference_t<G>:图引用类型vertex_range_t<G>:顶点范围类型vertex_iterator_t<G>:顶点迭代器类型vertex_t<G>:顶点值类型vertex_reference_t<G>:顶点引用类型vertex_id_t<G>:顶点 ID 类型vertex_value_t<G>:顶点值类型(可选)
边相关类型
vertex_edge_range_t<G>:顶点边范围类型vertex_edge_iterator_t<G>:边迭代器类型edge_t<G>:边值类型edge_reference_t<G>:边引用类型edge_value_t<G>:边值类型(可选)edge_id_t<G>:边 ID 类型(source_id, target_id)
六、Traits / 概念示例
has_degree<G>:是否支持degree(g,u)has_find_vertex<G>:是否支持find_vertex(g, uid)has_find_vertex_edge<G>:是否支持查找顶点边has_contains_edge<G>:是否支持contains_edge(g, uid, vid)- 无序边(unordered_edge) / 有序边(ordered_edge)
用于标记边类型是否是无序/有序的。 - 邻接矩阵概念:
template <class G> concept adjacency_matrix = is_adjacency_matrix_v<G>;
用于标记图是否采用邻接矩阵存储。
七、外部图集成
- 必需函数:
vertices(g)edges(g,u)target_id(g,uv)
- 可选函数:
graph_value(g)vertex_value(g,u)edge_value(g,uv)vertex_id(g,ui)source_id(g,uv)
- Niebloid / tag_invoke 机制:
rr_adaptor通过tag_invoke自定义vertices()。- 支持将外部图数据适配为标准接口。
八、其他图容器示例
rr_adaptor
- 包装
range of ranges的外部图。 - 支持顶点值向量 + 边范围 + 可选重复边。
- 示例类型:
using RR = std::vector<std::list<route>>;
using routes_rr_graph_type = rr_adaptor<RR, city_names_type>;
dynamic_graph
- 动态图,支持
sourced_edge。 - 使用
vofl_graph_traits进行类型配置:
using Traits = vofl_graph_traits<double, std::string, std::string>;
using G = dynamic_adjacency_graph<Traits>;
九、总结
- CSR 图:静态高性能图,适合查询和 Dijkstra 最短路径。
- rr_adaptor / dynamic_graph:外部图或动态图适配器。
- 接口统一:
vertices,edges,target_id是核心函数。 - 可选值 / Traits / Concepts:支持泛型算法和概念检查。
更多推荐



所有评论(0)