看数据结构写代码(37) 图的十字链表的表示与实现

图的邻接表在 查找 有向图的 出度 很 方便,但是 在 查找 入度 时,需要遍历整个图。如果想要 方便的 查找 入度,需要 建立 逆邻接表。十字链表 正好 就是 邻接表 和 逆邻接表的集合。具体结构图如下:

感觉 十字链表 在 查找 入度时 方便 一些,其他 跟 邻接表没什么区别。

源代码 网盘地址:点击打开链接

代码如下:

// CrossLinkGraph.cpp : 定义控制台应用程序的入口点。
//有向图的十字链表表示法

#include "stdafx.h"
#include <cstdlib>

#define MAX_VEX_NUM 20
enum E_State
{
	E_State_Error = 0,
	E_State_Ok = 1,
};

struct ArcNode//弧节点
{
	int tailIndex;//弧尾位置
	int headIndex;//弧头位置
	ArcNode * tailNext;//下一个弧尾相同的弧
	ArcNode * headNext;//下一个弧头相同的弧
};

typedef struct VNode
{
	char vexName;//顶点名称
	ArcNode * firstIn;
	ArcNode * firstOut;
}GraphList[MAX_VEX_NUM];//

struct Graph
{
	GraphList list;//顶点数组.
	int vexNum,arcNum;
};
//获取弧 的 头节点
ArcNode * getHeadNode(){
	ArcNode * pNode = (ArcNode *)malloc(sizeof(ArcNode));
	if (pNode){
		pNode->headIndex = pNode->tailIndex = -1;
		pNode->headNext = pNode->tailNext = NULL;
	}
	return pNode;
}

ArcNode * getArcNode(int tailIndex,int headIndex){
	ArcNode * pNode = getHeadNode();
	if (pNode){
		pNode->headIndex = headIndex;
		pNode->tailIndex = tailIndex;
	}
	return pNode;
}

int vexLocation(Graph g,char vex){
	for (int i = 0; i < g.vexNum; i++){
		if (g.list[i].vexName == vex){
			return i;
		}
	}
	return -1;
}

void createGrahp(Graph * g){
	printf("输入图的顶点数 和 边(弧)数\n");
	scanf("%d%d%*c",&g->vexNum,&g->arcNum);
    //构造顶点集
    printf("请输入顶点集\n");
    for (int i = 0; i < g->vexNum; i++){
        char name;
		scanf("%c",&name);
		g->list[i].vexName = name;
		g->list[i].firstIn = g->list[i].firstOut = getHeadNode();//建立 头节点,并让头指针指向头节点
    }
    //构造顶点关系
    fflush(stdin);
    printf("请输入顶点的关系\n");
    for (int i = 0; i < g->arcNum; i++){
        char vex1,vex2;
		scanf("%c%c%*c",&vex1,&vex2);
		int location1 = vexLocation(*g,vex1);
		int location2 = vexLocation(*g,vex2);
		ArcNode * pNode = getArcNode(location1,location2);
		pNode->tailNext = g->list[location1].firstOut->tailNext;
		g->list[location1].firstOut->tailNext = pNode;
		pNode->headNext = g->list[location2].firstIn->headNext;
		g->list[location2].firstIn->headNext = pNode;
    }
}

//只要删除所有顶点的弧尾(或者弧头)节点即可,
//同时删除弧头弧尾 ,内存错误
void destoryGraph(Graph * g){
	for (int i = 0; i < g->vexNum; i++){
		ArcNode * next = g->list[i].firstOut;//删除所有弧尾
		while (next != NULL){
			ArcNode * freeNode = next;
			next = next->tailNext;
			free(freeNode);
		}
		g->list[i].firstIn = g->list[i].firstOut = NULL;
		g->list[i].vexName = ' ';
		g->vexNum = g->arcNum = 0;
	}
}

//顶点vex1 和顶点vex2 是否相邻
bool graphIsAdj(Graph g,char vex1,char vex2){
	int location = vexLocation(g,vex1);
	ArcNode * next = g.list[location].firstOut->tailNext;
	while (next != NULL){
		if (g.list[next->headIndex].vexName == vex2){
			return true;
		}
		next = next->tailNext;
	}
	return false;
}

int graphDegree(Graph g,char vex){
	int degree = 0;
	int locaiton = vexLocation(g,vex);
	ArcNode * next = g.list[locaiton].firstOut->tailNext;//计算所有出度
	while (next != NULL){
		degree++;
		next = next->tailNext;
	}
	next = g.list[locaiton].firstIn->headNext;//计算所有入度
	while (next != NULL){
		degree++;
		next = next->headNext;
	}
	return degree;
}

char firstAdj(Graph g,char vex){
	int location = vexLocation(g,vex);
	ArcNode * next = g.list[location].firstOut->tailNext;
	if (next != NULL)
	{
		return g.list[next->headIndex].vexName;
	}
	return ' ';
}

char nextAdj(Graph g,char vex1,char vex2){
	int location = vexLocation(g,vex1);
	ArcNode * next = g.list[location].firstOut->tailNext;
	while (next != NULL){//查找到 vex2
		if (g.list[next->headIndex].vexName == vex2){
			break;
		}
		next = next->tailNext;
	}
	if (next != NULL){
		ArcNode * nextNode = next->tailNext;
		if (nextNode != NULL){
			return g.list[nextNode->headIndex].vexName;
		}
	}
	return ' ';
}
//插入边(弧)
void insertArc(Graph * g,char vex1,char vex2){
	int location1 = vexLocation(*g,vex1);
	int location2 = vexLocation(*g,vex2);
	ArcNode * node = getArcNode(location1,location2);
	node->tailNext = g->list[location1].firstOut->tailNext;
	g->list[location1].firstOut->tailNext = node;
	node->headNext = g->list[location2].firstIn->headNext;
	g->list[location2].firstIn->headNext = node;
	g->arcNum ++;
}
//删除边(弧)
void deleteArc(Graph * g,char vex1,char vex2){
	g->arcNum--;
	int location1 = vexLocation(*g,vex1);
	int location2 = vexLocation(*g,vex2);
	ArcNode * next = g->list[location1].firstOut->tailNext;
	ArcNode * pre = g->list[location1].firstOut;
	while (next != NULL){//在更改 尾部相同的 链表时,不能删除 弧
		if (next->headIndex == location2){
			pre->tailNext = next->tailNext;
			//free(next);
			break;
		}
		pre = next;
		next = next->tailNext;
	}
	next = g->list[location2].firstIn->headNext;
	pre = g->list[location2].firstIn;
	//在更改弧头相同的链表时,释放空间.
	while (next != NULL){
		if (next->tailIndex == location1){
			pre->headNext = next->headNext;
			free(next);
			break;
		}
		pre = next;
		next = next->headNext;
	}
}
//插入顶点
void insertVex(Graph * g, char vex){
	if (g->vexNum < MAX_VEX_NUM){
		g->list[g->vexNum].vexName = vex;
		g->list[g->vexNum].firstIn = g->list[g->vexNum].firstOut = getHeadNode();
		g->vexNum++;
	}
}
//删除顶点
void deleteVex(Graph * g,char vex){
	int location = vexLocation(*g,vex);
	//删除顶点 同样需要 遍历整个 图 查找 与 vex 相关的弧节点
	for (int i = 0; i < g->vexNum; i++){
		ArcNode * next = g->list[i].firstOut->tailNext;
		while (next != NULL){
			if (next->headIndex == location || next->tailIndex == location){
				ArcNode * delNode = next;
				next = next->tailNext;
				char delData1 = g->list[delNode->tailIndex].vexName;
				char delData2 = g->list[delNode->headIndex].vexName;
				deleteArc(g,delData1,delData2);
			}
			else{
				next = next->tailNext;
			}
		}
	}
	for (int i = 0; i < g->vexNum; i++){
		ArcNode * next = g->list[i].firstOut->tailNext;
		while (next != NULL){
			if(next->headIndex > location){
				next->headIndex --;
			}
			if(next->tailIndex > location){
				next->tailIndex --;
			}
			next = next->tailNext;
		}
	}
	free(g->list[location].firstIn);//释放头节点
	//vex下面的 顶点上移
	for (int i = location + 1; i < g->vexNum; i++){
		g->list[i-1] = g->list[i];
	}
	g->vexNum --;
}

void printGrahp(Graph g){
	for (int i = 0; i < g.vexNum; i++){
		printf("以%c为弧尾的 顶点有:",g.list[i].vexName);
		ArcNode * next = g.list[i].firstOut->tailNext;//删除所有弧尾
		while (next != NULL){
			printf("%c",g.list[next->headIndex].vexName);
			next = next->tailNext;
		}
		printf("\n以%c为弧头的 顶点有:",g.list[i].vexName);
		next = g.list[i].firstIn->headNext;//删除所有弧尾
		while (next != NULL){
			printf("%c",g.list[next->tailIndex].vexName);
			next = next->headNext;
		}
		printf("\n");
	}
}

int _tmain(int argc, _TCHAR* argv[])
{
	Graph g;
	createGrahp(&g);
	printGrahp(g);
	printf("图的顶点数:%d,边(弧)树为:%d\n",g.vexNum,g.arcNum);
	char * isAdj = graphIsAdj(g,'b','d')? "相邻" : "不相邻";
	int degree = graphDegree(g,'d');
	char first = firstAdj(g,'c');
	char next = nextAdj(g,'d','c');
	printf("c的第一个邻接点是%c,d的c邻接点的下一个邻接点是:%c\n",first,next);
	printf("b 和 d %s,d的度为:%d\n",isAdj,degree);
	insertVex(&g,'e');
	printf("插入e顶点之后图结构如下:\n");
	printGrahp(g);
	insertArc(&g,'a','e');
	printf("插入(a,e) 之后图结构如下:\n");
	printGrahp(g);
	deleteArc(&g,'d','c');
	printf("删除(d,c)之后图结构如下:\n");
	printGrahp(g);
	deleteVex(&g,'a');
	printf("删除顶点a之后图结构如下:\n");
	printGrahp(g);
	printf("图的顶点数:%d,边(弧)数为:%d\n",g.vexNum,g.arcNum);
	destoryGraph(&g);
	return 0;
}

运行截图:

时间: 2024-08-09 23:46:33

看数据结构写代码(37) 图的十字链表的表示与实现的相关文章

看数据结构写代码(21) 稀疏矩阵(十字链表方式)

写完 这个样例,花费了 我不少时间.大部分时间 花费在 调试 内存问题上. 比如在销毁十字链表时.多次释放节点空间,造成 _CrtIsValidHeapPointer(pUserData) 异常. 当使用malloc 分配 一个 空间时,会将这个空间的起始地址和长度 加到一个链表中去.free(p)的时候 ,会从 链表里 查找 是否 有 这个地址空间,找到了就将这个节点从链表中删除._CrtIsValidHeapPointer(pUserData)  这个函数 正是 检查 这个空间是否 在链表里

看数据结构写代码(4)单链表

单链表比较简单,中间倒也没出什么大问题,只是 在写 插入 和 删除的 算法的 时候 ,时间复杂度 是正常 算法的2倍.后来 改正了. 下面奉上代码.如有 bug,欢迎指出. // SingleList.cpp : 定义控制台应用程序的入口点. // #include "stdafx.h" #include <cstdlib> enum E_State { E_State_Error = 0, E_State_OK = 1, }; typedef int ElementTyp

看数据结构写代码(5)静态链表

静态链表用于 不能使用 指针的 编程语言中. 下面奉上代码: // StaticLinkList.cpp : 定义控制台应用程序的入口点. // #include "stdafx.h" #include <stdlib.h> //静态链表的 实现 typedef int Element; #define INIT_SIZE 10 //为了测试,故意将值设置的很小 enum E_STATE { E_STATE_ERROR = 0, E_STATE_OK = 1, }; str

看数据结构写代码(36) 图的邻接表表示与实现

图的邻接表表示法,是为每一个顶点建立一个链表,链表里存放着相同弧尾的 弧的信息,这些链表顺序存放在数组中.下面是无向图g2的邻接表 邻接表 比 邻接矩阵 节省空间,同时 也带来一些操作上的 不便,例如 看 两个顶点是否 相邻,需要 遍历 链表,在 求 无向图顶点的度时,只需 遍历 顶点的链表,而 求 有向图 顶点的度 需要 遍历 整个图 查找 弧头 为这个顶点的 个数. 如果 不想这样做,可以 建立 逆邻接表,即 链表里 存放着 相同 弧头的 弧 的信息. 下一节 要说的 十字链表 类似于这种结

看数据结构写代码(39) 图的遍历(深搜和广搜)

图的遍历算法 有两种 :深度优先搜索遍历 和 广度 优先搜索遍历.深度优先搜索遍历类似与 树的 先序遍历.广度优先搜索遍历类似与树的层序遍历.只不过 图 可以有 不连通的 节点,所以 得 遍历 整个顶点数组. 深搜遍历 总是 先访问当前节点的邻接点,而 广搜算法 是 先访问顶点的邻接点 要 先于 后访问顶点的邻接点 被 访问. 具体遍历顺序如下: 以下代码 以 图的 邻接多重表 为 基本结构进行 遍历. 首先更改 上节 的 查找 邻接点 和 下一个邻接点的 返回值,以及 邻接点的 代码 有误,少

看数据结构写代码(41) 强连通分量

首先介绍概念问题,在有向图中,若 顶点v1 到 v2 存成路径,并且 v2 到 v1 存成 路径,则称 顶点 v1 和 v2 是强连通的.若 有向图 任意两个节点 都是 强连通的,则 称为强连通图.非强连通图的 极大强连通子图,为 强连通分量. 特别说明,连通的概念 属于 无向图,强连通 属于 有向图.例如:无向图:连通图,连通分量,生成树: 有向图:强连通图,强连通分量. 数据结构书上 简单的 说了 一个 求 强连通 分量的 一个 办法,详细如下: 其实 自己 不太明白 这个算法的原理. 好在

看数据结构写代码(32) 赫夫曼树编码以及译码

杂谈:最近有点慵懒,不好不好.好几天都没写代码,原本准备上星期完结 树 这一章节的.现在 又耽误了.哎.要抓紧时间啊. 下面直接上代码: 可以到我的网盘下载源代码,或者 直接拷贝下面的源代码 运行 网盘地址:点击打开链接 // HuffmanTree.cpp : 定义控制台应用程序的入口点. //哈弗曼编码,译码 #include "stdafx.h" #include <stdlib.h> #include <cstring> enum E_State { E

看数据结构写代码(35) 图的邻接矩阵表示法

杂谈:最近清明小长假,好好的放松了一下.节前 和 节后 都有点 松懈.不好,不好.贵在坚持.加油. 图的邻接矩阵表示法是用 两个数组 来表示 图的数据结构.一个是顶点数组,另一个是邻接矩阵数组.邻接矩阵 里存放着 顶点的关系. 用邻接矩阵表示图,在 看 顶点之间 是否有边,或者 求顶点的度等操作时比较简单.但空间浪费巨大,在插入,删除 顶点 和边 操作时 需要 移动大量数据,造成不便.所以在插入删除比较多,节点数比较多的时候 不宜 使用这种结构. 下面上代码: 源代码网盘地址:点击打开链接 //

看数据结构写代码(38) 图的邻接多重表表示法与实现

图的邻接多重表 是 无向图的 另一种表示法.其与 邻接表 的差别 仅仅 在于 ,邻接表 用 两个 顶点 来表示 一条边,而 邻接多重表 用一个 顶点来表示一条边.这样使得 邻接多重表 在 某些操作 要 来的 方便.例如 将 搜索过的边 做记号 或者 删除 一条边. 下面是邻接多重表的结构: 下面的 6条边 用 6个弧 节点表示,用12个指针指向,每个弧节点被 指向2次.这样使得我们 在 释放内存的时候 需要格外小心. 下面上代码: 源码工程文件网盘地址:点击打开链接 // AMLGraph.cp