HDOJ-1100 Trees made to order

一、题目链接

http://acm.hdu.edu.cn/showproblem.php?pid=1100
二、题目分析

对二叉树的所有形态顺序编号,编号规则是:节点数越多的编号越大;节点数相等,左子树节点数越多的越大;节点数相等,左子树节点数也相等,则依此规则比较右子树。

现给定一个正整数,依题目要求输出对应编号的二叉树形态。

三、求解思路

由题目输出格式要求,很直观地联想到使用深度优先搜索dfs,以当前二叉树对应的编号为条件,依次递归输出左子树和右子树。

 1 int main(void)
 2 {
 3     int n;
 4     unsigned int arr[20] = {0};
 5 
 6     init(arr, 20);
 7 
 8     while (scanf("%d", &n) != EOF && n != 0) {
 9         dfs(n, arr, 20);
10         printf("\n");
11     }
12 
13     return 0;
14 }

 

其中arr存储的是不同节点数对应的二叉树形态数,即arr[i]表示有i个节点的二叉树总共有多少种形态。

 1 void dfs(int n, unsigned int *arr, int num)
 2 {
 3     int left, right;
 4 
 5     if (n == 1) {
 6         printf("X");
 7         return ;
 8     }
 9 
10     getChildren(&left, &right, n, arr, num);
11 
12     if (left != 0) {
13         printf("(");
14         dfs(left, arr, num);
15         printf(")");
16     }
17     printf("X");
18     if (right != 0) {
19         printf("(");
20         dfs(right, arr, num);
21         printf(")");
22     }
23 }

 

通过getChildren得到左右子树对应的编号,然后递归搜索即可。

 1 void getChildren(int *left, int *right, int n, unsigned int *arr, int num)
 2 {
 3     int i;
 4     int nall, nleft, nright;
 5     int div, rem;
 6 
 7     for (i = 1; i < num; i++) {
 8         if ((unsigned)n > arr[i]) {
 9             n -= arr[i];
10         } else {
11             nall = i;
12             break;
13         }
14     }
15 
16     for (i = 0; i < nall; i++) {
17         if ((unsigned)n > arr[i] * arr[nall - i - 1]) {
18             n -= arr[i] * arr[nall - i - 1];
19         } else {
20             nleft = i;
21             break;
22         }
23     }
24     nright = nall - nleft - 1;
25 
26     div = n / arr[nright];
27     rem = n % arr[nright];
28 
29     *left = *right = 0;
30 
31     if (nleft != 0) {
32         for (i = 1; i < nleft; i++) {
33             *left += arr[i];
34         }
35         *left += rem == 0 ? div : div + 1;
36 
37     if (nright != 0) {
38         for (i = 1; i < nright; i++) {
39             *right += arr[i];
40         }
41         *right += rem == 0 ? arr[nright] : rem;
42     }
43 }

计算左右子树对应序号的思路:

  1. 计算总节点数nall、左子树节点数nleft和右子树节点数nright;
  2. 在计算总节点数和左子树节点数的同时,刨除所有总节点数小于nall的二叉树形态,刨除所有左子树节点数小于nleft的二叉树形态;
  3. 那么下面如何确定当前左子树是nleft个节点的二叉树中的第几个?当前右子树又是nright个节点的二叉树中的第几个?且看一例
  4. 这里假定左子树有三种形态,分别是1,2,3,右子树有两种形态,分别是a,b。额,什么,没有哪个节点总数对应三种二叉树形态?拜托,举例子撒,画五种形态的很累的。什么,画的太难看?88
  5. 由这个小例子可以看出,要想确定左右子树对应的序号,需要依据右子树的形态数 ,对应的就是代码中的div,rem以及下面计算的blablabla

嗯,差不多就这样。为什么是arr[20]?怎么计算i个节点对应的二叉树形态数?wiki一下Catalan numbers, ok?

附完整代码:

  1 /* http://acm.hdu.edu.cn/showproblem.php?pid=1100 */
  2 
  3 #include <stdio.h>
  4 
  5 void    init(unsigned int *arr, int N);
  6 void    dfs(int n, unsigned int *arr, int num);
  7 void    getChildren(int *left, int *right, int n, unsigned int *arr, int num);
  8 
  9 int main(void)
 10 {
 11     int n;
 12     unsigned int arr[20] = {0};
 13 
 14     init(arr, 20);
 15 
 16     while (scanf("%d", &n) != EOF && n != 0) {
 17         dfs(n, arr, 20);
 18         printf("\n");
 19     }
 20 
 21     return 0;
 22 }
 23 
 24 void init(unsigned int *arr, int N) 
 25 {
 26     int i, j;
 27 
 28     arr[0] = arr[1] = 1;
 29     for (i = 2; i < N; i++) {
 30         for (j = 0; j < i; j++) {
 31             arr[i] += arr[j] * arr[i - j - 1];
 32         }
 33     }
 34 }
 35 
 36 void dfs(int n, unsigned int *arr, int num)
 37 {
 38     int left, right;
 39 
 40     if (n == 1) {
 41         printf("X");
 42         return ;
 43     }
 44 
 45     getChildren(&left, &right, n, arr, num);
 46 
 47     if (left != 0) {
 48         printf("(");
 49         dfs(left, arr, num);
 50         printf(")");
 51     }
 52     printf("X");
 53     if (right != 0) {
 54         printf("(");
 55         dfs(right, arr, num);
 56         printf(")");
 57     }
 58 }
 59 
 60 void getChildren(int *left, int *right, int n, unsigned int *arr, int num)
 61 {
 62     int i;
 63     int nall, nleft, nright;
 64     int div, rem;
 65 
 66     for (i = 1; i < num; i++) {
 67         if ((unsigned)n > arr[i]) {
 68             n -= arr[i];
 69         } else {
 70             nall = i;
 71             break;
 72         }
 73     }
 74 
 75     for (i = 0; i < nall; i++) {
 76         if ((unsigned)n > arr[i] * arr[nall - i - 1]) {
 77             n -= arr[i] * arr[nall - i - 1];
 78         } else {
 79             nleft = i;
 80             break;
 81         }
 82     }
 83     nright = nall - nleft - 1;
 84 
 85     div = n / arr[nright];
 86     rem = n % arr[nright];
 87 
 88     *left = *right = 0;
 89 
 90     if (nleft != 0) {
 91         for (i = 1; i < nleft; i++) {
 92             *left += arr[i];
 93         }
 94         *left += rem == 0 ? div : div + 1;
 95 
 96     if (nright != 0) {
 97         for (i = 1; i < nright; i++) {
 98             *right += arr[i];
 99         }
100         *right += rem == 0 ? arr[nright] : rem;
101     }
102 }

好吧,使用dfs(int n, int nall, int *arr, int num)或许能写的更简单些。

转载于:https://www.cnblogs.com/skyliuxu/p/5617644.html

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值