1. 二维动态数组
一般图结构创建是根据邻接矩阵的定义,采用链表的的方式实现。对于这里的邻接矩阵借鉴了存储图像数据的动态二维数组结构,他的构造和析构如下:
const int num = 5;
//分配空间
int **array = new int* [num];
for (int i=0; i<num; i++)
{
<span style="white-space:pre"> </span>array[i] = new int[num];
}
//释放资源
for (int i=0; i<num; i++)
{
delete[] array[i];
}
delete []array;
在C++11标准里面可以采用std::tr1::shared_ptr 来管理这片内存,特别是当涉及到跨文件进行处理的时候,因忘记析构而内存泄露的风险会减小很多。
2. 图及其遍历
2.1 基于链式存储的图数据结构
首先要一个数组,里面包含图的边数和顶点数。每个数组元素里面包含数据域和指向连接表头结点的头指针。
//基于链式存储的邻接表图数据结构
const int MAXVEX = 20; //定义图的最大顶点数目
typedef char DataType;
typedef struct arc //定义表结点类型
{
int adjvex; //顶点序号
int weight; //权值
struct arc* next; //指向下一个邻接点的指针
}ArcNode, *PArcNode;
typedef struct //顶点表结点类型
{
DataType data; //顶点信息
ArcNode* head; //编表头指针
}VexNode;
typedef struct //邻接表类型
{
VexNode list[MAXVEX]; //顶点表
int edges, vexs; //用于统计图的顶点与边数
}VGraph, *AdjList;
2.2 图的创建
图的创建是通过二维for循环实现的,外层for循环实现头结点和顶结点数据域的初始化,内层for循环实现与每个相连的顶点的创建(链表实现)和设置权值。
/************************************************************************/
/*
创建图结构
输入参数
grap: 邻接表类型指针
m: 邻接矩阵的数据
n: 顶点的总数
返回值
成功返回true 失败返回false
*/
/************************************************************************/
bool CGraphLink::CreateGraph(VGraph* grap, int** m, int n)
{
if ((nullptr==grap) || (nullptr==m) || (0>n))
{
return false;
}
grap->edges = 0; //初始化边的总数目和顶点的数目为0
grap->vexs = n;
for (int i=0; i<n; i++)
{
grap->list[i].data = 'A'+i; //给顶点信息赋值
grap->list[i].head = nullptr; //初始化头指针为空
for (int j=0; j<n; j++)
{
if (0 != m[i][j]) //如果二维数组m中的对应元素非0
{
ArcNode *p = new ArcNode;
if (nullptr == p) //分配内存错误
{
std::cout << "\n分配内存错误!\n";
return false;
}
p->adjvex = j; //设置顶点序号
p->next = grap->list[i].head; //从链表头部插入新结点
p->weight = m[i][j]; //设置权值
grap->list[i].head = p; //设置头指针指向p
grap->edges++; //边数加1
}
}
}
return true;
}
2.3 深度优先遍历
/************************************************************************/
/*
连通图的深度优先遍历方法 递归实现
grap: 输入的图结构
k: 起始的顶点序号
visited: 已经访问过的顶点的存储数组
*/
/************************************************************************/
void CGraphLink::DFS(VGraph* grap, int k, int* visited)
{
std::cout << grap->list[k].data; //显示顶点数据
visited[k] = 1; //标记已经访问了的顶点
int u = GetFirst(grap, k); //获取头结点指向的下一个顶点的编号
while (-1 != u) //还有节点存在
{
if (0 == visited[u]) //判断顶点是否被访问过
{
DFS(grap, u, visited); //若没有被访问过递归调用进行访问
}
u = GetNext(grap, k, u); //获取下一个邻接点
}
}
/************************************************************************/
/*
获取图邻接表的第k+1个的第一个顶点编号
grap: 输入的图结构
k: 第K+1个邻接链表,顶点的序号
返回值:
滴K+1个邻接链表的第一个值 若为空则返回为-1
*/
/************************************************************************/
int CGraphLink::GetFirst(VGraph* grap, int k)
{
if ((k<0) || (k>grap->vexs))
{
std::cout << "\n输入的K超出了范围!\n";
return -1;
}
if (nullptr == grap->list[k].head)
{
std::cout << "\n需要获取的连接表头指针为空!\n";
return -1;
}
else
{
return grap->list[k].head->adjvex;
}
}
/************************************************************************/
/*
在顶点k的边表中获取下一个邻接点 先在边表中找u,找到u之后,u的next指针域即指向下一个邻接点
*/
/************************************************************************/
int CGraphLink::GetNext(VGraph* grap, int k, int u)
{
if ((k<0) || (k>grap->vexs) || (u<0) || (u>grap->vexs))
{
std::cout << "\n输入的K超出了范围!\n";
return -1;
}
PArcNode p = grap->list[k].head;
while ((nullptr!=p->next) && (p->adjvex!=u))
{
p = p->next;
}
if (nullptr == p->next)
{
return -1;
}
else
{
return p->next->adjvex;
}
}
2.4 广度优先遍历
/************************************************************************/
/*
连通图数据结构的广度优先遍历 #_# 非连通图的遍历参照广度优先非连通图的遍历实现 #_#
grap: 需要广度优先遍历的图结构
k: 广度优先遍历的其实顶点
返回值
连通图遍历成功返回true 失败返回false
*/
/************************************************************************/
bool CGraphLink::BFS(VGraph* grap, int k, const int num)
{
//前期错误检查
if ((nullptr==grap) || (0>k))
{
std::cout << "\nSFS error occurred\n";
return false;
}
if (nullptr == grap->list[k].head)
{
std::cout << "\nBFS pos error\n";
return false;
}
int *visited = new int[num]; //构造一个访问与否标志位判别数组 0(没访问过)1(访问过)
memset(visited, 0, sizeof(int)*num); //将数组清零 也是初始化
std::vector<VexNode> vec; //定义一个向量 存储遍历到的顶点标号
vec.push_back(grap->list[k]); //将初始的顶点 压入到向量中 类型为VexNode
std::cout << "\n" << vec[k].data; //显示顶点处的信息 VecNode里面
visited[k] = 1; //设置初始位置的访问标志位为1
PArcNode p = nullptr; //定义一个遍历边链表的中间变量
/************************************************************************/
/*
遍历的思想(个人理解):
图结构的广度优先遍历与树结构的层次遍历具有相似的地方,下面的方法也是借鉴树结构的层次得来的。(回顾:在树的层次遍历中定义了cur和end两个
向量当前位置和长度标志变量,在第一层while中判别cur<vec.size();是遍历结束的条件也就是向量的元素个数不再增加。之后vec中cur到end元素的
左右孩子指针增加vec中元素的个数,直到最后遍历完成,vec中的元素不再增加cur=vec.size())。而在图结构中采用了回溯思想,每次取向量的最后
一个元素的头指针(访问过的顶点就删除出向量),以这个头指针为起点依次将没有走过的顶点存入向量中,也将数组中对应标志位置1。直到向量中的
元素为空,则图的广度优先遍历完成。
*/
/************************************************************************/
while (!vec.empty()) //判断向量是否为空 若为非空则继续遍历
{
VexNode q = vec.back(); //取出向量最后的顶点
p = q.head; //将它的头结点的指针取出
vec.pop_back(); //删除最后向量的最后一个元素 回溯思想
while (p) //判断p是不是空指针
{
if (0 == visited[p->adjvex]) //判断p中存储的对应顶点编号有没有被访问过
{
std::cout << "\n" << grap->list[p->adjvex].data; //显示对应顶点序号处 顶点的信息 data的值
visited[p->adjvex] = 1; //将访问过的顶点编号处的标志位赋值为1 (顶点标号与list里面的标号对应N*N的矩阵)
vec.push_back(grap->list[p->adjvex]); //将对应序号的顶点(VexNode)存入向量
}
p = p->next; //指向下一个顶点编号
}
}
return true;
}