学习数据结构最重要的就是落实到代码
下面是数据结构图的基本实现的代码,参考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的路径进行深度优先搜索