數據結構之二叉樹


  第一篇:數據結構之鏈表

  第二篇:數據結構之棧和隊列

 

  在這篇文章里面,我們主要探討和樹相關的話題。

  首先,我們來對樹進行定義:樹是n(n>= 0)個節點的有限集。在任何一個非空樹中:(1)有且僅有一個特定的稱為“根”的節點;(2)當n>1時,其余節點可分為m(m>0)個互相相關的有限集T1、T2、T3……,其中每一個集合本身又是一棵樹,並且稱為根的子樹。

  對於我們這篇文章里討論的二叉樹,它是一種特殊的樹形結構,每個節點至多只有兩顆子樹,並且子樹有左右之分,其次序不能隨意顛倒。

  接下來,我們使用java代碼來定義一棵樹:

二叉樹的定義
 1 public class BinNode {
 2     private int m_Value;
 3     private BinNode m_Left;
 4     private BinNode m_Right;
 5     public void setValue(int m_Value) {
 6         this.m_Value = m_Value;
 7     }
 8     public int getValue() {
 9         return m_Value;
10     }
11     public void setLeft(BinNode m_Left) {
12         this.m_Left = m_Left;
13     }
14     public BinNode getLeft() {
15         return m_Left;
16     }
17     public void setRight(BinNode m_Right) {
18         this.m_Right = m_Right;
19     }
20     public BinNode getRight() {
21         return m_Right;
22     }
23     
24     public boolean isLeaf()
25     {
26         return m_Left == null && m_Right == null;
27     }
28 }

  下面,開始討論和二叉樹相關的話題

  • 構造二叉樹,給出一個已排序的整型數組,如何根據它來構造一個BST(二叉搜索樹)。
    思路:二叉搜索樹的特點是左子樹的值<=父節點的值<=右子樹的值。我們可以從下標0開始遍歷數組,然后依次創建樹節點,這樣下來,對於樹的根節點來說,只有右子樹,沒有左子樹,整個樹不是平衡二叉樹。為了優化這一點,我們可以將數組的中間元素作為根節點,前半部分的值作為樹的左子樹,后半部分的值作為樹的右子樹,然后使用遞歸依次構建。
    根據升序數組構造平衡二叉搜索樹
     1 public static BinNode buildTree(int[] arrValue)
     2 {
     3     if(arrValue == null) return null;
     4     BinNode root = new BinNode();
     5     buildTree(arrValue, 0, arrValue.length - 1, root);
     6     return root;
     7 }
     8 
     9 private static void buildTree(int[] arrValue, int startPos, int endPos, BinNode tree)
    10 {
    11     if (startPos > endPos) return;
    12     
    13     int midPos = startPos + (endPos - startPos)/2;
    14     if (tree == null)
    15     {
    16         tree = new BinNode();                
    17     }
    18     tree.setValue(arrValue[midPos]);
    19     
    20     if (midPos - 1 >= startPos)
    21     {
    22         BinNode left = new BinNode();
    23         tree.setLeft(left);
    24         buildTree(arrValue, startPos, midPos - 1, left);
    25     }
    26     if (endPos >= midPos + 1)
    27     {
    28         BinNode right = new BinNode();
    29         tree.setRight(right);
    30         buildTree(arrValue, midPos + 1, endPos, right);
    31     }
    32 }
  • 樹的遍歷(前序遍歷、中序遍歷、后序遍歷、層次遍歷)
    思路:可以采用遞歸或者非遞歸的方式進行遍歷
    前序遍歷
    前序遍歷(遞歸)
     1 public static void preOrder(BinNode tree)
     2 {
     3     if (tree == null)
     4     {
     5         return;
     6     }
     7     System.out.println(tree.getValue());
     8     preOrder(tree.getLeft());
     9     preOrder(tree.getRight());
    10 }
    前序遍歷(非遞歸)
     1 public static void preOrder2(BinNode tree)
     2 {
     3     if (tree == null) return;
     4     Stack stack = new Stack(10);
     5     BinNode temp = tree;
     6     while(temp != null)
     7     {
     8         System.out.println(temp.getValue());
     9         if (temp.getRight() != null) stack.push(temp.getRight());
    10         temp = temp.getLeft();
    11     }
    12     while(stack.get_Count() > 0)
    13     {
    14         temp = (BinNode)stack.pop();
    15         System.out.println(temp.getValue());
    16         while(temp != null)
    17         {
    18             if (temp.getRight() != null)
    19             {
    20                 stack.push(temp.getRight());
    21             }
    22             temp = temp.getLeft();
    23         }
    24     }
    25 }

    中序遍歷

    中序遍歷(遞歸)
     1 public static void inOrder(BinNode tree)
     2 {
     3     if (tree == null)
     4     {
     5         return;
     6     }
     7     inOrder(tree.getLeft());
     8     System.out.println(tree.getValue());
     9     inOrder(tree.getRight());
    10 }
    中序遍歷(非遞歸)
     1 public static void inOrder2(BinNode tree)
     2 {
     3     if (tree == null) return;
     4     Stack stack = new Stack(10);
     5     BinNode temp = tree;
     6     while(temp != null)
     7     {
     8         stack.push(temp);
     9         temp = temp.getLeft();
    10     }
    11     while(stack.get_Count() > 0)
    12     {
    13         temp = (BinNode)stack.pop();
    14         System.out.println(temp.getValue());
    15         if (temp.getRight() != null)
    16         {
    17             temp = temp.getRight();
    18             stack.push(temp);
    19             while(temp != null)
    20             {
    21                 if (temp.getLeft() != null)
    22                 {
    23                     stack.push(temp.getLeft());
    24                 }
    25                 temp = temp.getLeft();
    26             }
    27         }            
    28     }        
    29 }

    后序遍歷

    后序遍歷(遞歸)
     1 public static void postOrder(BinNode tree)
     2 {
     3     if (tree == null)
     4     {
     5         return;
     6     }
     7     postOrder(tree.getLeft());
     8     postOrder(tree.getRight());
     9     System.out.println(tree.getValue());
    10 }
    后序遍歷(非遞歸)
     1 public static void postOrder2(BinNode tree)
     2 {
     3     if (tree == null) return;
     4     Stack stack = new Stack(10);
     5     BinNode temp = tree;
     6     while(temp != null)
     7     {
     8         stack.push(temp);
     9         temp = temp.getLeft();
    10     }
    11     
    12     while(stack.get_Count() > 0)
    13     {
    14         BinNode lastVisited = temp;
    15         temp = (BinNode)stack.pop();
    16         if (temp.getRight() == null || temp.getRight() == lastVisited)
    17         {
    18             System.out.println(temp.getValue());
    19         }
    20         else if (temp.getLeft() == lastVisited)
    21         {
    22             stack.push(temp);
    23             temp = temp.getRight();
    24             stack.push(temp);
    25             while(temp != null)
    26             {
    27                 if (temp.getLeft() != null)
    28                 {
    29                     stack.push(temp.getLeft());
    30                 }
    31                 temp = temp.getLeft();
    32             }
    33         }
    34     }        
    35 }

    層次遍歷

    層次遍歷
     1 public static void printTree(BinNode tree)
     2 {
     3     if (tree == null)
     4     {
     5         return;
     6     }
     7     Queue queue = new Queue(10);
     8     queue.enQueue(tree);
     9     while(queue.get_Count() > 0)
    10     {
    11         BinNode temp = (BinNode) queue.deQueue();
    12         System.out.println(temp.getValue());
    13         if (temp.getLeft() != null) queue.enQueue(temp.getLeft());
    14         if (temp.getRight() != null) queue.enQueue(temp.getRight());
    15     }        
    16 }
  • 判斷二叉樹是否是平衡二叉樹
    思路:平衡二叉樹的特點是左右子樹的深度差不能大於1,采用遞歸的方式。
    判斷是否是平衡二叉樹
     1 public static boolean isBalance(BinNode tree)
     2 {
     3     if (tree == null)
     4     {
     5         return true;
     6     }
     7     int lDepth = getDepth(tree.getLeft());
     8     int rDepth = getDepth(tree.getRight());
     9     if (lDepth - rDepth > 1 || lDepth - rDepth < -1)
    10     {
    11         return false;
    12     }
    13     return isBalance(tree.getRight()) && isBalance(tree.getLeft());
    14 }
    15 
    16 
    17 public static int getDepth(BinNode tree)
    18 {
    19     if(tree == null)
    20     {
    21         return 0;
    22     }        
    23     int lLength = getDepth(tree.getLeft());
    24     int rLength = getDepth(tree.getRight());
    25     
    26     return lLength > rLength ? lLength + 1 : rLength + 1; 
    27 }
  • 找出任何兩個節點的最近的共同父節點
    思路:用1表示根節點,2、3表示第二層節點,4、5、6、7表示第三層節點,以此類推,對於編號為n的節點,它的子節點編號為(2*n和2*n+1),通過這種方式重新標記樹后,我們分別找到兩個節點對應的編號,然后分別進行除2操作,直到找到相同的節點,這個節點就是最近的共同父節點。
    尋找最近的共同父節點
     1 public static BinNode findParentNode2(BinNode tree, BinNode node1, BinNode node2)
     2 {
     3     if (tree == null) return null;
     4     BinNode result = null;
     5     int pos1 = 1, pos2 = 1;
     6     int i = 1;
     7     java.util.HashMap<Integer, BinNode> table = new java.util.HashMap<Integer, BinNode>();        
     8     BinNode temp = tree;
     9     table.put(i, temp);
    10     Queue queue = new Queue(10);
    11     queue.enQueue(i);
    12     int count = 0;
    13     while(queue.get_Count() > 0)
    14     {
    15         i = Integer.valueOf(queue.deQueue().toString());
    16         if (table.get(i) != null && table.get(i).getValue() == node1.getValue())
    17         {
    18             pos1 = i;
    19             count++;
    20         }
    21         if (table.get(i) != null && table.get(i).getValue() == node2.getValue())
    22         {
    23             pos2 = i;
    24             count++;
    25         }
    26         if(count == 2) break;
    27         
    28         if(table.get(i) == null)
    29         {
    30             table.put(2*i, null);
    31             table.put(2*i + 1, null);
    32         }
    33         else
    34         {
    35             table.put(2*i, table.get(i).getLeft());
    36             table.put(2*i + 1, table.get(i).getRight());
    37         }
    38         queue.enQueue(2*i);
    39         queue.enQueue(2*i + 1);
    40         
    41     }
    42     if (pos1 == pos2)
    43     {
    44         result = table.get(pos1);
    45     }
    46     else if(pos1 > pos2)
    47     { 
    48         while(pos1 > pos2) pos1 = pos1/2;
    49     }
    50     else
    51     {
    52         while(pos2 > pos1) pos2 = pos2/2;
    53     }
    54     if(pos1 == pos2 || pos1/2 == pos2 || pos2/2 == pos1) 
    55     {
    56         result = table.get(pos1 > pos2 ? pos2 : pos1);
    57     }
    58     else
    59     {
    60         while(pos1 != pos2) 
    61         {
    62             pos1 = pos1/2;
    63             pos2 = pos2/2;
    64         }
    65     }
    66     result = table.get(pos1);
    67     
    68     return result;
    69 }

    如果我們已知樹是二叉排序樹,那么我們可以利用二叉排序樹的特點,分別尋找兩個節點,在尋找過程中,將掃描過的節點放入stack中,這樣可以獲得兩個stack,然后分別對兩個stack進行pop操作,得到的第一個相同的節點就是最近的共同父節點。

    尋找最近的共同父節點(方案2)
     1 public static BinNode findParentNode(BinNode tree, BinNode node1, BinNode node2)
     2 {
     3     if (tree == null)
     4     {
     5         return null;
     6     }
     7     
     8     BinNode result = null;
     9     Stack stack1 = new Stack(10);
    10     Stack stack2 = new Stack(10);
    11     stack1.push(tree);
    12     stack2.push(tree);
    13     BinNode temp = tree;
    14     while(temp != null && temp.getValue() != node1.getValue())
    15     {
    16         stack1.push(temp);
    17         if (temp.getValue()> node1.getValue())
    18         {
    19             temp = temp.getLeft();
    20         }
    21         else
    22         {
    23             temp = temp.getRight();
    24         }
    25     }
    26     temp = tree;
    27     while(temp != null && temp.getValue() != node2.getValue())
    28     {
    29         stack2.push(temp);
    30         if (temp.getValue()> node2.getValue())
    31         {
    32             temp = temp.getLeft();
    33         }
    34         else
    35         {
    36             temp = temp.getRight();
    37         }
    38     }
    39     if (stack1.get_Count() > stack2.get_Count())
    40     {
    41         while(stack1.get_Count() > stack2.get_Count())
    42         {
    43             stack1.pop();
    44         }
    45     }
    46     if (stack2.get_Count() > stack1.get_Count())
    47     {
    48         while(stack2.get_Count()>stack1.get_Count())
    49         {
    50             stack2.pop();
    51         }
    52     }
    53     while(stack1.get_Count() > 0 && stack2.get_Count() > 0)
    54     {
    55         if (stack1.peek() == stack2.peek())
    56         {
    57             result = (BinNode)stack1.peek();
    58             break;
    59         }
    60         stack1.pop();
    61         stack2.pop();
    62     }
    63     
    64     return result;
    65 }
  • 找出樹中節點值得和為某一值得所有路徑
    思路:肯定是需要使用遞歸來實現,使用stack來存儲掃描過的路徑。
    尋找和為某一值的所有路徑
     1 public static void findSum(BinNode tree, int sum, Stack stack)
     2 {
     3     if (tree == null) return;
     4     stack.push(tree);        
     5     if (tree.getValue() == sum)
     6     {
     7         Stack temp = new Stack(stack.get_Count());
     8         while(stack.get_Count()>0)
     9         {
    10             temp.push(stack.pop());
    11         }
    12         StringBuffer sb = new StringBuffer();
    13         while(temp.get_Count() > 0)
    14         {
    15             sb.append(((BinNode)temp.peek()).getValue()).append("->");
    16             stack.push(temp.pop());
    17         }
    18         System.out.println(sb.substring(0, sb.length() - 2));
    19     }
    20     else if (tree.getValue() < sum)
    21     {
    22         if (tree.getLeft() != null) findSum(tree.getLeft(), sum - tree.getValue(), stack);
    23         if (tree.getRight() != null) findSum(tree.getRight(), sum - tree.getValue(), stack);
    24     }
    25     stack.pop();
    26 }
  • 判斷給定數組是否是二叉搜索樹后序遍歷的結果
    思路:觀察二叉搜索樹后序遍歷的特點,根節點在最后,從下標0開始掃描,直到第一個大於根節點值得下標m,[0,(m-1)]的元素是節點的左子樹,[m, n]是節點的右子樹,並且右子樹中任何節點的值都大於根節點。這樣可以使用遞歸的方式以此判斷。
    判斷給定數組是否是BST后序遍歷結果
  • 給定一顆BST,另f=(max+min)/2,尋找距離f最近但大於f的節點
    思路:對於BST,最小值是最左邊的葉子節點,最大值是最右邊的葉子節點。
    尋找BST中指定節點
     1 public static BinNode findAvgInBST(BinNode tree)
     2 {
     3     if (tree == null) return null;
     4     BinNode temp = tree;
     5     while(temp.getLeft() != null) temp = temp.getLeft();
     6     int minValue = temp.getValue();
     7     temp = tree;
     8     while(temp.getRight() != null) temp = temp.getRight();
     9     int maxValue = temp.getValue();
    10     int avgValue = (minValue + maxValue)/2;
    11     return findNode(tree, avgValue);
    12 }
    13 
    14 private static BinNode findNode(BinNode tree, int value)
    15 {
    16     if (tree == null) return null;
    17     if (tree.getValue() >= value)
    18     {
    19         if (tree.getLeft() == null)
    20         {
    21             return tree;
    22         }
    23         else
    24         {
    25             BinNode temp = findNode(tree.getLeft(), value);
    26             return temp.getValue() < tree.getValue() ? temp:tree;
    27         }
    28     }
    29     else
    30     {
    31         if (tree.getRight() == null)
    32         {
    33             return tree;
    34         }
    35         else
    36         {
    37             BinNode temp = findNode(tree.getRight(), value);
    38             return temp.getValue() < tree.getValue() ? temp:tree;
    39         }
    40     }
    41 }
  • 給出一棵樹,得到這棵樹的鏡像
    思路:我們可以考慮前序遍歷的結果:父節點->左子樹->右子樹,而鏡像之后的結果:父節點->右子樹->左子樹,我們可以采用遞歸前序遍歷的方式來實現
    鏡像二叉樹
    1 public static void imageTree(BinNode tree)
    2 {
    3     if (tree == null) return;
    4     BinNode temp = tree.getLeft();
    5     tree.setLeft(tree.getRight());
    6     tree.setRight(temp);
    7     if (tree.getLeft() != null) imageTree(tree.getLeft());
    8     if (tree.getRight() != null) imageTree(tree.getRight());
    9 }

    我們也可以采用循環+棧的方式來實現

    鏡像二叉樹(方案二)
     1 public static void imageTrees(BinNode tree)
     2 {
     3     if (tree == null) return;
     4     Stack stack = new Stack(10);
     5     BinNode temp = tree;
     6     stack.push(temp);
     7     while(stack.get_Count() > 0)
     8     {
     9         temp = (BinNode)stack.pop();
    10         BinNode temp1 = temp.getLeft();
    11         temp.setLeft(temp.getRight());
    12         temp.setRight(temp1);            
    13         if (temp.getLeft() != null)
    14         {
    15             stack.push(temp.getLeft());
    16         }
    17         if (temp.getRight() != null)
    18         {
    19             stack.push(temp.getRight());
    20         }
    21     }
    22 }
  • 將BST轉換為排序的雙向鏈表
    思路:對於BST來說,中序遍歷的結果就是元素按照從小到大進行排序的結果
    將BST轉換成排序的雙向鏈表
     1 public static DoubleLink convertTreeToList(BinNode tree)
     2 {
     3     if (tree == null) return null;
     4     
     5     DoubleLink list = new DoubleLink();
     6     list.setValue(null);
     7     list.setPre(null);
     8     DoubleLink listNode = list;
     9     
    10     BinNode treeNode = tree;
    11     Stack stack = new Stack(10);
    12     
    13     while(treeNode != null)
    14     {
    15         stack.push(treeNode);
    16         treeNode = treeNode.getLeft();
    17     }
    18     
    19     while(stack.get_Count() > 0)
    20     {
    21         treeNode = (BinNode)stack.pop();
    22         DoubleLink tempListNode = new DoubleLink();
    23         tempListNode.setValue(treeNode);
    24         listNode.setNext(tempListNode);
    25         tempListNode.setPre(listNode);
    26         listNode = tempListNode;
    27         if (treeNode.getRight() != null)
    28         {
    29             treeNode = treeNode.getRight();
    30             stack.push(treeNode);
    31             while(treeNode != null)
    32             {
    33                 if (treeNode.getLeft() != null)
    34                 {
    35                     stack.push(treeNode.getLeft());
    36                 }
    37                 treeNode = treeNode.getLeft();
    38             }
    39         }
    40     }
    41     
    42     listNode.setNext(null);
    43     
    44     return list;
    45 }

    其中DoubleLink的定義如下

    雙向鏈表的定義
     1 public class DoubleLink {
     2     private Object value;
     3     private DoubleLink next;
     4     private DoubleLink pre;
     5     public void setValue(Object value) {
     6         this.value = value;
     7     }
     8     public Object getValue() {
     9         return value;
    10     }
    11     public void setNext(DoubleLink next) {
    12         this.next = next;
    13     }
    14     public DoubleLink getNext() {
    15         return next;
    16     }
    17     public void setPre(DoubleLink pre) {
    18         this.pre = pre;
    19     }
    20     public DoubleLink getPre() {
    21         return pre;
    22     }    
    23
  • 向BST中插入節點
    思路:對BST中插入節點,首先按照遍歷BST,找出所插接點的父節點,然后創建新節點
    向BST中插入節點
     1 public static void addValueToBST(BinNode tree, int value)
     2 {
     3     if (tree == null)
     4     {
     5         tree = new BinNode();
     6         tree.setValue(value);
     7         return;
     8     }
     9     
    10     BinNode temp = tree;
    11     while(true)
    12     {            
    13         if (temp.getValue() >= value)
    14         {
    15             if (temp.getLeft() == null) break;
    16             temp = temp.getLeft();
    17         }
    18         else
    19         {            
    20             if (temp.getRight() == null) break;
    21             temp = temp.getRight();
    22         }
    23     }
    24     
    25     BinNode node = new BinNode();
    26     node.setValue(value);
    27     if (temp.getValue() >= value)
    28     {
    29         temp.setLeft(node);
    30     }
    31     else
    32     {
    33         temp.setRight(node);
    34     }
    35 }
  • 向BST中刪除節點
    思路:首先是要能夠找到需要刪除的節點,然后根據節點是否有子節點分情況處理,如果沒有子樹,那么直接刪除該節點(從父節點上解除關聯);如果只有左子樹或者右子樹,那么刪除節點后將左子樹或者右子樹直接掛在父節點下;如果左右子樹均存在,那么需要找出左子樹的最大值或者右子樹的最小值作為父節點的直接子節點,然后將這個節點的左右子樹分別設置為刪除節點的左右子樹。
    刪除BST中的節點
     1 public static void delValueFromBST(BinNode tree, int value)
     2 {
     3     if (tree == null) return;
     4     BinNode deleteNode = findNodeInBST(tree, value);
     5     BinNode parentNode = findParentNodeInBST(tree, value);
     6     if (parentNode == null || deleteNode == null) return;
     7     if (deleteNode.isLeaf())
     8     {
     9         if (parentNode.getLeft() == deleteNode) parentNode.setLeft(null);
    10         else parentNode.setRight(null);
    11     }
    12     else if (deleteNode.getLeft() == null && deleteNode.getRight() != null)
    13     {
    14         if (parentNode.getValue() >= deleteNode.getRight().getValue()) parentNode.setLeft(deleteNode.getRight());
    15         else parentNode.setRight(deleteNode.getRight());
    16         deleteNode.setRight(null);
    17     }
    18     else if (deleteNode.getLeft() != null && deleteNode.getRight() == null)
    19     {
    20         if (parentNode.getValue() >= deleteNode.getLeft().getValue()) parentNode.setLeft(deleteNode.getLeft());
    21         else parentNode.setRight(deleteNode.getLeft());
    22         deleteNode.setLeft(null);
    23     }
    24     else
    25     {
    26         BinNode temp = deleteNode.getRight();
    27         BinNode parent = deleteNode.getRight();
    28         while(temp.getLeft() != null) 
    29         {
    30             parent = temp; 
    31             temp = temp.getLeft();
    32         }
    33         if (parent != temp)
    34         {
    35             parent.setLeft(null);
    36         }
    37         temp.setLeft(deleteNode.getLeft());
    38         temp.setRight(deleteNode.getRight());
    39         if (parentNode.getValue() >= temp.getValue()) parentNode.setLeft(temp);
    40         else parentNode.setRight(temp);
    41         deleteNode.setLeft(null);
    42         deleteNode.setRight(null);
    43     }
    44 }
    45 
    46 private static BinNode findNodeInBST(BinNode tree, int value)
    47 {
    48     if (tree == null) return null;
    49     if (tree.getValue() == value) return tree;
    50     else if (tree.getValue() > value) return findNodeInBST(tree.getLeft(), value);
    51     else return findNodeInBST(tree.getRight(), value);
    52 }
    53 
    54 private static BinNode findParentNodeInBST(BinNode tree, int value)
    55 {
    56     if (tree == null) return null;
    57     if (tree.getValue() == value) return null;
    58     if ((tree.getLeft() != null && tree.getLeft().getValue() == value) || 
    59             (tree.getRight() != null &&tree.getRight().getValue() == value)) return tree;
    60     else if (tree.getValue() > value) return findParentNodeInBST(tree.getLeft(), value);
    61     else return findParentNodeInBST(tree.getRight(), value);
    62 }

  最后,歡迎大家提出更多和二叉樹相關的面試題目,我們可以一起討論。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM