数据结构丨图

前排提醒:这一章对着我的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

图的存储

图的存储:

邻接矩阵(二维数组 + 一维数组)适合存储稠密图、

邻接表(顺序 + 链式存储)适合存储稀疏图,表示方式不唯一、

十字链表、邻接多重表

邻接矩阵

图论之邻接矩阵-CSDN博客

一维数组 - 顶点表(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博客】好好好。

图的广度优先遍历_白玖与歌的博客-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!!我点进来只是想安安静静写个博客,能不能不要老是搞出一堆弹窗啊!我也不需要流量什么的,能不能让我退出那个活动。。。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值