我 的 彩 虹 笔 记 !
前排提醒:这一章对着我的GoodNotes笔记的图看比较好,有图会方便理解很多!(甚至想着有机会的话可以录个讲解视频啥的x 但是我思路好像又没有清晰到能讲出来...
救命。。!!!我没怎么听课啊啊啊啊啊啊啊啊啊啊啊啊TT
图的基本概念
图论术语
ummmhmmm这个我稍后再整理。
阶(Order):图G中点集V的大小称作图G的阶。
子图(Sub-Graph):当图G'=(V',E')其中V‘包含于V,E’包含于E,则G'称作图G=(V,E)的子图。每个图都是本身的子图。
生成子图(Spanning Sub-Graph):指满足条件V(G') = V(G)的G的子图G'。
导出子图(Induced Subgraph):以图G的顶点集V的非空子集V1为顶点集,以两端点均在V1中的全体边为边集的G的子图,称为V1导出的导出子图;以图G的边集E的非空子集E1为边集,以E1中边关联的顶点的全体为顶点集的G的子图,称为E1导出的导出子图。
度(Degree):一个顶点的度是指与该顶点相关联的边的条数,顶点v的度记作d(v)。
入度(In-degree)和出度(Out-degree):对于有向图来说,一个顶点的度可细分为入度和出度。一个顶点的入度是指与其关联的各边之中,以其为终点的边数;出度则是相对的概念,指以该顶点为起点的边数。
自环(Loop):若一条边的两个顶点为同一顶点,则此边称作自环。
路径(Path):从u到v的一条路径是指一个序列v0,e1,v1,e2,v2,...ek,vk,其中ei的顶点为vi及vi - 1,k称作路径的长度。如果它的起止顶点相同,该路径是“闭”的,反之,则称为“开”的。一条路径称为一简单路径(simple path),如果路径中除起始与终止顶点可以重合外,所有顶点两两不等。
行迹(Trace):如果路径P(u,v)中的边各不相同,则该路径称为u到v的一条行迹。
轨道(Track):如果路径P(u,v)中的顶点各不相同,则该路径称为u到v的一条轨道。
闭的行迹称作回路(Circuit),闭的轨称作圈(Cycle)。
(另一种定义是:walk对应上述的path,path对应上述的track。Trail对应trace。)
桥(Bridge):若去掉一条边,便会使得整个图不连通,该边称为桥。
一些中英对照
之后的命名会用到很多次这些词(的缩写)。
图 graph
顶点 vertex
边 edge
弧 arc
邻接 adjoin / adjacency
矩阵 matrix
无穷 infinity
图的存储
图的存储:
邻接矩阵(二维数组 + 一维数组)适合存储稠密图、
邻接表(顺序 + 链式存储)适合存储稀疏图,表示方式不唯一、
十字链表、邻接多重表
邻接矩阵
一维数组 - 顶点表(Vertex List):存data
矩阵意义
二维数组 - 邻接矩阵(Adjacency Matrix)
- 不管data,用顶点表的下标作为key。
- 横向表示出度,纵向表示入度。so 无向图一定是对称矩阵。
不带权图
[1]:相互邻接
[0]:相互不邻接
带权图
[n]:邻接,且权为n
[∞]:不邻接,当作距离无穷大
[ ∞ ] 一般用int的最大值表示,就像这样:
#define INFINITY 65535 // 表示无穷大(带权图要用)
结构实现
前排插入一条语法小知识。
如果你要创建一个一维数组,有两种方式:
1. char* VerList;
2. char VerList[MaxVerNum];
如果用第2种方法的话,在构造函数里要:
GraphMatrix::GraphMatrix(int sz)
{
// 创建顶点表 // 分配内存
VerList = new char[sz];
......
}
好处大概就是你可以调整 sz 的值。
——
正文:(以不带权图为例。
// #define INFINITY 65535 // 表示无穷大(带权图要用)
#define MaxVerNum 50 // 定义顶点最大数量
// 邻接矩阵 // Adjacency Matrix Graph
class GraphMatrix
{
private:
int VerNum; // 顶点数
int EdgeNum; // 边/弧数
char* VerList; // 顶点表(一维数组)
int** Matrix; // 邻接矩阵(二维数组)
public:
GraphMatrix(int sz); // 构造函数
void createGraph(int n); // 通过直接输入一整个邻接矩阵来构建图
void setVerList(int m, char* ch); // 顶点表传参
int CharToIndex(char c); // 输入字符,返回下标
void connect(bool d, char p1, char p2); // 有向1/无向0 连接两个顶点
void connectByIndex(int i, int j); // 通过下标 有向 连接两个顶点
void visit(int v); // hmmm..其实就是一个小输出
void BFS(int v); // 广度优先遍历
void BFS_plus(int v); // 广度优先遍历plus
void DFS(int v); // 深度优先遍历
void DFS_plus(int v); // 深度优先遍历plus
void printMatrix(); // 打印邻接矩阵
};
// 构造函数
GraphMatrix::GraphMatrix(int sz)
{
// 创建顶点表
VerList = new int[sz];
// 为邻接矩阵分配内存
Matrix = new int* [sz];
for (int i = 0; i < sz; i++)
Matrix[i] = new int[sz];
// 初始化
for (int i = 0; i < sz; i++)
for (int j = 0; j < sz; j++)
Matrix[i][j] = -1; // 其实0应该就行。但是我保险一点...
}
创建方式 - 输入矩阵
直接输入一整个邻接矩阵
// 构建图
// 这里的构建方式是直接输入一整个邻接矩阵
void GraphMatrix::createGraph(int n)
{
VerNum = n;
// 初始化邻接矩阵
for (int i = 0; i < n; i++)
{
for (int j = 0; j < n; j++)
{
Matrix[i][j] = 0;
}
}
// 写入数据
for (int i = 0; i < n; i++)
{
for (int j = 0; j < n; j++)
{
cin >> Matrix[i][j];
}
}
}
创建方式 - 输入顶点和弧
输入M个顶点、N条弧
顶点:
void GraphMatrix::setVerList(int m, char* ch) {
VerNum = m;
for (int i = 0; i < m; i++) {
VerList[i] = ch[i];
}
}
顶点之间的连接:
// qwq..变成函数厨了!因为自己命名很好玩w。
int GraphMatrix::CharToIndex(char c) {
for (int i = 0; i < VerNum; i++) {
if (c == VerList[i]) return i;
}
}
void GraphMatrix::connectByIndex(int i, int j) {
Matrix[i][j] = 1;
}
void GraphMatrix::connect(bool d, char p1, char p2) {
// 找p1下标
int i = CharToIndex(p1);
// 找p2下标
int j = CharToIndex(p2);
// cout << "i:" << i << " j:" << j << endl;
// 有向图
if (d == 1) connectByIndex(i, j);
// 无向图(双向链接)
if (d == 0) {
connectByIndex(i, j);
connectByIndex(j, i);
}
}
一些别的函数
打印邻接矩阵
void GraphMatrix::printMatrix() {
cout << "邻接矩阵:" << endl << " ";
// 表头
for (int i = 0; i < VerNum; i++) {
cout << VerList[i] << " ";
}
cout << endl;
for (int i = 0; i < VerNum; i++) {
cout << VerList[i] << " ";
for (int j = 0; j < VerNum; j++) {
cout << Matrix[i][j] << " ";
}
cout << endl;
}
}
邻接表
参考:【邻接表详解(C/C++)-CSDN博客】可以参考一下创建。
组成结构
邻接表(Adjacency List):采用 顺序 + 链式 结构存储。
顺序:有一个顺序表(数组)用来存顶点们的信息。
链式:后面会接着一些串串。
结构实现
// #define INFINITY 65535 // 表示无穷大
#define MaxVerNum 50 // 定义顶点最大数量
// 边/弧
// 嗯。其实就是后面些串串的结点
typedef struct AdjNode {
int VerIndex; // 邻接点存的下标(已经完全习惯用 v(入顶点)->i(出顶点) 来表示线条了。。
AdjNode* next; // 指向下一个邻接点
}AdjNode;
// 顶点
// 用于制作顶点表
// typedef char VerType; // 顶点的数据类型为字符型
typedef struct VerNode {
// VerType data; // VerType为顶点的数据类型,根据需要定义
AdjNode* first; // 指向第一个邻接点
}VerNode;
// 邻接表 // Adjacency List Graph
class ALGraph
{
private:
int VerNum; // 顶点数
int EdgeNum; // 边/弧数
VerNode* VerList; // 顶点表(一维数组)
// VerNode VerList[MaxVerNum]; // 懒人用法
public:
ALGraph(int sz); // 构造函数
// void createGraph(int n); // 构建图
void setVerList(int m, char* ch); // 顶点表传参
int CharToIndex(char c); // 输入字符,返回下标
void connect(bool d, char p1, char p2); // 有向1/无向0 连接两个顶点
void connectByIndex(int i, int j); // 通过下标 有向 连接两个顶点
void visit(int v); // hmmm..其实就是一个小输出
void BFS(int v); // 广度优先遍历
void BFS_plus(int v); // 广度优先遍历plus
void DFS(int v); // 深度优先遍历
void DFS_plus(int v); // 深度优先遍历plus
};
// 构造函数
ALGraph::ALGraph(int sz)
{
// 创建顶点表 // 分配内存
VerList = new VerNode[sz];
// 初始化
for (int i = 0; i < sz; i++) {
VerList[i].data = '-'; // 是我奇怪的小习惯。。
VerList[i].first = NULL;
}
}
创建方式 - 输入邻接表
输入每个结点的邻接点序号(以-1作结束)
// 创建邻接表
// 输入每个结点的邻接点序号(以-1作结束)
// 头插入版(最好倒着输入)
void ALGraph::createGraph(int n)
{
VerNum = n;
// cout << "分别输入每个结点的邻接点序号,以-1作结束:";
// 写入数据
int a = 0;
// 共n行
for (int i = 0; i < n; i++)
{
// 烤串串
cin >> a;
// 这里可以省略 if (a == -1) continue; 了hehe
while (a != -1) {
// 头插入(?)省点事儿。
AdjNode* p = new AdjNode;
p->v = a;
p->next = Ver[i].first;
Ver[i].first = p;
cin >> a;
// a == -1 结束
}
}
}
创建方式 - 输入顶点和弧
输入M个顶点、N条弧
顶点:
void ALGraph::setVerList(int m, char* ch) {
VerNum = m;
for (int i = 0; i < m; i++) {
VerList[i].data = ch[i];
}
}
顶点之间的连接:
int ALGraph::CharToIndex(char c) {
for (int i = 0; i < VerNum; i++) {
if (c == VerList[i].data) return i;
}
}
void ALGraph::connectByIndex(int i, int j) {
// Matrix[i][j] = 1;
// 头插入
AdjNode* p = new AdjNode;
p->VerIndex = j;
p->next = VerList[i].first;
VerList[i].first = p;
}
void ALGraph::connect(bool d, char p1, char p2) {
// 找p1下标
int i = CharToIndex(p1);
// 找p2下标
int j = CharToIndex(p2);
// cout << "i:" << i << " j:" << j << endl;
// 有向图
if (d == 1) connectByIndex(i, j);
// 无向图(双向链接)
if (d == 0) {
connectByIndex(i, j);
connectByIndex(j, i);
}
}
图的遍历
废话/小结: "用邻接矩阵理解广搜,用邻接表理解深搜" 会比较容易!
参考:(其实这一章的参考好像没什么用?最后都变成 "我流图" 了w。
【数据结构——图篇(邻接矩阵、邻接表、深度优先搜索、广度优先搜索)CSDN博客】好好好。
插播几条广告!接下来我们会用到的:
visit 函数
void Graph::visit(int v)
{
// cout << "正在访问顶点:" << v << endl;
cout << v << " ";
return;
}
PS:也可以根据 v 输出顶点的数据 instead of 下标。
visited 数组
bool visited[MaxVerNum]; // 标记访问数组,初始值全部设为false
是个全局变量。在邻接矩阵/邻接表的广搜/深搜里面都需要用到,所以再次使用之前记得要初始化哦!
PS:在遍历之前要注意判断起始点序号(v)是否小于(VerNum)哦。(就是不要序号溢出了!
广度优先(BFS)
类似于树中的广度优先遍历,即层序遍历(救命啊这个我根本没好好学)。
首先会给定一个起始点,然后访问与它相邻的结点,然后再从那些结点里访问与之相邻的结点...
- 由于图中可能有回路,所以遍历过程中要顺便给访问过的结点做标记。
- 无向图和有向图可以用同一套算法。(总之我写出来的无向就跟有向一样。。过程里根本看不出是无向
邻接矩阵
// 广度优先遍历
void GraphMatrix::BFS(int v) //从顶点v出发,广度优先遍历图G
{
visit(v); // 访问初始顶点v
visited[v] = true; // 对v做已访问标记
// 伪造一个队列queue
int* queue = new int[MaxVerNum];
int pi = 0, pj = 0; // 队头、队尾(指向下一个插入的位置)
// 顶点v入队列
queue[pj] = v;
pj++;
while (pi != pj) // 队列不为空 // 当栈空的时候,结束广搜
{
// 出栈(没有栈,全部是队列。。
v = queue[pi];
pi++;
// 搜索出度
for (int i = 0; i < VerNum; i++) {
if (Matrix[v][i] != 0 && visited[i] != 1)
{
visit(i);
// 标记
visited[i] = 1;
// 入栈
queue[pj] = i;
pj++;
}
}
}
}
这里可能会出现个问题,就是有些顶点不一定能访问得到(路线不通之类的)。
所以我们可以添加一个plus版本:如果从初始顶点无法遍历到,应该从visited数组里看哪些没有遍历到,再一次执行BFS函数。(后面同理)
// 广度优先遍历plus
// plus版本:解决有向图可能有些地方遍历不到的问题。
void GraphMatrix::BFS_plus(int v)
{
BFS(v);
for (int i = 0; i < VerNum; i++) {
if (visited[i] == 0) {
BFS(i);
}
}
}
邻接表
// 广度优先遍历 // 从顶点v出发
void ALGraph::BFS(int v)
{
visit(v); // 访问初始顶点v
visited[v] = true; // 对v做已访问标记
// 伪造一个队列queue
int* queue = new int[MaxVerNum];
int pi = 0, pj = 0; // 队头、队尾(指向下一个插入的位置)
// 顶点v入队列
queue[pj] = v;
pj++;
while (pi != pj) // 队列不为空 // 当队列空的时候,结束广搜
{
// 出栈(其实没有栈,全部是队列。。
v = queue[pi];
pi++;
// 搜索出度(这里和邻接矩阵不同了)
// 遍历链表
AdjNode* p = VerList[v].first; // 新建指针
while (p != NULL) {
// 操作
int i = p->VerIndex; // 串串里的数
if (visited[i] != 1) // 没访问过
{
visit(i);
// 标记
visited[i] = 1;
// 入栈
queue[pj] = i;
pj++;
}
p = p->next;
}
}
}
// 广度优先遍历plus
// plus版本:解决有向图可能有些地方遍历不到的问题。
void ALGraph::BFS_plus(int v)
{
BFS(v);
for (int i = 0; i < VerNum; i++) {
if (visited[i] == 0) {
BFS(i);
}
}
}
深度优先(DFS)
类似于树的前序遍历。
邻接矩阵
// 深度优先遍历
void GraphMatrix::DFS(int v) //从顶点v出发,广度优先遍历图G
{
// 访问编号为v(编号从0开始)的顶点,并使用全局变量visited进行记录
visit(v); // 访问初始顶点v
visited[v] = true; // 对v做已访问标记
/* 递归不用stack
// 伪造一个栈stack
int* stack = new int[MaxVerNum];
int top = 0; //
*/
// 对该行的所有元素进行遍历
for (int i = 0; i < VerNum; i++)
{
if (Matrix[v][i] != 0 && visited[i] != 1)
{
DFS(i);
}
}
}
// 深度优先遍历plus
void GraphMatrix::DFS_plus(int v) {
DFS(v);
for (int i = 0; i < VerNum; i++) {
if (visited[i] == 0) {
DFS(i);
}
}
}
邻接表
// 深度优先遍历 // 从顶点v出发
void ALGraph::DFS(int v) {
// 访问编号为v(编号从0开始)的顶点,并使用全局变量visited进行记录
visit(v); // 访问初始顶点v
visited[v] = true; // 对v做已访问标记
/* 递归不用stack
// 伪造一个栈stack
int* stack = new int[MaxVerNum];
int top = 0; //
*/
// 对该行的所有元素进行遍历
// 遍历链表
AdjNode* p = VerList[v].first; // 新建指针
while (p != NULL) {
// 操作
int i = p->VerIndex;
if (visited[i] != 1) // 没访问过
{
DFS(i);
}
p = p->next;
}
}
// 深度优先遍历plus
void ALGraph::DFS_plus(int v) {
DFS(v);
for (int i = 0; i < VerNum; i++) {
if (visited[i] == 0) {
DFS(i);
}
}
}
最小生成树
也叫最小代价树。
【https://www.cnblogs.com/BigJunOba/p/9252298.html】
两种算法:
1. Prim 普里姆
同一个图可能会有不同的最小生成树,但最小代价是相同的。
2. Kruskal 克鲁斯卡尔
要用到并查集。
以下为我流(可以用,但不一定最优)用邻接矩阵实现两个算法的代码:
Prim 普里姆
我人傻了。。难以想象这是我能写出来的东西。。!!(上面那些参考完全没用上呃
以下代码是纯我流Prim算法:
class GraphMatrix // 有/无向带权
{
private:
int VerNum; // 顶点数
// int* VerList; // 顶点表(一维数组)
int** Matrix; // 邻接矩阵(二维数组)
public:
void Prim(int v); // Prim算法生成最小生成树
};
// Prim算法生成最小生成树
void GraphMatrix::Prim(int v) {
// 建立三个数组
bool* isJoin = new bool[VerNum]; // 标记顶点们是否已加入树
int* lowCost = new int[VerNum]; // 各顶点加入树的最低代价
int* costVer = new int[VerNum]; // (我自创的)最低代价由哪个顶点决定(就连哪个顶点)
// 初始化三个数组
for (int i = 0; i < VerNum; i++) {
isJoin[i] = 0;
lowCost[i] = Matrix[v][i];
costVer[i] = v;
}
isJoin[v] = 1; // 给v打勾
int minCost;
int minIndex = v; // 从v开始
// 以下循环只控制次数,真正有用的是minIndex
for (int i = 1; i < VerNum; i++) {
// 遍历lowCost找最小值
minCost = INFINITY; // 初始化min
for (int j = 0; j < VerNum; j++) {
// 在没被标记的情况下:
if (isJoin[j] == 0 && lowCost[j] < minCost)
{
minCost = lowCost[j];
minIndex = j;
}
}
// 找到最小的index,勾上
isJoin[minIndex] = 1;
// 更新lowCost表 // 同时标记上更新的index
// 遍历Matrix的minIndex行:
for (int j = 0; j < VerNum; j++) {
// 在没被标记的情况下:
if (isJoin[j] == 0 && Matrix[minIndex][j] < lowCost[j])
{
// 更新
lowCost[j] = Matrix[minIndex][j];
costVer[j] = minIndex;
}
}
// 最终结果:costVer[minIndex] -> minIndex
cout << costVer[minIndex] << " - " << minIndex << endl; // 输出结果
}
}
Kruskal 克鲁斯卡尔
要用到并查集。(我不会,所以用的是伪并查集。)
写是写出来了,但我总觉得这样时间太长。。(里面有八百个for循环。。
struct Edge {
int begin;
int end;
int weight;
}; // 给Kruskal算法里的函数用
class GraphMatrix // 有/无向带权
{
private:
int VerNum; // 顶点数
// int* VerList; // 顶点表(一维数组)
int** Matrix; // 邻接矩阵(二维数组)
public:
void Kruskal(); // Kruskal算法生成最小生成树(我才发现这个不区分初始顶点。。
void Sort(Edge* list, int EdgeNum); // 给costList进行非降序排序
void Switch(Edge* e, int i, int j); // 交换下标为i和j的所有数据
};
// Kruskal算法生成最小生成树
void GraphMatrix::Kruskal() {
// 求边数EdgeNum
int EdgeNum = 0;
// 遍历上三角
for (int i = 0; i < VerNum; i++) {
for (int j = i + 1; j < VerNum; j++) {
if (Matrix[i][j] != 0 && Matrix[i][j] != INFINITY) {
EdgeNum++;
}
}
}
// 需要的数组1
int* finalList = new int[VerNum];
// 初始化
for (int i = 0; i < VerNum; i++) {
finalList[i] = i; // 一开始final为自身
}
// 需要的数组2
Edge* costList = new Edge[EdgeNum];
// 遍历上三角
int k = 0;
for (int i = 0; i < VerNum; i++) {
for (int j = i + 1; j < VerNum; j++) {
if (Matrix[i][j] != 0 && Matrix[i][j] != INFINITY) {
costList[k].begin = i;
costList[k].end = j;
costList[k].weight = Matrix[i][j];
k++;
}
}
}
// 排序
Sort(costList, EdgeNum);
k = 0;
int v, w;
for (int i = 0; i < EdgeNum; i++) {
v = costList[i].begin;
w = costList[i].end;
// 满足生成条件:原本不连通
if (finalList[v] != finalList[w]) {
// 最终结果:v -> w
cout << v << " - " << w << endl; // 输出结果
// 更新finalList // 把小的final更新为大的final
if (finalList[v] > finalList[w]) {
for (int j = 0; j < VerNum; j++) {
// 等于小的时
if (finalList[j] == finalList[w]) {
finalList[j] = finalList[v]; // 更新为大的
}
-------------------------------------------
// !!!!!!!!!!我的老天!!这里是不是有bug?!?!?!?!?!因为更新之后那个==后面的值可能就变了!!!
-------------------------------------------
}
}
else if (finalList[w] > finalList[v]) {
for (int j = 0; j < VerNum; j++) {
// 等于小的时
if (finalList[j] == finalList[v]) {
finalList[j] = finalList[w]; // 更新为大的
}
}
}
k++; // 计数:连了多少条边
}
if (k == VerNum - 1) break; // 连到VerNum-1条则执行完毕
}
}
void GraphMatrix::Switch(Edge* e, int i, int j) {
int temp;
temp = e[i].begin;
e[i].begin = e[j].begin;
e[j].begin = temp;
temp = e[i].end;
e[i].end = e[j].end;
e[j].end = temp;
temp = e[i].weight;
e[i].weight = e[j].weight;
e[j].weight = temp;
}
void GraphMatrix::Sort(Edge* list, int EdgeNum) {
int i, j;
for (i = 0; i < EdgeNum; i++)
{
for (j = i + 1; j < EdgeNum; j++)
{
if (list[i].weight > list[j].weight)
{
Switch(list, i, j);
}
}
}
// 打印检查
/*
cout << "costList:" << endl;
for (i = 0; i < EdgeNum; i++)
{
cout << list[i].begin << "-" << list[i].end << " " << list[i].weight << endl;
} */
}
最小路径问题
单源最短路径:
1. BFS 算法(无权图)
2. Dijkstra 迪杰斯特拉 算法(带权/无权图)
各顶点间的最短路径:
only 1. Floyd 算法(带权/无权图)
BFS 算法
用一次广度优先遍历即可求得各个顶点到源点的最短路径。
(在广搜的代码基础上改就可以了。)
Dijkstra 算法
迪杰斯特拉 这个人提出了 "goto有害理论" (原来似你!
哇啊。原来是荷兰人!怪不得名字那么奇怪hhh
是我唯一听到老师上课讲的那个!(我还画了图
参考:【最小路径问题 | Dijkstra算法详解(附代码)】感觉很高级!但我看不懂啊啊啊啊啊
Yayyyy!!虽然但是我又看不懂参考了。。结果又是我流代码。..
#define INFINITY 65535 // 表示无穷大
// #define MaxVerNum 50 // 定义顶点最大数量
class GraphMatrix // 有/无向带权
{
private:
int VerNum; // 顶点数
// int* VerList; // 顶点表(一维数组)
int** Matrix; // 邻接矩阵(二维数组)
public:
GraphMatrix(int sz); // 构造函数
void createGraph(int n); // 构建图
void Dijkstra(int v); // Dijkstra算法求最小路径问题
};
// Dijkstra算法求最小路径问题
void GraphMatrix::Dijkstra(int v) {
// v:源点
// 数组们
bool* visited = new bool[VerNum];
int* dist = new int[VerNum];
int* path = new int[VerNum];
// 初始化
for (int i = 0; i < VerNum; i++) {
visited[i] = 0;
dist[i] = Matrix[v][i];
if (dist[i] != 0 && dist[i] != INFINITY) {
path[i] = v;
}
else {
path[i] = -1;
}
}
visited[v] = 1; // 标记
int minDict = INFINITY;
int minIndex = -1;
for (int j = 1; j < VerNum; j++) // 哇啊啊这里是j是因为我一开始漏写for循环了。。
// 我都不知道我该不该解释,
// 这里循环VerNum-1次是因为源点直接是0
// (没错。这个循环的作用也只是控制次数)
{ // 笑死我了这个花括号。sorryy-!!
// 1. 选minIndex
for (int i = 0; i < VerNum; i++) {
// 在没标记过的中选:dist[]最小的
if (visited[i] == 0) {
if (dist[i] < minDict) {
minDict = dist[i];
minIndex = i;
}
}
}
// 2. 找到了,标记上
visited[minIndex] = 1;
// 3. 更新dict[]数组
int newPath;
// 遍历那一行
for (int i = 0; i < VerNum; i++) {
// 如果有路
if (Matrix[minIndex][i] != 0 && Matrix[minIndex][i] != INFINITY) {
newPath = dist[minIndex] + Matrix[minIndex][i];
// 如果新路径更小,则更新
if (newPath < dist[i]) {
dist[i] = newPath;
}
}
}
// 初始化 // 不确定要不要但是先写上吧。
minDict = INFINITY;
minIndex = -1;
}
// 输出(?)
for (int i = 0; i < VerNum; i++) {
cout << i << " : " << dist[i] << endl;
}
}
Floyd 算法
拓扑排序
拓扑排序(Topological Order)
有向无环图 DAG图(Directed Acyclic Graph)
AOV网(Activity On Vertex NetWork,用顶点表示活动的网):用DAG图表示一个工程。
就是某道工序需要先于另一道工序这样..
拓扑排序就是要求出你的活动流程图。
我流代码again555:
// 定义邻接表类型
// Adjacency List Graph
class ALGraph
{
private:
VerNode Ver[MaxVerNum]; // 顶点表
int VerNum, EdgeNum; // 顶点数,边数
public:
bool TopologicalOrder(); // 拓扑排序
};
// 拓扑排序
bool ALGraph::TopologicalOrder() {
// 数组们
int* order = new int[VerNum]; // 存顺序
int* indegree = new int[VerNum]; // 存当前(下标)顶点的入度。会更新
// 初始化
// 初始化order[] // 初始值为-1
// 初始化indegree[] // 初始值为0 // 后续再算入度
for (int i = 0; i < VerNum; i++) {
order[i] = -1;
indegree[i] = 0;
}
// 赋初值
// 要遍历邻接表
// 遍历行
for (int i = 0; i < VerNum; i++) {
// 遍历链表
AdjNode* p = Ver[i].first; // 新建指针
while (p) {
indegree[p->v]++; // 入度++
p = p->next; // 变为当前块块的next域
}
}
// 选0入栈
// 新建栈
int* stack = new int[VerNum];
int top = -1;
// 遍历indegree[]来选0(??
for (int i = 0; i < VerNum; i++) {
if (indegree[i] == 0) {
// 请入栈
top++;
stack[top] = i;
}
}
// 开始大循环 // while栈不为空
int v = 0; // 当前顶点
int od = 0; // 用来给order[]计数
while (top != -1) {
// 1. 出栈
v = stack[top];
top--;
// 2. print // 写入order
order[od] = v;
od++; // 一开始忘了写这句然后差点死掉。。 // 我想着是最后写的,结果完全忘记了。。
// 2.1. 把indegree[]改为-1 // 可以执行欸!
indegree[v] = -1;
// 3. 度-1
// 遍历那一条链表
AdjNode* p = Ver[v].first; // 新建指针
while (p) {
indegree[p->v]--; // 入度--(居然和之前的操作对称了!好好玩。
p = p->next; // 变为当前块块的next域
}
// 4. 选0入栈 // 我直接复制了上面的代码
// 遍历indegree[]来选0(??
for (int i = 0; i < VerNum; i++) {
if (indegree[i] == 0) {
// 请入栈
top++;
stack[top] = i;
}
}
}
// 输出结果
// 判断是否有回路 // 有回路的进不了栈,所以最后order[]里会少几个
if (order[VerNum - 1] == -1) return false;
cout << "拓扑序列结果:";
for (int i = 0; i < VerNum; i++) {
cout << order[i] << " ";
}
return true;
}
吐槽
好想吐槽。。CSDN!!我点进来只是想安安静静写个博客,能不能不要老是搞出一堆弹窗啊!我也不需要流量什么的,能不能让我退出那个活动。。。