首页 > 技术文章 > 数据结构之图

BetterThanEver_Victor 2016-01-29 18:13 原文

 图基本的表示方式为邻接矩阵和邻接表,而且两者可以相互转化,本文将讨论简单图(结点没有到本身的边)的表示和遍历以及最小生成树的算法。

1.图的定义表示

#define _CRT_SECURE_NO_DEPRECATE
#include<stdio.h>
#include<stdlib.h>
#include<windows.h>
#define DataType char
#define QueueType int 
#define StackType int 
const int un = 65535;//边界常量,表示两点之间没有边
const int Maxsize = 10;//图结点最大值
const int MaxEdge = 50;//边最大值
boolean visited[Maxsize];//访问标识数组,用于遍历
//图的邻接矩阵结构定义
typedef struct 
{
	DataType vertex[Maxsize];//结点数组
	int arc[Maxsize][Maxsize];//邻接矩阵
	int vertexNum, arcNum;//结点数量和边数量
}AdjMatrix, Graph;
//图的邻接表定义
typedef struct ArcNode//邻接表边结点
{
	int adjvex;//邻接点域
	ArcNode*next;
}ArcNode;
typedef struct //定义邻接表结点
{
	DataType vertex;
	ArcNode *firstedge;
}VertexNode;
typedef struct //邻接表
{
	VertexNode adjlist[Maxsize];//定点结点数组
	int vertexNum, arcNum;//顶点数和边数
}AdjList;

  2.邻接表与邻接矩阵转化

  这里邻接矩阵只用1表示两个图结点之间有边,0表示没有边。

  给出一个有向图(无向图类似):

         

  邻接矩阵:

         

  邻接表:

   

 

  两种表示方式的转换:

//邻接矩阵转换为邻接表
void MatToList(AdjMatrix &A, AdjList &B)
{
	B.vertexNum = A.vertexNum;
	B.arcNum = A.arcNum;
	ArcNode *p;//边结点临时变量
	for (int i = 0; i < A.vertexNum; i++)
	{
		B.adjlist[i].vertex = A.vertex[i];
		B.adjlist[i].firstedge = NULL;
	}
	for (int i = 0; i < A.vertexNum;i++)//双循环找邻接矩阵所有边
		for (int j = 0; j < A.vertexNum; j++)
		{
			if (A.arc[i][j] != 0)
			{
				p = (ArcNode*)malloc(sizeof(ArcNode));
				p->adjvex = j;
				p->next = B.adjlist[i].firstedge;//头插法
				B.adjlist[i].firstedge = p;
			}
		}
}
//邻接表转为邻接矩阵
void ListToMat(AdjMatrix &A, AdjList &B)
{
	A.vertexNum = B.vertexNum;
	A.arcNum = B.arcNum;
	ArcNode *p;
	for (int i = 0; i < A.vertexNum; i++)
		for (int j = 0; j < A.vertexNum; j++)
			A.arc[i][j] = 0;
	for (int i = 0; i < A.vertexNum; i++)
	{
		p = B.adjlist[i].firstedge;
		while (p)
		{
			A.arc[i][p->adjvex] = 1;
			p = p->next;
		}
	}
}

  3.有向图邻接表建立逆邻接表

void List(AdjList A, AdjList &B)
{
	B.vertexNum = A.vertexNum;
	B.arcNum = A.arcNum;
	ArcNode* p,*q;
	for (int i = 0; i < A.vertexNum; i++)
		B.adjlist[i].firstedge = NULL;
	for (int i = 0; i < A.vertexNum; i++)//头单链表插法来构造
	{
		p = A.adjlist[i].firstedge;
		while (p)
		{
			q = (ArcNode*)malloc(sizeof(ArcNode));
			q->adjvex = i;
			q->next = B.adjlist[p->adjvex].firstedge;
			B.adjlist[p->adjvex].firstedge=q;
			p = p->next;
		}
	}
}

  4.统计出度

//统计出度为0的算法邻接矩阵(入度统计出度)
int SumZero(AdjMatrix &A)
{
	int i, j;
	int count = 0;
	for (i = 0; i < A.vertexNum; i++)
	{
		for (j = 0; j < A.vertexNum; j++)
		{
			if (A.arc[i][j] != 0)
				break;
		}
		if (j >= A.vertexNum)
			count++;
	}
	return count;
}
//统计出度为0的算法邻接表(入度可以设置一个数组帮助统计)
int SumZero(AdjList &A)
{
	int i;
	int count = 0;
	for (i = 0; i < A.vertexNum; i++)
	{
		if(!A.adjlist[i].firstedge)
		count++;
	}
	return count;
}

  5.图的遍历邻接矩阵表示法(邻接表类似)

  (1)广度优先遍历

  广度优先遍历用到队列,类似二叉树的层次遍历,队列在之前的算法中已经定义。

  广度优先遍历代码

void BFS_ADJTraverse(AdjMatrix G)//邻接矩阵广度
{
	int i, j,t;
	SqQueue Q;
	for (int i = 0; i < G.vertexNum; i++)
		visited[i] = false;
	InitQueue(Q);
	for (i = 0; i < G.vertexNum; i++)//循环每一个可能的结点,防止是非连通图
	{
		if (!visited[i])
		{
			printf("%3c", G.vertex[i]);
			visited[i] = true;
			Enqueue(Q, i);
			while (!QueueEmpty(Q))
			{
				Dequeue(Q, t);
				for (j = 0; j < G.vertexNum; j++)
				{
					if (G.arc[t][j] == 1 && !visited[j])
					{
						printf("%3c", G.vertex[j]);
						visited[j] = true;
						Enqueue(Q, j);
					}
				}
			}
		}
	}
}
//第二种算法,设置两个函数找邻接点(可以改造一下来针对邻接表,主函数不变)
int FirstNeighborADJ(AdjMatrix G, int vex)
{
	int i = 0;
	for (i = 0; i < G.vertexNum;i++)
	  if (G.arc[vex][i] ==1)
		break;
	return i;//如果没找到则是VertexNum
}
int NextNeighborADJ(AdjMatrix G, int vex, int cur)
{
	int i = 0;
	for (i = cur+1; i < G.vertexNum; i++)
	  if (G.arc[vex][i] ==1)
		break;
	return i;//如果没找到则是VertexNum
}
void BFS_ADJTraverse(AdjMatrix G)//邻接矩阵广度
{
	int i, j,t;
	SqQueue Q;
	for (int i = 0; i < G.vertexNum; i++)
		visited[i] = false;
	InitQueue(Q);
	for (i = 0; i < G.vertexNum; i++)//循环每一个可能的结点,防止是非连通图
	{
		if (!visited[i])
		{
			printf("%3c", G.vertex[i]);
			visited[i] = true;
			Enqueue(Q, i);
			while (!QueueEmpty(Q))
			{
				Dequeue(Q, t);
				for (j = FirstNeighborADJ(G, t); j < G.vertexNum; j = NextNeighborADJ(G, t, j))
				{
					if (!visited[j])
					{
						printf("%3c", G.vertex[j]);
						visited[j] = true;
						Enqueue(Q, j);
					}
				}
			}
		}
	}
}

  (2)深度优先遍历,递归方法和非递归方法(非递归用到的栈在之前的算法中已定义)

void DFSADJ(AdjMatrix G, int v)//邻接矩阵深度优先遍历递归,从编号为v的点深度遍历
{
	printf("%3c", G.vertex[v]);
	visited[v] = true;
	for (int i = FirstNeighborADJ(G, v); i < G.vertexNum;i=NextNeighborADJ(G,v,i))
		if (!visited[i])
			DFSADJ(G, i);
}
void DFS_ADJTraverse(AdjMatrix G)
{
	int i;
	for (int i = 0; i < G.vertexNum; i++)
		visited[i] = false;
	for (i = 0; i < G.vertexNum; i++)//循环每一个可能的结点,防止是非连通图
	{
		if (!visited[i])
			DFSADJ(G, i);
	}
}
void DFSADJStack(AdjMatrix G, int v)//深度遍历非递归
{
	int j, t;
	SqStack S;
	InitStack(S);
	Push(S, v);
	visited[v] = true;
	printf("%3c", G.vertex[v]);
	while (!StackEmpty(S))
	{
		Pop(S, t);
		Push(S, t);
		for (j = FirstNeighborADJ(G, t); j < G.vertexNum; j = NextNeighborADJ(G, t, j))
		{
			if (!visited[j])
			{
				printf("%3c", G.vertex[j]);
				visited[j] = true;
				Push(S, j);
				break;
			}
		}
		if (j == G.vertexNum)//此时与t结点所有的相连结点都找完了(参照NextNeighborADJ函数),t节点没有可用了,弹栈
			Pop(S, t);
	}
}
void DFS_ADJTraverseStack(AdjMatrix G)//邻接矩阵深度优先遍历递归
{
	int i;
	for (int i = 0; i < G.vertexNum; i++)
		visited[i] = false;
	for (i = 0; i < G.vertexNum; i++)
	{
		if (!visited[i])
			DFSADJStack(G, i);
	}
}
//利用深度优先遍历判断是否为树
bool IsTree(Graph G)//判断是否为树,从一点遍历到所有点,且边数n-1
{
	for (int i = 0; i < G.vertexNum; i++)
		visited[i] = false;
	int Vnum = 0, Arcnum = G.arcNum;
	if (Arcnum != G.vertexNum - 1)
		return false;
	int i;
	//for (int i = 0; i < G.vertexNum; i++)
	//	visited[i] = false;
	DFSADJStack(G, 0);//从0开始遍历连通变量中所有点,并统计所有访问点的数量(visit[i]==true)
	for (i = 0; i < G.vertexNum;i++)
		if (visited[i] == true)
			Vnum++;
	if (Vnum < G.vertexNum)
		return false;
	else
		return true;
} 

  6.最小生成树算法

  用到的加权无向图为

      

  (1)Prim算法,时间复杂度(O(n^2))

  (算法思想是按照边的大小来选点,生成树集合中加入新点,关键是定义了一个关联点数组和一个边集数组)

void MinTree_Prim(AdjMatrix G)
{
	int i = 0;
	int adjvex[Maxsize];//例如{1,2,4,6,3,0}是指该位置顶点分别与1 2 4 6 3 0有联系 
	int lowarc[Maxsize];//保持相关顶点间的权值,与adjvex相对应
	adjvex[0] = 0;
	lowarc[0] = 0;//v0加入,开始
	for (i = 1; i < G.vertexNum; i++)//初始化为与0的链接关系,边的权值(没有边为un=65535)
	{
		adjvex[i] = 0;
		lowarc[i] = G.arc[0][i];
	}
	for (i = 1; i < G.vertexNum; i++)//n-1个边中选出最小的
	{
		int min = un,j=1,k=0;
		while (j < G.vertexNum)
		{
			if (lowarc[j] != 0 && lowarc[j]<min)
			{
				min = lowarc[j];
				k = j;//存储最小的下标
			}
			j++;
		}
		printf("%2d--%2d,%3d\n", adjvex[k],k, lowarc[k]);
		lowarc[k] = 0;//此顶点完成任务,置零
		for (j = 1; j < G.vertexNum; j++)//用k行的所有边的值来替代lowarc现有大边,正是prim算法中点逐渐增多的过程
		{
			if ( G.arc[k][j] < lowarc[j])
			{
				lowarc[j] = G.arc[k][j];
				adjvex[j] = k;
			}
		}
	}
}

   (2)Kruskal算法,时间复杂度(O(eloge))

   (算法思想是按照点固定,边集合中加入新的小边,关键是定义了一个环路判断函数Find和一个边结构数组)

typedef struct Edge
{
	int begin, end;
	int weight;
}Edge;//边集
int Find(int parent[], int f)//环路判断函数
{
	while (parent[f] > 0)
		f = parent[f];
	return f;
}
int EdgeCreatSort(Edge edges[], AdjMatrix G)//边集数组按权值排序
{
	int n=0,i, j;
	Edge temp;
	for (i = 0; i < G.vertexNum; i++)
	{
		for (j = i + 1; j < G.vertexNum; j++)
		{
			if (G.arc[i][j]!=0&&G.arc[i][j] < un)
			{
				edges[n].begin = i;
				edges[n].end = j;
				edges[n].weight = G.arc[i][j];
				n++;
			}
		}
	}
	for (i = 1; i < n; i++)//插入排序
	{
		temp = edges[i];
		for (j = i - 1; j >= 0 && edges[j].weight > temp.weight; j--)
		{
			edges[j + 1] = edges[j];
		}
		edges[j+1 ] = temp;
	}
	return n ;
}
void MinTree_Kruskal(AdjMatrix G)
{
	int i, n, m,edgnum;
	Edge edges[MaxEdge];//边集数组
	int parent[Maxsize];//定义parent数组用来判断边与边是否构成环路,是否在一棵树上,存放此点所连树的终端节点
	edgnum=EdgeCreatSort(edges, G);
	for (i = 0; i < G.vertexNum; i++)
		parent[i] = 0;
	for (i = 0; i < edgnum; i++)
	{
		n = Find(parent, edges[i].begin);
		m = Find(parent, edges[i].end);
		if (n != m)
		{
			parent[n] = m;//将此边结尾号放到起点为下标的parent数组中,表示已在生成树中,遍历会到此树最大标号的节点
			printf("%3d--%3d,%3d\n", edges[i].begin, edges[i].end, edges[i].weight);
		}
	}
}

  7.测试函数

int main()
{
	AdjMatrix myGraph,myweightG;
	DataType s[6] = { 'A', 'B', 'C', 'D', 'E', 'F' };
	DataType sweight[6] = { 'A', 'B', 'C', 'D', 'E', 'F' };
	int arcs[][6] = {
		{ 0, 1, 0, 1, 0, 1 },
		{ 0, 0, 0, 0, 1, 0 },
		{ 0, 0, 0, 0, 1, 1 },
		{ 0, 1, 0, 0, 0, 0 },
		{ 0, 0, 0, 1, 0, 0 },
		{ 0, 0, 0, 0, 0, 0 } };
	/*int arcs[][6] = {		//此图是树
		{ 0, 1, 0, 1, 0, 1 },
		{ 0, 0, 0, 0, 0, 0 },
		{ 0, 0, 0, 0, 0, 0 },
		{ 0, 0, 1, 0, 1, 0 },
		{ 0, 0, 0, 0, 0, 0 },
		{ 0, 0, 0, 0, 0, 0 } };*/
	int arcsweight[][6] = {
		{ 0, 6, 1, 5, un, un },
		{ 6, 0, 5, un, 3, un },
		{ 1, 5, 0, 5, 6, 4 },
		{ 5, un, 5, 0, un, 2 },
		{ un, 3, 6, un, 0, 6 },
		{ un, un, 4, 2, 6, 0 } };
	};
	for (int i = 0; i < 6;i++)
		myGraph.vertex[i] = s[i];
	myGraph.vertexNum = 6;
	myGraph.arcNum = 0;
	for (int i = 0; i < myGraph.vertexNum;i++)
		for (int j = 0; j < myGraph.vertexNum; j++)
		{
			myGraph.arc[i][j] = arcs[i][j];
			if (myGraph.arc[i][j]==1)
				myGraph.arcNum++;
		}
	for (int i = 0; i <6; i++)
		myweightG.vertex[i] = sweight[i];
		myweightG.vertexNum = 6;
		myweightG.arcNum = 0;
	for (int i = 0; i < myweightG.vertexNum; i++)
		for (int j = 0; j < myweightG.vertexNum; j++)
		{
			myweightG.arc[i][j] = arcsweight[i][j];
			if (myweightG.arc[i][j] != 0 && myweightG.arc[i][j]<un)
				myweightG.arcNum++;
		}
     printf("广度优先遍历结果:\n"); BFS_ADJTraverse(myGraph); printf("\n");
     printf("深度优先遍历递归结果:\n"); DFS_ADJTraverse(myGraph); printf("\n");
   printf("深度优先遍历非递归结果:\n"); DFS_ADJTraverseStack(myGraph); printf("\n"); if (IsTree(myGraph)) printf("\n此图是树\n"); else printf("\n此图不是树\n"); printf("Prim算法生成树(弧尾--弧头,边权值):\n"); MinTree_Prim(myweightG); printf("\n"); printf("Kruskal算法生成树(弧尾--弧头,边权值):\n"); MinTree_Kruskal(myweightG); printf("\n"); system("pause"); return 0; }

  8.测试结果

      

 

推荐阅读