数据结构(图)的代码实现及测试(1)——邻接矩阵实现和DFS

本文详细介绍了如何在C++中使用邻接矩阵实现图的数据结构,包括图的初始化、顶点和边的操作,以及深度优先搜索(DFS)和广度优先搜索(BFS)的递归实现,以及Dijkstra算法求解最短路径。
摘要由CSDN通过智能技术生成

学习数据结构最重要的就是落实到代码

下面是数据结构图的基本实现的代码,参考Shaffer的书《数据结构与算法分析》(C++第三版)

下面的代码基本实现了图的基类和邻接矩阵实现

并递归实现了基本遍历方式的一种——DFS 深度优先搜索遍历

注:在这里我定义了两个宏,来表示顶点的访问状态 分别为

        UNVISITED 代表 0 ,表示未访问

        VISITED 代表 1,表示已访问

1.头文件 Graph.h

#define UNVISITED 0
#define VISITED 1
#define LIMIT 1000000
#pragma once

#include "Queue.h"
#include<bits/stdc++.h>



class Graph
{
private:
	void operator = (const Graph&) {}//重载等号运算符
	Graph(const Graph&){}  //保护复制构造函数
	
public:
	Graph() {}//默认构造函数
	virtual ~Graph() {}//析构函数
	virtual void Assert(bool a, std::string b);
	//初始化n个顶点的图形 ,最后的 =0 是纯虚函数声明必须要写的部分
	virtual void Init(int n) = 0;

	virtual int n() = 0;//返回顶点数
	virtual int v() = 0;//返回边数

	virtual int first(int v) = 0;//返回顶点v的第一个邻居顶点

	virtual int next(int v, int w) = 0;//返回v的下一个邻居

	//为边设置权重,v1,v2为边连接的两个顶点,whgt为该边的权重
	virtual void setEdge(int v1, int v2, int whgt) = 0;

	//删除一条边,v1,v2为该边上的顶点
	virtual void delEdge(int v1, int v2) = 0;

	//判断一个边是否在这个图中
	virtual bool isEdge(int i, int j) = 0;

	//返回边的权重
	virtual int weight(int v1, int v2) = 0;

	//获取节点的标号
	virtual int getMark(int v) = 0;

	//设置节点标号
	virtual void setMark(int v, int val) = 0;

	
};


class Graphm :public Graph {    //相邻矩阵实现图
private:
	int numVertex, numEdge;		//顶点数和边数
	int** matrix;				//指针,是图的相邻矩阵
	int* mark;					//存储顶点标志的数组,共getmark与setmark函数使用

public:
	Graphm(int numVert);		//构造函数

	~Graphm();

	void Init(int n);			//初始化这个图
	int n();					//读取顶点数
	int v();					//读取边数目
	int first(int v);			//返回v的第一个邻居
	int next(int v, int w);		//读取v的w之后的下一个邻居节点
	void setEdge(int v1, int v2, int wt);//为有向图设置边
	void setEdge_undirected(int v1,int v2,int wt);//为无向图设置边
	
	void delEdge(int v1, int v2);//删除边

	bool isEdge(int i, int j);	//判断i与j之间是否存在边

	int weight(int v1, int v2);	//读取这条边的权值
	int getMark(int v);			//读取这个顶点的标记值
	void setMark(int v, int val);//设置这个顶点的标记值
	void print();//打印相邻矩阵
	
};

void graphTraverse(Graph& G);//图的遍历函数
void DFS(Graph& G, int v);   //深度优先搜索函数
void BFS(Graph& G, int start,Queue<int>*Q);//广度优先搜索函数
void Dijkstra(Graph* G,int * D,int s);     //Dijkstra算法求最小路径,其中从D返回这个最小路径(D可以是数组) 从s开始找最短路径
int minVerex(Graph* G, int* D);//Dijkstra 函数中用的寻找未访问顶点的最小d值模块,时间复杂度为O(n2)

这里BFS还未作实现,因为需要用到队列,会在代码实现队列后再做这里的内容

 2.源文件 Graph.cpp

#include "Graph.h"

Graphm::Graphm(int numVert)
{
	Init(numVert);
}

Graphm::~Graphm()
{
	delete[] mark;
	for (int i = 0; i < numVertex; i++)//删除相邻矩阵
	{
		delete [] matrix[i];
	}
	delete [] matrix;
}

void Graphm::Init(int n)
{
	numVertex = n;
	numEdge = 0;
	mark = new int[n];						//初始化mark数组
	for (int i = 0; i < numVertex; i++)		//将mark数组的访问情况全部标为未放问
	{
		mark[i] = UNVISITED;
	}

	matrix = (int**) new int* [numVertex];	//构造矩阵,前面的写法是显示类型转换,转换为int**型
	for (int i = 0; i < numVertex; i++)
	{
		matrix[i] = new int[numVertex];
	}

	for (int i = 0; i < numVertex; i++)		//将相邻矩阵全部初始化为0
	{
		for (int j= 0; j < numVertex; j++)
		{
			matrix[i][j] = 0;
		}
	}
}

int Graphm::n()
{
	return numVertex;
}

int Graphm::v()
{
	return numEdge;
}

int Graphm::first(int v)
{
	for (int i = 0; i < numVertex; i++)
	{
		if (matrix[v][i]!=0)
		{
			return i;
		}
	}
	return numVertex;						//如果这一行没有边存在则返回顶点数
}

int Graphm::next(int v, int w)
{
	for (int i = w+1; i <numVertex ; i++)	//从w的下一个开始找有边的顶点
	{
		if (matrix[v][i]!=0)
		{
			return i;
		}
	}
	return numVertex;
}

void Graphm::setEdge(int v1, int v2, int wt)
{
	Assert(wt > 0, "非法的权值");
	if (matrix[v1][v2]==0)
	{
		numEdge++;
	}
	matrix[v1][v2] = wt;
}

void Graphm::setEdge_undirected(int v1, int v2, int wt)
{
	Assert(wt > 0, "非法的权值");
	if (matrix[v1][v2] == 0 && matrix[v2][v1]==0)
	{
		numEdge++;
	}
	matrix[v1][v2] = wt;
	matrix[v2][v1] = wt;

}

void Graphm::delEdge(int v1, int v2)
{
	if (matrix[v1][v2]!=0)
	{
		numEdge--;
	}
	matrix[v1][v2] = 0;
}

bool Graphm::isEdge(int i, int j)
{
	return matrix[i][j]!=0;
}

int Graphm::weight(int v1, int v2)
{
	return matrix[v1][v2];
}

int Graphm::getMark(int v)
{
	return mark[v];
}

void Graphm::setMark(int v, int val)
{
	mark[v] = val;
}

void Graphm::print()
{
	for (int i = 0; i < this->n(); i++)
	{
		for (int j = 0; j < this->n(); j++)
		{
			std::cout << this->matrix[i][j] << " ";
		}
		std::cout << std::endl;
	}
}


void graphTraverse(Graph& G)
{
	
	QueueA<int> Q;
	for (int v = 0; v < G.n(); v++)	 //预处理,将所有节点全部设置为未放问的状态,准备遍历
	{
		G.setMark(v, UNVISITED);
	}
	for (int v = 0; v < G.n(); v++)/*其实是从v = 0的顶点开始遍历,但是for循环的目的是为了检验是否遍历了所有的顶点
									如果有顶点没有被访问,则从这个顶点开始继续进行深度优先遍历	
									*/
	{
		if (G.getMark(v) == UNVISITED)
		{
			std::cout << v << std::endl;//该打印语句用于测试
			BFS(G,v,&Q);				//这个位置可以放  任意 遍历的算法,不只是DFS  BFS也可以
			//DFS(G,v);
		}
	}

}

void DFS(Graph& G, int v)			//而且 用递归的写法相当于用栈,递归调用的过程与栈的原理相似
{
	/*
		这个注释的位置可以放PreVisit()函数,遍历前进行操作
		一些图的遍历要求在深入DFS分支前对当前顶点进行处理
	*/
	G.setMark(v, VISITED);
	for (int w = G.first(v); w < G.n(); w = G.next(v,w))
	{
		if (G.getMark(w) == UNVISITED)
		{	
			std::cout << w << std::endl;//该打印语句用于测试,以便观察DFS所走过的路径
			DFS(G, w);
		}
	}
	/*
		这个注释的位置可以放PostVisit()函数,用于在遍历后进行操作
		一些图要求在处理完DFS分支顶点后再处理当前节点
	*/

}

void BFS(Graph& G, int start,Queue<int> *Q)
{
	int v;
	
	Q->enqueue(start);//从start节点开始遍历,先将start节点入队
	G.setMark(start, VISITED);//访问start节点并将其标记为已访问
	while (Q->length()!=0)
	{
		v = Q->dequeue();//如果队列不为空,取一个队首的元素
		/*
			这里可以放privisit(G,v)函数,以便对访问的v节点进行处理
		*/
		std::cout << v << std::endl;//用来检查bfs的路径
		for (int w = G.first(v); w < G.n(); w = G.next(v,w))//从这个v节点开始,遍历访问他的所有子节点
		{
			if (G.getMark(w) == UNVISITED )
			{
				G.setMark(w, VISITED);
				Q->enqueue(w);//将访问的所有子节点放到队尾,以便最后处理
			}
		}
	}
}

void Dijkstra(Graph* G, int* D, int s)
{
	int  v, w;
	for (int i = 0; i < G->n(); i++)//将所有节点路径数组初始化为无穷大,以便下面进行操作
	{
		D[i] = LIMIT;
	}
	D[s] = 0;//将第一个节点的距离初始化为0,表示从这个点到自己本身路径是0
	for (int i = 0; i < G->n(); i++)
	{
		v = minVerex(G, D);//我们通过这个函数来找出数组中最小的一个节点,下一个处理它
		if (D[v]==LIMIT)//如果数组中连最小的节点的距离都是无穷,那么这个图是无法达到别的顶点的
						   //因此直接结束dijkstra算法
		{
			return;
		}
		G->setMark(v, VISITED);//将找出的最小的v标记
		for ( w = G->first(v); w < G->n(); w = G->next(v,w))//这个for循环里,对v的所有子节点进行扫描
		{
			if ( D[w]>D[v]+G->weight(v,w))//如果发现通过v到达的节点的路径小于直接到达的路径,则在数组中更新改位置的值
			{
				D[w] = D[v] + G->weight(v, w);
			}
		}
	}
}

int minVerex(Graph* G, int* D)//实际上这里面的两个for循环的本质含义是,查找一个数组中元素的最小值
{
	int i = 0, v = -1;
	for ( i = 0; i < G->n(); i++)
	{
		if (G->getMark(i)==UNVISITED)//在所有节点中找到第一个没有被访问的节点然后跳出
		{
			v = i;
			break;
		}
	}
	for ( i++; i < G->n(); i++) //将后面的节点与已取出的一个节点的值进行比较,如果更小,则更新v的值
	{
		if (G->getMark(i) == UNVISITED && (D[i] < D[v]))
		{
			v = i;
		}
	}
	return v;
}

void Graph::Assert(bool a, std::string b)
{
	if (!a)
	{
		std::cout << b << std::endl;
	}
}

对上述代码进行测试

按照课本p256的图进行测试,将ABCDEF节点分别作为0,1,2,3,4,5

测试代码如下

#include"Graph.h"

int main() {
	Graphm A(6);	//创建一个6个节点的图
	A.setEdge(0, 2, 1);
	A.setEdge(0,4,1);
	A.setEdge(4, 5, 1);
	A.setEdge(2, 3, 1);
	A.setEdge(3, 5, 1);
	A.setEdge(2, 5, 1);
	A.setEdge(5, 1, 1);
	A.setEdge(1, 2, 1);
	A.print();
	graphTraverse(A);

	return 0;
}

运行结果为:表示以0,2,3,5,1,4的路径进行深度优先搜索


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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值