【转】ACM 2567 -- 树的Prufer编码
本文介绍北京大学ACM网站2567号题目的解法。介绍部分基本翻译自网站上的题目介绍。
题目介绍:
给定一棵各节点编号为整数1,2,3...n的树(例如,无环连通图),其Prufer编码(Prufer code,不知道有没有标准的译法,用金山词霸没有查到,用Google也没有搜索到)构造方法如下:从树中去掉编号值最小的叶子节点(仅与一条边邻接的节点),以及与它邻接的边后,记下与它邻接的节点的编号。在树中重复这个过程,知道只剩下一个节点(总是编号为n的节点)为止。记下的n-1个编号序列就是树的Prufer编码。你的任务是计算给定的树的Prufer编码。树用下列语法表示:
T-->(N S)
S--> T S | empty
N-->number
就是说,树由一对括号包围一个表示根节点的数,以及其后跟随的任意多个(可能没有)由单个空格分隔的子树组成。(这里的表示方法类似编译原理中的产生式)根据这个定义,树的根节点也可能是一个叶子节点。
输入:
由多个测试案例组成。每个案例用输入文件的一行以上述格式表示一棵树。EOF表示输入结束。可以假定1 <= n <= 50。
输出:
对于每个测试案例产生包含给定树的Prufer编码的单行输出。数之间由单个空格分隔。不得在行末尾添加任何空格。
示例输入:
(2 (6 (7)) (3) (5 (1) (4)) (8))
(1 (2 (3)))
(6 (1 (4)) (2 (3) (5)))
(1 (2 (3)))
(6 (1 (4)) (2 (3) (5)))
示例输出:
5 2 5 2 6 2 8
2 3
2 1 6 2 6
2 3
2 1 6 2 6
程序:
1 2 #include 3 #include 4 #include 5 6 #define END 0 // 结束 7 #define L_BRACKET 1 // 左括号 8 #define R_BRACKET 2 // 右括号 9 #define NUMBER 4 // 数 10 11 typedef struct _node 12 { 13 int value; // 节点值 14 int childs; // 子节点数 15 struct _node *parent; // 父节点 16 struct _node *first_child; // 第一个孩子节点 17 struct _node *sibling; // 下一个兄弟节点 18 }NODE; 19 20 typedef struct _token 21 { 22 int type; // 标记类型(左、右括号,数) 23 int value; // 值(仅当类型为数时有效) 24 }TOKEN; 25 26 typedef void (*FUNC)(NODE *); 27 static NODE *pSmallest = 0;// 指向最小的叶子节点 28 29 34 char *GetNextToken(char *input,TOKEN *pToken) 35 { 36 char *p,ch; 37 int value; 38 39 pToken->type = END; 40 41 p = input; 42 while ((ch = *p) && ch != '(' && ch != ')' && !isdigit(ch)) 43 p++; 44 switch(ch) 45 { 46 case '(': pToken->type = L_BRACKET; p++; break; 47 case ')': pToken->type = R_BRACKET; p++; break; 48 default: 49 50 if (isdigit(ch)) 51 { 52 value = 0; 53 while ((ch = *p) && isdigit(ch)) 54 { 55 value = 10 * value + (ch - '0'); 56 p++; 57 } 58 pToken->type = NUMBER; 59 pToken->value = value; 60 } 61 break; 62 } 63 64 return (*p == '\0' ? 0 : p); 65 } 66 67 71 NODE *BuildTree(char *input) 72 { 73 char *p; 74 TOKEN token; 75 NODE *pCur,*pNew,*pRoot,*pLast; 76 77 p = input; pCur = pLast = pRoot = 0; 78 do 79 { 80 p = GetNextToken(p,&token); 81 switch(token.type) 82 { 83 case L_BRACKET: 84 break; 85 case R_BRACKET: 86 87 pLast = pCur; 88 if (0 != pCur) 89 pCur = pCur->parent; 90 break; 91 case NUMBER: 92 pNew = (NODE *)malloc(sizeof(NODE)); 93 memset(pNew,0,sizeof(NODE)); 94 pNew->value = token.value; 95 96 pNew->parent = pCur; 97 if (0 != pLast) 98 { 99 pLast->sibling = pNew; 100 pLast = pNew; 101 } 102 if (0 != pCur) 103 { 104 if (0 == pCur->childs++) 105 pCur->first_child = pNew; 106 } 107 else pRoot = pNew; 108 109 p = GetNextToken(p,&token); 110 if (token.type == L_BRACKET) 111 { 112 pCur = pNew; 113 pLast = 0; 114 } 115 else if (token.type == R_BRACKET) 116 pLast = pNew; 117 break; 118 } 119 }while (0 != p); 120 return pRoot; 121 } 122 123 127 void TravelTree(NODE *pRoot,FUNC func) 128 { 129 NODE *pCur; 130 if (0 == pRoot) return; 131 (*func)(pRoot); 132 pCur = pRoot->first_child; 133 while (pCur) 134 { 135 TravelTree(pCur,func); 136 pCur = pCur->sibling; 137 } 138 } 139 140 141 int IsLeafNode(NODE *pNode) 142 { 143 return (0 != pNode && (0 == pNode->childs && 0 != pNode->parent) 144 || (1 == pNode->childs && 0 == pNode->parent)); 145 } 146 147 148 void LeafValueCompare(NODE *pNode) 149 { 150 if (IsLeafNode(pNode)) 151 if (0 == pSmallest) pSmallest = pNode; 152 else if (pNode->value < pSmallest->value) 153 pSmallest = pNode; 154 } 155 156 161 NODE *ReleaseLeafNode(NODE *pRoot,NODE *pNode) 162 { 163 NODE *pParent,*pSibling; 164 if (0 == pNode->childs) 165 { 166 pParent = pNode->parent; 167 if (pParent) 168 { 169 pSibling = pParent->first_child; 170 if (pNode == pSibling) 171 pParent->first_child = pNode->sibling; 172 else 173 { 174 while (pSibling && pSibling->sibling != pNode) 175 pSibling = pSibling->sibling; 176 if (pSibling) 177 pSibling->sibling = pNode->sibling; 178 } 179 pParent->childs--; 180 } 181 free(pNode); 182 } 183 else 184 { 185 pRoot = pNode->first_child; 186 pRoot->parent = 0; 187 free(pNode); 188 } 189 return pRoot; 190 } 191 192 void ReleaseTree(NODE *pRoot) 193 { 194 NODE *pCur,*pNext; 195 if (pRoot) 196 { 197 pCur = pRoot->first_child; 198 while (pCur) 199 { 200 pNext = pCur->sibling; 201 ReleaseTree(pCur); 202 pCur = pNext; 203 } 204 free(pRoot); 205 } 206 } 207 208 int main(int argc,char *argv[]) 209 { 210 NODE *pRoot; 211 char line[250]; 212 int n; 213 while (!feof(stdin)) 214 { 215 line[0] = '\0'; 216 gets(line); 217 pRoot = BuildTree(line); 218 n = 0; 219 while (pRoot && pRoot->childs != 0) 220 { 221 pSmallest = 0; 222 TravelTree(pRoot,LeafValueCompare); // 遍历树 223 if (0 == pSmallest->childs) 224 printf(n++ ? " %d" : "%d",pSmallest->parent->value); 225 else 226 printf(n++ ? " %d" : "%d",pSmallest->first_child->value); 227 pRoot = ReleaseLeafNode(pRoot,pSmallest); 228 } 229 if (0 != pRoot) 230 { 231 printf("\n"); 232 ReleaseTree(pRoot); 233 } 234 } 235 return 0; 236 } 237 |
说明:
1 解决此问题的第一个难点就在于如何利用输入的一行数据建立树。程序中BuildTree()函数用于建立树。函数中pCur表示当前处理的树层次父节点,当前层的节点都是它的孩子节点;pLast表示最近处理的一个节点,新建的节点将成为它的兄弟节点,放在其右边;pRoot当然就表示根节点了。BuildTree()读入一行中的各种Token进行处理,直到一行处理完成。
(1)第85至90行:遇到右括号时表示某子树结束,返回到上一层。因为子树也是树,而根据上面的定义T-->(N S)知道,右括号是树的结束符(编译原理里面有专门的术语的,不记得是不是“结束符”)。遇到右括号就表示某子树处理完成了,需要返回到上一级,处理这个子树的下一个右兄弟节点(可能是某子树的根)
(2)第92到94行:遇到一个数时,建立一个它所表示的新的树节点。
(4)第109到116行:根据下一个Token类型决定做什么动作,这个好像也跟编译原理里的某个理论有关系,也记不清楚了。这里我看代码115行时候有点疑惑了:如果下一个符号既不是左括号,又不是右括号呢?仔细想想,根据上面的语法定义,数后面是不会跟随一个数的(在编译原理上好像所谓的“尾随符号”就是表示这个的,在书上叫做follow符号集)
2 树建立好之后的任务就是生成Prufer编码。我采用的方法比较笨:不停地遍历树,每次遍历找到编号最小的叶子节点,输出它的父节点的编号,将它从树中去掉,直到树只剩下一个节点。
(1)127到138行:对树进行前序遍历,采用的是递归的方法。不过在遍历的之前先调用148行的LeafValueCompare()函数,来记录编号最小的叶子节点。
(2)141到145行:IsLeafNode()函数判断节点是不是叶子节点。注意最后一个条件:(1 == pNode->childs && 0 == pNode->parent)。这就是上文说的,树的根节点也可能是叶子节点。也就是树根节点在没有任何孩子节点时就也是叶子节点。
关于生成Prufer编码,我想应该有效率更高的方法:只进行一次遍历,记下各个节点的编号值以及节点指针,按照节点编号从小到大进行排序;以后从小到大进行搜索,搜索到一个叶子节点就把它去掉,记下其父节点编号。直到树只剩下一个节点。