二叉樹應用 二叉樹應用

二叉樹應用

1     求二叉樹中相距最遠的兩個節點之間的距離html

2     判斷二叉樹是否平衡二叉樹node

3     指定二叉樹,給定兩節點求其最近共同父節點算法

4     二叉樹的廣度遍歷、逐層打印二叉樹節點數據、只打印某層節點數據post

5     在二叉樹中找出和(葉子到根節點路徑上的全部節點的數據和)爲指定值的全部路徑。url

6     將二叉查找樹轉爲有序的雙鏈表.net

7     求二叉樹的鏡像指針

8     二叉樹前序、中序、後序遍歷的非遞歸實現htm

9     計算二叉樹高度的非遞歸實現blog

10    鏈接二叉樹同一層上的結點遞歸

 

 

特別說明:

 

本文中二叉樹結構定義爲:

struct Node {

  Node* left;

  Node* right;

  int data;

};

定義:空二叉樹的高度爲-1,只有根節點的二叉樹高度爲0,根節點在0層,深度爲0。

 

      1     求二叉樹中相距最遠的兩個節點之間的距離

兩個節點的距離爲兩個節點間最短路徑的長度。

求兩節點的最遠距離,實際就是求二叉樹的直徑。假設相距最遠的兩個節點分別爲A、B,它們的最近共同父節點(容許一個節點是其自身的父節點)爲C,則A到B的距離 =  A到C的距離 + B到C的距離

節點A、B分別在C的左右子樹下(假設節點C的左右兩子樹均包括節點C),不妨假設A在C的左子樹上,由假設「A到B的距離最大」,先固定B點不動(即B到C的距離不變),根據上面的公式,可得A到C的距離最大,即點A是C左子樹下距離C最遠的點,即:

A到C的距離 = C的左子樹的高度

同理,    B到C的距離 = C的右子樹的高度

   所以,本問題能夠轉化爲:「二叉樹每一個節點的左右子樹高度和的最大值」。

 

static int tree_height(const Node* root, int& max_distance)

{

  const int left_height  = root->left  ? tree_height(root->left,  max_distance)  + 1 : 0;

  const int right_height = root->right ? tree_height(root->right, max_distance)  + 1 : 0;

  const int distance = left_height + right_height;

  if (max_distance < distance) max_distance = distance;

  return (left_height > right_height ? left_height : right_height);

}

 

int tree_diameter(const Node* root)

{

  int max_distance = 0;

  if (root) tree_height(root, max_distance);

  return max_distance;

}

 

      2     判斷二叉樹是否平衡二叉樹

根據平衡二叉樹的定義:每一個結點的左右子樹的高度差小等於1,只須在計算二叉樹高度時,同時判斷左右子樹的高度差便可。

 

static int tree_height(const Node* root, bool& balanced)

{

  const int left_height = root->left ? tree_height(root->left, balanced) + 1 : 0;

  if (!balanced) return 0;

 

  const int right_height = root->right ? tree_height(root->right, balanced) + 1 : 0;

  if (!balanced) return 0;

 

  const int diff = left_height - right_height;

  if (diff < -|| diff > 1) balanced = false; 

  return (left_height > right_height ? left_height : right_height);

}

 

bool is_balanced_tree(const Node* root)

{

  bool balanced = true;

  if (root) tree_height(root, balanced);

  return balanced;

}

 

      3     指定二叉樹,給定兩節點求其最近共同父節點

遍歷二叉樹時,只有先訪問給定兩節點A、B後,纔可能肯定其最近共同父節點C,於是採用後序遍歷。

能夠統計任一節點的左右子樹「是否包含A、B中的某一個」(也能夠直接統計「包含了幾個A、B」)。當後序遍歷訪問到某個節點D時,可獲得三條信息:節點D是不是A、B兩節點之1、其左子樹是否包含A、B兩節點之1、其右子樹是否包含A、B兩節點之一。當三條信息中有兩個爲真時,就能夠肯定節點D的父節點(或節點D,若是容許一個節點是自身的父節點的話)就是節點A、B的最近共同父節點。另外,找到最近共同父節點C後應中止遍歷其它節點。

 

① 容許節點是其自身的父節點(若B是A的孩子,A、B的最近共同父節點爲A):

//代碼1:

static bool lca(const Node* root, const Node* va, const Node* vb, const Node*& result)

{

  const bool left = root->left ? lca(root->left, va, vb, result) : false;

  if (result) return false;  //剪枝,隨便返回一個值

 

  const bool right = root->right ? lca(root->right, va, vb, result) : false;

  if (result) return false;

 

  //因爲va可能等於vb,不要寫成: const int mid = (root == va) | (root == vb);

  const int mid = (root == va) + (root == vb); 

  int ret = left + right + mid;

  if (ret == 2) result = root;

  return (bool)ret;

}

 

const Node* lca(const Node* root, const Node* va, const Node* vb)

{

  const Node* result = NULL;

  if (root) lca(root, va, vb, result);

  return result;

}

 

上面的代碼中須要特別注意的是:判斷所訪問時節點是不是兩節點A、B時要寫成

constint mid = (root == va) + (root == vb);

而不是:  const int mid = (root == va) | (root == vb);

這樣當va等於vb時,能夠獲得正確結果。

 

若採用第二種方法,代碼能夠改寫爲:

//代碼2:

static int lca(const Node* root, const Node* va, const Node* vb, const Node*& result)

{

  const int N = 2;

  const int left = root->left ? lca(root->left, va, vb, result) : 0;

  if (left == N) return N;

 

  const int right = root->right ? lca(root->right, va, vb, result) : 0;

  if (right == N) return N;

 

  const int mid = (root == va) + (root == vb);

  const int ret = left + right + mid;

  if (ret == N) result = root;

  return ret;

}

 

const Node* lca(const Node* root, const Node* va, const Node* vb)

{

  const Node* result = NULL;

  if (root) lca(root, va, vb, result);

  return result;

}

 

 

② 節點不能是其自身的父節點(若B是A的孩子,A、B的最近共同父節點爲A的父節點):

只要再增長一個變量保存父節點便可。

 

static bool lca(const Node* root, const Node* va, const Node* vb,

                const Node* parrent, const Node*& result)

{

  bool left = false;

  if (root->left) {

    left = lca(root->left, va, vb, root, result);

    if (result) return false;

  }

 

  bool right = false;

  if (root->right) {

    right = lca(root-> right, va, vb, root, result);

    if (result) return false;

  } 

 

  const int mid = (root == va) + (root == vb);

  const int ret = left + right + mid;

  if (ret == 2) result = (mid != 0 ? parrent : root);  

  return (bool)ret;

}

 

const Node* lca(const Node* root, const Node* va, const Node* vb)

{

  const Node* result = NULL;

  if (root) lca(root, va, vb, NULL, result);

  return result;

}

 

 

      4      二叉樹的廣度遍歷、逐層打印二叉樹節點數據、只打印某層節點數據

廣度遍歷能夠用一個隊列保存中間結果。每訪問一個節點時,將不爲空的的左右孩子分別放入隊列中,而後從隊列頭部取出下一個節點,重複前面的操做直到隊列爲空。

若須要對同一層的節點數據進行一些特殊操做(好比:打印完一層後換行、只打印某一層),能夠記錄某一層的最後一個節點,當遍歷完該節點時(此時,隊列的中的最後一個元素剛好就是下一層的最後一個節點),再進行這些特殊操做。

 

//簡單的廣度遍歷

void bfs(const Node* root)

{

  if (root == NULL) return;

  std::deque<const Node*> dq;

  while (true) {

    if (root->left)  dq.push_back(root->left);

    if (root->right) dq.push_back(root->right);

    std::cout << root->data << " ";

    if (dq.empty()) break;

    root = dq.front();

    dq.pop_front();

  }

}

 

//逐層打印 

void bfs_level(const Node* root)

{

  if (root == NULL) return;

  std::deque<const Node*> dq; 

  const Node* end = root;

  while (true) {

    if (root->left)  dq.push_back(root->left);

    if (root->right) dq.push_back(root->right);

    std::cout << root->data;

    if (root != end) { std::cout << " "; }

    else {

      std::cout << "\n";

      if (dq.empty()) break;

      end = dq.back();

    }

    root = dq.front();

    dq.pop_front();

  }

}

 

//只打印某層

void bfs_nth_level(const Node* root, int level)  //root node is at level 0

{

  if (root == NULL || level < 0) return;

  std::deque<const Node*> dq; 

  const Node* end = root;

  while (true) {

    if (root->left)  dq.push_back(root->left);

    if (root->right) dq.push_back(root->right);

    if (level == 0)  std::cout << root->data << (root == end ?  "\n" : " ");

    if (root == end) {

      if (--level < 0 || dq.empty()) break;

      end = dq.back();

    }

    root = dq.front();

    dq.pop_front();

  }

}

 

 

      5     在二叉樹中找出和(葉子到根節點路徑上的全部節點的數據和)爲指定值的全部路徑。

要輸出全部的路徑,必須額外用一個棧來保存當前路徑信息。

當訪問到節點A時,節點A的信息要在訪問A的左右子樹時用到,於是,該信息必須在遍歷A的左右子樹前加入到棧中,而在遍歷完A的左右子樹後從棧中移除。

    每訪問一個節點,就計算當前路徑值(可直接利用父節點的路徑值),當其等於給定值且當前節點是葉子節點時,就打印路徑信息。

 

static void node_path(const Node* root, const int value, int sum, std::deque<int>& dq)

{

  sum += root->data;

  if (root->left == NULL && root->right == NULL) {

    if (sum != value) return;

    std::copy(dq.begin(), dq.end(), std::ostream_iterator<int>(std::cout, " "));

    std::cout << root->data << "\n";

    return;

  }

  dq.push_back(root->data);

  if (root->left)  node_path(root->left,  value, sum, dq);

  if (root->right) node_path(root->right, value, sum, dq);

  dq.pop_back();

}

 

 

void print_node_path(const Node *root, int value)

{

  if (root == NULL) return;

  std::deque<int> dq;

  node_path(root, value, 0, dq);

}

 

 

//非遞歸解法

void print_path_by_value(const Node *root, int value)

{

  typedef std::vector<const Node*> Container;

  Container node;

  node.reserve(64);

 

  int sum = 0;

  while (true) {

    for ( ; root != NULL; root = root->left) {

      sum += root->data;

      if (sum == value && root->left == NULL && root->right == NULL) {

        Container::const_iterator first = node.begin(), last = node.end();

        for ( ; first != last; ++first) printf("%d ", (*first)->data);

        printf("%d\n", root->data);

        sum -= root->data;

        break;

      }

      node.push_back(root);

    }

   

    while (true) {

      if (node.empty())  return;

      const Node* parrent = node.back();

      if (root != parrent->right) { root = parrent->right; break; }

      root = parrent;

      sum -= root->data;

      node.pop_back();

    }

  }

}

 

 

      6     將二叉查找樹轉爲有序的雙鏈表

實際上就是對二叉查找樹進行中序遍歷。能夠用兩個變量分別保存剛訪問的結點、新鏈表的頭結點,訪問某一個結點A時,設置該節點時left成員指向剛訪問過的結點B,再設置結點B的right成員指向結點A。通過這樣處理,獲得的新雙鏈表,除了頭結點的left成員、尾結點的right成員沒有設置外,其它的結點成員都被正確設置。而中序遍歷的特色決定了第一個訪問的數據節點的left成員必定爲空指針,最後一個訪問的數據節點的right成員也必定爲空指針。於是不須要再對這兩個成員進行額外的設置操做。

 

static void tree2list_inorder(Node* root, Node*& prev, Node*& list_head)

{

  if (root->left) tree2list_inorder(root->left, prev, list_head);

 

  root->left = prev;

  if (prev) prev->right = root;

  prev = root;

  if (list_head == NULL) list_head = root;

 

  if (root->right) tree2list_inorder(root->right, prev, list_head);

}

 

Node* tree2list(Node* root)

{

  Node* list_head = NULL;

  Node* prev = NULL;

  if (root) tree2list_inorder(root, prev, list_head);

  return list_head;

}

 

 

      7     求二叉樹的鏡像

①     在原來的二叉樹上進行修改。

static void mirror(Node* root)

{

  Node* const left  = root->left;

  Node* const right = root->right;

  root->left = right;

  root->right = left;  

  if (left)  mirror(left);  

  if (right) mirror(right);  

}

 

Node* mirror_node(Node* root)

{

  if (root) mirror(root);

  return root;

}

 

② 建立一個二叉樹的鏡像,注意內存分配失敗時的處理

static void clear_node(Node* root)

{

  Node* const left  = root->left;

  Node* const right = root->right;

  delete root;

 

  if (left)  clear_node(left);

  if (right) clear_node(right);

}

 

static void clone_mirror(const Node* root, Node*& position)

{

  Node *node = new Node;

  *node = *root;

  position = node;

 

  if (root->left)  clone_mirror(root->left,  node->right);

  if (root->right) clone_mirror(root->right, node->left);

}

 

 

Node* clone_mirror(const Node* root)

{

  Node* new_root = NULL;

  if (root) {

    try {

      clone_mirror(root, new_root);

    } catch (...) {

      if (new_root) clear_node(new_root);

      new_root = NULL;

    }     

  }

  return new_root;

}

 

 

      8     二叉樹前序、中序、後序遍歷的非遞歸實現

三種遍歷相同點是:從某節點出發向左走到頭(邊走邊記錄訪問過的節點),而後退回到該節點,再進入右子樹,再重複前面操做。

①  對前序遍歷,先訪問節點數據、之後再訪問該節點右孩子的數據,於是能夠不記錄該節點,而直接記錄該節點的右孩子。

②  對前序、中序遍歷,同一個節點可能要被訪問兩次:從上往下、(沿着左子樹)從下往上。

③  對後序遍歷,同一個節點可能要被訪問三次:從上往下、(沿着左子樹)從下往上、(沿着右子樹)從下往上。

後序遍歷相對麻煩的地方是:從下往上時,要判斷是沿着左子樹向上,仍是沿着右子樹向上,如果後者(或父節點的右孩子爲空節點)才訪問父節點數據。方向的判斷,只須判斷當前節點是不是其父節點的右孩子,不須要對每一個結點都設一個標誌!另外,若當前節點是某個葉子節點的左孩子(此時當前節點是空節點),能夠把當前節點看成是該葉子節點的右孩子處理,而不影響結果。

 

void preorder(const Node* root)

{

  std::deque<const Node*> dq;

  while (true) {

    while (root) {

      std::cout << root->data << " "; 

      if (root->right) dq.push_back(root->right);

      root = root->left;

    }

    if (dq.empty()) break;

    root = dq.back();

    dq.pop_back();

  }

}

 

void inorder(const Node* root)

{

  std::deque<const Node*> dq;

  while (true) {

    for ( ; root != NULL; root = root->left) dq.push_back(root);

    if (dq.empty()) break;

    root = dq.back();

    dq.pop_back();

    std::cout << root->data << " "; 

    root = root->right;

  }

}

 

void postorder(const Node* root)

{

  std::deque<const Node*> dq;

  while (true) {

    for ( ; root != NULL; root = root->left) dq.push_back(root);

    while (true) {

      if (dq.empty()) return;

      const Node* parrent = dq.back();

      //能夠不檢查parrent->right是否爲空指針

      const Node* right = parrent->right;

      if (right && root != right) { root = right; break;}

      std::cout << parrent->data << " ";

      root = parrent;

      dq.pop_back();

    }

  }

}

 

      9     計算二叉樹高度的非遞歸實現

計算二叉樹的高度,通常都是用後序遍歷,先算出左子樹的高度,再算出右子樹的高度,最後取較大者。但若直接將該算法改爲非遞歸形式是很是麻煩的。考慮到二叉樹高度與深度的關係,能夠有下面兩種方法:

 

①     先將算法改爲前序遍歷再改寫非遞歸形式。前序遍歷算法:遍歷一個節點前,先算出當前節點是在哪一層,層數的最大值就等於二叉樹的高度

 

②     修改上面提到的後序遍歷迭代寫法,上面的代碼中,所用到輔助棧(或雙端隊列),其大小達到的最大值減去1 就等於二叉樹的高度。於是只須記錄在往輔助棧放入元素後(或者在訪問結點數據時),輔助棧的棧大小達到的最大值。

 

int tree_height_preorder(const Node* root)

{

  struct Info {

    const Node* node;

    int level;

  };

 

  std::deque<Info> dq;

  int level  = -1;

  int height = -1;

 

  while (true) {

    while (root) {

      ++level;     

      if (root->right)  {

        Info info = {root->right, level};

        dq.push_back(info);

      }

      root = root->left;

    }

    height = max(height, level);

   

    if (dq.empty()) break;

    const Info& info = dq.back();

    root  = info.node;

    level = info.level;

    dq.pop_back();

  }

  return height;

}

 

int tree_height_postorder(const Node* root)

{

  std::deque<const Node*> dq;

  int height = -1;

  while (true) {

    for ( ; root != NULL; root = root->left) dq.push_back(root);

    height = max(height, (int)dq.size() - 1);

   

    while (true) {

      if (dq.empty()) return height;

      const Node* parrent = dq.back();

      //能夠不檢查parrent->right是否爲空指針

      const Node* right = parrent->right;

      if (right && root != right) { root = right; break;}

      root = parrent;

      dq.pop_back();

    }

  }

}

 

 

  10     鏈接二叉樹同一層上的結點

若二叉樹結構定義爲:

struct Node {

  Node *left;

  Node *right;

  Node *right_sibling; //

  int data;

};

其中,right_sibling指向同一層上右側的第一個結點(沒有的話則設爲空指針)。

要求設置各結點的right_sibling成員(其它成員已經初始化)。

 

 

本題能夠用遞歸,也能夠使用迭代,兩種方法都是時間複雜度O(n),空間複雜度O(1)(遞歸解法可能會棧溢出)。

 

遞歸法:訪問一個結點前,事先算出它的right_sibling。訪問該結點時,利用該結點的right_sibling指針,算出其左右孩子的right_sibling(找出該結點右側第一個不是葉子的結點B,結點B的某個孩子,就是該結點某個孩子的right_sibling)。須要特別注意的是:訪問二叉樹能夠採用前序遍歷,要先訪問右子樹,再訪問左子樹,這樣能夠保證訪問到某個結點,該結點及其右側的結點的right_sibling指針已被正確設置。

 

迭代法:訪問某一層前,先設置好該層全部節點的right_sibling,訪問該層時,利用已經設置好的right_sibling信息,設置下一層節點的right_sibling。

 

//遞歸解法:

static void set_sibling(Node* root, Node* sibling)

{

  root->right_sibling = sibling;

  Node* const left  = root->left;

  Node* const right = root->right;

  if (left == NULL && right == NULL) return;

 

  while (sibling) {

    if (sibling->left)  { sibling = sibling->left;  break;}

    if (sibling->right) { sibling = sibling->right; break;}

    sibling = sibling->right_sibling;

  }

 

  if (right) {

    set_sibling(right, sibling); 

    sibling = right;

  } 

 

  if (left) set_sibling(left, sibling);

}

 

void set_sibling(Node* root)

{

  if (root) set_sibling(root, NULL);

}

 

 

//非遞歸解法:

void set_right_sibling2(Node* root)

{

  if (root == NULL) return;

  root->right_sibling = NULL;

 

  Node* level_start = NULL;

  while (root) {

    Node* const left  = root->left;

    Node* const right = root->right;

    if (level_start == NULL)  level_start = (left ? left : right);

   

    Node* right_sibling = NULL;

    while (1) {

      root = root->right_sibling;

      if (root == NULL) { root = level_start; level_start = NULL; break; }

      if (root->left)   { right_sibling = root->left;  break;}

      if (root->right)  { right_sibling = root->right; break;}

    }

    if (right) { right->right_sibling = right_sibling;  right_sibling = right; }

    if (left)  { left->right_sibling  = right_sibling;}

  }

}

相關文章
相關標籤/搜索