图(2)--图的遍历和图的连通性

一:图的遍历

      1.概念: 从图中某一顶点出发访遍图中其余顶点,且使每一个顶点仅被访问一次(图的遍历算法是求解图的连通性问题拓扑排序和求关键路径等算法的基础。)

       2.深度优先搜索(DFS)

            1).基本思想:

                           (1)在访问图中某一起始顶点 v 后,由 v 出发,访问它的任一邻接顶点 w1;
                           (2)再从 w1 出发,访问与 w1邻接但还未被访问过的顶点 w2;
                           (3)然后再从 w2 出发,进行类似的访问,… 
                           (4)如此进行下去,直至到达所有的邻接顶点都被访问过为止。
                           (5)接着,退回一步,退到前一次刚访问过的顶点,看是否还有其它没有被访问的邻接顶点。
                                     如果有,则访问此顶点,之后再从此顶点出发,进行与前述类似的访问;
                                     如果没有,就再退回一步进行搜索。重复上述过程,直到连通图中所有顶点都被访问过为止。

            2)算法实现(明显是要用到(栈)递归):                           

Void DFSTraverse( Graph  G, Status (*Visit) (int v))
{         // 对图G做深度优先遍历
    for (v=0; v<G.vexnum; ++v)  
          visited[v] = FALSE;      // 访问标志数组初始化
    for (v=0; v<G.vexnum; ++v) //这个循环是防止图是非连通的
         if (!visited[v])   DFS(G,v);  // 对尚未访问的顶点调用DFS
 }
void DFS (Graph G,int v)//从第v个顶点出发递归地深度优先遍历图G
{
   visited[v]=TRUE ;  Visit(v);  //访问第v个顶点 
   for(w=FirstAdjVex(G,v)/*从图的第v个结点开始*/; w>=0; w=NextAdjVex(G,v,w)/*v结点开始的w结点的下一个结点*/)
       if (!visited[w])   DFS(G,w); 
      //对v的尚未访问的邻接顶点w递归调用DFS 
}

           3)DFS时间复杂度分析:

                     (1)如果用邻接矩阵来表示图,遍历图中每一个顶点都要从头扫描该顶点所在行,因此遍历全部顶点所需的时间为O(n2)。
                     (2)如果用邻接表来表示图,虽然有 2e 个表结点,但只需扫描 e 个结点即可完成遍历,加上访问 n 个头结点的时间,因此遍历图的时间复杂度为O(n+e)。

3.广度优先搜索(BFS)

     1).基本思想:

               (1)从图中某个顶点V0出发,并在访问此顶点后依次访问V0的所有未被访问过的邻接点,之后按这些顶点被访问的先后次序依次访问它们的邻接点,直至图中所有和V0                                     有 路径相通的顶点都被访问到;
                (2)若此时图中尚有顶点未被访问(非连通图),则另选图中一个未曾被访问的顶点作起始点;
                (3)重复上述过程,直至图中所有顶点都被访问到为止。

     2).算法实现(明显是要用到队列)              

void BFSTraverse(Graph G, Status (*Visit)(int v)){
            //使用辅助队列Q和访问标志数组visited[v] 
  for (v=0; v<G.vexnum; ++v)  visited[v] = FALSE; 
  InitQueue(Q); // 置空的辅助队列Q
  for ( v=0; v<G.vexnum; ++v )//外层for循环是防止有非连通图的情况
    if ( !visited[v]) {   // v尚未访问
      visited[v] = TRUE;   Visit(v);
      EnQueue(Q, v);    // v入队列 
      while (!QueueEmpty(Q)) {
        DeQueue(Q, u);  // 队头元素出队并置为u
        for (w=FirstAdjVex(G,u);w>=0;w=NextAdjVex(G,u,w))
          if ( ! visited[w]){  
           //w为u的尚未访问的邻接顶点
             visited[w] = TRUE; Visit(w);
             EnQueue(Q, w);
          }   //if
       }   //while
   }if
}  // BFSTraverse
      3).BFS时间复杂度分析:

               (1) 如果使用邻接表来表示图,则BFS循环的总时间代价为 d0 + d1 + … + dn-1 = 2e=O(e),其中的 di 是顶点 i 的度
               (2)如果使用邻接矩阵,则BFS对于每一个被访问到的顶点,都要循环检测矩阵中的整整一行( n 个元素),总的时间代价为O(n2)。


二.图的连通性问题:

     1. 相关术语:

                 (1)连通分量的顶点集:即从该连通分量的某一顶点出发进行搜索所得到的顶点访问序列;
                 (2)生成树:某连通分量的极小连通子图(深度优先搜索生成树广度优先搜索生成树);
                 (3)生成森林:非连通图的各个连通分量的极小连通子图构成的集合。

     2.最小生成树:

             1).Kruskal算法:

                      先构造一个只含n个顶点的森林,然后依权值从小到大从连通网中选择边加入到森林中去,并使森林中不产生回路,直至森林变成一棵树为止(详细代码见尾文)。

                      图(2)--图的遍历和图的连通性_第1张图片


                 2)Prim算法(还是看上图理解):

                          假设原来所有节点集合为V,生成的最小生成树的结点集合为U,则首先把起始点V1加入到U中,然后看比较V1的所有相邻边,选择一条最小的V3结点加入到集合U中,

                      然后看剩下的v-U结点与U中结点的距离,同样选择最小的.........一直进行下去直到边数=n-1即可。

                    

                   算法设计思路:

                          增设一辅助数组Closedge[ n ],每个数组分量都有两个域:

                          

                         要求:求最小的Colsedge[ i ].lowcost   

                        

           3.两种算法比较:

                      (1)普里姆算法的时间复杂度为 O(n2),与网中的边数无关,适于稠密图;
                      (2)克鲁斯卡尔算法需对 e 条边按权值进行排序,其时间复杂度为 O(eloge),e为网中的边数,适于稀疏图。                  

           4.完整最小生成树两种算法实现:          

#include<stdio.h>
#include<stdlib.h>
#include<iostream>
using namespace std; 

#define MAX_VERTEX_NUM 20

#define OK 1

#define ERROR 0

#define MAX 1000

typedef struct Arcell
{
	double adj;//顶点类型
}Arcell,AdjMatrix[MAX_VERTEX_NUM][MAX_VERTEX_NUM];

typedef struct
{
	char vexs[MAX_VERTEX_NUM]; //节点数组,
	AdjMatrix arcs; //邻接矩阵
	int vexnum,arcnum; //图的当前节点数和弧数
}MGraph;

typedef struct Pnode //用于普利姆算法
{

	char adjvex; //节点

	double lowcost; //权值

}Pnode,Closedge[MAX_VERTEX_NUM]; //记录顶点集U到V-U的代价最小的边的辅助数组定义

typedef struct Knode //用于克鲁斯卡尔算法中存储一条边及其对应的2个节点

{

	char ch1; //节点1

	char ch2; //节点2

	double value;//权值

}Knode,Dgevalue[MAX_VERTEX_NUM];

//-----------------------------------------------------------------------------------

int CreateUDG(MGraph & G,Dgevalue & dgevalue);

int LocateVex(MGraph G,char ch);

int Minimum(MGraph G,Closedge closedge);

void MiniSpanTree_PRIM(MGraph G,char u);

void Sortdge(Dgevalue & dgevalue,MGraph G);

//-----------------------------------------------------------------------------------

int CreateUDG(MGraph & G,Dgevalue & dgevalue) //构造无向加权图的邻接矩阵
{
	int i,j,k;
	cout<<"请输入图中节点个数和边/弧的条数:";
	cin>>G.vexnum>>G.arcnum;

	cout<<"请输入节点:";

	for(i=0;i<G.vexnum;++i)

		cin>>G.vexs[i];

	for(i=0;i<G.vexnum;++i)//初始化数组

	{

		for(j=0;j<G.vexnum;++j)

		{

			G.arcs[i][j].adj=MAX;

		}

	}

	cout<<"请输入一条边依附的定点及边的权值:"<<endl;

	for(k=0;k<G.arcnum;++k)

	{

		cin >> dgevalue[k].ch1 >> dgevalue[k].ch2 >> dgevalue[k].value;

		i = LocateVex(G,dgevalue[k].ch1);

		j = LocateVex(G,dgevalue[k].ch2);

		G.arcs[i][j].adj = dgevalue[k].value;

		G.arcs[j][i].adj = G.arcs[i][j].adj;

	}

	return OK;

}

int LocateVex(MGraph G,char ch) //确定节点ch在图G.vexs中的位置

{

	int a ;

	for(int i=0; i<G.vexnum; i++)

	{

		if(G.vexs[i] == ch)

			a=i;

	}

	return a;

}

void MiniSpanTree_PRIM(MGraph G,char u)//普利姆算法求最小生成树

{

	int i,j,k;

	Closedge closedge;

	k = LocateVex(G,u);

	for(j=0; j<G.vexnum; j++)

	{

		if(j != k)

		{

			closedge[j].adjvex = u;

			closedge[j].lowcost = G.arcs[k][j].adj;

		}

	}

	closedge[k].lowcost = 0;

	for(i=1; i<G.vexnum; i++)

	{

		k = Minimum(G,closedge);

		cout<<"("<<closedge[k].adjvex<<","<<G.vexs[k]<<","<<closedge[k].lowcost<<")"<<endl;

		closedge[k].lowcost = 0;

		for(j=0; j<G.vexnum; ++j)//新顶点并入U后重新选择最小边

		{

			if(G.arcs[k][j].adj < closedge[j].lowcost)

			{

				closedge[j].adjvex = G.vexs[k];

				closedge[j].lowcost= G.arcs[k][j].adj;

			}

		}

	}

}

int Minimum(MGraph G,Closedge closedge) //求closedge中权值最小的边,并返回其顶点在vexs中的位置

{

	int i,j;

	double k = 1000;

	for(i=0; i<G.vexnum; i++)

	{

		if(closedge[i].lowcost != 0 && closedge[i].lowcost < k)

		{

			k = closedge[i].lowcost;

			j = i;

		}

	}

	return j;

}

void MiniSpanTree_KRSL(MGraph G,Dgevalue & dgevalue)//克鲁斯卡尔算法求最小生成树
{
	int p1,p2,i,j;
	int bj[MAX_VERTEX_NUM]; //标记数组
	for(i=0; i<G.vexnum; i++) //标记数组初始化

		bj[i]=i;

	Sortdge(dgevalue,G);//将所有权值按从小到大排序

	for(i=0; i<G.arcnum; i++)
	{
		p1 = bj[LocateVex(G,dgevalue[i].ch1)];
		p2 = bj[LocateVex(G,dgevalue[i].ch2)];

		if(p1!=p2)
		{
			cout<<"("<<dgevalue[i].ch1<<","<<dgevalue[i].ch2<<","
				<<dgevalue[i].value<<")"<<endl;
			for(j=0; j<G.vexnum; j++)

			{

				if(bj[j] == p2)

					bj[j] = p1;

			}

		}

	}

}

void Sortdge(Dgevalue & dgevalue,MGraph G)//对dgevalue中各元素按权值按从小到大排序

{

	int i,j;

	double temp;

	char ch1,ch2;

	for(i=0; i<G.arcnum; i++)

	{

		for(j=i; j<G.arcnum; j++)

		{

			if(dgevalue[i].value > dgevalue[j].value)

			{

				temp = dgevalue[i].value;

				dgevalue[i].value = dgevalue[j].value;

				dgevalue[j].value = temp;

				ch1 = dgevalue[i].ch1;

				dgevalue[i].ch1 = dgevalue[j].ch1;

				dgevalue[j].ch1 = ch1;

				ch2 = dgevalue[i].ch2;

				dgevalue[i].ch2 = dgevalue[j].ch2;

				dgevalue[j].ch2 = ch2;

			}

		}

	}

}

void main()

{

	int i,j;

	MGraph G;

	char u;

	Dgevalue dgevalue;

	CreateUDG(G,dgevalue);

	cout<<"图的邻接矩阵为:"<<endl;

	for(i=0; i<G.vexnum; i++)

	{

		for(j=0; j<G.vexnum; j++)

			cout << G.arcs[i][j].adj<<" ";

		cout<<endl;

	}

	cout<<"=============普利姆算法===============\n";

	cout<<"请输入起始点:";

	cin>>u;

	cout<<"构成最小代价生成树的边集为:\n";

	MiniSpanTree_PRIM(G,u);

	cout<<"============克鲁斯科尔算法=============\n";

	cout<<"构成最小代价生成树的边集为:\n";

	MiniSpanTree_KRSL(G,dgevalue);

}
运行结果:

             

你可能感兴趣的:(数据结构,最小生成树,图的遍历)