数据结构随笔——图的基本概念及最小生成树

一、图的基本概念及术语

图( Graph )是由顶点的有穷非空集合和顶点之间边的集合组成,通常表示为: G(V,E),其中,G表示一个图,V是图G中顶点的集合,E是图G中边的集合。

无向边:若顶点v到Vj之间的边没有方向,则称这条边为无向边(Edge), 用无序偶对(Vi,Vj) 来表示。

如果图中任意两个顶点之间的边都是无向边,则称该图为无向图。

有向边 :若从顶点w到v的边有方向,则称这条边为有向边,也称为弧。用有序偶来表示,Vi 称为弧尾(Tail), Vj称为弧头(Head)。

在无向图中,如果任意两个顶点之间都存在边,则称该图为无向完全图。含有n个顶点的无向完全图有n(n-1)/2条边。在有向图中,如果任意两个顶点之间都存在方向互为相反的两条弧,则称该图为有向完全图。含有n个顶点的有向完全图有n (n-1)条边。

有些图的边或弧具有与它相关的数字,这种与图的边或弧相关的数叫做权(Weight)。这些权可以表示从一个顶点到另一个顶点的距离或耗费。这种带权的图通常称为网(Network)。

对于无向图G= (V,{E}), 如果边(v,v’) ∈E,则称顶点v和v’互为邻接点(Adjacent),即v和v’相邻接。边(v,v’) 依附(incident) 于顶点v和v’,或者说(v,v’)与顶点v和v’相关联。顶点v的度(Degree) 是和v相关联的边的数目

对于有向图G= (V,{E}), 如果弧∈E,则称顶点v邻接到顶点v’,顶点v’邻接自顶点v。弧和顶点v, v’相关联。以顶点v为头的弧的数目称为v的入度(InDegree),记为ID (v)。以v为尾的弧的数目称为v的出度(OutDegree),记为OD (v)。顶点v的度为TD (v) =ID (v) +OD (v)

在无向图G中,如果从顶点v到顶点v’有路径,则称v和v’是连通的。如果对于图中任意两个顶点vi、 vj∈E, vi和vj都是连通的,则称G是连通图(Connected Graph)。

在有向图G中,如果对于每一对 vi、vj∈V、vi≠vj,从vi到vj和从vj到vi 都存在路径,则称G是强连通图。有向图中的极大强连通子图称做有向图的强连通分量。

二、图的存储结构

1.邻接矩阵

数据结构随笔——图的基本概念及最小生成树_第1张图片数据结构随笔——图的基本概念及最小生成树_第2张图片

#define MAXVEX 100  //最大顶点数
#define INF 65535  //代表无穷大
typedef struct
{
	int vexs[MAXVEX];//顶点表
	int arc[MAXVEX][MAXVEX];
	int num_vertexes, num_edges;//顶点数,边数
}mgraph;
void create_mgraph(mgraph* G)
{
	cin >> G->num_vertexes >> G->num_edges;
	//输顶点数,边数
	for (int i = 0; i < G->num_vertexes; i++)
		cin >> G->vexs[i];
	//邻接矩阵初始化:
	for (int i = 0; i < MAXVEX; i++)
	{
		for (int j = 0; j < MAXVEX; j++)
			G->arc[i][j] = INF;
	}
	int vi, vj, w;
	for (int i = 0; i < G->num_vertexes; i++)
	{
		cin >> vi >> vj >> w;
		G->arc[vi][vj] = G->arc[vj][vi] = w;
	}
}

2.邻接表

数据结构随笔——图的基本概念及最小生成树_第3张图片数据结构随笔——图的基本概念及最小生成树_第4张图片

#define MAXVEX 100
typedef char  vertex_type; //顶点类型
typedef int   edge_type;  //权值类型
typedef struct edge_node
{
	int adjvex; //邻接点域,存储该点下标
	edge_type weight; //权值
	struct edge_node* next; //指向下一个邻接点
}edgenode;
typedef struct vertex_node //顶点表节点
{
	edge_type data;
	edge_node* first_edge;  //边表头指针
}vertexnode,AdjList[MAXVEX]; 
typedef struct
{
	AdjList adjlist;
	int num_vertexes, num_edges;//顶点数,边数
}graphadjlist;
void create_graphadjlist(graphadjlist* G)
{
	edge_node* e;
	cin >> G->num_vertexes >> G->num_edges;
	for (int i = 0; i < G->num_vertexes; i++)
	{
		cin >> G->adjlist[i].data;
		G->adjlist[i].first_edge = NULL;//将边表置为空表
	}
	//建立边表,对于无向图要插入两次
	for (int i = 0; i < G->num_edges; i++)
	{
		int vi, vj;
		cin >> vi >> vj;
		e = (edge_node *)malloc(sizeof(edge_node));
		e->adjvex = vj;
		e->next = G->adjlist[vi].first_edge;
		G->adjlist[vi].first_edge = e;

		e = (edge_node*)malloc(sizeof(edge_node));
		e->adjvex = vi;
		e->next = G->adjlist[vj].first_edge;
		G->adjlist[vj].first_edge = e;
	}
}

无向图中顶点vi的度为第i个单链表中的结点数

有向图中顶点vi的出度为第i个单链表中的结点个数,顶点vi的入度为整个单链表中邻接点域值是i的结点个数(以vi为弧头)

邻接矩阵多用于稠密图的存储(e接近n(n-1)/2)。而邻接表多用于稀疏图的存储( e<)

三、图的遍历

深度优先遍历(Depth, First Search),也有称为深度优先搜索,简称为DFS。深度优先遍历其实就是一个递归的过程,就像是一-棵树的前序遍历。

它从图中某个顶点v出发,访问此顶点,然后从v的未被访问的邻接点出发深度优先遍历图,直至图中所有和v有路径相通的顶点都被访问到。

#define MAXVEX 100  //最大顶点数
#define INF 65535  //代表无穷大
typedef struct
{
	int vexs[MAXVEX];//顶点表
	int arc[MAXVEX][MAXVEX];
	int num_vertexes, num_edges;//顶点数,边数
}mgraph;
bool visited[MAXVEX];
//邻接矩阵的DFS
void dfs(mgraph G, int i)
{
	visited[i] = true;
	cout << i << endl;//打印顶点
	for (int j = 0; j < G.num_vertexes; j++)
	{
		if (G.arc[i][j] == 1 && !visited[j])
			dfs(G, j);
	}
}
void dfs_traverse(mgraph G)
{
	for (int i = 0; i < G.num_vertexes; i++)
		visited[i] = false;
	for (int i = 0; i < G.num_vertexes; i++)
		if (!visited[i])
			dfs(G, i);//每个顶点搜索,如果是连通图只会执行一次
}

广度优先遍历(Breadth First Search),又称为广度优先搜索,简称BFS。如果说图的深度优先遍历类似树的前序遍历,那么图的广度优先遍历就类似于树的层序遍历了。

数据结构随笔——图的基本概念及最小生成树_第5张图片

void bfs(mgraph G)
{
	queue < int > q;
	for (int i = 0; i < G.num_vertexes; i++)
		visited[i] = false;
	for (int i = 0; i < G.num_vertexes; i++)
	{
		if (!visited[i])
		{
			visited[i] = true;
			cout << G.arc[i] << endl;//打印顶点
			q.push(G.vexs[i]);
			while (!q.empty())
			{
				q.pop();//出队列
				for (int j = 0; j < G.num_vertexes; j++)
				{
					if (G.arc[i][j] == 1 && !visited[j])
					//有边且该顶点未被访问
					{
						visited[j] = true;
						cout << G.vexs[j] << endl;
						q.push(G.vexs[j]);
					}
				}
			}
		}
	}
}

四、最小生成树

推荐教程,王道考研-数据结构-最小生成树

1.Prim算法

简单来说就是:将顶点分成两个部分,U(选中的部分),V(未选中的部分)。每次从两个部分中找出权值最小的边相连的顶点划入U中。重复上述步骤,直到顶点全部划入U中

复杂度在O(n^2)

我们需要建立两个数组,lowcost,保存顶点权值,和adjvex,保存顶点。首先要初始化,令一个点为第一个点,lowcost初始化为与其相连的点的边权值,adjvex值初始化为改点。而后循环:在lowcost中找到最小且不能为0(因为为0代表改点已经加入生成树),保存该权值及下标,将这个位置的lowcost值置为0,这就是生成树的一个边。更新lowcost数组,如果遇到与刚刚添加的点有边的且权值小于lowcost该位置的权值,则赋值给lowcost,同时把该位置的adjvex赋值为下标。结束循环,adjvex的数组下标与值的关系就是最小生成树。

#include
#include
using namespace std;
#define MAX 100  
#define MAXCOST 0x7fffffff  
int graph[MAX][MAX];
int prim(int graph[][MAX],int n)
{
	int min,min_id;
	int adjvex[MAX];//顶点下标
	int lowcost[MAX];//保存相关顶点权值
	adjvex[1] = 0;
	lowcost[1] = 0;//第一个顶点加入生成树
	int sum=0;
	for (int j = 2; j <= n; j++)
	{
		lowcost[j] = graph[1][j];//将v顶点与之有边的权值存入
		adjvex[j] = 1;//初始化都为第一个顶点
	}
	cout << n << endl;
	for (int i = 2; i <=n; i++)
	{
		min = MAXCOST;
		min_id = 0;
		for (int j = 2; j <= n; j++)
		{
			if (lowcost[j] < min && lowcost[j] != 0)
			{//如果该点不在生成树里且与之权值最小
				min = lowcost[j];
				min_id = j;

			}
		}
		cout << adjvex[min_id] << " " << min_id << " " << min << endl;
		//打印顶点及权值
		sum += min;
		lowcost[min_id] = 0;//将改点权值变为0,加入生成树
		for (int j = 2; j <= n; j++)
		{//若下标为mid_id的边权值小于此其这些未被加入生成树的权值
			if (graph[min_id][j] < lowcost[j])
			{
				lowcost[j] = graph[min_id][j];//较小权值加入
				adjvex[j] = min_id;//下标为min_id加入
			}
		}
	}
	return sum;
}
int main()
{
	int m, n;
	cin >> n >> m;//n=顶点的个数,m=边的个数
	for (int i = 0; i < MAX; i++)
	{
		for (int j = 0; j < MAX; j++)
			graph[i][j] = MAXCOST;
	}
	for (int k = 1; k <= m; k++)
	{
		int i, j, cost;
		cin >> i >> j >> cost;
		graph[i][j] = cost;
		graph[j][i] = cost;
	}
	int cost = prim(graph, n);
	cout << cost << endl;
}

2.Kruskal算法
简单描述:转化为边集数组,从小到大排序,然后循环每一条边,依次加入(如果形成一个环则不可以加入)。

复杂度:O(nlogn) n为边数

#define MAXVEX 100  
#define MAXEDGE 200
typedef struct node
{
	int weight;   //长度
	int begin; //边的起点
	int end;   //边的终点
}Edge;
typedef struct
{
	int vexs[MAXVEX];//顶点表
	int arc[MAXVEX][MAXVEX];
	int num_vertexes, num_edges;//顶点数,边数
}mgraph;
int find(int* parent, int f)
{
	while (parent[f]>0)
		f = parent[f];
	return f;
}
void kruskal(mgraph g)
{
	int n, m;
	Edge edges[MAXEDGE];//定义边集数组
	int parent[MAXEDGE];//判断是否形成回路
	//!!!将邻接矩阵变为边集数组再按照权值从小到达排序(此处省略)
	for (int i = 0; i <= MAXEDGE; i++)
		parent[i] = 0;
	for (int i = 0; i < g.num_edges; i++)//循环每一条边
	{
		n = find(parent, edges[i].begin);
		m = find(parent, edges[i].end);
		if (n != m)
		{
			parent[n] = m;
			//将结尾点放置在下标为开始点的位置上,表示已经加入生成树
			cout << edges[i].begin << edges[i].end << edges[i].weight << endl;
		}
	}
}

你可能感兴趣的:(数据结构及算法随笔)