数据结构-图

图的逻辑结构:

无向图:

每条边都没有方向,用(Vi,Vj)表示某条边

有向图:

每条边都有方向,用表示A指向B的边

无向图某顶点的度:

与顶点连接的边数

有向图某顶点的度:

入度:流入该顶点的边数
出度:流出该顶点的边数

简单图:

不存在重复的边的图

无向完全图:

图中任意两顶点之间存在边,边的条数为n(n-1)/2,n为顶点数

有向完全图:

图中任意两顶点之间存在方向相反的两条边,边的条数为n(n-1),n为顶点数

带权图(网):

每条边都带有权值的图

子图:

图中任意两个顶点和连接这两个顶点的边的组合体

路径:

路径是一个顶点到另一个顶点序列,路径长度是路径上边的条数

环(回路):

从顶点x(x为图中任一顶点)经过某条路径回到顶点x

简单路径:

序列中顶点不重复出现的路径

简单环:

序列中顶点不重复出现的环

连通图:

无向图中,Vi到Vj有路径,则这两个顶点连通;若任意一对顶点连通,称为连通图

连通分量:

无向图中的极大连通子图

强连通图:

有向图中,Vi到Vj有路径,则这两个顶点连通;若任意的Vi到Vj有路径,称为强连通图

强连通分量:

有向图中的极大连通子图

图的存储结构:

邻接矩阵的理解:

如由5个数字顶点所组成的有向不带权图,我们想要存储这个有向图的顶点信息和顶点之间关系的信息.
首先顶点之间关系有:
0~(0,1,2,3,4)
1~(0,1,2,3,4)
2~(0,1,2,3,4)
3~(0,1,2,3,4)
4~(0,1,2,3,4)
也即顶点关系为25种
如果用一个二维数组表示就是这样:

	0	1	2	3	4
0	x	x	x	x	x
1	x	x	x	x	x
2	x	x	x	x	x
3	x	x	x	x	x
4	x	x	x	x	x
x=1表示<Vi,Vj>存在;x=0表示<Vi,Vj>不存在

将上述图改成有向带权图,如果用一个二维数组表示就是这样:

	0	1	2	3	4
0	x	x	x	x	x
1	x	x	x	x	x
2	x	x	x	x	x
3	x	x	x	x	x
4	x	x	x	x	x
x=X(X∈N*)表示<Vi,Vj>边的权值数;x=∞表示<Vi,Vj>不存在
邻接矩阵:

邻接矩阵的结构体设计:

typedef struct
{
     
	int no;//顶点的下标
}VertexType;

图的结构体设计:

typedef struct
{
     
	//二维数组存放(不带权,有边:1,无边:2;带权,存放边的权值)
	float edges[maxSize][maxSize];
	int n,e;//存放顶点数和边数
	VertexType vex[maxSize];//存放顶点信息(这里只存放了下标)
}MGraph;
邻接表:

边结点的结构体设计:

typedef struct ArcNode
{
     
	int adjV;//邻接顶点
	struct ArcNode* next;//指向下一个边结点的指针
}ArcNode;
图顶点的结构体设计:
typedef struct
{
     
	int data;//顶点信息
	ArcNode* first;//指向头一个边结点的指针
}VNode;

图的结构体设计

typedef struct
{
     
	VNode adjList[maxSize];//存放结点信息
	int n,e;//顶点和边的个数
}AGraph;
十字链表:

设起点流出的边为出边,流入起点的边为入边
为了使邻接表能够同时满足邻接和逆邻接的需求,设计十字链表
边结点的结构体设计:

typedef struct ArcNode
{
     
	int start;//起点
	int end;//终点
	struct ArcNode* nextIn;//入边到下一个边结点的指针
	struct ArcNode* nextOut;//出边到下一个边结点的指针
}ArcNode;

图顶点的结构体设计:

typedef struct
{
     
	int data;//顶点信息
	ArcNode* firstIn;//入边到头一个边结点的指针
	ArcNode* firstOut;//出边到头一个边结点的指针
}VNode;

图的结构体设计

typedef struct
{
     
	VNode adjList[maxSize];//存放结点信息
	int n,e;//顶点和边的个数
}AGraph;

图的基础算法:

深度优先遍历:
void DFS(int v,AGraph *G)
{
     
	visit[v]=1; 
	Visit(v);
	ArcNode *q=G->adjList[v].first;
	while(q!=NULL)
	{
     
		if(visit[q->adjV]==0)
			DFS(q->adjV,G);
		q=q->next;
	}
}
广度优先遍历:
void BFS(AGraph *G,int v,int visit[maxSize])
{
     
	ArcNode *p;
	int que[maxSize],front=0,rear=0;
	int j;
	Visit(v);
	visit(v)=1;
	rear=(rear+1)%maxSize;
	que[rear]=v;
	while(front!=rear)
	{
     
		front=(front+1)%maxSize;
		j=que[front];
		p=G->adjList[j].first;
		while(p!=NULL)
		{
     
			if(visit(p->adjV)==0)
			{
     
				Visit(p->adjV);
				visit(p->adjV)=1;
				rear=(rear+1)%maxSize;
				que[rear]=p->adjV;
			}
			p=p->next;
		}
	}
}
贪心算法-Prim算法:

贪心算法的特点:

每一次选取局部最优,不去考虑全局最优

Prim算法的特点:

1.只能用图里的边
2.n个顶点正好用掉n-1条边
3.不能有回路

构造最小生成树的步骤:

1.从顶点A开始,选择与其相连的权值最小的边,连接顶点B,得到生成树AB
2.选择与生成树AB相连的权值最小的边,连接顶点C,得到生成树ABC
3.选择与生成树ABC相连的权值最小的边,连接顶点D,得到生成树ABCD
...
n.连接完所有的顶点,构造出一棵代价最小的生成树

求最小生成树所需:

1.一张具有n个顶点的带权图
2.三个数组:
-MGraph[ ][n]
 MGraph[v][j]//表示顶点v到当前顶点j的权值
-vSet[n]
 vSet[v]=10//1表示顶点v已经被并入生成树中,0表示未并入.
-lowCost[n]
 lowCost[v]//当前生成树到顶点v的最小边的权值.

求最小生成树代价的代码:

//
void Prim(int n,float MGraph[][n],int v0,float &sum)
{
     
	/*
	  v:始终指向刚并入的顶点.
	  min:最小权值.
	  sum:最小权值之和.
	 */
	int lowCost[n],vSet[n];
	int v,k,min;
	v=v0;
	vSet[v]=1;
	sum=0;
	//顶点v0已经并入生成树,还剩n-1个顶点未并入,需要处理n-1次
	for(i=0;i<n-1;i++)
	{
     
		min=veryBig;//veyBig代表无穷大(用一个比所有边都大的数表示)
		//遍历n个顶点
		for(int j=0;j<n;++j)
		{
     
			//如果该顶点未并入生成树且到v的权值最小
			if(vSet[j]==0 && lowCost[j]<min)
			{
     
				min=lowCost[j];//保存最小权值
				k=j;//j为局部变量,把j赋值给全局变量k
			}
		vSet[k]=1;//将下标为k的顶点并入生成树
		v=k;//为了确保指向新并入的顶点
		sum+=min;//累加最小权值
		/*并入了新顶点之后,当前生成树到其它顶点的最小权值发生了变化
		n个顶点的lowCost[]值需要更新n次
		*/
		for(int j=0;j<n;j++)
		{
     	//如果该结点未被并入生成树且顶点v到当前顶点的权值小于最小权值
			if(vSet[j]==0&&MGraph[v][j]<lowCost[j])
			{
     
				//lowCost[]更新当前顶点的权值
				lowCost[j]=MGraph[v][j];
			}
		}
		}
	}
}
贪心算法-Kruskal算法:

构造最小生成树的步骤:

1.画一张图:假设有A B C D E F六个顶点
2.将六个顶点放在并查集中:A B C D E F
3.假设这张图有9条边,分别为1~9
4.假设取权值为1的边AB,A、B不是同一棵树,合并生成树AB(A表示这棵树的根结点)
 A 
B 	C 	D 	E 	F
5.假设取权值为2的边CD,C、D不是同一棵树,合并生成树CD(C表示这棵树的根结点)
 A 	 C 	
B 	D 	E 	F
6.假设取权值为3的边BC时,发现B和C所在的两棵树的根结点不同,所以也不是同一棵树,
合并生成树ABCD(CD的根结点接在AB的叶子结点上)
 A 	  	
B       E 	F
 C
D
7.假设取权值为4的边AD时,发现A和D所在的两棵树都是以A作为根结点,是同一棵树,pass
8.假设取权值为5的边BE时,发现B和E所在的两棵树的根结点不同,所以也不是同一棵树,合并生成树ABCDEF(EF的根结点接在ABCD的叶子结点上)
 A 	  	
B      
 C
D
 E
F
结论:利用并查集可以很好的排除环,使得小树成功长成一棵大树

图的结构体设计

typedef struct
{
     
	int a,b;//边的起点和终点
	int weight;//边的权值
}Route;
Route route[maxSize];

求最小生成树代价的代码:

int getRoot(int p)
{
     
	while(p!=DSU[p])p=DSU[p];
	return p;
}
void Kruskal(Route route[],int n,int e,int &sum)
{
     
	int a,b;
	sum=0;
	for(int i=0;i<n;i++)DSU[i]=i;
	sort(route,e);
	for(int i=0;i<e;++i)
	{
     
		a=getRoot(route[i].a);
		b=getRoot(route[i].b);
		if(a!=b)
		{
     
		  DSU[a]=b;
		  sum+=route[i].weight;
		}
	}
}

你可能感兴趣的:(c++,数据结构)