图总结
图总结
一、思维导图
二、重要概念的笔记
1、图
图是一种数据结构,它拥有顶点的存储和边的关系,图的建立与哈希表拉链法相似,设置两个结构,一个做顶点的地址,另一个以链表的形式存储它的领接点,也可以用二维数组,用行下标作为顶点,列下标作为相关邻接点,数组内可以存放权值,也可以用1代表着两顶点间的关系
#include <stdio.h>
#include <stdlib.h>
#define maxn 1000
typedef struct _list {
int data;
int weight;
_list* next;
}list;
typedef struct _art {
list* first;
int info;
}art;
typedef struct _gra {
int e, n;
art adjlist[maxn];
}gra;
gra* cre(int a[maxn][maxn], int n, int e) {
gra* G = (gra*)malloc(sizeof(gra));
G->e = e;
G->n = n;
for (int i = 0; i < n; i++)G->adjlist[i].first = NULL;
for (int i = 0; i < n; i++) {
for (int j = n - 1; j >= 0; j--) {
if (a[i][j]!=0&&a[i][j]!=32767) {
list* p = (list*)malloc(sizeof(list));
p->data = j;
p->weight = a[i][j];
p->next = G->adjlist[i].first;
G->adjlist[i].first = p;
}
}
}
}
void Dip(gra* G) {
int i;
list* p;
for (int i = 0; i < G->n; i++) {
p = G->adjlist[i].first;
printf("%3d: ", i);
while (p) {
printf("%3d[%d]->", p->data, p->weight);
p = p->next;
}
printf("^\n");
}
}
2、DFS
/* 邻接表存储的图 - DFS */
void Visit( Vertex V )
{
printf("正在访问顶点%d\n", V);
}
/* Visited[]为全局变量,已经初始化为false */
void DFS( LGraph Graph, Vertex V, void (*Visit)(Vertex) )
{ /* 以V为出发点对邻接表存储的图Graph进行DFS搜索 */
PtrToAdjVNode W;
Visit( V ); /* 访问第V个顶点 */
Visited[V] = true; /* 标记V已访问 */
for( W=Graph->G[V].FirstEdge; W; W=W->Next ) /* 对V的每个邻接点W->AdjV */
if ( !Visited[W->AdjV] ) /* 若W->AdjV未被访问 */
DFS( Graph, W->AdjV, Visit ); /* 则递归访问之 */
}
类似树的先序遍历,就是先访问一个节点,然后vis标记,再遍历它的连接点,如果没有访问过就递归访问它,若全部访问过,这一递归结束,系统退栈,返回上一个节点。
bool dfs(int a, int b) {
vis[a] = 1;
for (int i = 1; i <= n; i++) {
if (!vis[i] && mp[a][i] == 1) {
if (i == b) return true;
return dfs(i, b);
}
}
return false;
}
这是同时对两个结点进行深度搜索,判断两结点之间有没有关联
3、BFS
/* 邻接矩阵存储的图 - BFS */
/* IsEdge(Graph, V, W)检查<V, W>是否图Graph中的一条边,即W是否V的邻接点。 */
/* 此函数根据图的不同类型要做不同的实现,关键取决于对不存在的边的表示方法。*/
/* 例如对有权图, 如果不存在的边被初始化为INFINITY, 则函数实现如下: */
bool IsEdge( MGraph Graph, Vertex V, Vertex W )
{
return Graph->G[V][W]<INFINITY ? true : false;
}
/* Visited[]为全局变量,已经初始化为false */
void BFS ( MGraph Graph, Vertex S, void (*Visit)(Vertex) )
{ /* 以S为出发点对邻接矩阵存储的图Graph进行BFS搜索 */
Queue Q;
Vertex V, W;
Q = CreateQueue( MaxSize ); /* 创建空队列, MaxSize为外部定义的常数 */
/* 访问顶点S:此处可根据具体访问需要改写 */
Visit( S );
Visited[S] = true; /* 标记S已访问 */
AddQ(Q, S); /* S入队列 */
while ( !IsEmpty(Q) ) {
V = DeleteQ(Q); /* 弹出V */
for( W=0; W<Graph->Nv; W++ ) /* 对图中的每个顶点W */
/* 若W是V的邻接点并且未访问过 */
if ( !Visited[W] && IsEdge(Graph, V, W) ) {
/* 访问顶点W */
Visit( W );
Visited[W] = true; /* 标记W已访问 */
AddQ(Q, W); /* W入队列 */
}
} /* while结束*/
}
类似于树的层序遍历,将顶点入队,然后出队,将它的连接点进队(没访问过的话),进队后标记。
4、欧拉回路
一个无向图存在欧拉回路,当且仅当该图所有顶点度数都为偶数,且该图是连通图。
一个有向图存在欧拉回路,所有顶点的入度等于出度且该图是连通图。
5、最小生成树
prim算法
首先先设定INF值,作为最大量,用来表示两路不通的情况,然后定义结构体用来存储边和顶点,进入主函数后,首先初始化数组,对于同一点的权设为零,其他点默认为INF,然后读入数据,如果边数M小于N-1,那么不可能是连通图,不需要考虑,否则进行prim算法,生成最小生成树,即找出连接各点的最小值。
具体算法如下,首先传入邻接矩阵和起点,然后设立数组lowcost和close,以下标对应顶点,lowcost存放该点到最近点的权值,close就是存放对应的最近点,两点形成一条边路,初始时,先遍历v所在数组,即将v与各点的权值存入lowcost,并将close全部设为起点,很显然,起点自身的lowcost为零,到其他点的权值对应,不通的点则为INF,然后进行n-1次循环,每次循环遍历lowcost数组,找出不为零的最小权值,即为起点接下去的连接点,记为k,然后将lowcost对应的点设为零,接下来遍历图表中的k顶点,将k连接的各点权值与lowcost中的权值比较,如果lowcost不为零(即已经走过的点),且k的权值更小,则更新lowcost的权值,并将对应点的起点设为k,循环遍历直到结束,如果图不连通,那么在循环中显然会有空转,即有点没有走过,那么对应的lowcost就为INF,因此我们只要再次遍历lowcost,就能判断图是否连通,而close则可以帮助我们看到图的全貌,如果要算出权的最小值,只需要用一个sum记录每次的权即可
#include <stdio.h>
#include <stdlib.h>
#define INF 0x7fffffff
#define maxn 1001
typedef struct _gra {
int n, e;
int data[maxn][maxn];
}gra;
void prim(gra* g, int v);
int main()
{
gra* g = (gra*)malloc(sizeof(gra));
for (int i = 0; i < maxn; i++)
for (int j = 0; j < maxn; j++) {
if (i == j)
g->data[i][j] = 0;
else g->data[i][j] = INF;
}
int n, m, k, p, c;
scanf("%d%d", &n, &m);
g->n = n;
g->e = m;
for (int i = 0; i < m; i++) {
scanf("%d%d%d", &k, &p, &c);
g->data[k][p] = g->data[p][k] = c;
}
if(m<n-1)printf("-1");
else
prim(g, 1);
return 0;
}
void prim(gra* g, int v) {
int sum = 0;
int lowcost[maxn];
int close[maxn];
for (int i = 1; i <= g->n; i++) {
lowcost[i] = g->data[v][i];
close[i] = v;
}
lowcost[v] = 0;
for (int i = 2; i <= g->n; i++) {
int min = INF, k = 0;
for (int j = 1; j <= g->n; j++) {
if (lowcost[j] < min && lowcost[j] != 0) {
min = lowcost[j];
k = j;
}
}
sum += lowcost[k];
lowcost[k] = 0;
for (int j = 1; j <= g->n; j++) {
if (lowcost[j] != 0 && g->data[k][j] < lowcost[j]) {
lowcost[j] = g->data[k][j];
close[j] = k;
}
}
}
int flag = 0;
for (int i = 1; i <= g->n; i++) {
if (lowcost[i]==INF) {
flag = 1;
break;
}
}
if(flag)printf("-1");
else printf("%d", sum);
}
6、最短路径
1、无权无向图的最短路径,类似于bfs,只不过改用dist标记是否走过,首先初始化dist,原点为0,其他为INF,再用path记录前驱点,初始化为原点,每次出队一个元素,遍历连接点,若为INF,则改为dist+1,且更改path,然后入队。
2、有权图的单源最短路
/* 邻接矩阵存储 - 有权图的单源最短路算法 */
Vertex FindMinDist(MGraph Graph, int dist[], int collected[])
{ /* 返回未被收录顶点中dist最小者 */
Vertex MinV, V;
int MinDist = INFINITY;
for (V = 0; V < Graph->Nv; V++) {
if (collected[V] == false && dist[V] < MinDist) {
/* 若V未被收录,且dist[V]更小 */
MinDist = dist[V]; /* 更新最小距离 */
MinV = V; /* 更新对应顶点 */
}
}
if (MinDist < INFINITY) /* 若找到最小dist */
return MinV; /* 返回对应的顶点下标 */
else return ERROR; /* 若这样的顶点不存在,返回错误标记 */
}
bool Dijkstra(MGraph Graph, int dist[], int path[], Vertex S)
{
int collected[MaxVertexNum];
Vertex V, W;
/* 初始化:此处默认邻接矩阵中不存在的边用INFINITY表示 */
for (V = 0; V < Graph->Nv; V++) {
dist[V] = Graph->G[S][V];
if (dist[V] < INFINITY)
path[V] = S;
else
path[V] = -1;
collected[V] = false;
}
/* 先将起点收入集合 */
dist[S] = 0;
collected[S] = true;
while (1) {
/* V = 未被收录顶点中dist最小者 */
V = FindMinDist(Graph, dist, collected);
if (V == ERROR) /* 若这样的V不存在 */
break; /* 算法结束 */
collected[V] = true; /* 收录V */
for (W = 0; W < Graph->Nv; W++) /* 对图中的每个顶点W */
/* 若W是V的邻接点并且未被收录 */
if (collected[W] == false && Graph->G[V][W] < INFINITY) {
if (Graph->G[V][W] < 0) /* 若有负边 */
return false; /* 不能正确解决,返回错误标记 */
/* 若收录V使得dist[W]变小 */
if (dist[V] + Graph->G[V][W] < dist[W]) {
dist[W] = dist[V] + Graph->G[V][W]; /* 更新dist[W] */
path[W] = V; /* 更新S到W的路径 */
}
}
} /* while结束*/
return true; /* 算法执行完毕,返回正确标记 */
}
易理解,就是用dist,path和一个collect记录距离,前驱和访问情况,因为访问过的点后续不会影响,只要修改前驱和距离即可,然后每次找一个未收录的最小点,找不到则返回,找到就收录,然后更新相邻的未收录的点
7、多源最短
要注意不能将INF设为无穷,不然相加会超限,变成负数
bool Floyd( MGraph Graph, WeightType D[][MaxVertexNum], Vertex path[][MaxVertexNum] )
{
Vertex i, j, k;
/* 初始化 */
for ( i=0; i<Graph->Nv; i++ )
for( j=0; j<Graph->Nv; j++ ) {
D[i][j] = Graph->G[i][j];
path[i][j] = -1;
}
for( k=0; k<Graph->Nv; k++ )
for( i=0; i<Graph->Nv; i++ )
for( j=0; j<Graph->Nv; j++ )
if( D[i][k] + D[k][j] < D[i][j] ) {
D[i][j] = D[i][k] + D[k][j];
if ( i==j && D[i][j]<0 ) /* 若发现负值圈 */
return false; /* 不能正确解决,返回错误标记 */
path[i][j] = k;
}
return true; /* 算法执行完毕,返回正确标记 */
}
8、拓扑排序
bool TopSort( LGraph Graph, Vertex TopOrder[] )
{ /* 对Graph进行拓扑排序, TopOrder[]顺序存储排序后的顶点下标 */
int Indegree[MaxVertexNum], cnt;
Vertex V;
PtrToAdjVNode W;
Queue Q = CreateQueue( Graph->Nv );
/* 初始化Indegree[] */
for (V=0; V<Graph->Nv; V++)
Indegree[V] = 0;
/* 遍历图,得到Indegree[] */
for (V=0; V<Graph->Nv; V++)
for (W=Graph->G[V].FirstEdge; W; W=W->Next)
Indegree[W->AdjV]++; /* 对有向边<V, W->AdjV>累计终点的入度 */
/* 将所有入度为0的顶点入列 */
for (V=0; V<Graph->Nv; V++)
if ( Indegree[V]==0 )
AddQ(Q, V);
/* 下面进入拓扑排序 */
cnt = 0;
while( !IsEmpty(Q) ){
V = DeleteQ(Q); /* 弹出一个入度为0的顶点 */
TopOrder[cnt++] = V; /* 将之存为结果序列的下一个元素 */
/* 对V的每个邻接点W->AdjV */
for ( W=Graph->G[V].FirstEdge; W; W=W->Next )
if ( --Indegree[W->AdjV] == 0 )/* 若删除V使得W->AdjV入度为0 */
AddQ(Q, W->AdjV); /* 则该顶点入列 */
} /* while结束*/
if ( cnt != Graph->Nv )
return false; /* 说明图中有回路, 返回不成功标志 */
else
return true;
}
上述程序很清楚,它是用邻接表做的,首先个每个顶点设立degree数组,存储入度,然后对于统计每个顶点的入度,将入度为零的点入队,当队不空时,弹出一个顶点,存在结果序列中,然后遍历它的邻接点,每个邻接点的入度减一,如果为零则入列,当结果列中元素与总顶点数相同,说明无回路,排序成功
三、疑难问题及解决方案
很棒的图类题解
分而治之,题目大概就是给你个图,然后让你消掉n个点之后,问你每个点之间是否孤立,常规想法是二维数组,但是会内存超限,用链表的话时间不够,怎么办呢?
有一个好办法,就是用一个数组存储连通的两个元素,因为题目不需要我们遍历这个图,只要判断剩下的城市之间有没有通路而已,所以我们只需要把每条路径存储起来,然后判断各路径上的城市是否两端都存在,若果存在,证明存在连通,输出NO
#include <iostream>
using namespace std;
#define maxn 10001
int main()
{
int m, n, k, a, b,p;
cin >> n >> p;
int(*gra)[2] = new int[maxn][2];
int* vis = new int[maxn];
for (int i = 1; i <= n; i++)
for (int j = 1; j <= n; j++)gra[i][j] = 0;
for (int i = 0; i < p; i++) {
cin >> a >> b;
gra[i][0] = a;
gra[i][1] = b;
}
cin >> k;
for (int i = 0; i < k; i++) {
cin >> m;
for (int j = 1; j <= n; j++)vis[j] = 0;
int flag = 0;
for (int j = 0; j < m; j++) {
cin >> a;
vis[a] = 1;
}
for (int j = 0; j <p; j++) {
if (!vis[gra[j][0]] && !vis[gra[j][1]]) {
flag = 1;
break;
}
}
if (flag)printf("NO\n");
else printf("YES\n");
}
return 0;
}