图的存储结构C++实现

图结构作为一种比线性表和树更为复杂的数据结构,任意两个数据元素之间都有可能相关。在离散数学中有图论一章,就是专门研究图的性质的。

目录

邻接矩阵表示法

 邻接表表示法

图的遍历(邻接表的实现)

不足之处


数据的逻辑结构是非线性的图结构,一个节点可能有多个前驱、多个后继;

从存储结构上来说,只有链式存储结构,没有顺序存储结构。但是,可以通过一个邻接矩阵表示两结点之间的连接关系。

图的具体分为:无向图、有向图、无向网、有向网

邻接矩阵表示法

 

#pragma once
#include<iostream>
using namespace std;

const int MaxInt = 32767;
const int MVNum = 10;
typedef char Elemtype;

typedef enum { DG, DN, UDG, UDN } GraphKind;//创建一个枚举类型GraphKind
//DG:有向图      DN:有向网      UDG:无向图       UDN:无向网


class AMGraph
{

public:
	AMGraph(GraphKind k);
	void Create();//创建网or图
	int LocateVex(const Elemtype s);//定位邻接矩阵中的1或者权值的位置
	void Printarcs();//打印邻接矩阵


private:
	GraphKind kind;
	Elemtype vexs[MVNum];//创建一个顶点表
	int arcs[MVNum][MVNum];//邻接矩阵
	int vex_num;//当前的顶点数
	int arc_num;//当前的边数

};

AMGraph::AMGraph(GraphKind k)
{ 
	this->kind = k; 
	this->vex_num = 0;
	this->arc_num = 0;
}


void AMGraph::Create()
{
	cout << "输入总顶点数:"; cin >> this->vex_num;
	cout << "输入总边数:"; cin >> this->arc_num;
	cout << "初始化顶点表!" << endl;  //也就是说输入点的信息
	//初始化一下这个矩阵
	for (int i = 0; i < this->vex_num; i++)
	{
		cout << "请输入第" << i + 1 << "个点的信息:";
		cin >> vexs[i];
	}
	for (int i = 0; i < this->vex_num; i++)
	{
		for (int j = 0; j < this->vex_num; j++)
		{
			if (this->kind == 0 || this->kind == 2)
				this->arcs[i][j] = 0;
			else
				this->arcs[i][j] = MaxInt;
		}
	}
	//正式构建邻接矩阵
	for (int i = 0; i < this->arc_num; i++)
	{
		Elemtype s1, s2;
		int val=1;
		int locate1=-1, locate2=-1;
		cout << "输入第一个点:" << endl; cin >> s1;
		cout << "输入第二个点:" << endl; cin >> s2;
		if (this->kind == 1 || this->kind == 3)
		{
			cout << "输入权值:"; cin >> val;
		}
		locate1 = this->LocateVex(s1);
		locate2 = this->LocateVex(s2);
		if (locate1 == -1 || locate2 == -1)
			return;
		switch (this->kind)
		{
		case  DG:
			this->arcs[locate1][locate2] = 1;
			break;
		case DN:
			this->arcs[locate1][locate2] = val;
			break;
		case UDG:
			this->arcs[locate1][locate2] = 1;
			this->arcs[locate2][locate1] = 1;
			break;
		case UDN:
			this->arcs[locate1][locate2] = val;
			this->arcs[locate2][locate1] = val;
			break;
		default:
			break;
		}
	}
}



int AMGraph::LocateVex(const Elemtype s)
{
	/*cout << typeid(this->vexs[0]).name() << endl;
	cout << typeid(s).name() << endl;*/
	for (int i = 0; i < this->vex_num; i++)
	{
		if (s == this->vexs[i])
			return i;//如果找到喽 就返回在顶点表中的下标
	}
	return -1;//如果没找到,就返回-1
}

void AMGraph::Printarcs()
{
	for (int i = 0; i < this->vex_num; i++)
	{
		for (int j = 0; j < this->vex_num; j++)
		{
			cout << this->arcs[i][j] << "\t";
		}
		cout << endl;
	}
}

 邻接表表示法

图中的每一个顶点建立一个单链表,把与结点相邻接的顶点放在链表中,顶点(表头节点)组成一个线性表,表头结点的指针域指向边表,时间复杂度O(n+e)(e:边数)

查看源图像

 关于邻接表表示法的相关理论可参考诸多数据结构与算法书籍,下面给出邻接表的表示方法:

#pragma once
#include<iostream>
using namespace std;

typedef char Elemtype;
const int MaxInt = 32767;

typedef enum { DG, DN, UDG, UDN } GraphKind;//创建一个枚举类型GraphKind
//DG:有向图      DN:有向网      UDG:无向图       UDN:无向网

//可以通过邻接表实现四种kind的计算机存储结构
//邻接表的三部分定义:边结点、存放顶点的头结点线性表、图的类型

class ArcNode//定义了边结点
{
public:
	int adjvex;//邻接顶点所在表中下标
	int weight;//边权重
	ArcNode* nextarc;//指向下一条边的指针
};
class VNode
{
public:
	Elemtype data;
	ArcNode* firstarc;
};
class ALGraph
{
public:
	ALGraph(GraphKind k);
	void CreateALG();//创建邻接表
	int LocateVex(const Elemtype s);
private:
	VNode* vertices;
	GraphKind kind;
	int vex_num;
	int arc_num;
};

ALGraph::ALGraph(GraphKind k)
{
	this->vex_num = 0;
	this->arc_num = 0;
	this->kind = k;
	this->vertices = nullptr;
}


void ALGraph::CreateALG()
{
	cout << "输入总顶点数:"; cin >> this->vex_num;
	cout << "输入总边数:"; cin >> this->arc_num;
	//初始化顶点表
	this->vertices = new VNode[vex_num];
	for (int i = 0; i < this->vex_num; i++)
	{
		cout << "输入顶点表中的数据域:"; cin >> vertices[i].data;
		vertices[i].firstarc = nullptr;
	}
	//边结点的链接
	for (int i = 0; i < this->arc_num; i++)
	{
		Elemtype s1, s2;
		int locate1 = -1, locate2 = -1;
		cout << "输入第1点:" << endl; cin >> s1;
		cout << "输入第2点:" << endl; cin >> s2;
		locate1 = this->LocateVex(s1);
		locate2 = this->LocateVex(s2);
		int val=1;
		if (this->kind == 1 || this->kind == 3)//如果创建的是有向网\无向网就输入对应的权值
		{
			cout << "输入权值:"; cin >> val;
		}
		ArcNode* p;
		p = new ArcNode;
		p->weight = val;
		p->adjvex = locate2;
		p->nextarc = this->vertices[locate1].firstarc;
		this->vertices[locate1].firstarc = p;
		if (this->kind == 2 || this->kind == 3)//如果无向图/无向网
		{
			ArcNode* p1;
			p1 = new ArcNode;
			p1->weight = val;
			p1->adjvex = locate1;
			p1->nextarc = this->vertices[locate2].firstarc;
			this->vertices[locate2].firstarc = p1;
		}
	}
}

int ALGraph::LocateVex(const Elemtype s)
{
	for (int i = 0; i < this->vex_num; i++)
	{
		if (s == this->vertices[i].data)
			return i;//如果找到喽 就返回在顶点表中的下标
	}
	return -1;//如果没找到,就返回-1
}

 邻接表缺点:对于图来说,需要开辟两个空间来记录两个结点之间的关系,这对于后续的删除以及图的遍历都是不利的,为了克服这一缺点,引入邻接多重表;

对于有向图/网来说,一个结点只能记录他的出度,却不能记录入度,因此引入了十字链表,求得顶点的入度和出度.

图的遍历(邻接表的实现)

下面的两段程序是针对邻接表进行的深度优先搜索(DFS)和广度优先搜索(BFS),下面的程序是邻接表程序上又加了几个成员函数

以邻接表为表示方法,深度优先算法,更像是看到一个结点,一看他没被访问过,就立马就再从表结点出发,以该结点为出度边,递归访问,这样来回得穿梭于表头结点和边结点之间,从图形上是说是“一条道走到黑”;

而广度优先算法,更像是从邻接表上起始点出发,一条道走到黑,路上把所有遍历的且之前未访问过的结点,放到容器中,走到黑之后,再掉头从queue中输出队列元素,继续再邻接表上一条道走到黑,不用递归噢,走到黑之后,让容器再给你吐出来一个起始点。

类.hpp

#pragma once
#pragma once
#include<iostream>
#include<queue>
using namespace std;

typedef char Elemtype;
const int MaxInt = 32767;

typedef enum { DG, DN, UDG, UDN } GraphKind;//创建一个枚举类型GraphKind
//DG:有向图      DN:有向网      UDG:无向图       UDN:无向网

//可以通过邻接表实现四种kind的计算机存储结构
//邻接表的三部分定义:边结点、存放顶点的头结点线性表、图的类型

class ArcNode//定义了边结点
{
public:
	int adjvex;//邻接顶点所在表中下标
	int weight;//边权重
	ArcNode* nextarc;//指向下一条边的指针
};
class VNode
{
public:
	Elemtype data;
	ArcNode* firstarc;
};
class ALGraph
{
public:
	ALGraph(GraphKind k);
	void CreateALG();//创建邻接表
	void InitArry();//标志数组置空
	void DFS_AL(const Elemtype s);//输入时图中的某一个结点,深度优先遍历,采用邻接表表示,本质上操作对象还是表头结点表,而非依靠边结点输出
	void BFS_AL(const Elemtype s);//广度优先访问连通图,还有构建一个标志数组
private:
	int LocateVex(const Elemtype s);//根据输入的s值,确定要连接的两个点在表头结点中的位置


	VNode* vertices;
	GraphKind kind;
	int vex_num;
	int arc_num;
	int* visitedDFS;
	queue<int> Q;
};

ALGraph::ALGraph(GraphKind k)
{
	this->vex_num = 0;
	this->arc_num = 0;
	this->kind = k;
	this->vertices = nullptr;

}


void ALGraph::CreateALG()
{
	//初始化顶点表
	cout << "输入总顶点数:"; cin >> this->vex_num;
	cout << "输入总边数:"; cin >> this->arc_num;
	//初始化标志数组
	this->visitedDFS = new int[vex_num];
	for (int j = 0; j < this->vex_num; j++)
	{
		visitedDFS[j] = 0;//默认都没有访问过
	}
	//初始化表头
	this->vertices = new VNode[vex_num];
	for (int i = 0; i < this->vex_num; i++)
	{
		cout << "输入顶点表中的数据域:"; cin >> vertices[i].data;
		vertices[i].firstarc = nullptr;
	}
	//边结点的链接
	for (int i = 0; i < this->arc_num; i++)
	{
		Elemtype s1, s2;
		int locate1 = -1, locate2 = -1;
		cout << "输入第1点:" ; cin >> s1;
		cout << "输入第2点:" ; cin >> s2;
		locate1 = this->LocateVex(s1);
		locate2 = this->LocateVex(s2);
		int val = 1;
		if (this->kind == 1 || this->kind == 3)//如果创建的是有向网\无向网就输入对应的权值
		{
			cout << "输入权值:"; cin >> val;
		}
		ArcNode* p;
		p = new ArcNode;
		p->weight = val;//头插法
		p->adjvex = locate2;
		p->nextarc = this->vertices[locate1].firstarc;
		this->vertices[locate1].firstarc = p;
		if (this->kind == 2 || this->kind == 3)//如果无向图/无向网
		{
			ArcNode* p1;
			p1 = new ArcNode;
			p1->weight = val;
			p1->adjvex = locate1;
			p1->nextarc = this->vertices[locate2].firstarc;
			this->vertices[locate2].firstarc = p1;
		}
	}
}

int ALGraph::LocateVex(const Elemtype s)
{
	for (int i = 0; i < this->vex_num; i++)
	{
		if (s == this->vertices[i].data)
			return i;//如果找到喽 就返回在顶点表中的下标
	}
	return -1;//如果没找到,就返回-1
}

void ALGraph::InitArry()//为了防止一次程序中反复遍历图,所以提供给标志数组置0,这样的话,进行多次遍历的时候主程序都将其置空一次;
{
	for (int i = 0; i < this->vex_num; i++)
	{
		this->visitedDFS[i] = 0;
	}
}

void ALGraph::DFS_AL(const Elemtype s)
{//邻接表的展现方式
	cout << s << "\t";//麻溜把结点输出来;
	int locate = this->LocateVex(s);
	this->visitedDFS[locate] = 1;
	ArcNode* p;
	p = this->vertices[locate].firstarc;
	while (p)
	{
		int index = p->adjvex;//获得点的索引下标
		if (!this->visitedDFS[index])
			this->DFS_AL(this->vertices[p->adjvex].data);
		p = p->nextarc;
	}
}

void ALGraph::BFS_AL(const Elemtype s)//不需要遍历,但需要一个队列容器
{
	cout << s << "\t";
	int index = this->LocateVex(s);
	this->visitedDFS[index] = 1;
	this->Q.push(index);
	while (!Q.empty())
	{
		int num = Q.front();
		Q.pop();
		ArcNode* p;
		p = this->vertices[num].firstarc;
		while (p)
		{
			if (this->visitedDFS[p->adjvex] == 0)
			{
				cout << this->vertices[p->adjvex].data << "\t";
				this->visitedDFS[p->adjvex] = 1;
				Q.push(p->adjvex);
			}
			p = p->nextarc;//如果被访问过了,还要继续去顺着边结点寻找噢,所以这个程序不能放在if语句中
		}
	}
	cout << endl;
}

主程序.cpp

#include <iostream>
#include"图邻接表实现.hpp"
using namespace std;

int main()
{ 
	ALGraph K(UDG);
	K.CreateALG();
	K.DFS_AL('A');
	cout << endl;
	K.InitArry();
	K.BFS_AL('A');

	system("pause");
	return 0;
}

 结果:答案不唯一,由于子图在遍历的时候没有顺序,所以先访问A之后访问B或C都行

原型:

 

不足之处

这里动态开辟的空间都没释放噢!

  • 2
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值