图的表示:
稀疏图:邻接表(邻接链表数组) (e.g A[2]里面放的是一个链表,是和节点2相连的节点)
稠密图:邻接矩阵 (n个接点,nxn个矩阵,i-j相连,A[i][j], A[j][i] 为true)
DFS(深度优先搜索):
基本思路: 不撞南墙不回头
寻找离起点最远的顶点,只在碰到死胡同时才回来
用了一个下压栈结构,获取下一个顶点的方式是最晚加入的顶点
非递归伪代码:
用途:无向图/有向图找连通分量,拓扑排序(优先级限制下的调度问题),寻找有向环
BFS(广度优先搜索)
基本思路:一圈一圈的扫荡
首先扫荡距离起点为1的点,然后扫荡距离起点为2的点,按照与起点的距离的顺序来遍历所有的顶点
基本实现:用了一个队列,先把起点0入队,标记0 mark[0]=1,然后循环条件 当队列不为空的时候, 先出队一个v, 遍历它的邻接点w,假如没被标记过,标记,设置edge[w]=v,入队到队尾
用途:找最短路径
void DFS_nr(int start) {
visited[start]= true;
cout<< "visit: "<< start<< endl;
stack<int> s;
s.push(start);
bool is_push;
while (!s.empty()) {
is_push= false;
int top= s.top();
for (int i= 0; i< N; i++) {
if (!visited[i]&& mat[top][i]) {
visited[i]= true;
cout<< "visit: "<< i<< endl;
s.push(i);
is_push= true;
break;
}
}
if (!is_push) {
s.pop();
}
}
}
无向图
表示:邻接表/邻接矩阵
可达性: DFS (BFS也可)
单点最短路径:BFS
连通分量: DFS (BFS 并查集)
有向图:
表示:邻接表/邻接矩阵
可达性: DFS (BFS也可)
单点最短有向路径:BFS
检查有无环:DFS
拓扑排序:DFS(逆后序)
强连通分量: kosaraju(实际用了两次DFS)
加权无向图:(一般求最小生成树)
表示:邻接表 但链表节点是edge
prim算法和kruskal算法
加权有向图(一般目的就是求最小路径树)
表示:邻接表,链表节点是有向edge
权重非负:Dijkstra算法 (将distTo[] 最小的非树顶点放松,并加入树中)
无环(权值可以负):按拓扑顺序 放松顶点
一般情况(权值可以负,可以有环,但别有负权重环,存在负权重环,最短路径树就不存在了):Bellman-Ford算法
https://blog.csdn.net/a60782885/article/details/72781811
拓扑排序
必须是无环图 才有拓扑排序
有向无环图 把所有顶点排序,保证所有的有向边都是排在前面的顶点指向后面的顶点
1)用队列实现,入度为0的顶点入队, 然后进行循环,条件队列不为空,第一个元素出队,同时设为已访问且放进一个数组A里面,对这个元素的所有领接点入度减1,假如入度为0 了,就入队,
退出循环之后,遍历所有点,看有没有没访问的,有就是有环,也就没有拓扑排序,没有那就输出数组A,就是拓扑排序
2)用DFS的逆后序,这个方法首先需要检查有没有环
检查的方法是,加一个bool数组,size为节点个数的,Onstack,还有个int * cycle 放找到的第一个有向环 ,然后对所有的节点,没有被mark的v,挨个做DFS,具体是,把这个onstack[v]和mark[v]设为true,然后遍历它的邻接点w,假如有cycle不等于nullptr,也就是已经找到环了,直接return,假如没有,再检查有没有被mark过else if,没有就,edge[w] = v,再递归做dfs, 再检查w有没有在栈里面 else if (onstack[w] ) 在就是找到环了(因为意味着前面存在w到v,然后现在v到w,所以是个环),然后从v开始 沿着edgeto 直到w 放进cycle,最后再放v。
最后把onstack[v] 重新变回false
检查如果没环,这时候再用dfs,这时候,每个递归完之后,把这个v压入栈里面,最后把栈弹光就是图的拓扑排序、
强连通分量
kosaraju算法:有向图找强连通分量
1) G^R 求拓扑排序
2)按上面的排序 对G 进行DFS
最小生成树
只有连通图(从图的任意一个顶点能到任意另一个顶点)才存在最小生成树
生成树是图的一颗含有其所有顶点的无环连通子图,最小生成树,就是权值和最小的生成树
我们讨论的最小生成树是在一副加权连通无向图中的,(权值相不相同,无所谓,权值是正是负也无所谓)
两个算法不能处理有向图,所以一般最小生成树都是针对加权无向图而言的都基于两个基础
1)图的任意切分,横切边权重最小者必定属于最小生成树
2)贪心算法:命题k p392 每次找到一种切分,它的横切边 均不为黑色(黑色代表MST的一个边),把最小权值的横切边涂成黑色,重复,直到有 顶点数-1个边为止(最小生成树的边数量一定是顶点数-1)
kruskal算法:
基本思路,把所有边由小到大排序,每次取最小的边,假如不会和已经在MST中的边形成一个环,那就加进来,下一个,假如会形成环,抛弃,下一个。循环直到有 v-1个边
具体方法:最小堆把边排序,并查集识别会形成环的边,队列保存MST结果
时空复杂度:v个顶点,E条边
空间: E
时间: ElogE (最坏)
prim算法:
从节点0开始 每次把 有且只有一个节点在MST里面的边,且这个边是符合条件的最小权值,这个边加到MST里面,直到有V-1个边
lazy实现 时空复杂度:v个顶点,E条边 空间: E 时间: ElogE (最坏)
eager实现: 空间:v 时间ElogE(最坏)