C++ 实现图的存储和遍历

定义一个图类

#include <iostream>
#include <iomanip>
using namespace std;
#include <stack>
#define MAX 100
#define INF 0x3f3f3f3f	//一个很大的数

class Graph{
	public:
		Graph();
		~Graph();
		//邻接矩阵存储方式
		void createMGraph(int a[][MAX],int n,int e); 
		void dispMGraph();
		void matToList(); //将带权图的邻接矩阵转换成连接表
		//邻接表存储方式
		void creatALGraph(int a[][MAX],int n,int e);
		void dispALGraph();
		void listToMat();//将带权图的邻接表转换成邻接矩阵
		//图的遍历
		void DFS(int v); //深度优先搜索递归算法
		void DFSN(int v);//深度优先搜索非递归算法
		void BFS(int v);//广度优先递归算法
	private:
		void DFS1(ALGraph *G,int v);
		void BFS1(ALGraph *G,int v);
		MGraph g;
		ALGraph *G;
		int visited[MAX];
};

Graph::Graph(){
	G = NULL;
}

Graph::~Graph(){
	if(G!=NULL){
		int i;
		ANode *pre,*p;
		for(i=0;i<G->n;i++){//遍历所有头结点
			pre = G->adjlist[i].firstacr;
			if(pre!=NULL){
				p = pre->nextTar;
				while(p!=NULL){//释放adjlist[i]的所有边结点
					delete pre;
					pre = p;
					p = p->nextTar;
				}
				delete pre;
			}
		}
		delete G;//释放G所指的头结点数组的内存空间
	}
}

(一)邻接矩阵储存方法

  邻接矩阵是表示顶点之间相邻关系的矩阵。设G=(V,E)是含有n(设n>0)个顶点的图,各顶点的编号为0~n-1,则G的邻接矩阵A是n阶方阵,其定义如下:

  1. 如果G是不带权无向图图,则
    A [ i ] [ j ] = { 1 若(i,j)∈E(G) 0 其他 A[i][j]= \begin{cases} 1& \text{若(i,j)∈E(G)}\\ 0& \text{其他} \end{cases} A[i][j]={10(i,j)∈E(G)其他
  2. 如果G是不带权有向图,则
    A [ i ] [ j ] = { 1 若<i,j>∈E(G) 0 其他 A[i][j]= \begin{cases} 1& \text{若<i,j>∈E(G)}\\ 0& \text{其他} \end{cases} A[i][j]={10<i,j>∈E(G)其他
  3. 如果G是带权无向图,则
    A [ i ] [ j ] = { w i j 若i!=j且(i,j)∈E(G) 0 若i=j ∞ 其他 A[i][j]= \begin{cases} w_{ij}& \text{若i!=j且(i,j)∈E(G)}\\ 0& \text{若i=j}\\ ∞& \text{其他} \end{cases} A[i][j]=wij0i!=j(i,j)∈E(G)i=j其他
  4. 如果G是带权有向图,则
    A [ i ] [ j ] = { w i j 若i!=j且<i,j>∈E(G) 0 若i=j ∞ 其他 A[i][j]= \begin{cases} w_{ij}& \text{若i!=j且<i,j>∈E(G)}\\ 0& \text{若i=j}\\ ∞& \text{其他} \end{cases} A[i][j]=wij0i!=j<i,j>∈E(G)i=j其他

连接矩阵的特点

  1. 图的邻接矩阵表示是唯一的。
  2. 对于含有n个顶点的图,在采用邻接矩阵存储时,其存储空间均为O(n2),所以适合存储边数较多的稠密图。
  3. 无向图的邻接矩阵一定是一个对称矩阵,可以用对称矩阵的压缩存储方法减少存储空间。
  4. 无向图的邻接矩阵的i行(或第j列)非零元素(或非∞元素)的个数正好是顶点i的度。
  5. 有向图的邻接矩阵的i行(或第j列)非零元素(或非∞元素)的个数正好是顶点i的出度(或入度)。

邻接矩阵类型MGraph定义

//图的邻接矩阵存储方法
class VertexType{//顶点类型 
	public:
		int no;
		char data[MAX];
};
class MGraph{//图邻接矩阵类型 
	public:
		int edges[MAX][MAX];
		int n,e;
		VertexType vexs[MAX];
};

建立图的邻接矩阵

void Graph::createMGraph(int a[][MAX],int n,int e){
	int i,j;
	g.n = n;//置顶点数
	g.e = e;//置边数
	for(i=0;i<g.n;i++){
		for(j=0;j<g.n;j++){
			g.edges[i][j] = a[i][j];
		}
	}
}

输出图的邻接矩阵

void Graph::dispMGraph(){
	int i,j;
	for(i=0;i<g.n;i++){
		for(j=0;j<g.n;j++){
			if(g.edges[i][j]==INF){
				cout << setw(4) << "∞" ;
			}else{
				cout << setw(4) << g.edges[i][j];
			}
		}
		cout << endl;
	}
}

(二)邻接表储存方法

  图的邻接表储存方法是一种将顺序分配与链式分配相结合的储存方法。在表示n个顶点的图的邻接表中,每个顶点建立一个单链表,第i(0≤i≤n-1)个单链表中的结点表示依附于顶点i的边(对于有向图是以顶点i为尾的边)。每个单链表上附设一个表头结点,将所有表头结点构成一个表头结点数组。
边结点和表头结点的结构

  1. 边结点:
      adjvex指示与顶点i邻接的顶点的编号,nexTar指示下一条边的结点,weight存储与边的权重。
  2. 表头结点:
      data存储顶点i的名称或其他信息,firstacr指向顶点i的链表中的第一个边结点。

邻接表的特点:

  1. 图的邻接表表示不唯一,这是因为在每个顶点对应的单链表中,各边结点的链接次序可以是任意的,取决于建立邻接表的算法以及边的输入次序。
  2. 对于有n个顶点和e条边的无向图,其邻接表有n个表头结点和2e个边结点;对于有n个顶点和e条边的有向图,其邻接表有n个表头结点和e个边结点。邻接表更适合存储边数较少的稀疏图。
  3. 对于无向图,邻接表的顶点i(0≤i≤n-1)对应的第i个单链表的边结点个数正好是顶点i的度
  4. 对于有向图,邻接表的顶点i(0≤i≤n-1)对应的第i个单链表的边结点个数仅仅是顶点i的出度,顶点i的入度为邻接表中所有adjvex域置为i的边结点个数。

邻接表类型ALGraph定义

//图的邻接表存储方法 
class ANode{//边结点类型 
	public:
		int adjvex;
		ANode *nextTar;
		int weight;
};

class VNode{//表头节点类型 
	public:
		char data[MAX];
		ANode *firstacr;
};

class ALGraph{//图的连接表类型 
	public:
		VNode adjlist[MAX];
		int n,e;
};

建立图的邻接表

void Graph::creatALGraph(int a[][MAX],int n,int e){
	int i,j;
	ANode *p;
	G = new ALGraph();//创建图的邻接表
	G->n = n;//设置顶点数
	G->e = e;//设置边数
	for(i=0;i<G->n;i++){
	//给邻接表中所有头结点的指针域置初值
		G->adjlist[i].firstacr = NULL;
	}
	for(i=0;i<G->n;i++){//检查边数组a中的每一个元素
		for(j=G->n-1;j>=0;j--){
			if(a[i][j]!=0&&a[i][j]!=INF){//存在一条边
				p = new ANode();//创建边结点
				p->adjvex = j;
				p->weight = a[i][j];
				p->nextTar = G->adjlist[i].firstacr;//采用头插法插入
				G->adjlist[i].firstacr = p;
			}
		}
	}
}

输出图的邻接表

void Graph::dispALGraph(){
	int i;
	ANode *p;
	for(i=0;i<G->n;i++){
		cout << "[" << i << "]";
		p = G->adjlist[i].firstacr;//p指向第一个邻接点
		if(p!=NULL){
			cout << "→" ;
		}
		while(p!=NULL){
			cout << " " << p->adjvex << "(" << p->weight << ")";
			p = p->nextTar;//p移向下一个邻接点
			if(p!=NULL){
				cout << "→" ;
			}else{
				cout << "∧";
			}
		}
		cout << endl;
	}
}

(三)邻接矩阵和邻接表的相互转换

  1. 邻接矩阵转换成邻接表
void Graph::matToList(){
	int i,j;
	ANode *p;
	this->G = new ALGraph();//给邻接表分配空间
	for(i=0;i<this->g.n;i++){
	//给邻接表中所有头结点的指针域置初值
		G->adjlist[i].firstacr = NULL;
	}
	for(i=0;i<this->g.n;i++){//继承邻接矩阵中的每个元素
		for(j=this->g.n-1;j>=0;j--){
			if(this->g.edges[i][j]!=0&&this->g.edges[i][j]!=INF){//存在一条边
				p = new ANode();
				p->adjvex = j;
				p->weight = this->g.edges[i][j];
				p->nextTar = G->adjlist[i].firstacr;//采用头插法
				G->adjlist[i].firstacr = p;
			}
		}
	}
	G->n = this->g.n;//置顶点数
	G->e = this->g.e;//置边数
}
  1. 邻接表转换成邻接矩阵
void Graph::listToMat(){
	int i,j;
	ANode *p;
	ALGraph *GT = new ALGraph();
	GT = this->G;
	for(i=0;i<G->n;i++){//初始化邻接矩阵
		for(j=0;j<G->n;j++){
			if(i==j){
				this->g.edges[i][j] = 0;
			}else{
				this->g.edges[i][j] = INF;
			}
		}
	}
	for(i=0;i<G->n;i++){//遍历邻接表的所有表头结点
		p = G->adjlist[i].firstacr;
		while(p!=NULL){//当存在一条边时
			this->g.edges[i][p->adjvex] = p->weight;//置相应的权值
			p = p->nextTar;
		}
	}
	this->g.n=G->n;//置顶点数
	this->g.e=G->e;//置边数
}

(四)图的邻接表的广度优先遍历和深度优先遍历

  1. 广度优先遍历
void Graph::BFS(int v){
	int i;
	for(i=0;i<this->G->n;i++){
		visited[i] = 0; //将visited数组元素置为0
	}
	BFS1(this->G,v);                         
}

void Graph::BFS1(ALGraph *G,int v){
	ANode *p;
	int w;
	int qu[MAX];//定义一个循环队列
	int front=0,rear=0;//将循环队列的队头和队尾初始化
	cout << v << " ";
	visited[v] = 1;//输出访问顶点后设置标记
	rear = (rear+1)%MAX;
	qu[rear] = v;//访问过进队
	while(front!=rear){//队列不为空时循环
		front = (front+1)%MAX;
		w = qu[front];//出队并赋值给w
		p = G->adjlist[w].firstacr;//找与顶点w邻接的第一个顶点
		while(p!=NULL){
			if(visited[p->adjvex]==0){//若当前邻接顶点未被访问
				cout << p->adjvex << " ";
				visited[p->adjvex] = 1;//输出访问顶点后设置标记
				rear = (rear+1)%MAX;//该顶点进队
				qu[rear] = p->adjvex;
			}
			p = p->nextTar;//找下一个邻接顶点
		}
	}
}
  1. 深度优先遍历
    ①递归
void Graph::DFS(int v){
	int i;
	for(i=0;i<this->G->n;i++){//将visited数组元素均置为0
		visited[i] = 0;
	}
	DFS1(this->G,v);
}

void Graph::DFS1(ALGraph *G,int v){
	int w;
	ANode *p;
	visited[v] = 1;//置已访问标记
	cout << v << " ";
	p = G->adjlist[v].firstacr;//指向顶点v的第一个邻接点
	while(p!=NULL){
		w = p->adjvex;
		if(visited[w]==0){
			DFS1(G,w);//若w顶点未访问,递归访问它
		}
		p = p->nextTar;//下一个邻接点
	}
}

②非递归

void Graph::DFSN(int v)//深度优先(非递归)
{
	ANode* p;
	stack<int> St;//访问后的元素存放进栈
	int x,w,i;
	for (i=0;i<this->G->n;i++){
		visited[i] = 0;//顶点全部置为0
	}
	cout << v << " ";//访问顶点v
	visited[v] = 1;//访问后标记	
	St.push(v);//v进栈
	while(!St.empty())
	{
		x = St.top();//取出x
		p = this->G->adjlist[x].firstacr;//p指向x的相邻结点	
		while(p!=NULL)//第二重循环		
		{
			w = p->adjvex;//有相邻结点,将值赋给w	
			if (visited[w] == 0)//未访问过	
			{
				cout << w << " ";//访问		
				visited[w] = 1;//标记	
				St.push(w);//进栈		
				break;//直接退出第二重循环,不执行p=p->nextarc	
			}
			p = p->nextTar;//否则(访问过),找下一个结点
		}
		if (p == NULL){
			St.pop();
		}
	}
	cout << endl;
}

(五)main函数

int main() {
	Graph g;
	int n=6,e=9;//n表示顶点数,e表示边数
	int A[MAX][MAX] = {{0,5,INF,7,INF,INF},{INF,0,4,INF,INF,INF},{8,INF,0,INF,INF,9},{INF,INF,5,0,INF,6},{INF,INF,INF,5,0,INF},{3,INF,INF,INF,1,0}};
	//A数组是通过每个顶点到其它顶点建立起来的,自己到自己就为0,可达就为该路径权值,不可达就为INF(一个很大的数)
	cout << "该图对应的连接表为:" << endl; 
	g.creatALGraph(A,n,e);
	g.dispALGraph();
	cout << endl;
	cout << "该图对应的邻接矩阵为:" << endl;
	g.createMGraph(A,n,e);
	g.dispMGraph();
	cout << endl;
	cout << "递归深度优先结果:";
	g.DFS(0);
	cout << endl;
	cout << "非递归深度优先结果:";
	g.DFSN(0);
	cout << endl;
	cout << "广度优先:"; 
	g.BFS(0); 
	cout << endl;
	return 0;

}
  • 3
    点赞
  • 13
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
一、递归遍历二叉树的设计思想 递归遍历二叉树是一种常用的方法,它可以通过简单的代码实现对二叉树的遍历操作。递归遍历二叉树的基本思想是:先访问当前节点,然后再递归遍历左子树和右子树。具体实现可以分为前序遍历遍历和后序遍历三种。 1. 前序遍历 前序遍历实现思路是:先访问当前节点,然后再递归遍历左子树和右子树。具体实现代码如下: ``` void preorderTraversal(TreeNode* root) { if (root == NULL) return; visit(root); preorderTraversal(root->left); preorderTraversal(root->right); } ``` 2. 遍历 遍历实现思路是:先递归遍历左子树,然后访问当前节点,最后再递归遍历右子树。具体实现代码如下: ``` void inorderTraversal(TreeNode* root) { if (root == NULL) return; inorderTraversal(root->left); visit(root); inorderTraversal(root->right); } ``` 3. 后序遍历 后序遍历实现思路是:先递归遍历左子树,然后再递归遍历右子树,最后访问当前节点。具体实现代码如下: ``` void postorderTraversal(TreeNode* root) { if (root == NULL) return; postorderTraversal(root->left); postorderTraversal(root->right); visit(root); } ``` 递归遍历二叉树的优点是代码简单,易于理解,但是它也有一些缺点。递归遍历二叉树需要使用函数调用栈,如果二叉树的深度较大,就会导致栈溢出。此外,递归遍历二叉树的效率不如非递归遍历二叉树。 二、非递归遍历二叉树的设计思想 非递归遍历二叉树是通过使用栈来模拟递归过程实现的。它的基本思想是:先将根节点入栈,然后循环执行以下操作:取出栈顶节点,访问该节点,将其右子树入栈,再将其左子树入栈。具体实现可以分为前序遍历遍历和后序遍历三种。 1. 前序遍历 前序遍历的非递归实现思路是:先将根节点入栈,然后循环执行以下操作:取出栈顶节点,访问该节点,将其右子树入栈,再将其左子树入栈。具体实现代码如下: ``` void preorderTraversal(TreeNode* root) { if (root == NULL) return; stack<TreeNode*> s; s.push(root); while (!s.empty()) { TreeNode* node = s.top(); s.pop(); visit(node); if (node->right != NULL) { s.push(node->right); } if (node->left != NULL) { s.push(node->left); } } } ``` 2. 遍历 遍历的非递归实现思路是:先将根节点入栈,然后循环执行以下操作:如果当前节点不为空,则将其入栈并将左子树作为当前节点;如果当前节点为空,则取出栈顶节点,访问该节点,将右子树作为当前节点。具体实现代码如下: ``` void inorderTraversal(TreeNode* root) { if (root == NULL) return; stack<TreeNode*> s; TreeNode* node = root; while (!s.empty() || node != NULL) { if (node != NULL) { s.push(node); node = node->left; } else { TreeNode* tmp = s.top(); s.pop(); visit(tmp); node = tmp->right; } } } ``` 3. 后序遍历 后序遍历的非递归实现思路是:先将根节点入栈,然后循环执行以下操作:取出栈顶节点,如果该节点没有子节点,或者其子节点都已经访问过了,则访问该节点;否则将其右子节点和左子节点依次入栈。具体实现代码如下: ``` void postorderTraversal(TreeNode* root) { if (root == NULL) return; stack<TreeNode*> s; TreeNode* last = NULL; while (!s.empty() || root != NULL) { if (root != NULL) { s.push(root); root = root->left; } else { TreeNode* node = s.top(); if (node->right != NULL && node->right != last) { root = node->right; } else { visit(node); last = node; s.pop(); } } } } ``` 非递归遍历二叉树的优点是可以避免递归过程的函数调用栈,从而提高遍历效率。但是它也需要使用辅助栈来存储节点,增加了空间复杂度。此外,非递归遍历二叉树的实现过程比递归遍历二叉树更加复杂,需要考虑各种情况的处理方式。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值