图的基本操作(邻接矩阵)

图是比较常用的一种数据结构,我针对期末考试对其进行了大概整理,形成了本文。

声明

#include<iostream>
#include<fstream>
#include<queue>
#include<stack>
using namespace std;
#define MAX_V 20 // 最大顶点数目
#define READMODE 2//文件内容格式,1是完整的矩阵,2是读入边
typedef char ElemType;//元素类型
typedef int GraphKind;//图形类型
typedef struct
{
	ElemType info; // 顶点其它信息
}VertexType; // 顶点类型定义
class MGraph
{
private:
	int arcs[MAX_V][MAX_V]; // 邻接矩阵
	int num1, num2; // 图包含的顶点数以及边数
	VertexType vexs[MAX_V]; // 存放顶点信息
	GraphKind type;//如果提供的数据区分不了,默认是无向图,十位数为一代表网,没有数代表图,个位数为一代表无向,为二代表有向
public:
	MGraph();
	void BFS();//广度遍历输出图
	void DFS();//深度遍历输出图
	void dfs(bool* f, int i);//深度遍历辅助函数
	void print();//输出图的邻接矩阵
	void DJ(int b);//迪杰斯特拉(Dijkstra)算法求最短路径
	void prim();//普里姆(Prim)算法求最小生成树,输出其邻接矩阵
	void DPauxiliary(int b, int* f, bool* v, int* pre);//迪杰斯特拉算法和普里姆算法的辅助函数
};

图的建立

整体上是基于文件进行图的建立,有两种文件内容格式,READMODE ==1时,是读入顶点个数,顶点信息以及邻接矩阵,READMODE ==2时,是读入顶点个数,顶点信息,边的个数,边的信息,样例如下:
READMODE ==1
READMODE ==2
两种读入形成的邻接矩阵都是从1开始存,0不存数据。

READMODE ==1

if (READMODE == 1)
	{
		for (i = 1; i <= num1; i++)
		{
			for (int j = 1; j <= num1; j++)
			{
				file >> arcs[i][j];
				if (arcs[i][j] != 0 && i != j)
					num2++;
				if (arcs[i][j] > 1)
					type += 10;//网
			}
		}
		i = 0;
		if (num2 % 2 == 0)
		{
			type += 1;//无向
			num2 /= 2;//提前去掉重复统计的边
			for (int i = 1; i <= num1; i++)
			{
				for (int j = 1; j <= i; j++)
				{
					if (arcs[i][j] != arcs[j][i])
					{
						num2 *= 2;//还原边
						type += 1;//有向
						break;
					}
				}
				if (type % 10 == 2)
					break;
			}
		}
	}

READMODE==2

if (READMODE == 2)//默认是无向
	{
		file >> num2;
		for (int i = 1; i <= num1; i++)
		{
			for (int j = 1; j <= num1; j++)
			{
				if (i != j)
					arcs[i][j] = 0;
				else
					arcs[i][j] = 1;
			}
		}
		type += 1;
		for (i = 1; i <= num2; i++)
		{
			int x, y, l;
			file >> x >> y >> l;
			arcs[x][y] = arcs[y][x] = l;
			if (l > 1)
				type += 10;
		}
		
	}

遍历

无论是广度遍历还是深度遍历,其目的都是将所有点的信息进行遍历输出,不能有遗落,只不过BFS是广度优先,DFS是深度优先。

广度遍历(BFS)

广度优先的字面意思很好理解,其思想也差不多,我理解的就是一层一层访问,以最开始访问的顶点作为基准,记其层数为0,其他点几条线与之相连就是第几层,每一层的访问顺序由其上层(指的是最开始的分支层)的标号大小决定,编号小的先访问,上层完全相同的,比较本身编号大小,编号小的先访问。对于非连通图来讲,将图分成几个连通的子图,按照标号顺序访问整个子图。
这个只是个人思路,仅供参考,但是代码思路还是很清晰的:
1.先将起点入队
2.出队输出,并将与出队点直接相连的所有点入队
3.反复执行第二步
4.遍历,将所有未入队过的点从第1步开始执行,直至所有点都入队过
5.反复出队输出,直至队空

void MGraph::BFS()
{
	queue<int> q;
	bool* f = new bool[num1 + 1];
	fill(f, f + num1 + 1, 0);
	cout << "广度遍历结果为:";
	for (int i = 1; i <= num1; i++)
	{
		if (f[i] == 1)
			continue;
		q.push(i);
		f[i] = 1;
		for (int j = 1; j <= num1; j++)
		{
			if (arcs[i][j] == 0 || f[j] == 1 || j == i)//两点之间没有边,该点已经访问过,该点与先行点是一个的情况排除
				continue;
			q.push(j);//压入队列
			f[j] = 1;
		}
		cout << vexs[q.front()].info << ' ';
		q.pop();
	}
	while (!q.empty())
	{
		cout << vexs[q.front()].info << ' ';
		f[q.front()] = 1;
		q.pop();
	}
	cout << endl;
}

深度遍历(DFS)

同样的,广度优先的字面意思也很好理解,我的解释是,一条线一条线的遍历,出现分支的话,先进入编号小的一边,访问到头后,回到分支点,访问其他分支,同样的,对于非连通图来讲,将图分成几个连通的子图,按照标号顺序访问整个子图。 代码思路就是递归,首先一个大循环防止是非连通图导致访问不全。然后就是一条线一条线的进行访问,到头就返回。

void MGraph::DFS()
{
	bool* f = new bool[num1 + 1];
	fill(f, f + num1 + 1, 0);
	cout << "深度遍历结果为:";
	for (int i = 1; i <= num1; i++)
	{
		if (f[i] == 1)
			continue;
		dfs(f, i);
	}
	cout << endl;
}
void MGraph::dfs(bool* f, int i)
{
	cout << vexs[i].info << ' ';
	f[i] = 1;
	for (int j = 1; j <= num1; j++)
	{
		if (i == j || arcs[i][j] == 0 || f[j] == 1)
			continue;
		dfs(f, j);
	}
}

最短路径和最小生成树(迪杰斯特拉算法和普里姆算法)

迪杰斯特拉算法和普里姆算法很像,**都是以一个点为基准,反复从未接入过的点中选出与之最近的点,接入图中,然后更新与新接入点直接相连的点到基准点的最短路径长度,只不过迪杰斯特拉算法说的最短针对的是起点,而普里姆算法的最短针对的是新的图。**但是思想还是差不多的。
另外我写的代码是针对无向的,有向没试,但应该不行。

迪杰斯特拉算法

找最短路径就是一个大循环内嵌两个循环,大循环是为了遍历到所有的点,第一个小循环是为了寻找未接入过的点中选出与基准点最近的点,第二个小循环就是最短路径长度和前驱点的更新。

void MGraph::DJ(int b)
{
	if (type % 10 == 2)
	{
		cout << "有向图不行" << endl;
		return;
	}
	int* f;
	bool* v;
	int* pre;
	v = new bool[num1 + 1];//标记是否经过
	f = new int[num1 + 1];//记录最短距离
	pre = new int[num1 + 1];//记录最短路径的上一节点
	fill(f, f + num1 + 1, INT_MAX);
	fill(v, v + num1 + 1, false);
	for (int i = 1; i <= num1; i++)
		pre[i] = i;
	f[b] = 0;
	for (int i = 1; i <= num1; i++)
	{
		int near = -1;
		int min = INT_MAX;
		for (int j = 1; j <= num1; j++)//每次寻找到的点,都是未选过的,距离起点最近的点
		{
			if (v[j] == false && f[j] < min)
			{
				near = j;
				min = f[j];
			}
		}
		if (near == -1)
			break;
		v[near] = 1;
		for (int j = 1; j <= num1; j++)
		{
			if (arcs[near][j] != 0 && j != near && v[j] == false)
			{
				if (min + arcs[near][j] < f[j]) {
					f[j] = min + arcs[near][j];
					pre[j] = near;//pre保存当前结点的上一个节点,以便输出最短路线
				}
			}
		}
	}
	for (int i = 1; i <= num1; i++)
	{
		if (f[i] == INT_MAX)
		{
			cout << b << "和" << i << "不连通" << endl;
			continue;
		}
		if (i == b)
			continue;
		stack<int>path;
		int j = i;
		while (j != b)
		{
			path.push(j);
			j = pre[j];
		}
		cout << b << "-";
		while (!path.empty())
		{
			if (path.top() != b)
				cout << path.top();
			path.pop();
			if (!path.empty())
				cout << '-';
		}
		cout <<"        " << "最短距离是" << f[i] << endl;
	}
}

最小生成树

我是直接输出构成最小生成树的边了,找边跟最短路径差不多

void MGraph::prim()
{
	bool* f = new bool[num1 + 1];//是否接入新图
	int* linknode = new int[num1 + 1];//与新的图的连接点
	int *k = new int[num1 + 1];//与新的图的最小长度
	fill(f, f + num1 + 1, false);
	for (int i = 2; i <= num1; i++)
	{
		k[i] = arcs[i][1];
		if (k[i] == 0)
		{
			k[i] = 999;
			linknode[i] = 0;
		}
		else
			linknode[i] = 1;
	}
	k[1] = 0;
	f[1] = 1;
	for (int i = 2; i <= num1; i++)
	{
		int min = 999;
		int minnode = 0;
		for (int j = 2; j<= num1; j++)
		{
			if (f[j])
				continue;
			if (k[j] < min)
			{
				min = k[j];
				minnode = j;
			}
		}
		if (min == 999)
			break;
		f[minnode] = 1;
		cout << linknode[minnode] << ' ' << minnode << ' ' << k[minnode] << endl;
		for (int j = 2; j <= num1; j++)//更新
		{
			if (f[j])
				continue;
			if (arcs[j][minnode] != 0 && k[j] > arcs[j][minnode])
			{
				k[j] = arcs[j][minnode];
				linknode[j] = minnode;
			}
		}
	}
}

源代码

#include<iostream>
#include<fstream>
#include<queue>
#include<stack>
using namespace std;
#define MAX_V 20 // 最大顶点数目
#define READMODE 2//文件内容格式,1是完整的矩阵,2是读入边
typedef char ElemType;//元素类型
typedef int GraphKind;//图形类型
typedef struct
{
	ElemType info; // 顶点其它信息
}VertexType; // 顶点类型定义
class MGraph
{
private:
	int arcs[MAX_V][MAX_V]; // 邻接矩阵
	int num1, num2; // 图包含的顶点数以及边数
	VertexType vexs[MAX_V]; // 存放顶点信息
	GraphKind type;//如果提供的数据区分不了,默认是无向图,十位数为一代表网,没有数代表图,个位数为一代表无向,为二代表有向
public:
	MGraph();
	void BFS();
	void DFS();
	void dfs(bool* f, int i);
	void print();
	void DJ(int b);
	void prim();
	void DPauxiliary(int b, int* f, bool* v, int* pre);
};
int main()
{
	MGraph G;
	G.print();
	G.DFS();
	G.BFS();
	G.DJ(1);
	G.prim();
}
MGraph::MGraph()
{
	type = 0;
	num2 = 0;
	fstream file("data1.txt", ios::in);
	file >> num1;
	int j = 1, i = 1;
	while (i <= num1)
		file >> vexs[i++].info;
	if (READMODE == 1)
	{
		for (i = 1; i <= num1; i++)
		{
			for (int j = 1; j <= num1; j++)
			{
				file >> arcs[i][j];
				if (arcs[i][j] != 0 && i != j)
					num2++;
				if (arcs[i][j] > 1)
					type += 10;//网
			}
		}
		i = 0;
		if (num2 % 2 == 0)
		{
			type += 1;//无向
			num2 /= 2;//提前去掉重复统计的边
			for (int i = 1; i <= num1; i++)
			{
				for (int j = 1; j <= i; j++)
				{
					if (arcs[i][j] != arcs[j][i])
					{
						num2 *= 2;//还原边
						type += 1;//有向
						break;
					}
				}
				if (type % 10 == 2)
					break;
			}
		}
	}
	if (READMODE == 2)//默认是无向
	{
		file >> num2;
		for (int i = 1; i <= num1; i++)
		{
			for (int j = 1; j <= num1; j++)
			{
				if (i != j)
					arcs[i][j] = 0;
				else
					arcs[i][j] = 1;
			}
		}
		type += 1;
		for (i = 1; i <= num2; i++)
		{
			int x, y, l;
			file >> x >> y >> l;
			arcs[x][y] = arcs[y][x] = l;
			if (l > 1)
				type += 10;
		}
		
	}
	else
		type += 2;//有向
	cout << "这是一个" << num1 << "个顶点" << num2 << "条边" << "的";
	if (type % 10 == 2)
		cout << "有向";
	else
		cout << "无向";
	if (type / 10 > 0)
		cout << "网" << endl;
	else
		cout << "图" << endl;
}
void MGraph::BFS()
{
	queue<int> q;
	bool* f = new bool[num1 + 1];
	fill(f, f + num1 + 1, 0);
	cout << "广度遍历结果为:";
	for (int i = 1; i <= num1; i++)
	{
		if (f[i] == 1)
			continue;
		q.push(i);
		f[i] = 1;
		for (int j = 1; j <= num1; j++)
		{
			if (arcs[i][j] == 0 || f[j] == 1 || j == i)//两点之间没有边,该点已经访问过,该点与先行点是一个的情况排除
				continue;
			q.push(j);//压入队列
			f[j] = 1;
		}
		cout << vexs[q.front()].info << ' ';
		q.pop();
	}
	while (!q.empty())
	{
		cout << vexs[q.front()].info << ' ';
		f[q.front()] = 1;
		q.pop();
	}
	cout << endl;
}
void MGraph::dfs(bool* f, int i)
{
	cout << vexs[i].info << ' ';
	f[i] = 1;
	for (int j = 1; j <= num1; j++)
	{
		if (i == j || arcs[i][j] == 0 || f[j] == 1)
			continue;
		dfs(f, j);
	}
}
void MGraph::DFS()
{
	bool* f = new bool[num1 + 1];
	fill(f, f + num1 + 1, 0);
	cout << "深度遍历结果为:";
	for (int i = 1; i <= num1; i++)
	{
		if (f[i] == 1)
			continue;
		dfs(f, i);
	}
	cout << endl;
}
void MGraph::print()
{
	for (int i = 1; i <= num1; i++)
	{
		for (int j = 1; j <= num1; j++)
			cout << arcs[i][j] << ' ';
		cout << endl;
	}
}
void MGraph::DJ(int b)
{
	if (type % 10 == 2)
	{
		cout << "有向图不行" << endl;
		return;
	}
	int* f;
	bool* v;
	int* pre;
	v = new bool[num1 + 1];//标记是否经过
	f = new int[num1 + 1];//记录最短距离
	pre = new int[num1 + 1];//记录最短路径的上一节点
	fill(f, f + num1 + 1, INT_MAX);
	fill(v, v + num1 + 1, false);
	for (int i = 1; i <= num1; i++)
		pre[i] = i;
	f[b] = 0;
	for (int i = 1; i <= num1; i++)
	{
		int near = -1;
		int min = INT_MAX;
		for (int j = 1; j <= num1; j++)//每次寻找到的点,都是未选过的,距离起点最近的点
		{
			if (v[j] == false && f[j] < min)
			{
				near = j;
				min = f[j];
			}
		}
		if (near == -1)
			break;
		v[near] = 1;
		for (int j = 1; j <= num1; j++)
		{
			if (arcs[near][j] != 0 && j != near && v[j] == false)
			{
				if (min + arcs[near][j] < f[j]) {
					f[j] = min + arcs[near][j];
					pre[j] = near;//pre保存当前结点的上一个节点,以便输出最短路线
				}
			}
		}
	}
	for (int i = 1; i <= num1; i++)
	{
		if (f[i] == INT_MAX)
		{
			cout << b << "和" << i << "不连通" << endl;
			continue;
		}
		if (i == b)
			continue;
		stack<int>path;
		int j = i;
		while (j != b)
		{
			path.push(j);
			j = pre[j];
		}
		cout << b << "-";
		while (!path.empty())
		{
			if (path.top() != b)
				cout << path.top();
			path.pop();
			if (!path.empty())
				cout << '-';
		}
		cout <<"        " << "最短距离是" << f[i] << endl;
	}
}
void MGraph::prim()
{
	bool* f = new bool[num1 + 1];
	int* linknode = new int[num1 + 1];
	int *k = new int[num1 + 1];
	fill(f, f + num1 + 1, false);
	for (int i = 2; i <= num1; i++)
	{
		k[i] = arcs[i][1];
		if (k[i] == 0)
		{
			k[i] = 999;
			linknode[i] = 0;
		}
		else
			linknode[i] = 1;
	}
	k[1] = 0;
	f[1] = 1;
	for (int i = 2; i <= num1; i++)
	{
		int min = 999;
		int minnode = 0;
		for (int j = 2; j<= num1; j++)
		{
			if (f[j])
				continue;
			if (k[j] < min)
			{
				min = k[j];
				minnode = j;
			}
		}
		if (min == 999)
			break;
		f[minnode] = 1;
		cout << linknode[minnode] << ' ' << minnode << ' ' << k[minnode] << endl;
		for (int j = 2; j <= num1; j++)
		{
			if (f[j])
				continue;
			if (arcs[j][minnode] != 0 && k[j] > arcs[j][minnode])
			{
				k[j] = arcs[j][minnode];
				linknode[j] = minnode;
			}
		}
	}
}
  • 4
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值