图总结
1.思维导图
2.重要概念笔记
图的定义
图的结构定义:图是由定点集V和一个顶点间的关系集合组成的数据结构。
假设图有n个顶点,e条边,则:
(1)含有e=n(n-1)/2条边的无向图称为完全图。
(2)含有e=n(n-1)条弧的有向图称作有向完全图。
图的存储结构
邻接矩阵:相当一个二维数组,里面存储着顶点,边(弧)的信息。
#define MAXVEXNUM 最大顶点个数
typedef int ArcCell;
typedef char VexType;
typedef struct {
VexType vexs[MAXVEXNUM];//点的集合
ArcCell arcs[MAXVEXNUM][MAXVEXNUM];//边的集合
int vexNum, arcNum;
}MyGraph;
邻接表:图中的每个顶点都建立一个单链表来存储所有依附于该顶点的边或弧。
typedef struct { //图的结构定义
AdjList vertices;
int vexnum, arcnum;
}ALGraph;
typedef struct ArcNode
{
int adjvex; //该边所指向顶点的位置
struct ANode* nextarc; //指向下一条边的指针
InfoType *info; //该边相关信息的指针
} ArcNode; //边表节点类型
typedef struct Vnode
{
Vertex data; //顶点信息
ArcNode* firstarc; //指向第一条边
} VNode, AdjList[MAXV];
typedef struct
{
AdjList adjlist; //邻接表
int n, e; //图中顶点数n和边数e
} AdjGraph;
图的遍历
定义:从图中某个顶点出发游历图,访问图中其余顶点,并使图中每个顶点只被访问一次的过程
1.深度优先搜索:
深度优先搜索遍历连接图的过程类似树的先根遍历。
深度优先遍历图的实质:对每个顶点查找其邻接点的过程。
void DFSTravse(MyGraph& G, int v) //深度遍历
{
int i;
cout << G.vexs[v] << " ";
G.visited[v] = 1;
for (i = 0; i < G.vexNum; i++)
{
if (G.arcs[v][i]) {
if (G.visited[i] == 0)
DFSTravse(G, i);
}
}
}
2.广度优先搜索:
广度优先遍历的实质:通过边或弧找邻接点的过程。
void BFSTravse(MyGraph& G, int v) //广度遍历
{
queue<int>q;
G.visited[v] = 1;
q.push(v);
while (!q.empty())
{
int k = q.front();
q.pop();
cout << G.vexs[k] << " ";
for (int i = 0; i < G.vexNum; i++)
{
if (G.arcs[k][i] != 0 && G.visited[i] == 0){
q.push(i);
G.visited[i] = 1;
}
}
}
}
DFS和BFS算法比较:
(1)空间复杂度相同,都是o(n).
(2)时间复杂度只与存储路径(邻接表或邻接矩阵)有关,与搜索路径(遍历的顺序)无关。
(3)搜索路径是由存储路径和算法共同决定的。
最小生成树
普里姆(prim)算法:
基本思想:
(1)取图中任意一个顶点v作为生成树的根。
(2)往生成树上添加新的顶点w。在添加的顶点w和已经在生成树上的顶点v之间必定存在一条边,并且该边的权值在所有连通顶点v和w之间的边中取值最小。
(3)继续往生成树上添加顶点,直至生成树上含有n个顶点为止。
克鲁斯卡尔(Kruskal)算法
基本思想:
(1)构造一个只含n个顶点的子图SG。
(2)从权值最小的边开始,若他的添加不使SG中产生回路,则在SG上增加这条边
(3)如此重复,直至加上n-1条边为止。
比较两种算法:
普里姆算法:o(n的平方)适用于稠密图。
克鲁斯卡尔算法:适用于稀疏图。
注:
构造的最小生成树不一定唯一,但最小生成树的权值之和一定相同。
最短路径
最短路径:各边上权值之和最小的路径。
Dijkstra算法
这是一个按路径长度递增的次序产生最短路径的算法。时间复杂度为O(n2)。
算法实现:
void ShortesPath_DIJ(AMGraph G, int v0) {
n = G.vexnum;
for (v = 0; v < n; ++v) {
s[v] = false;//s初始值为空集
D[v] = G.arcs[v0][v];//v0到各个顶点的弧的权值
if (D[v] < MaxInt)
Path[v] = v0;//v0和v之间有弧
else Path[v] = -1;//v0和v之间无弧
}
S[v0] = true;//v0加入S;
D[v] = 0;//源点到源点距离为0;
}
弗洛伊德算法
比较经过顶点的权值,如果经过的顶点路径比原两点间的路径更短,将当前两点间的权值设为更小的一个。时间复杂度为O(n3)。
拓扑排序
拓扑排序:按照有向图给出的次序关系,将图中顶点排成一个线性序列,对于有向图中没有限定次序关系的顶电,则可以人为加上任意的次序关系。由次得到顶点的线性序列称为拓扑有序序列。
AOV网:用顶点表示活动,用弧表示活动间的优先关系的有向图。
注:
(1)AOV网中不应该出现有向环。
(2)对给定的AOV网需先判断网中是否有环。
如何进行拓扑排序:
(1)从有向图中选取一个没有前驱的顶点,并输出之。
(2)从有向图中删去此顶点以及所有以它为尾的弧。
(3)重复上述两步,直至图空,或者图不空但找到无前驱的顶点为止。
关键路径
整个工程的完成时间:从有向图的源点到汇点的最长路径。
关键活动:该弧上的权值增加将使有向图上的最长路径的长度增加。
顶点表示事件;弧表示活动。
事件vi的最早发生时间:从开始点v1到vi的最长路径长度。
事件vi的最迟发生时间:在不推迟整个工期的前提下,事件vi允许发生的最晚时间
疑难问题及解决方法
7-1 图着色问题 (25分)
#include<iostream>
#include<string>
#include<vector>
#include<set>
using namespace std;
vector<vector<int>>G(501);
int color[501] = { 0 };
bool check(int V)
{
for (int i = 1; i <= V; i++)
for (int j = 0; j < G[i].size(); j++)
if (color[i] == color[G[i][j]])
return false;
return true;
}
int main()
{
int V, E, K, N;
cin >> V >> E >> K;
for (int i = 0; i < E; i++)
{
int start, end;
cin >> start >> end;
G[start].push_back(end);
G[end].push_back(start);
}
cin >> N;
for (int i = 0; i < N; i++)
{
set<int>color_kind;
for (int j = 1; j <=V; j++)
{
cin >> color[j];
color_kind.insert(color[j]);
}
if (check(V) && color_kind.size() == K)
cout << "Yes" << endl;
else
cout << "No" << endl;
}
return 0;
}
一开始题目都看的很懵逼,在老师的提醒下才慢慢理解了题目。
7-4 公路村村通 (30分)
#include <stdio.h>
#include <stdlib.h>
#define INFINITY 65535
#define MAX 1005
#define ERROR -1
int N, M, A[MAX][MAX], dist[MAX]; //Parent[MAX]; //这题中用不到Parent
int FindMinDist() {
int MinDist = INFINITY, MinV;
for(int V = 0; V < N; V++) {
//如果V未被收录且dist[V]更小
if( dist[V] != 0 && dist[V] < MinDist ) {
MinDist = dist[V];
MinV = V;
}
}
if( MinDist < INFINITY )
return MinV;
else return ERROR;
}
int Prim() {
int TotalWeight, V, W, cnt;
//初始化,默认初始点下标为0
for( V = 0; V < N; V++ ) {
dist[V] = A[0][V];
//Parent[V] = 0; //暂定所有顶点的父结点都是0
}
TotalWeight = 0; //初始化权重和
cnt = 0; //初始化收录的顶点数
dist[0] = 0; //将初始点0收录
cnt++;
//Parent[0] = -1;
while(1) {
//V = 未被收录顶点中dist最小者
V = FindMinDist();
//若V不存在,则结束
if( V == ERROR )
break;
//收录V
TotalWeight += dist[V];
dist[V] = 0;
cnt++;
for( W = 0; W < N; W++)
//若W未被收录且是V的邻接点
if( dist[W] != 0 && A[V][W] < INFINITY )
//如果收录V使得dist[W]变小
if( A[V][W] < dist[W] ) {
dist[W] = A[V][W];
//Parent[W] = V;
}
}
if( cnt < N )
TotalWeight = ERROR;
return TotalWeight;
}
int main(){
int a, b, ans;
scanf("%d %d", &N, &M);
//初始化
for(int i = 0; i < N; i++){
dist[i] = INFINITY;
//Parent[i] = -1;
for(int j = 0; j < N; j++)
A[i][j] = INFINITY;
}
//read
for(int i = 0; i < M; i++) {
scanf("%d %d", &a, &b);
scanf("%d", &A[--a][--b]); //题中编号从1开始
A[b][a] = A[a][b];
}
ans = Prim();
if( ans == ERROR )
printf("-1\n");
else
printf("%d\n", ans);
system("pause");
return 0;
}