ACM菜鸟入门培训4

第三讲 邻接矩阵和邻接表
图的存储结构主要分两种,一种是邻接矩阵,一种是邻接表。
1.邻接矩阵
图的邻接矩阵存储方式是用两个数组来表示图。
一个一维数组存储图中顶点信息;
一个二维数组(邻接矩阵)存储图中的边或弧的信息。

设图G有n个顶点,则邻接矩阵是一个n*n的方阵,定义为:

(1)无向图
看一个实例,下图左就是一个无向图。
在这里插入图片描述
从上面可以看出,无向图的边数组是一个对称矩阵。所谓对称矩阵就是n阶矩阵的元满足aij = aji。即从矩阵的左上角到右下角的主对角线为轴,右上角的元和左下角相对应的元全都是相等的。
从这个矩阵中,很容易知道图中的信息。
1)要判断任意两顶点是否有边无边就很容易了;
2)要知道某个顶点的度,其实就是这个顶点vi在邻接矩阵中第i行或(第i列)的元素之和;
3)求顶点vi的所有邻接点就是将矩阵中第i行元素扫描一遍,arc[i][j]为1的j就是邻接点;
(2)有向图
若图G是有向图(网),有n个顶点,则邻接矩阵是一个nn的方阵,定义为:
在这里插入图片描述
假如权值都是1(不考虑权值),则有向图的入度和出度容易求得,顶点vi的入度是第i列各数之和。顶点vi的出度为第i行的各数之和。
在这里插入图片描述
对于带权值的网图,可以在边表结点定义中再增加一个weight的数据域,存储权值信息即可。如下图所示。
在这里插入图片描述
2. 邻接表
邻接矩阵是不错的一种图存储结构,但是,对于边数相对顶点较少的图,这种结构存在对存储空间的极大浪费。因此,找到一种数组与链表相结合的存储方法称为邻接表。
邻接表的处理方法是这样的:
(1)图中顶点用一个一维数组存储,当然,顶点也可以用单链表来存储,不过,数组可以较容易的读取顶点的信息,更加方便。
(2)图中每个顶点vi的所有邻接点构成一个线性表,由于邻接点的个数不定,所以,用单链表存储,无向图称为顶点vi的边表,有向图则称为顶点vi作为弧尾的出边表。例如,下图就是一个无向图的邻接表的结构。
从图中可以看出,顶点表的各个结点由data和firstedge两个域表示,data是数据域,存储顶点的信息,firstedge是指针域,指向边表的第一个结点,即此顶点的第一个邻接点。边表结点由adjvex和next两个域组成。adjvex是邻接点域,存储某顶点的邻接点在顶点表中的下标,next则存储指向边表中下一个结点的指针。
3. 总结
(1)边表个数
对于一个具有n个顶点e条边的无向图,它的邻接表表示有n个顶点表结点2e个边表结点;
对于一个具有n个顶点e条边的有向图,它的邻接表表示有n个顶点表结点e个边表结点。
(2)适用条件
如果图中边的数目远远小于n2称作稀疏图,这是用邻接表表示比用邻接矩阵表示节省空间;
如果图中边的数目接近于n2,对于无向图接近于n
(n-1)称作稠密图,考虑到邻接表中要附加链域,采用邻接矩阵表示法为宜。
4. 有向图的邻接表和逆邻接表
(1)在有向图的邻接表中,第i个单链表链接的边都是顶点i发出的边。
(2)为了求第i个顶点的入度,需要遍历整个邻接表。因此可以建立逆邻接表。
(3)在有向图的逆邻接表中,第i个单链表链接的边都是进入顶点i的边。

在这里插入图片描述
    (a) 邻接表       (b) 逆邻接表
思考:已知邻接表求逆邻接表?n为边数,则时间复杂度O(n2)还是O(n)?

  1. 无向图的遍历(from 阳光岛主)
    (1)邻接矩阵
    #include “stdafx.h”
    #include <stdlib.h>
    #include <malloc.h>
    #define MAX_VEX 20// 最多20个顶点
    #define INFINITY 65535
    int *visited;//全局变量,数组,初值为0,访问到则置为1

//struct _node
//{
// int vex_num;
// struct _node *next;
//};
//typedef struct _node node, *pnode;//定义顶点表(链表,顶点数组的编号,//从1开始),本例中没有使用
思考:为什么要用链表?
struct _graph
{ char *vexs; // 顶点data的数组
int arcs[MAX_VEX][MAX_VEX]; // 弧矩阵二维表
int vexnum, arcnum;//顶点个数,弧的个数
};
typedef struct _graph graph, *pgraph;//定义邻接矩阵及其指针(顶点data
//的数组、弧矩阵二维表、顶点个数、//弧个数),本例中指针没有使用
int locate(graph g, char ch)//搜索编号,顶点data==ch
{
int i;
for(i=1; i<=g.vexnum; i++)//顶点编号从1开始 :)
if(g.vexs[i]==ch)
return i;
return -1;
}
graph create_graph()//建邻接矩阵,程序有错误,但可以正确执行
{
int i, j, w, p1, p2;
char ch1, ch2;
graph g;
printf(“Enter vexnum arcnum: “);//输入顶点和弧的个数
scanf(”%d %d”, &g.vexnum, &g.arcnum);
getchar();//中断程序运行,歇会儿
for(i=1; i<=g.vexnum; i++)//顶点编号从1开始
for(j=1; j<g.vexnum; j++)//顶点编号从1开始
g.arcs[i][j]=INFINITY;//初始化弧的权值为无穷大

  g.vexs=(char *)malloc(sizeof(char));//??申请数组空间,一块不够,应该

//使用calloc函数

printf("Enter %d vexnum.../n", g.vexnum);
for(i=1; i<=g.vexnum; i++)//顶点编号从1开始
{
	printf("vex %d: ", i);
	scanf("%c", &g.vexs[i]);//输入顶点data
	getchar();//中断程序运行,歇会儿
}//所有顶点信息录入完毕
printf("Enter %d arcnum.../n", g.arcnum);
for(i=1; i<=g.arcnum; i++)
{
	printf("arc %d: ", i);
	scanf("%c %c %d", &ch1, &ch2, &w);//输入弧的起点、终点和权值
	getchar();//中断程序运行,歇会儿
	p1=locate(g, ch1); // 返回当前弧起始顶点编号
	p2=locate(g, ch2); // 返回当前弧终止顶点编号
	g.arcs[p1][p2]=g.arcs[p2][p1]=w;
}//所有弧信息录入完毕
return g;//返回生成的图(结构体实例)

}
int firstvex_graph(graph g, int i)//返回当前邻接矩阵中第i个节点的第一个邻//接点的编号
{
int k;
if(i>=1 && i<=g.vexnum)
for(k=1; k<=g.vexnum; k++)
if(g.arcs[i][k]!=INFINITY)
return k;
return -1;
}
int nextvex_graph(graph g, int i, int j)//返回第i个节点的j编号邻接点之后的//下一个邻接点编号
{
int k;
if(i>=1 && i<=g.vexnum && j>=1 && j<=g.vexnum)
for(k=j+1; k<=g.vexnum; k++)
if(g.arcs[i][k]!=INFINITY)
return k;
return -1;
}
void dfs(graph g, int i)//从邻接矩阵g的第i个节点开始深度优先搜索
{
int k, j;
if(!visited[i])
{
visited[i]=1;
printf("%3c", g.vexs[i]);
for(j=firstvex_graph(g, i); j>=1; j=nextvex_graph(g, i, j))
if(!visited[j])
dfs(g, j);
}
}
void dfs_graph(graph g)//深度优先搜索邻接矩阵g
{
int i;
visited=(int *)malloc((g.vexnum+1)sizeof(int));
for(i=1; i<=g.vexnum; i++)
visited[i]=0;
for(i=1; i<g.vexnum; i++)
if(!visited[i])
dfs(g, i);
}
int _tmain(int argc, _TCHAR
argv[]) //VS2008测试环境
{
graph g;
g=create_graph();
printf(“DFS: “);
dfs_graph(g);
printf(”/n”);
return 0;
}
在这里插入图片描述
(2)邻接表
#include “stdafx.h”
#include <stdlib.h>
#include <malloc.h>
#define MAX_VEX 20
int visited;
typedef struct _ArcNode
{
int ivex; /
next ivex */
struct _ArcNode nextarc; / next node */
int info; / arc weight */
};
typedef struct _ArcNode ArcNode, pArcNode;//邻接表的链表节点及其指//针类型
struct _VNode
{
char adjvex; /
note vex */
ArcNode *firstarc;//链表表头
};
typedef struct _VNode VNode;
struct _ALGraph
{ VNode AdjList[MAX_VEX];//数组,每个数组元素包括:顶点data和指向
//邻接点的指针
int vexnum, arcnum;
int kink;
};
typedef struct _ALGraph ALGraph;
在这里插入图片描述
int locate(ALGraph g, char ch)
{
int i;
for(i=1; i<=g.vexnum; i++)
if(g.AdjList[i].adjvex==ch)
return i;
return -1;
}
ALGraph create_graph()
{
int i, j, w, p1, p2;
char ch1, ch2;
pArcNode pnode, pnode1, pnode2;
ALGraph g;
printf(“Enter vexnum arcnum: “);
scanf(”%d %d”, &g.vexnum, &g.arcnum);
getchar();

printf("Enter %d vexnum.../n", g.vexnum);
for(i=1; i<=g.vexnum; i++)
{
	printf("vex %d: ", i);
	scanf("%c", &g.AdjList[i].adjvex);
	getchar();
	g.AdjList[i].firstarc=NULL;
}
printf("Enter arc.../n");
for(i=1; i<=g.arcnum; i++)
{
	printf("arc %d: ", i);
	scanf("%c %c", &ch1, &ch2);
	getchar();
	p1=locate(g, ch1);//起点
	p2=locate(g, ch2);//终点
	pnode1=(pArcNode)malloc(sizeof(ArcNode));
	pnode2=(pArcNode)malloc(sizeof(ArcNode)); //下面开始维护无向

//图的邻接表(链表)
pnode1->ivex=p2;
pnode1->nextarc=g.AdjList[p1].firstarc;//每次生成新的邻接点,都
//要插入到链表头节点之后。
g.AdjList[p1].firstarc=pnode1;// 当前是p2作为p1的第一个邻接点
pnode2->ivex=p1;
pnode2->nextarc=g.AdjList[p2].firstarc;
g.AdjList[p2].firstarc=pnode2; //类似的,无向图中,p1也作为p2的
//第一个邻接点
}
return g;
}
int firstvex_graph(ALGraph g, int i)//返回邻接表g中第i个顶点的第一个邻
//接点编号
{ //int k;
if(i>=1 && i<=g.vexnum)
if(g.AdjList[i].firstarc)
return g.AdjList[i].firstarc->ivex;
return -1;
}
int nextvex_graph(ALGraph g, int i, int k)
{//返回邻接表g中第i个顶点的第k个邻接点编号
pArcNode pnode;
if(i>=1 && i<=g.vexnum && k>=1 && k<=g.vexnum)
{
pnode=g.AdjList[i].firstarc;
int count=0;
while(pnode->nextarc)
{ count++;
kk=pnode->nextarc->ivex;
if(k==count)
return kk;
else
pnode=pnode->nextarc;
}
}
return -1;
}
void dfs(ALGraph g, int i)
{
int k;
if(!visited[i])
{
visited[i]=1;
printf("%c", g.AdjList[i].adjvex);
for(k=firstvex_graph(g, i); k>=1; k=nextvex_graph(g, i, k))
if(!visited[k])
dfs(g, k);
}
}
void dfs_graph(ALGraph g)
{
int i;
visited=(int *)malloc((g.vexnum+1)sizeof(int));
for(i=1; i<=g.vexnum; i++)
visited[i]=0;
for(i=1; i<=g.vexnum; i++)
if(!visited[i])
dfs(g, i);
}
int _tmain(int argc, _TCHAR
argv[])
{
ALGraph g;
g=create_graph();
dfs_graph(g);
printf("/n");
return 0;
}
在这里插入图片描述
作业:邻接矩阵和邻接表的广度优先遍历
总结:

  1. 数据结构是解决问题的基础,把问题和已知条件用某种数据结构表达,是决定算法方向的关键,不能回避,是基本功。
  2. 能用复杂数据结构(遍历或搜索)解决的ACM问题,一般都是水题,必须优先解决。
  3. 对于树和图,深度和广度两种遍历搜索思路可以解决不同的问题,需要细心体会。例如求树的高度,应该选择深度优先算法。
    后面需要自行研究和总结的方向:基于各种数据结构的各种搜索、排序技术。

谢谢大家的坚持

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值