/********************
1 struct: DIR_LINE MINI_GENERATE_TREE
2 insert_line_into_queue(DIR_LINE** ppLine, int start, int end, int weight)
delete_line_from_queue(DIR_LINE** ppLine, DIR_LINE* pLine)
3 get_mini_tree_from_graph(GRAPH* pGraph)
get_dir_line_from_graph(GRAPH* pGraph, MINI_GENERATE_TREE* pMiniTree, DIR_LINE* pDirLine)
delete_unvalid_line_from_list(DIR_LINE** ppHead, MINI_GENERATE_TREE* pMiniTree)
check_valid_for_line(DIR_LINE* pDirLine, MINI_GENERATE_TREE* pMiniTree)
sort_for_line_list(DIR_LINE** ppNode)
insert_for_sort_operation(DIR_LINE** ppNode, DIR_LINE* pNode)
insert_line_into_queue(&pMiniTree->pLine, pDirLine.start, pDirLine.end, pDirLine.weight);
insert_node_into_mini_tree(&pDirLine, pMiniTree)
**************************/
前面我们讨论了图的创建、添加、删除和保存等问题。今天我们将继续讨论图的一些其他问题,比如说如何在图的环境下构建最小生成树。为什么要构建最小生成树呢?其实原理很简单。打个比方,现在某一个乡镇有n个村,那么这n个村肯定是联通的。现在我们打算在各个村之间搭建网线,实现村村通的工程。那么有什么办法可以实现村村互通,同时又使得最后的总距离最小呢?要达到这个目的,就必须在已有的图中构建最小生成树。
生成最小生成树的方法很多,prim方法就是其中的一种。那么生成最小生成树的基本步骤是什么呢?很简单,听我慢慢道来:
1)以某一个点开始,寻找当前该点可以访问的所有的边;
2)在已经寻找的边中发现最小边,这个边必须有一个点还没有访问过,将还没有访问的点加入我们的集合,记录添加的边;
3)寻找当前集合可以访问的所有边,重复2的过程,直到没有新的点可以加入;
4)此时由所有边构成的树即为最小生成树。
那么,代码应该怎么编写呢?朋友们可以自己好好思考一下。
a)首先,我们定义基本的数据结构。
- 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;
b)DIR_LINE的基本操作
- STATUS insert_line_into_queue(DIR_LINE** ppLine, int start, int end, int weight)
- {
- DIR_LINE* pLine;
- if(NULL == ppLine)
- return FALSE;
- if(NULL == *ppLine){
- *ppLine = create_new_dir_line(start, end, weight);
- return TRUE;
- }
- pLine = create_new_dir_line(start, end, weight);
- pLine->next = *ppLine;
- *ppLine = pLine;
- return TRUE;
- }
- STATUS delete_line_from_queue(DIR_LINE** ppLine, DIR_LINE* pLine)
- {
- DIR_LINE* prev;
- if(NULL == ppLine || NULL == *ppLine || NULL == pLine)
- return FALSE;
- if(pLine == *ppLine){
- *ppLine = pLine->next;
- goto final;
- }
- prev = *ppLine;
- while(pLine != prev->next)
- prev = prev->next;
- prev->next = pLine->next;
- final:
- free(pLine);
- return TRUE;
C)编写最小生成树,涉及创建、挑选和添加过程
- MINI_GENERATE_TREE* get_mini_tree_from_graph(GRAPH* pGraph)
- {
- MINI_GENERATE_TREE* pMiniTree;
- DIR_LINE pDirLine;
- if(NULL == pGraph || NULL == pGraph->head)
- return NULL;
- pMiniTree = (MINI_GENERATE_TREE*)malloc(sizeof(MINI_GENERATE_TREE));
- assert(NULL != pMiniTree);
- memset(pMiniTree, 0, sizeof(MINI_GENERATE_TREE));
- pMiniTree->node_num = 1;
- pMiniTree->pNode = (int*)malloc(sizeof(int) * pGraph->count);
- memset(pMiniTree->pNode, 0, sizeof(int) * pGraph->count);
- pMiniTree->pNode[0] = pGraph->head->start;
- while(1){
- memset(&pDirLine, 0, sizeof(DIR_LINE));
- get_dir_line_from_graph(pGraph, pMiniTree, &pDirLine);
- if(pDirLine.start == 0)
- break;
- pMiniTree->line_num ++;
- insert_line_into_queue(&pMiniTree->pLine, pDirLine.start, pDirLine.end, pDirLine.weight);
- insert_node_into_mini_tree(&pDirLine, pMiniTree);
- }
- return pMiniTree;
- }
d) 构建挑选函数,选择最合适的边
- void get_dir_line_from_graph(GRAPH* pGraph, MINI_GENERATE_TREE* pMiniTree, DIR_LINE* pDirLine)
- {
- DIR_LINE* pHead;
- DIR_LINE* prev;
- VECTEX* pVectex;
- LINE* pLine;
- int index;
- int start;
- pHead = NULL;
- for(index = 0; index < pMiniTree->node_num; index++){
- start = pMiniTree->pNode[index];
- pVectex = find_vectex_in_graph(pGraph->head, start);
- pLine = pVectex->neighbor;
- while(pLine){
- insert_line_into_queue(&pHead, start, pLine->end, pLine->weight);
- pLine = pLine->next;
- }
- }
- if(NULL == pHead)
- return;
- delete_unvalid_line_from_list(&pHead, pMiniTree);
- if(NULL == pHead)
- return;
- sort_for_line_list(&pHead);
- memmove(pDirLine, pHead, sizeof(DIR_LINE));
- while(pHead){
- prev = pHead;
- pHead = pHead->next;
- free(prev);
- }
- return;
- }
e)添加节点函数,将尚不是最小生成树的点纳入到最小生成树当中去
- void insert_node_into_mini_tree(DIR_LINE* pLine, MINI_GENERATE_TREE* pMiniTree)
- {
- int index;
- for(index = 0; index < pMiniTree->node_num; index ++){
- if(pLine->start == pMiniTree->pNode[index]){
- pMiniTree->pNode[pMiniTree->node_num++] = pLine->end;
- return;
- }
- }
- pMiniTree->pNode[pMiniTree->node_num++] = pLine->start;
- return;
- }
注意事项:
(1)d、e是c中调用的子函数,如果大家观察一下就明白了
(2)最小生成树是按照自顶向下的顺序编写的,虽然c中的子函数完成了,但是d中还有两个子函数没有着落
(3)d中的函数delete_unvalid_line_from_list、sort_for_line_list会在下一篇中继续介绍
(4)算法只要能够按照手工计算的流程编写出来,基本上问题不大,但是一些细节还是要小心注意的
前两篇博客我们讨论了prim最小生成树的算法,熟悉了基本的流程。基本上来说,我们是按照自上而下的顺序来编写代码的。首先我们搭建一个架构,然后一步一步完成其中的每一个子功能,这样最后构成一个完成prim算法计算过程。
f)将DIR_LINE队列中不符合的数据删除,主要是双节点都已经访问过的DIR_LINE数据。
- void delete_unvalid_line_from_list(DIR_LINE** ppHead, MINI_GENERATE_TREE* pMiniTree)
- {
- DIR_LINE* prev;
- DIR_LINE* pcur;
- STATUS result;
- prev = NULL;
- pcur = *ppHead;
- while(pcur){
- if(!check_valid_for_line(pcur, pMiniTree)){
- result = delete_line_from_queue(ppHead, pcur);
- assert(TRUE == result);
- if(NULL == prev)
- pcur = *ppHead;
- else
- pcur = prev->next;
- continue;
- }
- prev = pcur;
- pcur = pcur->next;
- }
- return;
- }
g) 在f)函数中使用了判定DIR_LINE合法性的函数,我们需要完善一下。
- int check_valid_for_line(DIR_LINE* pDirLine, MINI_GENERATE_TREE* pMiniTree)
- {
- int index;
- int flag_start;
- int flag_end;
- flag_start = 0;
- flag_end = 0;
- for(index = 0; index < pMiniTree->node_num; index ++){
- if(pDirLine->start == pMiniTree->pNode[index]){
- flag_start = 1;
- break;
- }
- }
- for(index = 0; index < pMiniTree->node_num; index ++){
- if(pDirLine->end == pMiniTree->pNode[index]){
- flag_end = 1;
- break;
- }
- }
- return (1 == flag_start && 1 == flag_end) ? 0 : 1;
- }
h) 最后就是对当前已经入队的DIR_LINE数据排序,其实就是 链表排序 。
- void insert_for_sort_operation(DIR_LINE** ppNode, DIR_LINE* pNode)
- {
- DIR_LINE* prev;
- DIR_LINE* cur;
- /* 在第一个数据之前插入pNode */
- if(pNode->weight < (*ppNode)->weight){
- pNode->next = *ppNode;
- *ppNode = pNode;
- return;
- }
- cur = *ppNode;
- while(cur){
- if(pNode->weight < cur->weight)
- break;
- prev = cur;
- cur = cur->next;
- }
- pNode->next = prev->next;
- prev->next = pNode;
- return;
- }
- void sort_for_line_list(DIR_LINE** ppNode)
- {
- DIR_LINE* prev;
- DIR_LINE* curr;
- if(NULL == ppNode || NULL == *ppNode)
- return;
- curr = (*ppNode) ->next;
- (*ppNode) ->next = NULL;
- while(curr){
- prev = curr;
- curr = curr->next;
- insert_for_sort_operation(ppNode, prev);
- }
- }
算法总结:
1)算法本身还有改进的空间,比如是不是内存分配上每一次都要重建DIR_LINE队列有待商榷
2)算法编写不是一部就位的,中间有反复更有删改,写四五次是很正常的事情
3)编写代码的时候最好做到边修改、边测试,这样可以一方面增加代码的健壮度,一方面还能提高自己的信心
4)如果存在可能,可以复用以前写过的、稳定的算法代码,比如说排序、查找、堆栈、二叉树之类的代码