数据结构之图的十字链表表示法

            

       上篇博客介绍了图的邻接表表示法(图的邻接表表示法) 然而,对于有向图来说,邻接表是有缺陷的。关心了出度问题,想了解入度就必须要遍历整个图才可以,反之,逆邻接表解决了入度却不了解出度的情况。有没有可能把邻接表与逆邻接表结合起来呢? 答案是肯定的,就是把 它 们整合在一起。这就是我们现在要讲的有向图的 一种存储方法: 十字链表(Orthogonal List) 。


十字链表的数据存储结构如下(图来自c语言中文网):


#define MAX_VERTEX_NUM 20

typedef int VertexType;


//图的种类包括有向图/有向网
typedef enum {DG,DN} GraphKind;

typedef struct ArcBox {
	int tailvex;	//该弧的尾顶点的位置
	int headvex;	//该弧的头顶点的位置
	struct ArcBox *hlink;  //指向下一个起点相同的弧
	struct ArcBox *tlink;	//指向一个终点相同的弧
	int weight;	//弧的权值
}ArcBox;


typedef struct VexNode{
	VertexType data;
	ArcBox *firstin;	//指向该顶点的第一条入弧
	ArcBox *firstout;	//指向该顶点的第一条出弧
}VexNode;


typedef struct {
	VexNode xlist[MAX_VERTEX_NUM];
	int vexnum;	//顶点数
	int arcnum;	//弧数
	GraphKind kind;	//图的种类(有向图/有向网)
}OLGraph;


 

                     数据结构之图的十字链表表示法_第1张图片


    

                      数据结构之图的十字链表表示法_第2张图片



头文件:olgraph.h


/*----------------------------------------------------------------------------
	* file: algraph.h
	* date:10-11-2014
	* author:[email protected]
	* version:1.0
	* description:十字链表实现图的基本操作
	* more:十字链表仅仅能表述有向图/网
-----------------------------------------------------------------------------*/


#define TRUE 1
#define FALSE 0
#define OK 1
#define ERROR 0
#define OVERFLOW -2

#define MAX_VERTEX_NUM 20

typedef int Status;

typedef int VertexType;


//图的种类包括有向图/有向网
typedef enum {DG,DN} GraphKind;

typedef struct ArcBox {
	int tailvex;	//该弧的尾顶点的位置
	int headvex;	//该弧的头顶点的位置
	struct ArcBox *hlink;  //指向下一个起点相同的弧
	struct ArcBox *tlink;	//指向一个终点相同的弧
	int weight;	//弧的权值
}ArcBox;


typedef struct VexNode{
	VertexType data;
	ArcBox *firstin;	//指向该顶点的第一条入弧
	ArcBox *firstout;	//指向该顶点的第一条出弧
}VexNode;


typedef struct {
	VexNode xlist[MAX_VERTEX_NUM];
	int vexnum;	//顶点数
	int arcnum;	//弧数
	GraphKind kind;	//图的种类(有向图/有向网)
}OLGraph;


//访问标记数组
int visited[MAX_VERTEX_NUM];


//创建图(有向图/有向网)
Status CreateGraph(OLGraph *G);

//定位顶点值v的位置,无则返回-1
int LocateVex(OLGraph G,VertexType v);

//销毁图
void DestoryGraph(OLGraph *G);

//返回顶点v的值
VertexType GetVex(OLGraph G,int v);

//对值为v的顶点赋值为value
Status PutVex(OLGraph *G,VertexType v,VertexType value);

//获取v的下一个邻接点的序号
int FirstAdjVex(OLGraph G,VertexType v);

//返回顶点v相对于w的下一个邻接点的序号
int NextAdjVex(OLGraph G,VertexType v,VertexType w);

//添加一个值为v的顶点
Status InsertVex(OLGraph *G,VertexType v);

//插入一段弧
Status InsertArc(OLGraph *G,VertexType v,VertexType w);

//删除一个顶点和相关的弧
Status DeleVex(OLGraph *G,VertexType v);


//删除一段弧
Status DeleArc(OLGraph *G,VertexType v,VertexType w);

//深度优先遍历
Status DFSTraverse(OLGraph G,Status (*Visit) (VertexType));

//深度优先递归遍历
void DFS(OLGraph G,int i,Status (*Visit) (VertexType));


//广度优先遍历
Status BFSTraverse(OLGraph G,Status (*Visit) (VertexType));

//打印元素
Status PrintElem(VertexType elem);



主程序:OLGraph.c


/*----------------------------------------------------------------------------
 * file: OLGraph.c
 * date:10-11-2014
 * author:[email protected]
 * version:1.0
 * description:十字链表实现图的基本操作
 * more:十字链表仅仅能表述有向图/网,
 还有在遍历等操作时,沿着出
 -----------------------------------------------------------------------------*/


#include 
#include 
#include "olgraph.h"
#include "linkqueue.h"

/*
 * @description:创建图
 */
Status CreateGraph(OLGraph *G) {
	int i,j,w,k;
	VertexType tv,hv;
	ArcBox *p;

	//确定图的种类
	printf("please enter the kind of graph(DG:0,DN:1):");
	scanf("%d",&(*G).kind);

	//确定顶点数和弧数
	printf("please enter the vexnum and arcnum:");
	scanf("%d,%d",&(*G).vexnum,&(*G).arcnum);

	//确定各个顶点
	printf("please enter each value of graph:");
	for(i = 0; i < (*G).vexnum ; i++) { 
		scanf("%d,",&(*G).xlist[i].data);
		(*G).xlist[i].firstin = NULL;
		(*G).xlist[i].firstout = NULL;
	}

	//建立各个顶点间关系,即建立入/出表
	if((*G).kind == DN)
		printf("please enter the heads,tails and weights:\n");
	else 
		printf("please enter the heads and tails:\n");

	for(k = 0;k < (*G).arcnum ; k++) {
		if((*G).kind == DN)	
			scanf("%d,%d,%d",&tv,&hv,&w);
		else
			scanf("%d,%d",&tv,&hv);

		i = LocateVex(*G,tv);	//弧尾
		j = LocateVex(*G,hv);	//弧头

		p = (ArcBox *) malloc(sizeof(struct ArcBox));

		if(!p)
			exit(OVERFLOW);

		p->tailvex = i;	//弧的起点 -->入表
		p->headvex = j;	//弧的终点 -->出表
		p->hlink = (*G).xlist[j].firstin;	//指向下一个起点相同的弧节点
		p->tlink = (*G).xlist[i].firstout;	//指向下一个终点相同的弧节点

		//注意不管是出还是入表都是在表头插入的,中表示v1有出,v2有入
		(*G).xlist[i].firstout = (*G).xlist[j].firstin = p;

		if((*G).kind == DN)
			p->weight = w;
	}


	return OK;
}


/*
 * @description:定位顶点值v的位置,无则返回-1
 */
int LocateVex(OLGraph G,VertexType v) {
	int i;

	for(i = 0; i < G.vexnum ; i++)
		if( G.xlist[i].data == v)
			return i;

	return -1;
}


/*
 * @description:销毁图
 * @more:遍历或是销毁等等一个思路可以仅仅按照出表来进行
 */
void DestoryGraph(OLGraph *G) {
	ArcBox *p,*pre;
	int i;

	for(i = 0;i < (*G).vexnum ; i++) {

		p = (*G).xlist[i].firstout; 	//找到每个顶点向量对应的十字链表

		while(p) {
			pre = p;
			p = p->tlink;
			free(pre);
		}

	}
}


/*
 * @description:返回顶点v的值
 */
VertexType GetVex(OLGraph G,int v) {

	if(v > G.vexnum || v < 0 ) 
		exit(OVERFLOW);

	return G.xlist[v].data;
}


/*
 * @description:对值为v的顶点赋值为value
 */
Status PutVex(OLGraph *G,VertexType v,VertexType value) {
	int i;

	i = LocateVex(*G,v);

	if( i < 0)
		return ERROR;

	(*G).xlist[i].data = value;

	return OK;
}


/*
 * @description:返回顶点值为v的下一个邻接顶点的序号
 * @more:这里的第一个邻接顶点理解为顶点对应链表中的第一个节点
 */
int FirstAdjVex(OLGraph G,VertexType v) {
	int i;

	i = LocateVex(G,v);

	if( i < 0)
		return -1;

	if(G.xlist[i].firstout)
		return G.xlist[i].firstout->headvex;
	
	return -1;
}


/*
 * @description:返回顶点为v相对顶点值为w的下一个邻接顶点的序号,w为v的一个邻接顶点
 */
int NextAdjVex(OLGraph G,VertexType v,VertexType w) {
	int i,j;
	ArcBox *p;

	i = LocateVex(G,v);
	j = LocateVex(G,w);

	if(i < 0 || j < 0 ) 
		return -1;

	p = G.xlist[i].firstout;	//顶点v对应的弧链表
	
	while(p && p->headvex != j) 
		p = p->tlink;

	while(p && p->headvex == j) {
			p = p->tlink;
		if(p)
			return p->headvex;
	}
	
	return -1;
}


/*
 * @description:添加一个顶点值为v
 * @more:注意这里不处理边
 */
Status InsertVex(OLGraph *G,VertexType v) {
	int i;
	i = (*G).vexnum;

	(*G).xlist[i].data = v;
	(*G).xlist[i].firstin = NULL;
	(*G).xlist[i].firstout = NULL;
	(*G).vexnum++;

	return OK;
}


/*
 * @description:删除一个顶点值为v的顶点,并删除相关的弧
 * @more:这里的删除在于出表和入表是交叉的同时可能还要更新其他顶点的firstin等指针
 * 思路和邻接表中的不同的是这里不能先进行顶点向量的更新,那样无法找到对应的出/入指针
 * 顶点向量的更新应该放在最后面
 * 关于十字交叉的出入表的处理思路如下:
 * 1.顶点v的出弧也是其他顶点的入弧,故要改变其他顶点的出弧指针(仅仅改变),整个出弧直接删除
 * 2.顶点v的入弧也是其他顶点的出弧,故要改变其他顶点的入弧指针(仅仅改变),整个入弧直接删除
 * 3.更新向量数组,将数组元素上移动
 * 4.更新相应节点的headvex和tailvex
 */
Status DeleVex(OLGraph *G,VertexType v) {
	int k,j;
	ArcBox *p,*pre;

	k = LocateVex(*G,v);

	if(k < 0)
		return ERROR;


	//顶点v的出弧也是其他顶点的入弧,调整其他顶点的入弧指针
	for(j = 0; j < (*G).vexnum ; j++)  {
		if(j == k)
			continue;

		p = (*G).xlist[j].firstin;
		//注意这里仅仅是调整,不涉及释放空间,释放的操作在随后的出弧处理中进行
		while(p) {
			//如果是第一个节点
			if(p->tailvex == k && p == (*G).xlist[j].firstin) {
				(*G).xlist[j].firstin = p->hlink;
				break;
			}
			//没有找到相应的节点
			else if(p->tailvex != k) {
				pre = p;
				p = p->hlink;
			}
			//不是第一个节点
			else {
				pre->hlink = p->hlink;
				break;
			}
		}


	}

	//删除整个出弧链表
	p = (*G).xlist[k].firstout;
	while(p) {
		pre = p->tlink;
		free(p);
		p = pre;
		//边数减
		(*G).arcnum--;
	}


	//顶点v的入弧同时也是其他顶点的出弧
	for(j = 0; j < (*G).vexnum ;j++) {
		if(j == k)
			continue;

		p = (*G).xlist[j].firstout;

		while(p) {
			//入表中的第一个节点
			if(p->headvex == k && (*G).xlist[j].firstout == p) {
				(*G).xlist[j].firstout = p->tlink;
				break;
			}
			else if(p->headvex != k) {
				pre = p;
				p = p->tlink;
			}
			else {
				pre->tlink = p->tlink;
				break;
			}
		}
	}


	//删除顶点对应的整个入表
	p = (*G).xlist[k].firstin;
	while(p){
		pre = p->hlink;
		free(pre);
		p = pre;
		//边数减
		(*G).arcnum--;
	}


	//顶点向量数组上移动
	for(j = k + 1;j < (*G).vexnum; j++)
		(*G).xlist[j - 1] = (*G).xlist[j];

	//更新相应节点的headvex和tailvex
	for(j = 0; j < (*G).vexnum ;j++) {
		//处理出表
		p = (*G).xlist[j].firstout;
		while(p) {
			if(p->headvex > k)
				p->headvex--;
			if(p->tailvex > k)
				p->tailvex--;

			p = p->tlink;
		}
	}

	(*G).vexnum--;

	return OK;
}




/*
 * @description:在顶点值v和顶点值w间添加一段弧
 */
Status InsertArc(OLGraph *G,VertexType v,VertexType w) {
	int i,j;
	ArcBox *p;

	i = LocateVex(*G,v);	//弧尾
	j = LocateVex(*G,w);	//弧头

	if(i < 0 || j < 0)
		return ERROR;

	p = (ArcBox *) malloc(sizeof(struct ArcBox));

	if(!p)
		exit(OVERFLOW);

	p->tailvex = i;	//弧的起点 -->入表
	p->headvex = j;	//弧的终点 -->出表
	p->tlink = (*G).xlist[j].firstin;	//指向下一个起点相同的弧节点
	p->hlink = (*G).xlist[i].firstout;	//指向下一个终点相同的弧节点

	//注意不管是出还是入表都是在表头插入的,中表示v1有出,v2有入
	(*G).xlist[i].firstout = (*G).xlist[j].firstin = p;

	if((*G).kind == DN) {
		printf("please enter the weight:");
		scanf("%d",&p->weight);
	}

	return OK;
}


/*
 * @description:删除顶点值w和顶点值v间的一段弧
 */
Status DeleArc(OLGraph *G,VertexType v,VertexType w) {
	int i,j;
	ArcBox *p,*pre;

	i = LocateVex(*G,v);
	j = LocateVex(*G,w);

	if(i < 0 || j < 0)
		return ERROR;
	
	//这里需要删除一个节点,调整一个指针,因为v是w的起点则w是v的终点

	//首先出
	p = (*G).xlist[i].firstout;

	if(p && p->headvex == j) 
		(*G).xlist[i].firstout = p->tlink;
	else {
		//往后一直找
		while(p && p->headvex != j) {
			pre = p;
			p = p->tlink;
		}
		//找到了,且没有到最后
		if(p) 
			pre->tlink = p->tlink;
	}


	//处理入
	p = (*G).xlist[j].firstin;
	//如果需要处理的是第一个节点
	if(p && p->tailvex == i)
		(*G).xlist[j].firstin = p->hlink;

	else {
		while(p && p->tailvex != i) {
			pre = p;
			p = p->hlink;
		}

		if(p) 
			pre->hlink = pre->hlink;
	}

	free(p);
	(*G).arcnum--;

	return OK;
}

/*
 * @description:深度优先遍历图
 */
Status DFSTraverse(OLGraph G,Status (*Visit) (VertexType)) {
	int i;

	//重新初始化访问标记数组
	for(i = 0; i < G.vexnum; i++)
		visited[i] = FALSE;

	//保证每个顶点都能被访问到
	for(i = 0; i < G.vexnum ; i++) 
		if(!visited[i])
			DFS(G,i,Visit);

	return OK;
}


/*
 * @description:深度优先递归遍历邻接点
 */
void DFS(OLGraph G,int i,Status (*Visit) (VertexType)) {
	ArcBox *p;

	Visit(G.xlist[i].data);
	visited[i] = TRUE;

	p = G.xlist[i].firstout;	//出表第一个节点指针

	while(p && visited[p->headvex])	//如果出表中当前节点已经被访问则往下找终点相同的点
		p = p->tlink;

	if(p && !visited[p->headvex])
		DFS(G,p->headvex,Visit);

}




/*
* @description:广度优先遍历图
*/
Status BFSTraverse(OLGraph G,Status (*Visit) (VertexType)) {
	LinkQueue Q;
	int i,v,w;

	InitQueue(&Q);
	//重新初始化访问标记数组
	for(i = 0;i < G.vexnum; i++)
		visited[i] = FALSE;

	//外层循环保证每个节点都能访问到
	for(i = 0;i < G.vexnum ; i++) {
		if(!visited[i]) {
			Visit(G.xlist[i].data);
			visited[i] = TRUE;
			
			EnQueue(&Q,i);

			while(!QueueEmpty(Q)) {
				DeQueue(&Q,&v);

				for(w = FirstAdjVex(G,GetVex(G,v)); w >= 0; w = NextAdjVex(G,GetVex(G,v),GetVex(G,w))  ) {
					if(!visited[w]) {
						Visit(G.xlist[w].data);
						visited[w] = TRUE;
						EnQueue(&Q,w);
					}
				}
			}
		}
	}

	return OK;

}


/*
 * @description:打印元素
 */
Status PrintElem(VertexType elem) {
	printf("%d",elem);
	return OK;
}



测试文件:test.c



/*----------------------------------------------------------------------------
	* file: test.c --> test file for OLGraph
	* date:10-11-2014
	* author:[email protected]
	* version:1.0
	* description:十字链表实现图的基本操作
	* more:十字链表仅仅能表述有向图/网,由于没有进行输入检测,在输入前
	应该想好结构,所有程序亲测
-----------------------------------------------------------------------------*/


#include 
#include "olgraph.h"

void main() {
	OLGraph G;
	//创建图
	CreateGraph(&G);
	//广度优先遍历
	BFSTraverse(G,PrintElem);
	printf("\n");
	/*
	please enter the kind of graph(DG:0,DN:1):0
	please enter the vexnum and arcnum:4,5
	please enter each value of graph:0,1,2,3, 
	please enter the heads and tails:
	0,3
	1,2
	2,1
	2,0
	1,0
	0312

	*/
}


数据结构之图的十字链表表示法_第3张图片


测试对应的图:

              数据结构之图的十字链表表示法_第4张图片


完整的代码看:GitHub



你可能感兴趣的:(Data,Structure)