用C++和邻接矩阵实现图算法——图算法有手就行

本文介绍了图的基本概念,包括顶点、边、权重、连通图等,并详细探讨了邻接矩阵的实现方式,以及其在图算法中的应用,如最短路径和最小生成树算法。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >



1. 图的介绍

1.1 图到底是个什么东西

图(graph)按是由顶点(vertex)连接顶点的边(edges)构成的离散结构。离散数学里面的定义如下:

图G = (V,E)由顶点(或结点)的非空集V和边集E构成,每条边有一个或两个顶点与它相连,这样的顶点称为边的端点。边连接它的端点。

这样干讲属实有点无聊,来个图做例子:
在这里插入图片描述

1.2 图到底用来干嘛

图几乎可以用来表现所有类型的结构或系统,从交通网络到通信网络,从下棋游戏到最优流程,从任务分配到人际交互网络,图都有广阔的用武之地。打个比较具体一点的比方:
在这里插入图片描述
GPS导航就用到了图的算法。

1.3 为什么要有图

图的提出是为了更好的解决多对多的关系,线性表他仅仅局限于一个直接前驱和一个直接后继的关系,而树也仅仅只能有一个直接前驱也就是父节点。

图论〔Graph Theory〕是数学的一个分支。它以图为研究对象。图论中的图是由若干给定的点及连接两点的线所构成的图形,这种图形通常用来描述某些事物之间的某种特定关系,用点代表事物,用连接两点的线表示相应两个事物间具有这种关系。
在这里插入图片描述

2. 图的概念介绍

为以后介绍图算法,提供背景知识。

2.1 顶点和边(vertex and edge)

上面介绍过了,不多重复,这里给出一些比较精准的定义,装一下b,虽然这个确实没什么用。
在这里插入图片描述

2.2 权重(weight)

边的权重(又或者是边的长度),是一个比较核心的概念,即每条边所对应的值。边的权重可以是零甚至可以是负数,比较不是一个真正的地图,给个带权重的简单图看一下。
在这里插入图片描述
在上面的地图例子中,对应的就是广州到东莞之间的距离(这个距离是我乱写,的不要当真):
在这里插入图片描述

2.3 路径与最短路径(path and the shortest path)

在图上任取两顶点,分别作为起点(start vertex)终点(end vertex),我们可以规划许多条由起点到终点的路线,这个路线就叫做路径,如果两点之间存在路径,那么这两个顶点是连通的。
在这里插入图片描述
路径也有权重。路径经过的每一条边,沿路加权重,权重总和就是路径的权重(通常只加边的权重,而不考虑顶点的权重),所谓的最短路径就是权重最小的边。
在这里插入图片描述
那么,在后面我会用Dijkstra算法来介绍最短路径

2.4 连通图/连通分量(connected graph/connected component)

每个点之间都存在路径,那么称这个图为连通图。
在这里插入图片描述
在这里插入图片描述
上面那个图,虽然不是一个连通图,但是他有两个连通子图,我们把一个图的最大连通子图称为它的连通分量,连通分量这些特点:

  1. 是子图
  2. 子图是连通的
  3. 子图含有最大顶点数
    最大连通子图”指的是无法再扩展了,不能包含更多顶点和边的子图。

打个比方:
在这里插入图片描述

2.5 环(loop)

环,也称为环路,是一个与路径相似的概念。在路径的终点添加一条指向起点的边,就构成一条环路。通俗点说就是画圈圈
在这里插入图片描述
在这里插入图片描述
深度优先搜索算法里面用到了环的概念

2.6 有向图与无向图(Directed Graph and Undirected Graph)

有向图和无向图之间的区别在于方向性,用只有一条边的简单图打个比方,下面这个无向图
在这里插入图片描述
这个是有向图
在这里插入图片描述
你可以把无向图抽象成这样的有向图
在这里插入图片描述

2.7 顶点的度

顶点的度,指的是图中和顶点V相关联的边的数目,度 = 入度 + 出度。在有向图中,所谓入度和出度,通俗的讲,入度指的是箭头指向自己这个点,出度指的是箭尾从自己那个点出来。在无向图中,由于没有方向的限制,出度等于入度。
在这里插入图片描述

2.8 稀疏图和稠密图(sparse graph and dense graph)

这个说白了就是边多少的问题,最主要是用在图论算法的构建中,时间复杂性和空间复杂性的选择问题,下面是定义

稀疏图:边的条数|E|远远小于顶点V:|V|^2的图
稠密图:边的条数|E|远远接近于顶点V:|V|^2的图

在这里插入图片描述
在这里插入图片描述

3. 图的实现方式

那么好,对于图而言,有两种常见的实现方式:邻接矩阵和邻接链表。那么在leetcode刷题的时候我发现,大部分图算法都是直接在矩阵上面进行,所以我会重点讲解邻接矩阵,邻接链表一笔带过邻接链表十分直观的把图给表现出来,用链表的形式存储A和指向B的指针,以及边等乱七八糟的,而邻接矩阵是将整个图所在的空间抽象成一个矩阵,在这个矩阵上有点就为1,没点就为0。这里先给出示例图,有点印象。图算法的实现我都是基于无向邻接矩阵,当然我会介绍如何把无向变成有向。
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

4. 邻接链表的实现方式

邻接链表:

  1. 对于每个点,存储着一个链表,用来指向所有与该点直接相连的点
  2. 对于有权图来说,链表中元素值对应着权重

下面是算法导论里面的定义,然后再给出设计思路:
在这里插入图片描述
在这里插入图片描述
那么好,对于设计好的结点的结构体,应该包含几个元素

  1. 自己的字母存储,例如ABCDEFG
  2. 一个关于边的链表,用来存储与其他点所连接的边的权值
  3. 在边的结构体那里应该可以指出,两个点是什么,两点确定一条直线,以及下一个边的指针构成一个链表
    因此就有了以下示意图
    在这里插入图片描述
    对着这个示意图进行设计,具体做法:
//头文件:AdjListUndirGraph.h
#ifndef ADJLIST_UNDIR_GRAPH_H    //邻接表实现无向图
#define ADJLIST_UNDIR_GRAPH_H
#include <iostream>
#include <queue>
using namespace std;
#define DEFAULT_SIZE 4

struct AdjListArcNode    //边结点定义
{
   
	int adjVex;
	AdjListArcNode *nextArc;

	AdjListArcNode():nextArc(NULL) {
   }

	AdjListArcNode(int _adjVex,AdjListArcNode *_nextArc=NULL)
	{
   
		adjVex=_adjVex;
		nextArc=_nextArc;
	}
};

struct AdjListVexNode   //顶点结点定义
{
   
	char data;
	AdjListArcNode *firstArc;

	AdjListVexNode():firstArc(NULL) {
   }
	
	AdjListVexNode(char VexValue,AdjListArcNode *_firstArc=NULL)
	{
   
		data=VexValue;
		firstArc=_firstArc;
	}
};



enum VisitStatus{
   VISIT,UNVISIT};

class AdjListUndirGraph     //邻接表实现无向图定义
{
   
public:
    AdjListUndirGraph(int _vexMaxNum=DEFAULT_SIZE);
	AdjListUndirGraph(char *Vexs,int _vexMaxNum,int _vexNum);
	~AdjListUndirGraph();
	void InsertArc(int vex1,int vex2);   //输入需合法,不考虑平行边,且vex1不等于vex2
	void Show();
	void DFSTraverse();
	void BFSTraverse();
	//friend istream& operator>>(istream &in,)

private:
	VisitStatus *tag;
	AdjListVexNode *vexTable;
	int vexNum;
	int vexMaxNum;
	int arcNum;

	void DFS(int startVex);    //深度优先搜索
	void BFS(int startVex);    //广度优先搜索
};

AdjListUndirGraph::AdjListUndirGraph(int _vexMaxNum)
{
   
	vexMaxNum=_vexMaxNum;
	vexNum=0;
	arcNum=0;
	tag=new VisitStatus[vexMaxNum];
	vexTable=new AdjListVexNode[vexMaxNum];
}

AdjListUndirGraph::AdjListUndirGraph(char *Vexs,int _vexMaxNum,int _vexNum)
{
   
	vexMaxNum=_vexMaxNum;
	vexNum=_vexNum;
	arcNum=0;
	tag=new VisitStatus[vexMaxNum];
	vexTable=new AdjListVexNode[vexMaxNum];
	for(int i=0;i<vexNum;i++)
	{
   
		tag[i]=UNVISIT;
		vexTable[i].data=Vexs[i];
	}
}

AdjListUndirGraph::~AdjListUndirGraph()
{
   
	if(tag!=NULL)
		delete[] tag;
	if(vexTable!=NULL)
		delete[] vexTable;
}

void AdjListUndirGraph::InsertArc(int vex1,int vex2)
{
   
	vexTable[vex1].firstArc=new AdjListArcNode(vex2,vexTable[vex1].firstArc);
	vexTable[vex2].firstArc=new AdjListArcNode(vex1,vexTable[vex2].firstArc);
	arcNum++;
}

void AdjListUndirGraph::Show()
{
   
	for(int i=0;i<vexNum;i++)
	{
   
		cout<<vexTable[i].data<<": ";
		AdjListArcNode *p=vexTable[i].firstArc;
		while(p!=NULL)
		{
   
			cout<<p->adjVex<<" ";
			p=p->nextArc;
		}
		cout<<endl;
	}
}

void AdjListUndirGraph::DFS(int startVex)
{
   
	if(tag[startVex]!=VISIT)
	{
   
		cout<<vexTable[startVex].data;
		tag[startVex]=VISIT;
		AdjListArcNode *p=vexTable[startVex].firstArc;
		for(;p!=NULL;p=p->nextArc)
		     DFS(p->adjVex);
	}
}

void AdjListUndirGraph::DFSTraverse()
{
   
	for(int i=0;i<vexNum;i++)
		if(tag[i]==UNVISIT)
		{
   
			DFS(i);
	        cout<<endl;
		}
}

void AdjListUndirGraph::BFS(int startVex)
{
   
	if(tag[startVex]!=VISIT)
	{
   
		queue<int> q;
		q.push(startVex);
		tag[startVex]=VISIT;
		cout<<vexTable[startVex].data;
		int i;
		AdjListArcNode *p;
		while(!q.empty())
		{
   
			i=q.front();
			q.pop();
			for(p=vexTable[i].firstArc;p!=NULL;p=p->nextArc)
			{
   
				if(tag[p->adjVex]==UNVISIT)
				{
   
					tag[p->adjVex]=VISIT;
					cout<<vexTable[p->adjVex].data;
					q.push(p->adjVex);
				}
			}
		}
	}
	
}

void AdjListUndirGraph::BFSTraverse()
{
   
	for(int i=0;i<vexNum;i++)
		if(tag[i]==UNVISIT)
		{
   	
			BFS(i);
			cout<<endl;
	    }
}
#endif
//main.cpp
#include "AdjListUndirGraph.h"

int main()
{
   
	char vexs[12]="ABCDEFGHIJK";
	AdjListUndirGraph graph(vexs,11,11);
	graph.InsertArc(0,1);
	graph.InsertArc(0,4);
	graph.InsertArc(0,3);
	graph.InsertArc(1,2);
	graph.InsertArc(1,4);
	graph.InsertArc(2,4);
	graph.InsertArc(4,5);
	graph.InsertArc(5,8);
	graph.InsertArc(4,6);
	graph.InsertArc(6,7);
	graph.InsertArc(3,6);
	graph.InsertArc(9,10);
	graph.Show();
	graph.BFSTraverse();
	return 0;
}

5. 邻接矩阵的实现

邻接矩阵:

  1. 在 n 个顶点的图需要有一个 n × n 大小的矩阵
  2. 在一个无权图中,矩阵坐标中每个位置值为 1 代表两个点是相连的,0 表示两点是不相连的
  3. 在一个有权图中,矩阵坐标中每个位置值代表该两点之间的权重,0 表示该两点不相连
  4. 在无向图中,邻接矩阵关于对角线相等

算法导论的定义
在这里插入图片描述
在这里插入图片描述在这里插入图片描述
很烦,很长,我先给出一个示意图解释怎么看邻接矩阵,然后再进行构造思路说明:
在这里插入图片描述
在这里插入图片描述
边权表和顶点表的读法差不多,先讲顶点表(数字比较简单好看一点):

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值