图 graph
定义
包含多对多的关系
包含
- 一组顶点:V
- 一组边 E
- 边是顶点对
- 不考虑自回路和重边(两点间有多条直接相连的边)
- 可以没有边,但一定有点
图主要进行以下操作
- DFS
- BFS
- 最短路
- 最小生成树
网络:带权的有向图和无向图的总称
图的表示
邻接矩阵
- 无向图只利用了一半的空间
- 可用数组代替,n个节点需要数组长度n(n+1)/2,数组下标对应(i,j)-> i*(i+1)/2+j 。转成三角矩阵后,元素排列为等差数列,第0行为1,第i-1行为i,共i行,i(i+1)/2 ,再加上第i行的j个
- 对角为0,没有自身到自身的边
- 方便计算度
- 适合稠密图,特别是完全图,但对于稀疏图比较浪费
- 邻接表
- 节约空间,但一条边存了两次
- 方便算度,计算链表长度即可
- 不便检查两点间是否有边
图的遍历
DFS(depth first search) 深度优先搜索
void dfs(int g[][],int p){
visit[p] = 1;
for(i=0;i<g.size;i++){
if(g[p][i] ==1 &&!visit[j])
dfs(g,j);
}
}
void dfs(){
int g[][],int p,visit[];
stack s;
s.push(p);
visit[p] = 1;
while(!s.empty()){
for(i=0;i<size;i++)
if(g[p][i] && !visit[i]){
p = i;
visit[p] = 1;
s.push(p);
}
p = s.top();
s.pop();
}
}
BFS breadth first search 广度优先搜索
void BFS(){
queue q;
vector g;
visit[0]= 1;
q.push(g[0]);
while(!q.empty()){
next = q.top();
q.pop();
for(i=0;i<next.son.size();i++){
if(!visit[next.son[i]]){
q.push(next.son[i]);
vistit[next.son[i])]= 1;
}
}
}
}
图不连通
- 连通:两个节点间存在一条路径,则两个节点连通
- 路径:一系列节点的结合。路径长度是路径中边的数量,若路径中所有节点都不同,则是简单路径
- 回路:起点等于终点的路径
- 连通图:任意两个定点均连通
- 连通分量:无向图的极大连通子图。再多一个定点就不连通了,包含子图中所有定点相连的所有边。
- 强联通:有向图中,两个定点之间存在双向路径,则两个顶点是强联通的
- 强联通图:任意两个顶点都是强联通的
- 每次进行BFS,DFS都是遍历一个连通分量。
最短路
分类
- 单源,某点到其他点
- 多源,任意两点
单源
无权图
只需要考虑各边数,利用广度优先遍历即可。
有权图
要求:没有负值圈
用
D
i
j
k
s
t
r
a
算
法
Dijkstra算法
Dijkstra算法解决
- 建立空的最短路集合,将源点加入。将所有节点到源点的距离设为正无穷。
- 更新与新加入点相邻的节点到源点的距离
- 遍历所有未加入最短路集合的节点,找到到源点距离最短的节点,加入最短路集合,回到2.直到没有节点能再加入
- 判断是否所有节点都加入,若没有,则图不连通
int nodes[][];
int visit[];
int dist[];
int pre[];
int s;
next = s;
visit[s] = 1;
dist[s] = 0;
while(1)[
for(i=0;i<n;i++)
if(nodes[next][i] &&! visit[i] && dist[i]>dist[next]+nodes[next][i]){
dist[i] = dist[next]+nodes[next][i] ;
pre[i] = next;
}
min = INT_MAX;
for(i=0;i<nodes.size();i++){
if(!visit[i] && dist[min] >dist[i]){
min = i;
}
}
visit[min] = 1;
next = min;
}
多源最短路
Floyd算法
-
针对稠密图
-
若稀疏,则单源最短路调用n次
-
D k [ i ] [ j ] = 路 径 i → l ≤ k → j D^k[i][j]=路径{i \rightarrow{l\leq k\rightarrow j}} Dk[i][j]=路径i→l≤k→j的最短路径。i节点经过节点 l l l到达j的最短路径。节点 l l l是节点k之前
-
D k [ i ] [ j ] D^k[i][j] Dk[i][j] k=n-1时,即最短路径
-
由 D k − 1 [ i ] [ j ] D^{k-1}[i][j] Dk−1[i][j] 推 D k [ i ] [ j ] D^k[i][j] Dk[i][j] 时,
- 当k在最短路上,则更新 D k [ i ] [ j ] D^k[i][j] Dk[i][j] = D k [ i ] [ k ] D^k[i][k] Dk[i][k] + D k [ k ] [ j ] D^k[k][j] Dk[k][j]
- 当k不在最短路上,不跟新 D k [ i ] [ j ] D^k[i][j] Dk[i][j] = D k − 1 [ i ] [ j ] D^{k-1}[i][j] Dk−1[i][j]
int nodes[][];
for(i=0;i<N;i++){
for(j=0;j<N;j++){
path [i][j] = -1 ;// ij两点未连通
// nodes 初始化为邻接矩阵,对角线为0
}
}
for(k=0;k<N;k++){
for(i=0;i<N;i++){
for(j=0;j<N;j++){
if(D[i][k]+D[k][j] <D[i][j]){
D[i][j] = D[i][k]+D[k][j];
path[i][j]] = k;
}
}
}
}
最小生成树
最小生成树定义
最小生成树:
- 是棵树
- 无回路
- n个顶点有n-1条边
- 生成树
- 包含全部顶点
- 边都在图中
- 边的权重和最小
想生成树添加任意一条边都会形成回路。
如果能生成最小生成树,则图一定连通,反之亦然。
最小生成树的算法都是贪心算法
prim算法
小树长大,每次选一条到生成树最小的边
int edge[N][N];
int visit[N];
int dist[N];// 到最小生成树的距离,初始化为无穷大
dist[0] = 1; // 假设从0开始
next = 0;
while(1){
// update weight
for(i=0;i<N;i++){
if(edge[next][i] && dist[i] && dist[i] >edge[next][i]){// 两点相连,第i个点未加入,权重要更新
dist[i] = edge[next][i];
}
}
next = heap.pop();
if(next == -1)
break;
}
kruskal算法
森林合并成树
int set[N];
vector<int> nodes;
void init(){
for(i=0;i<N;i++){
set[i] = -1;
}
}
void union(int x, int y){
int a,b;
a = find(x);
b = find(y);
// 按秩归并
if(a != b){
if(set[a]<set[b]){
set[b] = a;
}else{
set[a] = b;
}
}
}
int find(int x){
int a = x,t;
while(set[x]>=0){
x = set[x];
}
// 路径压缩
while(a != x){
t = set[a] ;
set[a] = x;
a = t;
}
}
int del(){
int r = nodes[0];
int next = nodes[nodes.size()];
nodes.erase(nodes.end());
for(p=0;i*2<nodes.size();p=c){
c = p*2;
if(p*2+1 < nodes.size() && nodes[p*2] <nodes[p*2+1])
c = p*2+1;
if(nodes[p] <nodes[c]){
nodes[p] = nodes[c] ;
}else{
break;
}
}
ndoes[p] = next;
returun r;
}
void kruskal(){
init();
}
拓扑排序
拓扑序:若图中v到w有一条有向边,则v一定排在w之前,满足条件的都是拓扑序,获得拓扑序的过程是拓扑排序
若AOV网络存在合理的拓扑序,则必定是有向无环图DAG。存在环,则不可能。
int nodes[][];
queue<int> q ;
int count[];
for(i=0;i<N;i++){
flag = 0;
for(j=0;j<N;j++){
if(nodes[i][j] == 1){
flag = 1;
count[j] ++;
}
}
if(!flag)
q.push_back(q);
}
while(c<=0){
if(!q.empty()){
next = q.top();
q.pop();
}else{
printf(" 存在环“);
break;
}
for(i=0;i<N;i++){
nodes[next][j] = 0;
count[j] --;
if(count[j] == 0)
q.push(j);
}
}
关键路径是AOE网,AOE(activity on edge)
trick
最短路
- 求最短路数量时。在加入节点后,对其周围节点更新时,若找到更短的路径进行更新,则到更新节点的最短路数量等于其前驱节点最短路数量;若两条最短路相等,则更新节点的最短路数量++
- 若求边数最少的最短路或其他两种排序(先按距离排,再按其他条件排)即需要在更新时,当两条边相等时,需要按第二要求进行更新。
reference
浙江大学 数据结构mook 树https://www.icourse163.org/learn/ZJU-93001?tid=1003013004#/learn/announce