首页 > 编程语言 >C++图笔记(三)有向无环图(及最小生成树(略))以及剩下的排序

C++图笔记(三)有向无环图(及最小生成树(略))以及剩下的排序

时间:2024-08-19 19:53:21浏览次数:14  
标签:int 拓扑 入度 C++ 环图 pragma 顶点 排序

目录

一,定义:

1,有向无环图

 2,拓朴排序

 1,每个顶点出现且仅仅出现一次。

 2,若存在一条从顶点 A 到顶点 B 的路径,那么在序列中顶点 A 出现在顶点 B 的前面。

二,DAG的性质

性质1.   从任意一个起点进行dfs,必不会陷入死循环。

性质2.   入度为0的点信息确定,删掉入度为0的点以及出边后,新的入度为0的点信息也唯一确定。

三,拓扑排序的遍历

 四,例题

1,有向图的拓扑序列

使用图片理解拓扑排序(也是该图的模拟过程)

1,在不考虑边权的情况下 输入数据为:

由此建立邻接表。可得第一次入度为零的点为“1”“2” 将此两点入度

所以先将1指向的点和边删除,并将指向的点(3,4)的入度-1;得到下图

由于第一次入度为零的点还有“2”,将1删除后,点“2”还在队中,所以同上操作,得到图三

由上图得:点3的入度为零,所以按照操作将“3”入队,出队,删边,删除边指向的点的入读-1;

同上操作,此时删除点“4;则点“5”的入读=0;

经过删除。得到点六,所以存在拓扑排序,该图为DAG

2,求最长路

五,最小生成树

1,定义:

2,基本性质

1. 生成树是一个连通子图,是给定图的一个子集,它连接了所有节点且没有环。(有环可以减少一条边)

2. 生成树形态不止一种,且含有图中全部n个顶点,以及包含图中n-1条边。

3,求解方法:

        1、Kruskal  复杂度:n(mlogm)

        2、例题 详见下:​编辑

代码:


一,定义:

1,有向无环图

如果有一个非有向无环图,且A点出发向B经C可回到A,形成一个环。将从C到A的边方向改为从A到C,则变成有向无环图。有向无环图的生成树个数等于入度非零的节点的入度积。(from baidu)

若一个有向图不存在环,则称为有向无环图【字面意思】这个东西就叫做DAG(Directed Acyclic Graph),

如图所示:

 2,拓朴排序

在图论中,拓扑排序(Topological Sorting)是一个有向无环图的所有顶点的线性序列。且该序列必须满足下面两个条件:

 1,每个顶点出现且仅仅出现一次。


 2,若存在一条从顶点 A 到顶点 B 的路径,那么在序列中顶点 A 出现在顶点 B 的前面。

由拓朴排序的定义可知:只有DAG才有拓扑排序,其余没有拓扑排序一说。

二,DAG的性质

性质1.   从任意一个起点进行dfs,必不会陷入死循环。


性质2.   入度为0的点信息确定,删掉入度为0的点以及出边后,新的入度为0的点信息也唯一确定。

三,拓扑排序的遍历

在dij和spfa等算法中,任意点都会重复更新多次,所以会导致时间复杂度提高。在拓扑排序中,不是从起点(任意点)开始遍历,而是从入度为0的点开始遍历,这样该点就不会有其它点进行更新它。所以这样每个点和边都只会被遍历一次。

所以拓朴排序的时间复杂度为n(n+m).

拓扑排序的大致过程:

  1. 从 图中选择一个 入度为0的顶点并入度。
  2. 当该节点出队时,将该节点所指向边的节点入度-1如果新节点入度为零,则将新节点入度。
  3. 重复 1 和 2 直到当前图为空或当前图中不存在入度为零的顶点为止。后一种情况说明有向图中必然存在环。当所有节点都入过队列,则当前图存在拓扑排序,则该图为DAG图。

 四,例题

1,有向图的拓扑序列

题目:(要求详见图)

使用图片理解拓扑排序(也是该图的模拟过程)

1,在不考虑边权的情况下 输入数据为:
6 7(点数,边数)
1 3
1 4
2 5
4 5
3 6
5 6
3 4

由此建立邻接表。可得第一次入度为零的点为“1”“2” 将此两点入度

所以先将1指向的点和边删除,并将指向的点(3,4)的入度-1;得到下图

 

由于第一次入度为零的点还有“2”,将1删除后,点“2”还在队中,所以同上操作,得到图三

由上图得:点3的入度为零,所以按照操作将“3”入队,出队,删边,删除边指向的点的入读-1;

同上操作,此时删除点“4;则点“5”的入读=0;

经过删除。得到点六,所以存在拓扑排序,该图为DAG

在删除边的过程中,将出队但未被删除的点存入一个vector容器。最后将该容器按照顺序输出则得到了该图的拓扑排序。

如果该容器中存下的点的数量 不等于一开始输入的点的数量,则该图不存在拓扑排序,即该图不是DAG图。

#include<bits/stdc++.h>//万能头
#pragma GCC s//日常优化
#pragma GCC optimize(2)//相信肯定有人要借鉴代码,这可不是好习惯哦,所以注释++++++
#pragma GCC optimize(3)
#pragma GCC fast

using namespace std;

const int N = 2e5 + 100;//提前定义节省时间
const int M = 4e5 + 100;
int head[N], Next[M], ver[M], tot,deg[N],n,m;

void add(int x, int y) {//建立邻接表
    ver[++tot] = y;
    Next[tot] = head[x];
    head[x] = tot;
}

long long read() {//日常快读,节省时间
	int x=0,f=1;
	char c=getchar();
	while(c<'0'||c>'9') {
		if(c=='-') f=-1;
		c=getchar();
	}
	while(c>='0'&&c<='9') {
		x=x*10+c-'0';
		c=getchar();
	}
	return x*f;
}

vector<int>v;	
priority_queue<int,vector<int>,greater<int> > qc;//因为题目要求按照字典序最小输出,所以建立优先队列
void topsort() {//拓朴排序
    for (int i=1;i<=n;i++)
        if (!deg[i])            qc.push(i);        
    while (qc.size()) {
        int x = qc.top(); qc.pop();
       	v.push_back(x);//在出队时进行记录
        for (int i = head[x];i;i=Next[i]) {
            int y=ver[i];
            if (!--deg[y])            qc.push(y);
        }    
	}
}

int main() {
    int x, y;
    n=read();m=read();//运用快读(点的个数,边的个数)
    for (int i=1;i<=m;i++) {
        x=read();y=read();//快读+2
        add(x,y);
        deg[y]++;//将被指向的结点入度加1
    }
    topsort();//开始排序
    if (v.size()<n)//如果有的点没有被遍历过说明不存在拓扑排序,即不存在DAG
        cout <<-1;
    else {
    	for(int i=0;i<v.size();i++)		cout<<v[i]<< " ";
	}
    return 0;
}

2,求最长路

题目见图++;

题目分析:如果按照常规操作,这道题肯定要TLE,另外,数据范围也要注意(开小了PAC)所以,这道题只能快读+拓扑排序才能过

#include<bits/stdc++.h>//万能头文件
#pragma GCC s//O优化
#pragma GCC optimize(2)
#pragma GCC optimize(3)
#pragma GCC fast

using namespace std;
const int N=5e5+5,M=3e6+5;//根据题目范围提前定义
int head[N], Next[M],ver[M],du[N],dis[M],w[M],tot,n,m;//关于结点的范围是N,和边有关的大小为M;

long long read() {//快读
	int x=0,f=1;
	char c=getchar();
	while(!isdigit(c)) {
		if(c=='-') f=-1;
		c=getchar();
	}
	while(isdigit(c)) {
		x=x*10+c-'0';
		c=getchar();
	}
	return x*f;
}
void add(int x, int y,int z) {//建立邻接表
	++tot;
    ver[tot]=y;
    w[tot]=z;
    Next[tot]=head[x];
    head[x]=tot;
}
void bfs() {//开始遍历
	queue<int>q;
    for (int i=1;i<=n;i++) {   
        if(!du[i])q.push(i);
		dis[i]=-2e9;
	}
	dis[1]=0;
    while (!q.empty()) {
        int x=q.front();//存下来要遍历的节点
		q.pop();//将该节点出队
        for (int i = head[x];i;i=Next[i]) {
            int y=ver[i];
            du[y]--;
            if(dis[x]!=-2e9){
            	dis[y]=max(dis[y],dis[x]+w[i]);
			}
			if(!du[y]) {
				q.push(y);//将节点入队,等待重新遍历
			}
        }    
	}
}
int main() {
    int x,y,z;
    n=read();//使用快读
    m=read();
    for(int i=1;i<=m;i++) {
    	x=read();//源节点
     	y=read();//指向的节点
      	z=read();//边权
        add(x,y,z);
        du[y]++;//被指向的节点入度++
    }
    bfs();
    cout<<dis[n];//输出最大路径
    return 0;
}

五,最小生成树

1,定义:

最小生成树(Minimum Spanning Tree,MST)是指在连通图的所有生成树中,各边的权值之和最小的生成树。它是一个连通的无向图,其中所有顶点都连通,且没有环,边的权值之和最小。生成树是在无向连通图中,将图中所有顶点以最少的边连通的子图。即建图转化为树,用最少的边保证每个节点的连通性。

2,基本性质

1. 生成树是一个连通子图,是给定图的一个子集,它连接了所有节点且没有环。(有环可以减少一条边)


2. 生成树形态不止一种,且含有图中全部n个顶点,以及包含图中n-1条边。

3,求解方法:

        1、Kruskal  复杂度:n(mlogm)

        2、例题 详见下:

代码:

#include<bits/stdc++.h>//万能头文件
#pragma GCC optimize(1)//O优化
#pragma GCC optimize(2)
#pragma GCC optimize(3)
#pragma Gcc optinize("o1")
#pragma Gcc optinize("o2")
#pragma Gcc optinize("o3")
#pragma GCC optimize("Ofast")
using namespace std;
const int N  =2e5+5;
int n,m,mst=0,fa[N],cnt=0;//记录长度之和
struct node{//运用结构体来存边,按照边权大小进行排序
	int u,v,w;
}pt[N];
long long read() {//日常快读
	int x=0,f=1;
	char c=getchar();
	while(c<'0'||c>'9') {
		if(c=='-') f=-1;
		c=getchar();
	}
	while(c>='0'&&c<='9') {
		x=x*10+c-'0';
		c=getchar();
	}
	return x*f;
}
bool operator<(node x,node y){//重载一下运算符,方便进行结构体比较
	return x.w<y.w;
}
int find(int x){
	if(fa[x]==x)return  x;
	return fa[x]=find(fa[x]);
}
void kruscal(){
	for(int i=1;i<=n;i++) fa[i]=i;
	for(int i=1;i<=m;i++){
		int a=find(pt[i].u), b=find(pt[i].v);
		if(a!=b){
			fa[a]=b;
			mst+=pt[i].w;
			cnt++;
			
		}
		if(cnt==n-1) break;
	}
}
int main(){
	n=read();m=read();
	for(int i=1;i<=m;i++){
		int x,y,z;
		x=read();y=read();z=read();
		pt[i]={x,y,z};
	} 
	sort(pt+1,pt+m+1);
	kruscal();
	if(cnt<n-1) cout<<"orz";
	else cout<<mst;
}

标签:int,拓扑,入度,C++,环图,pragma,顶点,排序
From: https://blog.csdn.net/weixin_70468627/article/details/141330557

相关文章

  • C++--二叉搜索树
     目录 1.1二叉搜索树概念1.2二叉搜索树操作 1.2.1查找1.2.2插入1.2.3删除2.3二叉搜索树实现 2.4二叉搜索树的应用 2.5二叉搜索树的性能分析 1.1二叉搜索树概念二叉搜索树又称二叉排序树,它或者是一棵空树,或者是具有以下性质的二叉树:--若它的左子......
  • C/C++语言基础--指针三大专题详解2(指针与数组关系,动态内存分配,代码均可)
    本专栏目的更新C/C++的基础语法,包括C++的一些新特性前言指针是C/C++的灵魂,和内存地址相关联,运行的时候速度快,但是同时也有很多细节和规范要注意的,毕竟内存泄漏是很恐怖的指针打算分三篇文章进行讲解,本专题是二,介绍了指针和数组的关系、动态内存如何分配和释放等问题专题......
  • Eclipse下编写C++
    Eclipse下运行C++程序 文章目录Eclipse下运行C++程序装CDT插件测试运行C++程序附加说明众所周知Eclipse是常用于编写java的集成开发工具,但是像CodeBlocks和Dev-C++这类集成开发工具就只能编写C或C++的程序,极为不方便。写java的时候又得开......
  • 小白学习c++P7【1003】对齐输出(c++)详细讲解
     EXTRA##网站......
  • 小白学习c++P8【1004】字符三角形(c++)详细讲解
    目录EXTRA##PT1-知识点引入......
  • 排序算法 基数排序 RadixSort --C语言实现
    基数排序基数排序(radixsort)属于“分配式排序”(distributionsort),又称“桶子法”(bucketsort)或binsort,顾名思义,它是透过键值的部份资讯,将要排序的元素分配至某些“桶”中,藉以达到排序的作用,基数排序法是属于稳定性的排序,其时间复杂度为O(nlog(r)m),其中r为所采取的基数,而m为堆数......
  • 辨析C++多态的概念
    1.什么是多态我们都知道面向对象语言的三大特性,封装,继承,多态;封装:封装就是将数据封装在一个类里面,提供对数据更好的管控;继承:继承就是类设计层次的代码复用。那多态是什么呢?多态是一种现象,这种现象要通过封装和继承才能实现。多态就是在同一继承体系下,不同的类的对象调用相同......
  • el-table使用sortablejs推拽排序卡顿问题解决
    使用sortablejs拖拽el-table排序时,对于纯文本表格,正常使用即可,不会卡顿initSort(){consttbody=document.querySelector('.el-table__body-wrappertbody')const_this=thisSortable.create(tbody,{draggable:'.el-table__row',......
  • 深入理解C++多态
    目录一、引言二、多态的基本概念   1.定义   2.分类三、动态多态的实现原理   1.虚函数   2.虚函数表   3.动态绑定四、多态的应用   1.父类指针指向子类对象   2.纯虚函数与抽象类五、总结        本文将详细介绍C++多态的概念、......
  • c++ 命名空间别名踩坑
    c++命名空间别名踩坑遇到的问题在现代c++以前(c++11),实现别名的主要方式主要是通过typedef关键字实现的.今天写boost/asio代码的时候,遇到了这样的一个问题,催生了我的这篇文章加深我的印象.//我想写boost.asio中的内容,经常使用boost::asio::ip::xxxxxxx.//正是因为......