链式前向星和拓扑排序专题

多日以来被图论狠狠的羞辱,下定决心学习图论基础链式前向星和拓扑排序

图的存储方式

邻接表

规模大的稀疏图可用邻接表,存储复杂度为\(O(n + m)\)。n表示点的数量,m表示边的数量。

struct edge{
	int from, to, w;
	edge(int a, int b, int c) {
		from = a; to = b; w = c;
	}
}
vector<edge>e[N];
for (int i = 1; i <= n; i++) 
	e[i].clear();//初始化
//存边
e[a].push_back(edge(a, b, c));
//遍历所有邻居
for (int i = 0; i < e[u].size(); i++) {
	//for (int v:e[u])
	int v = e[u][i].to, w = e[u][i].w;
}

链式前向星

实际在存储邻接边的过程也是先定位某个点的第一条边,再定位第二条边,由此可以设计没有任何空间浪费、变成非常简单的存图方法。

  • \(head[]\)作为静态数组,\(head[u]\)指向\(u\)的一个邻居的存储位置。
  • \(struct\ edge\)是结构静态数组,\(edge[i].to\)存储邻居节点编号,\(edge[i].next\)指向下一个邻居节点的存储位置\(j\),对应的下一个邻居存储在\(edge[j]\)中。
  • \(cnt\)代表当前的存储位置,或者说新加入的边会存储在\(edge[cnt]\)
  • 获取\(u\)对应新的边,插入到\(head[u]\)的方式为头插法,将其插入到\(head[u]\)对应的第一个位置。
    也可以将edge结构体转化成为两个不同的数组\(e[]\)\(ne[]\)进行实现
int head[N],cnt;
struct {
	int from, to, next;//起点,终点,下一个邻居
	int w;//边权
}edge[M];//数组存储边
void init(){
	for (int i = 0; i < N; ++i) head[i] = -1;//点初始化
	//由于头插法,初始化点的-1一定会被传递到链末端,所以可以不初始化边
	for (int i = 0; i < M; ++i) edge[i].next = -1;//边初始化
	cnt = 0;
}
void addedge(int u, int v, int w) {
	edge[cnt].from = u;
	edge[cnt].to = v;
	edge[cnt].w = w;
	//头插法插入节点
	edge[cnt].next = head[u];//下一个邻居的存储位置
	head[u] = cnt++;//cnt代表新边存储位置
	
	//附上数组对应的实现方式
	//e[cnt] = v;
	//ne[cnt] = head[u];
	//w[cnt] = w;
	//head[u] = cnt++;
}
//~1仅对-1有效,0已经是有效空间
for (int i = head[2]; ~i; i = edge[i].next)//遍历节点2的所有邻居,i != -1
	printf(" %d - %d", edge[i].from, edge[i].to);

拓扑排序

终于到不知道为什么总能发挥作用的拓扑排序部分

拓扑排序的概念

一个图能够进行拓扑排序的充要条件是它是一个有向无环图(DAG)。
如果图中有环,可以使用拓扑排序找到环。若本身为无向图,可以通过拓扑排序的BFS,依次将度(即包含出度和入度)为1的点和对应的边去除,若邻居度变为1则入队,直到队列为空。剩余度大于1的点为环上的点。这种无向图找环的方式仅适用于只有一个环的基环树。

基于BFS的拓扑排序

  1. 无前驱的顶点优先
    1. 将所有入度为0的点放入队列。
    2. 弹出队首,邻居点入度-1,入度为0的邻居节点入队。
    3. 继续直到队列为空。
      队列已空但还有点未入队,说明图不是DAG,没有进入队列的就是环上的点
  2. 无后继的顶点优先:即出度为0的点开始

BFS拓扑排序的时间复杂度

  • 查找入度为0的点,检查每条边复杂度\(O(m)\)
  • 队列操作中,每个点进出队列一次,并且检查对应所有邻居\(O(n + m)\)

基于DFS的拓扑排序

DFS深度搜索是沿着一条路径一直搜索到底部,然后逐层回退,正好体现点与点的先后关系,天然符合拓扑排序。
对于有向无环图,如果只有一个点u是0入度,直接进行DFS递归返回的顺序就是逆序的拓扑排序。

需要处理的细节

  1. 如何找到入度为0的点开始DFS?如何处理多个入度为0的点?
    设置一个虚拟的点,单向连接到所有其它的点。实际编程直接遍历每个点轮流执行一遍DFS即可。
  2. 如果图不是DAG,说明是有环图,递归时,会出现**回退边*

输出拓扑排序

使用回溯输出所有的拓扑排序
使用topo[]记录排序,vis标记是否访问,indegree记录入度
void dfs(int z, int cnt) {
	int i;
	topo[cnt] = z;
	if (cnt == n - 1) {
		for (int i = ; i < n; i++) printf("%c", topo[i] + 'a');
		printf("\n");
		return ;
	}
	vis[z] = 1;
	for (int i = 0; i < n; i++) {
		if (!vis[a[i]] && dir[z][a[i]])
			indegree[a[i]]--;//所有下属入度减1
	}
	//使用回溯的方式遍历所有可能的拓扑排序
	for (int i = 0; i < n; i++)
		if (!indegree[a[i]] && !vis[a[i]])//入读为0继续取
			dfs(a[i], cnt + 1);
	for (int i = 0; i < n; i++) {
		if (!vis[a[i]] && dir[z][a[i]])
			indegree[a[i]]++;
	}
	vis[z] = 0;

}
//从所有入度为0的点开始
for (int i = 0; i < n; i++) {
	if (!indegree[a[i]])
		dfs(a[i], 0);
	printf("\n");
}
posted @ 2024-06-14 10:19  tanch25  阅读(4)  评论(0编辑  收藏  举报