图
1.介绍:
图是一种比线性表和树更为复杂的数据结构。图的应用极为广泛,已渗入诸如物理、化学、通信、计算机,以及数学等领域。图反映的是数据之间一对多的关系。
2.定义:
定义
图(Graph)G由两个集合V和E组成,记为G=(V,E),其中V是顶点的有穷非空集合,E是V中顶点偶对的有穷集合,这些顶点偶对称为边。V(G)和E(G)通常分别表示图G的顶点集合和边集合,E(G)可以为空集。若E(G)为空,则图G只有顶点而没有边。
对于图G,若边集E(G)为有向边的集合,则称该图为有向图;若边集E(G)为无向边的集合,则称该图为无向图。
有向图
在有向图中,顶点对<x,y>是有序的,它称为从顶点x到顶点y的一条有向边。因此,<x,y>与<y,x>是不同的两条边。
无向图
在无向图中,顶点对(x,y)是无序的,它称为与顶点x和顶点y相关联的一条边。这条边没有特定的方向,(x,y)与(y,x)是同一条边。
为了有别于有向图,否向图的顶点对用对圆括写括起来。
3.基本术语
子图:
假设有两个图G=(V,E)和G1=(V1,E1),如果V1包含于V,E1包含于E,则称G1为G的子图。
完全图:
任意两个顶点都有一条边相连。(指的是无向图)
无向完全图和有向完全图:对于无向图,若具有n(n-1)/2条边,则称为无向完全图;对于有向图,若具有n(n-1)条弧,则称为有向完全图。
稀疏图和稠密图:
有很少条边或弧(如e<nlogn)的图称为稀疏图,反之称为稠密图
权和网:
在实际应用中,每条边可以标上具有某种含义的数值,该数值称为该边上的权,这些权可以表示从一个顶点到另一个顶点的距离或耗费。这种带权的图通常称为网。
邻接点:
对于无向图G,如果图的边(v, v1)E,则称顶点v和v1互为邻接点,即v和v1相邻接。关联(依附):边/弧与顶点之间的关系;边(v, v’)依附于顶点v和v1,或者说边(v, v1)与顶点v和v1相关联。
度:
与顶点相关联的边的数目,记为TD(v);在有向图中,顶点的度等于该顶点的入度与出度之和,顶点v的入度是以v为终点的有向边的条数,记作ID(v),顶点的出度是以v为始点的有向边的条数,记作OD(v)。
路径和路径长度:
路径:接续的边构成的顶点序列
路径长度:路径上边或弧的数目/权值之和
回路(环):
第一个顶点和最后一个顶点相同的路径
简单路径和简单回路(简单环):
简单路径:除路径起点和终点可以相同外,其余顶点均不相同的路径
简单回路(简单环):除路径起点和终点相同外,其余顶点均不相同的路径。
连通,连通图,连通分量:
连通:两个顶点之间有路径,则称这两个顶点是连通的。
连通图:对于图中任意两个顶点都是连通的,则称该图是连通图
连通分量:指的是无向图中的极大连通子图(极大连通子图意思为该子图是G的连通子图,将G的任何不在该子图中的顶点加入,子图不再连通)
强连通图和强连通分量:
强连通图:在有向图中对于任意两个顶点是连通的,则称该图是强连通图。
强连通分量:有向图的极大强连通子图
连通图的生成树:
包含无向图所有顶点的极小连通子图
有向树和生成森林
有向树:有一个顶点的入度为0,其余顶点的入度均为1的有向图称为有向树
生成森林:对于非连通图,由各个连通分量的生成树的集合
4.案例引入:
六度空间理论:你和任何一个陌生人之间所间隔的人不会超过6个,也就是说,最多通过6个中间人你就可以认识任何一个陌生人。
5.存储结构:
邻接矩阵表示法:
定义
邻接矩阵是表示顶点之间相邻关系的矩阵
通过对A[ i ][ j ]赋值为权值(网)或为 1 表示顶点 i 和 j 之间有边
若没有边则赋值为MAX_INT(网)或为 0。
图的邻接矩阵存储表示代码实现:
//----------------------------图的邻接矩阵存储表示---------------------------------
#define MaxInt 32767 //表示极大值
#define MVNum 100 //最大顶点数
typedef char VerTexType; //假设顶点的数据结构类型为字符型
typedef int ArcType; //假设边的权值类型为整型
typedef struct
{
VerTexType vex[MVNum]; //顶点表
ArcType arcs[MVNum][MVNum]; //邻接矩阵
int vexnum, arcnum; //顶点数和边数
}AMgraph;
采用邻接矩阵表示法创建无向网
//-------------------------采用邻接矩阵表示法创建无向网-----------------------------
// 若图G中存在顶点u,则返回该顶点在图中的位置;否则返回其它信息;
int LocateVex(OLGraph G, VertexType u) {
int i;
for (i = 0; i < G.vexnum; ++i)
if (u == G.xList[i].data)
return i;
return -1;
}
void CreateUDN(AMgraph& G)
{
cin >> G.vexnum >> G.arcnum; //输入总顶点和总边数
for (int i=0;i<G.vexnum;++i)
cin >> G.vex[i]; //输入顶点信息
for (int i = 0; i < G.arcnum; i++)
for (int j = 0; j < G.arcnum; j++)
G.arcs[i][j] = MaxInt; //初始化边的权值,均设置为极大值Maxint
for (int k = 0; k < G.arcnum; k++) //构造邻接矩阵
{
int v1, v2,w;
cin >> v1 >> v2 >> w; //输入一条边依附的顶点及权值
int i = LocateVex(G, v1); //确定v1,v2的下标
int j = LocateVex(G, v2);
G.arcs[i][j] = G.arcs[j][i]= w; //置<v1,v2>及其对称边的权值为w
}
}
邻接表示表示法
定义
邻接表(Adjacency List)是图的一种链式存储结构。在邻接表中,对图中每个顶点 Vi 建立一个单链表,把与v相邻接的顶点放在这个链表中。邻接表中每个单链表的第一个结点存放有郑点的信息,把这一结点看成链表的表头,其余结点存放有关边的信息,这样邻接表便由两部分组成:表头结点表和边表。
(1)表头结点表:由所有表头结点以顺序结构的形式存储,以便可以随机访问任一顶点边链表。表头结点包括数据域(data)和链域(frstarc)两部分。其中,数据域用于存储顶点u的名称或其他有关信息;链域用于指向链表中第一个结点(与顶点v邻粮第一个邻接点)。
(2)边表:由表示图中顶点间关系的n个边链表组成。边链表中边结点包括邻接(adjvex)、数据域(info)和链域(nextarc)3个部分,其中,邻接指示与顶点v邻接的点在图中的位置;数据域存储和边相关的信息,如权值等;链域指示与v邻接的下一条边的结点。
图的邻接表存储表示代码实现:
//-----------------------------图的邻接表存储表示----------------------------------
#define MVNum 100
typedef char VerTexType;
typedef struct ArcNode //边界点
{
int adjvex; //该边指向的顶点的位置(下标)
struct ArcNode* nextarc;//指向下一条边的指针
// Otherinfo info; //其他信息(这里省去)
}ArcNode;
typedef struct VNode //顶点节点
{
VerTexType data; //顶点信息(char类型)
ArcNode* firstarc; //指向顶点的第一条边
}VNode,AdjList[MVNum]; //AdjList表示邻接链表类型
typedef struct
{
AdjList vertice;
int vexnum, arcnum; //图的当前顶点数和边数
}ALGraph;
采用邻接表表示法创建无向网
//-------------------------采用邻接表表示法创建无向网-----------------------------
void CreateUDG(ALGraph& G) //用邻接表创建无向图
{
cin >> G.vexnum >> G.arcnum; //输入总顶点数,总边数
for (int i = 0; i < G.vexnum; i++)
{
cin >> G.vertice[i].data; //输入顶点信息
G.vertice[i].firstarc = NULL;//初始化
}
for (int k = 0; k < G.arcnum; k++)
{
char v1, v2; //输入边的两个顶点
cin >> v1 >> v2;
int i, j;
i = LocateVex(G, v1); //定位v1,v2的下标
j = LocateVex(G, v2);
//将边处理
G.vertice[i].firstarc = new ArcNode{j, G.vertice[i].firstarc};
G.vertice[j].firstarc = new ArcNode{i, G.vertice[j].firstarc};
}
}
十字链表
定义
十字链表(Orthogonal List)是有向图的另一种链式存储结构,可以看成将有向图的邻接表和逆邻接表结合起来得到的一种链表。
在弧结点中有5个城:其中尾域(tailvex)和头域(headvex)分别指本弧尾孤头这两个顶点在图中的位置,链域hlink指向弧头相同的不条弧、而链域tlink指向远尾相同的下一条弧,info域指向该弧的相关信息。弧头相同的弧在同一链表上,弧尾相同的弧也在同一链表上它们的头结点即顶点结点,由3个域组成:其中data存储和顶点相关的信息,如顶点的名称等;firstin和firstout为两个链域,分别指向以该顶点为弧头或弧尾的第一个弧结点。
代码
//---------------------------有向图的十字链表存储表示-----------------------------
typedef string InfoType;
typedef string VertexType;
typedef struct ArcBox {
int tailVex; //该弧的尾顶点的位置
int headVex; //该弧的头顶点的位置
struct ArcBox* hLink; //弧头相同的弧的链域
static ArcBox* tLink; //弧尾相同的弧的链域
InfoType info; //弧的相关信息
}ArcBox;
typedef struct VexNode {
VertexType data; //顶点的数据域
ArcBox* firstIn; //指向该顶点的第一条入弧
ArcBox* firstOut; //指向该顶点的第一条出弧
}VexNode;
#define MAX_VERTEX_BUM 20
typedef struct {
VexNode xList[MAX_VERTEX_BUM]; //表头向量
int vexnum; //图的顶点个数
int arcnum; //图的边数
}OLGraph;
邻接多重表
定义
邻接多重表的结构和十字链表类似。在邻接多重表中,每一条边用一个结点表示,其中,mark为标志域,可用以标记该条边是否被搜索过;ivex和jvex为该边依附的两个顶点在图中的位置;ilink指向下一条依附于顶点ivex的边;jlink指向下条依附于顶点jvex的边,info为指向和边相关的各种信息的指针域。当需要对边增删查改的时候,用邻接多重表就更加合适。
无向图的邻接多重表存储表示
//-------------------------无向图的邻接多重表存储表示------------------------------
#define MAX_VERTEX_NUM 20
typedef enum{unvisited,visited}VisitIf;
typedef struct Ebox
{
VisitIf mark; //访问标记
int ivex, jvex; //该边依附的两个顶点的位置
struct Ebox* ilink, * jlink; //分别指向依附这两个顶点的下一条边
InfoType* info; //该边信息指针
}Ebox;
typedef struct Vexbox
{
VertexType data; //指向第一条依附这两个顶点的下一条边
Ebox* firstedge;
}Vexbox;
typedef struct
{
Vexbox adjmulist[MAX_VERTEX_NUM];//无向图的当前顶点数和边数
int vexnum, edgenum;
}AMLGraph;
6.遍历:
深度优先搜索
特点
不断访问新的顶点,并记录访问过的顶点,直到全部访问再返回原来已被访问过的顶点(一条路走到黑)直到所有顶点都被访问过,搜索结束
代码实现
采用邻接矩阵表示法图的深度优先搜索遍历
//----------------------采用邻接矩阵表示法图的深度优先搜索遍历----------------------
void DFS_AM(AMgraph G, int v)
{ //图G为邻接矩阵类型,从第v个顶点出发深度优先搜索遍历图G
cout << G.vex[v]<<" "; //访问第v个顶点
visited[v] = true; //置访问标记数组相应分量值为true
for (int i = 0; i < G.vexnum; i++) //依次检查邻接矩阵v所在的行
{
if ((G.arcs[v][i] != MaxInt) && (!visited[i]))
DFS_AM(G, i); //如果未被访问则递归调用DFS_AM()
}
}
采用邻接表表示法图的深度优先搜索遍历
//-----------------采用邻接表表示法图的深度优先搜索遍历-----------------------------
void DFS_AL(ALGraph G, int v)
{ //图G为邻接表类型,从第v个顶点出发
cout << G.vertice[v].data<<" "; //访问第v个顶点
visited[v] = true;
ArcNode* p = G.vertice[v].firstarc; //p指向v的边链表的第一个结点
while (p)
{
int w = p->adjvex; //表示w是v的邻接点
if (!visited[w])DFS_AL(G, w); //如果w未被访问,则递归调用函数
p = p->nextarc; //p指向下一个边结点
}
}
广度优先搜索
特点
尽可能先对横向进行搜索,先访问的顶点其邻接点先被访问
代码实现
采用邻接矩阵表示法图的深度优先搜索遍历
//----------------------采用邻接矩阵表示法图的广度优先搜索遍历----------------------
void BFS_AL(ALGraph G, int v)
{
cout << G.vertice[v].data << " "; //访问第v个顶点
visited[v] = true;
queue<int>q;
q.push(v);
while (q.size())
{
int u = q.front();
q.pop();
for (ArcNode* p = G.vertice[u].firstarc; p != NULL; p = p->nextarc)
{
int w = p->adjvex;
if (!visited[w])
{
cout << G.vertice[w].data << " ";
visited[w] = true;
q.push(w);
}
}
}
}
采用邻接表表示法图的广度优先搜索遍历
void BFS_AL(ALGraph G, int v)
{
cout << G.vertice[v].data << " "; //访问第v个顶点
visited[v] = true;
queue<int>q;
q.push(v);
while (q.size())
{
int u = q.front();
q.pop();
for (ArcNode* p = G.vertice[u].firstarc; p != NULL; p = p->nextarc)
{
int w = p->adjvex;
if (!visited[w])
{
cout << G.vertice[w].data << " ";
visited[w] = true;
q.push(w);
}
}
}
}
7.总代码
存储方式二选一输入图即可获得其深度优先和广度优先的遍历序列
#include<iostream>
#include<algorithm>
#include<string>
#include<queue>
using namespace std;
//-----------图的邻接矩阵存储表示---------
#define MaxInt 32767 //表示极大值
#define MVNum 100 //最大顶点数
typedef string VerTexType; //假设顶点的数据结构类型为string型
typedef int ArcType; //假设边的权值类型为整型
typedef struct
{
VerTexType vex[MVNum]; //顶点表
ArcType arcs[MVNum][MVNum]; //邻接矩阵
int vexnum, arcnum; //顶点数和边数
}AMgraph;
int LocateVex(AMgraph G,VerTexType v)
{
int i;
for (i = 0; i < G.vexnum; i++)
if (G.vex[i] == v)return i;
return -1;
}
void CreateUDN(AMgraph& G) //采用邻接矩阵表示法,创建无向图G
{
cout << "请输入总顶点和总边数:";
cin >> G.vexnum >> G.arcnum; //输入总顶点和总边数
for (int i=0;i<G.vexnum;++i)
{
cout<<"请输入顶点信息:";
cin >> G.vex[i]; //输入顶点信息
}
for (int i = 0; i < G.arcnum; i++)
for (int j = 0; j < G.arcnum; j++)
G.arcs[i][j] = MaxInt; //初始化边的权值,均设置为极大值MaxInt
for (int k = 0; k < G.arcnum; k++) //构造邻接矩阵
{
int w;
string v1, v2;
cout << "请输入一条边依附的顶点及这条边的权值:";
cin >> v1 >> v2; //输入一条边依附的顶点
cin >> w; //及权值
int i = LocateVex(G, v1); //确定v1,v2的下标
int j = LocateVex(G, v2);
G.arcs[i][j] = G.arcs[j][i]= w; //置<v1,v2>及其对称边的权值为w
}
}
bool visited[MVNum] = {0}; //标记辅助数组,标记顶点是否访问过
void DFS_AM(AMgraph G, int v)
{ //图G为邻接矩阵类型,从第v个顶点出发深度优先搜索遍历图G
cout << G.vex[v]<<" "; //访问第v个顶点
visited[v] = true; //置访问标记数组相应分量值为true
for (int i = 0; i < G.vexnum; i++) //依次检查邻接矩阵v所在的行
{
if ((G.arcs[v][i] != MaxInt) && (!visited[i]))
DFS_AM(G, i); //如果未被访问则递归调用DFS_AM()
}
}
void BFS_AM(AMgraph G, int v)
{
cout << G.vex[v] << " ";
visited[v] = true;
queue<int>q;
q.push(v);
while (q.size())
{
int u = q.front();
q.pop();
for (int w=0;w<G.vexnum;w++)
{
if ((!visited[w]) && (G.arcs[u][w] != MaxInt))
{
cout << G.vex[w]<< " ";
visited[w] = true;
q.push(w);
}
}
}
}
//----------图的邻接表存储表示---------------
typedef struct ArcNode //边界点
{
int adjvex; //该边指向的顶点的位置(下标)
struct ArcNode* nextarc;//指向下一条边的指针
}ArcNode;
typedef struct VNode //顶点节点
{
VerTexType data; //顶点信息(char类型)
ArcNode* firstarc; //指向顶点的第一条边
}VNode,AdjList[MVNum]; //AdjList表示邻接链表类型
typedef struct
{
AdjList vertice;
int vexnum, arcnum; //图的当前顶点数和边数
}ALGraph;
int LocateVex_(ALGraph G, VerTexType v)
{
for (int i = 0; i < G.vexnum; i++)
{
if (G.vertice[i].data == v)
return i;
}
return -1;
}
void CreateUDG(ALGraph& G) //用邻接表创建无向图
{
cout << "请输入总顶点和总边数:";
cin >> G.vexnum >> G.arcnum; //输入总顶点数,总边数
for (int i = 0; i < G.vexnum; i++)
{
cout << "请输入顶点信息:";
cin >> G.vertice[i].data; //输入顶点信息
G.vertice[i].firstarc = NULL;//初始化
}
for (int k = 0; k < G.arcnum; k++)
{
string v1, v2; //输入边的两个顶点
cout << "请输入一条边依附的顶点:";
cin >> v1 >> v2;
int i, j;
i = LocateVex_(G, v1); //定位v1,v2的下标
j = LocateVex_(G, v2);
//将边处理
ArcNode* p1 = new ArcNode;
p1->adjvex = j;
p1->nextarc = G.vertice[i].firstarc;
G.vertice[i].firstarc = p1;
ArcNode* p2 = new ArcNode;
p2->adjvex = i;
p2->nextarc = G.vertice[j].firstarc;
G.vertice[j].firstarc = p2;
}
}
void DFS_AL(ALGraph G, int v)
{ //图G为邻接表类型,从第v个顶点出发
cout << G.vertice[v].data<<" "; //访问第v个顶点
visited[v] = true;
ArcNode* p = G.vertice[v].firstarc; //p指向v的边链表的第一个结点
while (p)
{
int w = p->adjvex; //表示w是v的邻接点
if (!visited[w])DFS_AL(G, w); //如果w未被访问,则递归调用函数
p = p->nextarc; //p指向下一个边结点
}
}
void BFS_AL(ALGraph G, int v)
{
cout << G.vertice[v].data << " "; //访问第v个顶点
visited[v] = true;
queue<int>q;
q.push(v);
while (q.size())
{
int u = q.front();
q.pop();
for (ArcNode* p = G.vertice[u].firstarc; p != NULL; p = p->nextarc)
{
int w = p->adjvex;
if (!visited[w])
{
cout << G.vertice[w].data << " ";
visited[w] = true;
q.push(w);
}
}
}
}
void InitVis() //由于此辅助数组需多次使用,则每次遍历完后要重新进行初始化
{
for (int i = 0; i < MVNum; i++)
visited[i] = false;
}
int main()
{
AMgraph G1;
cout << "采用邻接矩阵表示法创建图:\n";
CreateUDN(G1);
cout << "从第一个顶点开始其深度后的深度优先树为:";
DFS_AM(G1,0);
cout << endl;
InitVis();
cout << "从第一个顶点开始其深度后的广度优先树为:";
BFS_AM(G1, 0);
/*ALGraph G2;
cout << "采用邻接表表示法创建图:\n";
CreateUDG(G2);
cout << "从第一个顶点开始其深度后的深度优先树为:";
DFS_AL(G2,0);
cout << endl;
InitVis();
cout << "从第一个顶点开始其深度后的广度优先树为:";
BFS_AL(G2, 0);*/
return 0;
}