非递归创建二叉树

前言

  使用递归(Recursion)创建二叉树(Binary Tree)的非顺序存储结构(即二叉链表),能够简化算法编写的复杂程度,可是递归效率低,并且容易致使堆栈溢出,于是颇有必要使用非递归算法。node

引入

  不管是单链表仍是二叉树,建立时要解决问题就是关系的创建,即单链表中前驱节点与当前节点的关系和二叉树中父节点与子节点的关系。算法

  首先,思考一下创建单链表的过程,为了使链表各个节点链接起来,在建立当前节点(q)的时候,需借助一个指针(p)指向前一个节点,而后p->next = q。数组

  由此推广至二叉树,把二叉树每一层比做是链表的节点,接着借助一个指针列表(parent_list)存放父层的全部节点,而后每建立当前层的一个节点(current_node)时就与父层次的节点创建关系。spa

分析

  引入中提出了建立二叉树的总体思想,同时也抛出一个问题,如何创建父节点与子节点的关系?指针

  下图为一棵普通的二叉树,下面对其进行一些处理。code

  首先,将其补全,用#表明空节点,补全规则为:将只有一个或没有子节点的节点(空节点除外),用空节点补全为两个子节点。blog

  为便于后续分析,将其节点左结构化并去掉关系线。递归

  如今,回顾一下引入中提到的“把二叉树每一层比做链表的节点”,而创建单链表每次都只涉及两个节点,于是下面每次分析都只涉及两层。内存

  其中,规定第二层为当前层,第一层为当前层的父层,且当前层为下次分析的父层。io

  在规定,父层为一个数组p[i](i为父层节点数,后同),当前层为数组q[j]

  下图为第一次,选中层的节点标记为深灰底色。

  能够容易看出,当前层的两个节点与父层节点的关系:

    p[0]->next = q[0]

    p[0]->next = q[1]

  为其添加关系线,而后再看下一次。

  一样,其关系以下:

    p[0]->next = q[0]

    p[0]->next = q[1] = #

    p[1]->next = q[2]

    p[2]->next = q[3]

  由前两次不可贵出,j/2 = i (注意这里 / 运算结果只取整数),这个结果和彻底二叉树的性质相同,可是注意这里不是一棵彻底二叉树。

  接着看下一次。

  如图所示,为了创建正确的二叉树关系,父层的节点必定不能为空节点。

  总观整个结构,能够得出一个规律(该规律可用于动态改变父层列表,以减小内存占用):父层节点数(不包括空节点)的两倍刚好为当前层的节点数(包括空节点)。

  此时,还有个小问题,即是判断当前节点是左子树(Left Subtree)仍是右子树(Right Subtree)?

  这里解决方法很简单,即是计算 j % 2 ,若为0则为左子树,不然为右子树。

实现

  如今,理清一下思路:

    1.以从上到下、从左到右的顺序建立二叉树,所以有两层循环。

    2.有个父层列表(parent_list)用于存放父层全部节点的地址,且外层循环一次就更新一次(即让父层列表等于当前层列表,代码中为tmp_list),同时释放旧父层列表。

    3.内层循环建立每一层的节点,若输入数据不为“#”则不为空节点,而后申请内存空间并赋值,再根据上述论述进行父节点和当前节点(current_node)创建关系。

  下面给出代码:

#include <stdio.h>
#include <malloc.h>

// 布尔类型
typedef enum {FALSE=0,TRUE=1} bool;
// 用于标识当前创建左子树仍是右子树
typedef enum {LEFT=0,RIGHT=1} flag;
// 节点存放数据的类型
typedef char data_type;
// 二叉树节点类型
typedef struct node {
  data_type data;
  struct node *left_subtree,
  *right_subtree;
} node , *bin_tree;

bool create_bin_tree(bin_tree *root)
{
  /* 建立根节点 */
  data_type data = '\0';
  scanf("%c",&data);
  if(data == '#'){
    return FALSE; // 根节点为空,建立失败
  }
  else
  {
    *root = (node*)malloc(sizeof(node));
    (*root)->data = data;
    (*root)->left_subtree = NULL;
    (*root)->right_subtree = NULL;
  }
  
  /* 建立非根节点 */
  // 存放父层的节点列表
  node **parent_list = (node**)malloc(sizeof(node*));
  parent_list[0] = *root;
  // 父节点个数
  int parent_amount = 1;  
  
  while(1)
  {
    // 当前节点个数,设置为父节点个数的两倍
    int current_amount = parent_amount * 2;
    // 建立临时列表存放当前深度的节点
    node **tmp_list = (node**)malloc(sizeof(node*)
    * current_amount);
    // 用于记录当前深度节点非空节点个数
    int count = 0;
    // 建立当前层次的全部节点
    int j = 0;
    for(;j < current_amount;++j)
    {
      data = '\0';
      scanf("%c",&data);
      if(data != '#')  // 不为空节点
      {
        // 新建节点并赋值
        node *current_node = (node*)malloc(sizeof(node));
        current_node->data = data;
        current_node->left_subtree = NULL;
        current_node->right_subtree = NULL;
        // 加入到临时列表中
        tmp_list[count] = current_node;
        // 非空节点数加1
        count++;
        // 与父节点创建关系
        if(j%2 == LEFT)
        {
          (parent_list[j/2])->left_subtree = current_node;
        }
        else
        {
          (parent_list[j/2])->right_subtree = current_node;
        }
      }
    }  // for循环结束
    
    // 释放父层列表
    free(parent_list);
    // 更新父层列表
    parent_list = tmp_list;
    // 更新父节点数
    parent_amount = count;
    // 若非空节点数为0,则中止建立
    if(count == 0) break;
  }
  return TRUE;
}

  上述代码中,因为根节点较特殊且须要传出地址,为了下降代码编写复杂程度,于是独立于内层循环。

后话

  写文章除了用于记录外,其实也无形中能理清想问题的思路。如写这篇文章前,代码虽实现了,但总感受思路很乱。而文章写完了,便也豁然开朗了。

相关文章
相关标签/搜索