数据结构与算法(C语言) | 图的遍历及最小生成树问题

                                                                                       图的遍历

从图中某个顶点出发系统地访问图中所有顶点,使得每个顶点仅被访问一次,这一过程称作图的遍历。

  • 深度优先搜索

——连通图的深度优先搜索遍历:从图中某个顶点V0 出发,访问此顶点,然后依次从V0的各个未被访问的邻接点出发深度优先搜索遍历图,直至图中所有和V0有路径相通的顶点都被访问到。

《数据结构与算法(C语言) | 图的遍历及最小生成树问题》

 

在下图假设约定右手原则:在没有碰到重复顶点的情况下,分叉路口始终是向右手边走,每路过一个顶点就做一个记号。

《数据结构与算法(C语言) | 图的遍历及最小生成树问题》

则此图的访问次序是:ABCDEFGHI

#include<stdio.h>
#include<stdlib.h>
// 邻接矩阵的深度有限递归算法
#define TRUE 1
#define FALSE 0
#define MAX 256
//---------图的数组(邻接矩阵)存储表示-------
#define INFINITY 65535      //最大值 代表无穷大
#define MAX_VERTEX_NUM 20   //最大顶点数


typedef struct{
    char vex[20];     //顶点向量
    int  arcs[MAX_VERTEX_NUM][MAX_VERTEX_NUM];                    //邻接矩阵
    int vexnum,arcnum;                  //图的顶点数和弧数
}MGraph;

int visited[MAX_VERTEX_NUM];
//标记数组


void CreateMGraph(MGraph *G)
{
    int i, j, k, w;
    printf("请输入顶点数:\n");
    scanf("%d",&G->vexnum);
    printf("请输入边数:\n");
    scanf("%d",&G->arcnum);

    printf("请输入顶点:\n");

    for( i=0; i < G->vexnum; i++ )
	{
		scanf("%c", &G->vex[i]);
	}

    //初始化矩阵
    for(i=0 ; i<G->vexnum ;i++)
    {
        for(j=0 ;j <G->vexnum ;j++)
        {
            G->arcs[i][j] = 0;
        }
    }
     //输入由顶点构成的边
     printf("请输入与边关联的两个顶点的序号:\n");
    for(j=k ; k<G->arcnum ; k++)
    {
        scanf("%d %d",&i ,&j);
        G->arcs[i][j] = 1;
        G->arcs[j][i] = 1;
    }
}

void DFS(MGraph *G,int v)
{
    int w;
    printf("%c ",G->vex[v]);
    visited[v] = 1;
    for(w=0; w<G->vexnum ;w++)
    {
        if(G->arcs[v][w] == 1&& ! visited[w])
        {
            DFS(G,w);
        }
    }
}

//邻接矩阵的深度遍历操作
void DFSTraverse(MGraph *G)
{
    int v;
    for(v=0 ; v <G->vexnum ; v++)
    {
        visited[v] = 0;
    }

    for(v = 0;v<G->vexnum ;v++)
    {
        if(!visited[v])
        {
            DFS(G,v);
        }
    }

}

int main()
{
    MGraph G;
    CreateMGraph(&G);
    printf("输出遍历的顶点:\n");
    DFSTraverse(&G);
    return 0;

}

 

 

  • 广度优先搜索 

 类似于树的按层次遍历,从图中的某个顶点V0出发,并在访问此顶点之后依次访问V0的所有未被访问过的邻接点,之后按这些顶点被访问的先后次序依次访问它们的邻接点,直至图中所有和V0 有路径相通的顶点都被访问到。

    若此时图中尚有顶点未被访问,则另选图中一个未曾被访问的顶点作起始点,重复上述过程,直至图中所有顶点都被访问到为止。

《数据结构与算法(C语言) | 图的遍历及最小生成树问题》

采用队列——

// 邻接矩阵的广度遍历算法
void BFSTraverse(MGraph G)
{
	int i, j;
	Queue Q;
	
	for( i=0; i < G.numVertexes; i++ )
	{
		visited[i] = FALSE;
	}
	
	initQueue( &Q );
	
	for( i=0; i < G.numVertexes; i++ )
	{
		if( !visited[i] )
		{
			printf("%c ", G.vex[i]);
			visited[i] = TRUE;
			EnQueue(&Q, i);
			
			while( !QueueEmpty(Q) )
			{
				DeQueue(&Q, &i);
				for( j=0; j < G.numVertexes; j++ )
				{
					if( G.art[i][j]==1 && !visited[j] )
					{
						printf("%c ", G.vex[j]);
						visited[j] = TRUE;
						EnQueue(&Q, j);
					}
				}
			}
		}
	}
}

理解栈——

《数据结构与算法(C语言) | 图的遍历及最小生成树问题》

 

《数据结构与算法(C语言) | 图的遍历及最小生成树问题》

 

 

 

                                                                                     最小生成树

连通图(或强连通图)的所有顶点加上遍历过程中经过的边(或弧)可以构成图的生成树,根据遍历策略的不同,分别称为深度优先生成树广度优先生成树 对于不连通的无向图或不是强连通的有向图,从任意一个顶点出发一般不能系统地访问所有顶点,往往需要有两个或两个以上的出发点,这样便得到了生成森林

《数据结构与算法(C语言) | 图的遍历及最小生成树问题》

《数据结构与算法(C语言) | 图的遍历及最小生成树问题》

最小生成树——

问题:假设要在 n 个城市之间建立通讯联络网,则连通 n 个城市只需要修建 n-1条线路,如何在最节省经费的前提下建立这个通讯网

该问题等价于:构造网的一棵最小生成树,即:在 e 条带权的边中选取 n-1 条边(不构成回路),使“权值之和”为最小。

连通网的所有生成树中,各边权的总和为最小的生成树,称作该连通网的最小生成树最小代价生成树)。

 

算法一:Prime(普里姆)算法

《数据结构与算法(C语言) | 图的遍历及最小生成树问题》

    Prim算法在生成树的构造过程中,图中 n 个顶点分属两个集合已落在生成树上的顶点集 U 和尚未落在生成树上的顶点集V-U ,则应在所有连通U中顶点和V-U中顶点的边中选取权值最小的边。 

     设置一个辅助数组,对当前VU集中的每个顶点,记录和顶点集U中顶点相连接的代价最小的边:

struct {
     VertexType  adjvex;  // U集中的顶点序号
     VRType     lowcost;  // 边的权值
} closedge[MAX_VERTEX_NUM];

 

#define MAXVEX 100			// 最大顶点数
#define INFINITY 65535		// 用65535来代表无穷大

typedef struct
{
	char vexs[MAXVEX];				// 顶点表
	int arc[MAXVEX][MAXVEX];		// 邻接矩阵
	int numVertexes, numEdges;		// 图中当前的顶点数和边数
} MGraph;


void MiniSpanTree_Prime(MGraph G)
{
    int min, i, j, k;
    int adjvex[MAXVEX];
    int lowcost[MAXVEX];

    lowcost[0] = 0;
    adjvex[0] = 0;

    //初始化操作
    for(i=1 ; i<G.numVertexes; i++)
    {
        lowcost[i] = G.arc[0][i];
        adjvex[i] = 0;
    }

    //真正构造最小生成树的过程
    for(i=1; i<G.numVertexes; i++)
    {
        min = INFINITY;
        j = 1;
        k = 0;

        //遍历全部顶点
        while(j<G.numVertexes )
        {
            //找出lowcosr数组已存储的最小权值
            if(lowcost[j]!=0 && lowcost[j] < min)
            {
                min = lowcost[j] ;
                k = j;          // 将发现的最小权值的下标存入k,以待使用。
            }
            j++;
        }

        // 打印当前顶点边中权值最小的边
        printf("(%d,%d)",adjvex[k], k);
        lowcost[k] = 0;
        // 将当前顶点的权值设置为0,表示此顶点已经完成任务,进行下一个顶点的遍历

        
        // 邻接矩阵k行逐个遍历全部顶点
        for(j = 1; j<G.numVertexes; j++)
        {
            if(lowcost[j] != 0 && G.arc[k][j]<lowcost[j]);
            adjvex[j] = k;
        }
    }
}

 

 

 

算法二:Kruskal(克鲁斯卡尔)算法 

考虑问题的出发点: 为使生成树上边的权值之和达到最小,则应使生成树中每一条边的权值尽可能地小。

具体做法: 首先构造一个只含 n 个顶点的子图 T,然后选一条权值最小的边,若在T 中添加它后不产生回路,则在 T 中加上这条边,否则舍弃之,选另一条权值最小的边,如此重复,直

至在 T 中加上第 n-1 条边为止。 

 

用将邻接矩阵转化为边集数组

伪代码:


int Find(int *parent, int f)
{
	while( parent[f] > 0 )
	{
		f = parent[f];
	}
	
	return f;
}

// Kruskal算法生成最小生成树
void MiniSpanTree_Kruskal(MGraph G)
{
	int i, n, m;
	Edge edges[MAGEDGE];	// 定义边集数组
	int parent[MAXVEX];		// 定义parent数组用来判断边与边是否形成环路
	
	for( i=0; i < G.numVertexes; i++ )
	{
		parent[i] = 0;
	}
	
	for( i=0; i < G.numEdges; i++ )
	{
		n = Find(parent, edges[i].begin);	// 4 2 0 1 5 3 8 6 6 6 7
		m = Find(parent, edges[i].end);		// 7 8 1 5 8 7 6 6 6 7 7
		
		if( n != m )		// 如果n==m,则形成环路,不满足!
		{
			parent[n] = m;	// 将此边的结尾顶点放入下标为起点的parent数组中,表示此顶点已经在生成树集合中
			printf("(%d, %d) %d ", edges[i].begin, edges[i].end, edges[i].weight);
		}
	}
}

 

《数据结构与算法(C语言) | 图的遍历及最小生成树问题》

    原文作者:数据结构之图
    原文地址: https://blog.csdn.net/qq_35924276/article/details/81836219
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞