一、图的逻辑结构
1、在线性表中,元素个数可以为零,称为空表;
在树中,结点个数可以为零,称为空树;
在图中,顶点个数不能为零,但可以没有边。
2、一般情况下,图中的路径不惟一。
3、连通分量:非连通图的极大连通子图称为连通分量。
1).含有极大顶点数;
2). 依附于这些顶点的所有边。
二、图的抽象数据类型定义
1、图的遍历操作
从图中某一顶点出发,对图中所有顶点访问一次且仅访问一次。
-
选取遍历的起始顶点:从编号小的顶点开始
-
在线性表中,数据元素在表中的编号就是元素在序列中的位置,因而其编号是唯一的;
-
在树中,将结点按层序编号,由于树具有层次性,因而其层序编号也是唯一的;
-
在图中,任何两个顶点之间都可能存在边,顶点是没有确定的先后次序的,所以,顶点的编号不唯一。
-
为了定义操作的方便,将图中的顶点按任意顺序排列起来,比如,按顶点的存储顺序。
-
多次调用从某顶点出发遍历图的算法,解决从某个起点始可能到达不了所有其它顶点的问题
-
因图中可能存在回路,某些顶点可能会被重复访问。可附设访问标志数组visited[n] ,来避免遍历不会因回路而陷入死循环
-
在图中,一个顶点可以和其它多个顶点相连,当这样的顶点访问过后,使用深度优先遍历和广度优先遍历,选取下一个要访问的顶点
2、图的存储结构及其实现
基本思想: -
用一个一维数组存储图中顶点的信息
-
用一个二维数组(称为邻接矩阵)存储图中各顶点之间的邻接关系。
3、构造函数
template <class T>
MGraph::MGraph(T a[ ], int n, int e) {
//确定图的顶点个数和边的个数:
vertexNum=n; arcNum=e;
for (i=0; i<vertexNum; i++)
//输入顶点信息存储在一维数组vertex中:
vertex[i]=a[i];
for (i=0; i<vertexNum; i++) //初始化邻接矩阵
for (j=0; j<vertexNum; j++)
//初始化邻接矩阵:
arc[i][j]=0;
for (k=0; k<arcNum; k++) {
//依次输入每条边存储在邻接矩阵arc中:
//输入边依附的两个顶点的序号i, j:
cin>>i>>j; //边依附的两个顶点的序号
//将邻接矩阵的第i行第j列的元素值置为1:
//将邻接矩阵的第j行第i列的元素值置为1:
arc[i][j]=1; arc[j][i]=1; //置有边标志
}
}
4、深度优先遍历
int visited[MaxSize];
template <class T>
void MGraph::DFSTraverse(int v){
//访问顶点v:
cout<<vertex[v]; visited [v]=1;
for (j=0; j<vertexNum; j++)
//从v的未被访问的邻接点中选取一个顶点w,从w出发进行深度优先遍历
if (arc[v][j]==1 && visited[j]==0)
//重复上述两步,直至图中所有和v有路径相通的顶点都被访问到
//递归定义
: DFSTraverse( j );
}
5、广度优先遍历
int visited[MaxSize];
template <class T>
void MGraph::BFSTraverse(int v){
front=rear=-1; //假设采用顺序队列且不会发生溢出
// 访问顶点v:
int Q[MaxSize]; cout<<vertex[v]; visited[v]=1; Q[++rear]=v;
//依次访问v的各个未被访问的邻接点v1, v2, …, vk:
while (front!=rear) {
v=Q[++front];
for (j=0; j<vertexNum; j++)
//分别从v1,v2,…,vk出发依次访问它们未被访问的邻接点,并使“先被访问顶点的邻接点”先于“后被访问顶点的邻接点”被访问。
//直至图中所有与顶点v有路径相通的顶点都被访问到
if (arc[v][j]==1 && visited[j]==0 ) {
cout<<vertex[j]; visited[j]=1; Q[++rear]=j;
}
}
}
6、增加一个顶点
在存储顶点的一维数组中插入该顶点的信息,
在邻接矩阵中插入一行、一列。
template <class T>
void MGraph<T>::InsertVex(int num,T name) {
if ( num<0|| num>vertexNum) throw "位置";
int row, col, numv;
numv = vertexNum-1;
vertexNum++;
for(int i=numv;i>=num;i--) vertex[i++]=vertex[i];
vertex[num]=name;
for(row=numv;row>=0;row--) {所有行上num列之后的列后移,增加一列,
for(col=numv;col>=num;col--) arc[row][col+1]=arc[row][col];
arc[row][num]=0;
}
for(row=numv;row>=num;row--)
for(col=0;col<=numv+1;col++) arc[row+1][col]=arc[row][col];
for(col=0;col<vertexNum;col++) arc[num][col]=0;
}
7、删除一个顶点
在存储顶点的一维数组中删除该顶点的信息
在邻接矩阵中删除一行、一列
template <class T> void MGraph<T>::DeleteVex(int pos){
if ( pos<0|| pos>MaxSize) throw "位置";
int row, col;
int numv=vertexNum;
for(int i=pos;i<numv;i++) vertex[i]=vertex[i+1];
vertexNum--;
for(row=0;row<numv;row++) { //删除一列
for(col=pos;col<numv;col++) arc[row][col]=arc[row][col+1];
}
for(row=pos;row<numv;row++)
for(col=0;col<numv;col++)
arc[row][col]=arc[row+1][col];
}
}
8、增加一条边
修改相应的矩阵元素的值
tmplate <class T>
void MGraph<T>::InsertArc(int i, int j)
{
if ( i>MaxSize|| j>MaxSize) throw "位置";
arc[i][j]=1;
arc[j][i]=1;
}
9、删除一条边
修改相应的矩阵元素的值
template <class T>
void MGraph<T>::DeleteArc(int i, int j)
{
if ( i>MaxSize|| j>MaxSize) throw "位置";
arc[i][j]=arc[j][i]=0;
}
10、邻接表
邻接表有两种结点结构:顶点表结点和边表结点。
vertex:数据域,存放顶点信息。
firstedge:指针域,指向边表中第一个结点。
adjvex:邻接点域,边的终点在顶点表中的下标。
next:指针域,指向边表中的下一个结点。
1)、定义邻接表结点
struct ArcNode{
int adjvex;
ArcNode *next;
};
template
struct VertexNode{
T vertex;
ArcNode *firstedge;
};
2)、构造函数
template <class T>
ALGraph::ALGraph(T a[ ], int n, int e)
{
vertexNum=n; arcNum=e;
for (i=0; i<vertexNum; i++)
{
adjlist[i].vertex=a[i];
adjlist[i].firstedge=NULL;
}
3)、深度优先遍历
template <class T>
void ALGraph::DFSTraverse(int v){
cout<<adjlist[v].vertex; visited[v]=1;
p=adjlist[v].firstedge;
while (p!=NULL) {
j=p->adjvex;
if (visited[j]==0) DFSTraverse(j);
p=p->next;
}
}
4)、广度优先遍历
template <class T>
void ALGraph::BFSTraverse(int v){
front=rear=-1;
cout<<adjlist[v].vertex; visited[v]=1; Q[++rear]=v;
while (front!=rear) {
v=Q[++front]; p=adjlist[v].firstedge;
while (p!=NULL) {
j= p->adjvex;
if (visited[j]==0) {
cout<<adjlist[j].vertex; visited[j]=1; Q[++rear]=j;
}
p=p->next;
}
}
}
三、最小生成树
1、普里姆(Prim)算法
- 数组lowcost[n]:用来保存集合V-U中各顶点与集合U中顶点最短边的权值,lowcost[v]=0表示顶点v已加入最小生成树中;
- 数组adjvex[n]:用来保存该边所依附的(集合V-U中各顶点与集合U中顶点的最短边)集合U中的顶点。
void prime(MGraph G){
for(int i=1;i<G.vertexNu;i++){
// 初始化两个辅助数组lowcost(=arc[0][i])和adjvex(=0)(0是始点):
lowcost[i]=G.arc[0][i]; adjvex[i]=0;
}
lowcost[0]=0;
//输出顶点u0,将顶点u0加入集合U中:
for(i=1;i<G.vertexNum;i+++){
k=MinEdge(lowcost,G.vertexNum)
cout<<K<<adjvex[k]<<lowcost[k];
lowcost[k]=0;
// 重复执行下列操作n-1次:
for(j=1;j<G.vertexNum;j++)
//在lowcost中选取最短边(lowcost[k]),取对应的顶点序号k:
//输出顶点k和对应的权值:
//将顶点k加入集合U中(lowcost[k]=0):
//调整数组lowcost和adjvex:
if((G.arc[k][j]<lowcost[j]){
lowcost[j]=G.arc[k][j];
arcvex[j]=k;
}
}
}
2、克鲁斯卡尔(Kruskal)算法
- 初始化:U=V; TE={ };
- 循环直到T中的连通分量个数为1
2.1 在E中寻找最短边(u,v);
2.2 如果顶点u、v位于T的两个不同连通分量,则
2.2.1 将边(u,v)并入TE;
2.2.2 将这两个连通分量合并为一个;
2.3 在E中标记边(u,v),使得(u,v)不参加后续最短边的选取;
int main(){
int arcNum, int vertexNum;
EdgeNode *edge;
int *parent;
cout<<"please input the number of vertexNum:"; cin>>vertexNum;
cout<<"please input the number of edges:"; cin>>arcNum;
edge=new EdgeNode[arcNum]; parent=new int[vertexNum];
for(int i=0;i<arcNum;i++) {
cout<<"Please input the edges:";
cin>>edge[i].from>>edge[i].to>>edge[i].weight;
}
sort(edges, G); //对边集数组进行堆排序,时间复杂性为O(eloge)
for (i=0;i<vertexNum;i++)
parent[i]=-1; //每个节点分属于不同的集合
int k=0,begin,end,count=0;
cout<<"next is the MST :"<<endl;
for (k=0;k<arcNum;k++) {
begin=edge[k].from; end=edge[k].to;
int m,n;
m=Find(parent,begin); n=Find(parent,end);
if(m!=n) {
cout<<begin<<","<<end<<","<<edge[k].weight<<endl;
parent[n]=m;
count++;
if(count==vertexNum-1) break;
}
}
return 0;
}
int Find(int *parent, int node)
{
int f;
f=node;
while(parent[f]>-1)
f=parent[f];
return f;
}
四、最短路径
1、Dijkstra算法
-
图的存储结构:邻接矩阵存储结构
-
数组dist[n]:每个分量dist[i]表示当前所找到的从始点v到终点vi的最短路径的长度。
初态为:若从v到vi有弧,则dist[i]为弧上权值;否则置dist[i]为∞。 -
数组path[n]:path[i]是一个字符串,表示当前所找到的从始点v到终点vi的最短路径。初态为:若从v到vi有弧,则path[i]为vvi;否则置path[i]空串。
-
数组s[n]:存放源点和已经找到最短路径的终点,其初态为只有一个源点v。
const int MAX=1000;
void Dijkstra(MGraph g, int v){
for ( i =0; i<g.vexnum ; i++){
dist[i]=g.arcs[v][i];
if ( dist[i]!= MAX)
path [i]=g.vertex[v]+g.vertex[i];
else
path[i]=“”;
}
S[0]=g.vertex[v];
num=1;
while (num<g.vextexNum){
k=0;
for(i=0;i<G.vertexNum;i++)
if((dist[i]<dist[k]) k=i
cout<<dist[k]<<path[k];
s[num++]=G.vertex[k];
for(i=0;i<G.vertexNum;i++)
if(dist[k]+g.arc[k][i]<dist[i] {
dist[i]=dist[k]+g.arc[k][i];
path[i]=path[k]+g.vertex[i];
}
}
}
2、 Floyd算法
设图g用邻接矩阵法表示,求图g中任意一对顶点vi、vj间的最短路径。
(-1)将vi到vj 的最短的路径长度初始化为(vi,vj), 然后进行如下n次比较和修正:
(0)在vi、vj间加入顶点v0,比较(vi, v0, vj)和(vi, vj)的路径的长度,取其中较短的路径作为vi到vj的且中间顶点号不大于0的最短路径。
void Floyd(MGraph G)
{
for (i=0; i<G.vertexNum; i++)
for (j=0; j<G.vertexNum; j++)
{
dist[i][j]=G.arc[i][j];
if (dist[i][j]!=∞)
path[i][j]=G.vertex[i]+G.vertex[j];
else path[i][j]="";
}
for (k=0; k<G.vertexNum; k++)
for (i=0; i<G.vertexNum; i++)
for (j=0; j<G.vertexNum; j++)
if (dist[i][k]+dist[k][j]<dist[i][j]) {
dist[i][j]=dist[i][k]+dist[k][j];
path[i][j]=path[i][k]+path[k][j];
}
}