/********************
kruskal(GRAPH* pGraph)
从graph中找出ptree 、pline、 line_number、 vexter_number
_kruskal(MINI_GENERATE_TREE* pTree[], int length, DIR_LINE* pLine[], int number)
从pline到line_number
bubble_sort(void* array[], int length, int (*compare)(void*, void*), void(*swap)(void**, void**))
pline的start、 end 是否已经都在最小生成树中
isDoubleVectexExistInTree(MINI_GENERATE_TREE* pTree[], int length, int start, int end)
getVectexNumFromTree(MINI_GENERATE_TREE* pTree, int start, int end)
合并两个最小生成树
mergeTwoMiniGenerateTree(MINI_GENERATE_TREE* pTree[], int length, int start, int end, int weight)
find_tree_by_index(pTree, length, start)
delete_mini_tree_from_group(MINI_GENERATE_TREE* pTree[], int length, MINI_GENERATE_TREE* pIndex) insert_line_into_queue(&pFirst->pLine, line->start, line->end, line->weigh)
****************************/
克鲁斯卡尔算法是计算最小生成树的一种算法。和prim算法(上,中,下)按照节点进行查找的方法不一样,克鲁斯卡尔算法是按照具体的线段进行的。现在我们假设一个图有m个节点,n条边。首先,我们需要把m个节点看成m个独立的生成树,并且把n条边按照从小到大的数据进行排列。在n条边中,我们依次取出其中的每一条边,如果发现边的两个节点分别位于两棵树上,那么把两棵树合并成为一颗树;如果树的两个节点位于同一棵树上,那么忽略这条边,继续运行。等到所有的边都遍历结束之后,如果所有的生成树可以合并成一条生成树,那么它就是我们需要寻找的最小生成树,反之则没有最小生成树。
上面的算法可能听上去有些费解,我们可以用一个示例说明一下,
- /*
- * 9
- * D -----------
- * 3 | |
- * | 6 |
- * A ------- B
- * | |
- * | 7 | 5
- * -------C----
- **/
- /*
- *
- * D
- *
- * A B
- *
- * C
- **/
- /*
- *
- * D
- * 3 |
- * |
- * A B
- *
- *
- * C
- **/
- /*
- *
- * D
- * 3 |
- * |
- * A B
- * |
- * | 5
- * C----
- **/
- /*
- * D
- * 3 |
- * | 6
- * A---------- B
- * |
- * | 5
- * C----
- **/
那么最小生成树的数据结构是什么,应该怎么定义,不知道朋友们还记得否?我们曾经在prim算法中讨论过,
- /* 直连边 */
- typedef struct _DIR_LINE
- {
- int start;
- int end;
- int weight;
- struct _DIR_LINE* next;
- }DIR_LINE;
- /* 最小生成树 */
- typedef struct _MINI_GENERATE_TREE
- {
- int node_num;
- int line_num;
- int* pNode;
- DIR_LINE* pLine;
- }MINI_GENERATE_TREE;
- /* 节点边信息 */
- typedef struct _LINE
- {
- int end;
- int weight;
- struct _LINE* next;
- }LINE;
- /*节点信息*/
- typedef struct _VECTEX
- {
- int start;
- int number;
- LINE* neighbor;
- struct _VECTEX* next;
- }VECTEX;
- /* 图信息 */
- typedef struct _GRAPH
- {
- int count;
- VECTEX* head;
- }GRAPH;
【 声明:版权所有,欢迎转载,请勿用于商业用途。 联系信箱:feixiaoxing @163.com】
前面说到,克鲁斯卡尔的算法是按照各个line的权重依次进行添加的,那么这就涉及到一个权重的排序问题。怎么排序呢?可以采用最简单的冒泡排序算法。可是这里排序的是数据结构,怎么办呢?那就只好采用通用排序算法了。
- void bubble_sort(void* array[], int length, int (*compare)(void*, void*), void(*swap)(void**, void**))
- {
- int outer;
- int inner;
- for(outer = length -1; outer >0; outer --){
- for(inner = 0; inner < outer; inner ++){
- if(compare(array[inner], array[inner + 1]))
- swap(&array[inner], &array[inner + 1]);
- }
- }
- return;
- }
- int compare(void* data1, void* data2)
- {
- DIR_LINE* line1 = (DIR_LINE*) data1;
- DIR_LINE* line2 = (DIR_LINE*) data2;
- return (line1->weight > line2->weight) ? 1 : 0;
- }
- void swap(void** data1, void** data2)
- {
- DIR_LINE* median;
- DIR_LINE** line1;
- DIR_LINE** line2;
- line1 = (DIR_LINE**) data1;
- line2 = (DIR_LINE**) data2;
- median = *line1;
- *line1 = *line2;
- *line2 = median;
- }
- int getVectexNumFromTree(MINI_GENERATE_TREE* pTree, int start, int end)
- {
- int index;
- int total;
- total = 0;
- for(index = 0; index < pTree->node_num; index++){
- if(start == pTree->pNode[index]){
- total ++;
- continue;
- }
- if(end == pTree->pNode[index]){
- total ++;
- }
- }
- return total;
- }
- int isDoubleVectexExistInTree(MINI_GENERATE_TREE* pTree[], int length, int start, int end)
- {
- int index = 0;
- int value = 0;
- int number = 0;
- for(index = 0; index < length; index ++){
- number = getVectexNumFromTree(pTree[index], start, end);
- if(number > value)
- value = number;
- }
- return (value == 2) ? 1 : 0;
- }
- void mergeTwoMiniGenerateTree(MINI_GENERATE_TREE* pTree[], int length, int start, int end, int weight)
- {
- MINI_GENERATE_TREE* pFirst;
- MINI_GENERATE_TREE* pSec;
- DIR_LINE* line;
- int index;
- /* 删除多余的最小生成树 */
- pFirst = find_tree_by_index(pTree, length, start);
- pSec = find_tree_by_index(pTree, length, end);
- delete_mini_tree_from_group(pTree, length, pSec);
- /* 合并节点 */
- for(index = 0; index < pSec->node_num; index ++){
- pFirst->pNode[pFirst->node_num + index] = pSec->pNode[index];
- }
- pFirst->node_num += pSec->node_num;
- /* 合并线段 */
- for(line = pSec->pLine; line; line = line->next){
- insert_line_into_queue(&pFirst->pLine, line->start, line->end, line->weight);
- }
- insert_line_into_queue(&pFirst->pLine, start, end, weight);
- /* 函数返回 */
- return;
- }
文章总结:
(1)本篇主要介绍了克鲁斯卡尔算法编写中需要处理的三个问题,排序、查找和合并
(2)复杂的函数都是由简单的函数构造而成的,我们可以把算法分成几个独立的部分,各个击破
(3)解决了这三个问题,下一篇博客就可以进行归总分析处理,逻辑上也十分清晰了
前面在讨论克鲁斯卡尔的算法的时候,我们分析了算法的基本过程、基本数据结构和算法中需要解决的三个问题(排序、判断、合并)。今天,我们继续完成剩下部分的内容。合并函数中,我们调用了两个基本函数,find_tree_by_index和delete_mini_tree_from_group,下面给出详细的计算过程。
- MINI_GENERATE_TREE* find_tree_by_index(MINI_GENERATE_TREE* pTree[], int length, int point)
- {
- int outer;
- int inner;
- for(outer = 0; outer < length; outer++){
- for(inner = 0; inner < pTree[outer]->node_num; inner ++){
- if(point == pTree[outer]->pNode[inner])
- return pTree[outer];
- }
- }
- return NULL;
- }
- void delete_mini_tree_from_group(MINI_GENERATE_TREE* pTree[], int length, MINI_GENERATE_TREE* pIndex)
- {
- int index;
- for(index = 0; index < length; index ++){
- if(pIndex == pTree[index])
- break;
- }
- memmove(&pTree[index +1], &pTree[index], sizeof(MINI_GENERATE_TREE*) * (length -1 - index));
- return;
- }
- MINI_GENERATE_TREE* _kruskal(MINI_GENERATE_TREE* pTree[], int length, DIR_LINE* pLine[], int number)
- {
- int index;
- if(NULL == pTree || NULL == pLine)
- return NULL;
- for(index = 0; index < number; index ++){
- bubble_sort((void**)pLine, number, compare, swap);
- if(2 == isDoubleVectexExistInTree(pTree, length, pLine[index]->start, pLine[index]->end))
- continue;
- mergeTwoMiniGenerateTree(pTree, length, pLine[index]->start, pLine[index]->end, pLine[index]->weight);
- length --;
- }
- return (1 != length) ? NULL : pTree[0];
- }
- MINI_GENERATE_TREE* kruskal(GRAPH* pGraph)
- {
- MINI_GENERATE_TREE** pTree;
- DIR_LINE** pLine;
- int count;
- int number;
- if(NULL == pGraph)
- return NULL;
- count = pGraph->count;
- number = get_total_line_number(pGraph);
- pTree = get_tree_from_graph(pGraph);
- pLine = get_line_from_graph(pGraph);
- return _kruskal(pTree, count, pLine, number);
- }
总结:
(1)代码中没有考虑内存的释放问题,需要改进和提高
(2)部分代码可以复用prim算法中的内容,数据结构也一样
(3)算法的编写贵在理解,只要步骤对了,再加上测试,一般问题都不大