数据结构与算法分析 习题9-15

Kruskal算法 采取的贪心算法的思想

基本思路

  • 将图的每一条边以顺序排列 之后从小到大进行选边
  • 选边的条件是将每选择一条边就要将其两端的顶点合并成一个 ,表示已经将这条边添加进来了 防止出现圈

所需要的结构
图存储

// 为了方便 使用数组来存储图的边
typedef struct node node;
typedef struct node* pnode;
struct node {
	int weight; // 用来存储图的权值
	int start;  // 用来记录起始点 用于数字
	int stop;   // 用来记录终止点 数字 
	char st; // 用来记录起始点字母
	char sp; // 用来记录终止点字母
};

上面实际上是通过一个函数将对应的坐标的字母转换成了数字
转换:A- - -0 B- - -1 如此下去

static int alpha_to_digit(char ch)
{
	int re = 0;
	if ('A' <= ch && ch <= 'Z')
		re = ch - 'A';
	else
		re = ch - 'a';
	return re;
}

图的存储方法

static pnode create_graph(int MAX)
{
	pnode G = (pnode)malloc(sizeof(node) * MAX); // 这里给出边的具体容量 也就是MAX
	if (G == NULL)
	{
		printf("Out of space\n");
		exit(-1);
	}
	return G;
}

图的初始化方法
将字母转换成数字 是为了后续节点计算的方便性 但是不直接输入数字 是为了后面看结果方便一点

static void Initial_graph(pnode G, int edges)
{
	for (int i = 0; i < edges; i++)
	{ // 这里根据边来存储
		getchar();
		int weight;
		char start, stop;
		scanf("%c %c %d", &start, &stop, &weight); // 起始点 终止点 权重 
		G[i].st = start;  // 用来存储起始字母
		G[i].sp = stop;	// 用来存储结束字母
		G[i].start = alpha_to_digit(start); // 通过字母转数字方法进行转化
		G[i].stop = alpha_to_digit(stop);
		G[i].weight = weight;
	}
}

优先队列实现(左式堆实现)
左式堆的性质

  • NULL节点的零路径长为-1,0个或者1个节点的零路径长为0
  • 左式堆的左子节的零路径长最少等于右子节点的零路径长
  • 父节点的零路径长为子节点中零路径长最小的+1

队列结构定义

typedef struct queue queue;
typedef struct queue* position;
typedef struct queue* priorityqueue;
struct queue {
	node item; // 存放图的节点
	struct queue* left;
	struct queue* right; 
	int Npl; // 零路径长
};

队列的创建

static priorityqueue create_priorityqueue()
{
	priorityqueue H;
	H = NULL;
	return H;
}

队列的插入操作
这里采用宏替换是为了保证和二叉堆的insert的兼容 因为左式堆的需要返回 而二叉堆的insert什么也不返回 或者 采用**指针 这会使得代码中充满额外的*(数据结构原话)

#define Insert(X, H) (H = Insert1(X, H)); // 采用宏替换
static priorityqueue Insert1(node N, priorityqueue H)
{
	// 为节点N生成一个指针用来存放相应的节点
	priorityqueue SingleNode;
	SingleNode = (priorityqueue)malloc(sizeof(queue));
	if (SingleNode == NULL)
	{
		printf("out of space\n");
		exit(-1);
	}
	else {
		SingleNode->item = N;
		SingleNode->Npl = 0; // 零节点的元素Npl为0
		SingleNode->left = SingleNode->right = NULL;
		H = Merge(SingleNode, H); // 然后将新节点与斜堆合并
	}
	return H;
}

队列的判断是否为空函数

static int IsEmpty(priorityqueue H)
{
	return H == NULL;
}

队列的合并操作
Merge函数是用来确定是哪一个点和哪一个点合并
合并之后 左式堆会判断一下合并之后的点是否满足左式堆的Npl 如果右子节点大于左子节点 则进行交换

static void SwapChildren(priorityqueue H)
{
	queue tmp = *H->left;  // 将left的值取出
	*H->left = *H->right;
	*H->right = tmp;
}


static priorityqueue Merge1(priorityqueue H1, priorityqueue H2)
{
	// 如果H1.left为NULL 则说明H1是没有子节点的
	// 因为左式堆的左子节点的NPL >= 右子节点 可以直接将H2 直接赋值给H1
	if (H1->left == NULL)
		H1->left = H2; 
	else {
		H1->right = Merge(H1->right, H2);
		if (H1->left->Npl < H1->right->Npl)
			// 维持左式堆的性质
			SwapChildren(H1);
		// 左式堆的左子节点的NPL >= 右子节点的NPL
		// 左式堆父节点的NPL为节点中NPL最小的+1
		H1->Npl = H1->right->Npl + 1;
	}
	return H1;
}

static priorityqueue Merge(priorityqueue H1, priorityqueue H2)
{
	//  与空节点合并 直接返回非空节点
	if (H1 == NULL)
		return H2;
	if (H2 == NULL)
		return H1;
	// 这里是根据item的weight权重进行比较
	if (H1->item.weight < H2->item.weight)
		return Merge1(H1, H2); // 保证堆序性质 使得小的在前面
	else
		return Merge1(H2, H1);
}

队列的删除操作
同样也是使用宏替换 这里的X是用来带回被删除的节点中存放的图节点

#define DeleteMin(H, X) H = DeleteMin1(H, X);
static priorityqueue DeleteMin1(priorityqueue H, pnode X)
{
	priorityqueue LeftHeap, RightHeap;
	if (IsEmpty(H))
	{
		printf("Priorityqueue is empty\n");
		return H;
	}
	LeftHeap = H->left; // 用于指向被删除的左子节点
	RightHeap = H->right; // 用于指向被删除的右子节点
	*X = H->item; // 用X将要删除的node带出来 
	free(H);
	return Merge(LeftHeap, RightHeap);//然后两个合并就成了另一个左式堆
}

最后采用的不相交集合 Disjoin_set
结构

#define MAX 20
static int visited[MAX];  // 用来确定访问节点
static int Rank[MAX];  // 用来控制树高 不会太高

初始化

static void Initial_DisjoinSet()
{
	for (int i = 0; i < MAX; i++)
	{
		visited[i] = i; // 将visited[i]的值指向自己 用来结束标记
		Rank[i] = 0; // 为后续节点进行访问的时候 不会使森林变得很高
	}
}

查找元素

static int find(x)
{
	if (visited[x] == x) // 指向元素自己 说明是根节点
		return x;
	else {
		return visited[x] = find(visited[x]); // 一直查询到根节点元素
	}
}

合并两个元素

// 合并两个元素 并且 该元素都是指向自己的root型节点
static void unite(int x, int y)
{
	if (Rank[x] < Rank[y]) // 说明x的树高是小于y的树高 直接将x指向y 避免树高增加
		visited[x] = y;
	else {
		visited[y] = x;
		if (Rank[x] == Rank[y])
			Rank[x]++;
	}
}
static void link(int x, int y)
{
	// 首先通过find查询到当前元素所属的子集
	unite(find(x), find(y));
}

比较两个元素是否处于同一个集合中


static int same(int x, int y)
{
	// 比较两个元素的根节点是否相同
	return find(x) == find(y);
}

kruskal算法

static void kruskal(pnode G,int edges)
{
	priorityqueue H = NULL;
	for (int i = 0; i < edges; i++)
	{
		// 将所有的边都插入进优先队列中
		Insert(G[i], H);
	}
	Initial_DisjoinSet();// 将互质集合全部初始化
	// 从优先队列中取出权值最小的边
	int totalCost = 0;
	while (!IsEmpty(H))
	{
		node i;
		DeleteMin(H, &i); // 要删除的节点由i带回
		if (same(i.start, i.stop))
			// 说明这条边的两个点 已经被选取了
			continue;
		link(i.start, i.stop);
		printf("边起始%c 结束%c 权值为%d被选择\n", i.st, i.sp, i.weight);
		totalCost += i.weight;
	}
	printf("总花费%d", totalCost);
}

展示一下图的边

static void show_graph(pnode G, int edges)
{
	for (int i = 0; i < edges; i++)
	{
		printf("起始点为%c, 终止为%c, 权值为%d\n", G[i].st, G[i].sp, G[i].weight);
	}
}

主程序

int main()
{
	int n;
	scanf("%d", &n); // 确定边的数量
	pnode G = create_graph(n);
	Initial_graph(G, n);
	show_graph(G, n);
	kruskal(G, n);
}

测试例程 采用的数据结构习题9-15
19
A D 4
A E 4
A B 3
D E 5
D H 6
H I 4
I J 7
J G 8
B C 10
B E 2
B F 3
E H 2
E F 11
E I 1
C F 6
C G 1
F G 2
F J 11
F I 3
测试结果
在这里插入图片描述
至此 kruskal算法结束了
(如果有什么不对的地方请提出来 大佬轻喷)

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值