图总结

图总结

一、思维导图

二、重要概念的笔记

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;
}
posted @ 2020-05-13 16:05  Leesu  阅读(109)  评论(0编辑  收藏  举报