非遞歸創建二叉樹

前言

  使用遞歸(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;
}

  上述代碼中,因爲根節點較特殊且須要傳出地址,爲了下降代碼編寫複雜程度,於是獨立於內層循環。

後話

  寫文章除了用於記錄外,其實也無形中能理清想問題的思路。如寫這篇文章前,代碼雖實現了,但總感受思路很亂。而文章寫完了,便也豁然開朗了。

相關文章
相關標籤/搜索