資料結構之二叉樹
阿新 • • 發佈:2020-08-06
一、概述
1.樹的基本定義
樹是由n(n>=1)個有限結點構成的一個具有層次關係的集合。之所以叫做“樹”,是因為看起來像是一顆倒掛的、根在上葉在下的樹。
樹具有如下特徵:
沒有父結點的結點為根結點;
沒有子結點的結點為葉節點;
每個非根結點有且僅有一個父結點;
每個結點有零或多個子結點;
2.樹的相關術語
- 孩子結點:一個結點的直接後繼結點稱為該結點的孩子結點。
- 雙親結點(父結點):一個結點的直接前驅稱為該結點的雙親結點。
- 兄弟結點:同一雙親結點的孩子結點間互稱兄弟結點。
- 葉結點:度為0的結點稱為葉結點,也可以叫做終端結點
- 分支結點:度不為0的結點稱為分支結點,也可以叫做非終端結點
- 結點的度:一個結點含有的子樹的個數稱為該結點的度。
- 樹的度:樹中所有結點的度的最大值。
- 結點的層次:從根結點開始,根結點的層次為1,根的直接後繼層次為2,以此類推。
- 樹的高度(深度):樹中結點的最大層次。
- 結點的層序編號:將樹中的結點,按照從上層到下層,同層從左到右的次序排成一個線性序列,把他們編成連續的自然數。
- 森林:m(m>=0)個互不相交的樹的集合,將一顆非空樹的根結點刪去,樹就變成一個森林;給森林增加一個統一的根結點,森林就變成一棵樹。
3.二叉樹的分類
- 二叉樹:就是度不超過 2的樹(每個結點最多有兩個子結點)。
- 滿二叉樹:一個二叉樹,如果每一個層的結點樹都達到最大值,則這個二叉樹就是滿二叉樹。
- 完全二叉樹:葉節點只能出現在最下層和次下層,並且最下面一層的結點都集中在該層最左邊的若干位置的二叉樹。
二、二叉查詢樹的實現
1.基本功能:新增、獲取、刪除
-
結構:可以用連結串列或線性表,下面以取連結串列為例。Node類的成員變數包括鍵值對的key和value,以及左孩子結點和右孩子結點。
- 特點:對於每一個子樹,左子樹的元素都小於其根結點,右子樹的元素都大於其根結點。
- 實現思路:
- 插入元素:
- 有返回值遞迴版:從根結點開始。如果是往空子樹中插入,只需建立新結點返回即可,上層會將返回的新結點設為根結點;如果是往非空子樹中插入,就需要根據key值的大小選擇左子樹或右子樹進行遞迴遍歷,並將新增元素後的新左/右子樹賦值給原左/右子樹。即,會統一
- 無返回值重複呼叫版:分為兩種情況,一種是往空子樹中插入,建立新結點設為根結點即可,一種是往非空子樹中插入,根據key的大小選擇左子樹或右子樹往下深入遍歷,自行負責建立結點和結點數增加。
- 有返回值遞迴版:從根結點開始。如果是往空子樹中插入,只需建立新結點返回即可,上層會將返回的新結點設為根結點;如果是往非空子樹中插入,就需要根據key值的大小選擇左子樹或右子樹進行遞迴遍歷,並將新增元素後的新左/右子樹賦值給原左/右子樹。即,會統一
- 獲取元素:遍歷獲取返回。
- 刪除元素:如果是刪除的葉結點好說,但如果刪除的是一箇中間結點,那樹就會散架成為森林,因此需要一個替換結點來代替刪除結點的位置,此結點比較合適的就是比刪除結點大一點的,也就是右子樹的最左節點。過程如圖。
- 插入元素:
- 程式碼實現:
package com.ex.tree; /** * 實現二叉查詢樹 */ public class BinaryTree<K extends Comparable<K>,V> { //根結點 private Node root; //元素個數 private int N; private class Node { //鍵 private K key; //值 private V value; //左孩子 private Node left; //右孩子 private Node right; public Node(K key, V value, Node left, Node right) { this.key = key; this.value = value; this.left = left; this.right = right; } } public BinaryTree() { } //獲取樹中元素的個數 public int size(){ return N; } //向樹中插入一個鍵值對 public void put(K key,V value){ root = put(root,key,value); } //給指定樹x上,插入一個鍵值對,並返回新增後的以x為根節點的新樹 private Node put(Node x,K key,V value){ //如果樹為空: if (x==null){ N++; return new Node(key,value,null,null); } //如果樹不為空: int comp = key.compareTo(x.key); if (comp>0){ //如果key大於x的key,繼續搜尋右子樹 x.right = put(x.right,key,value); }else if (comp<0){ //如果key小於x的key,繼續搜尋左子樹 x.left = put(x.left,key,value); }else { //如果key等於x的key,直接替換該結點value x.value=value; } return x; } //根據key,從樹中找出對應結點,返回value public V get(K key){ return get(root,key); } //從指定的樹x中,找出key對應的value private V get(Node x,K key){ //如果樹為空: if (x==null){ return null; } //如果樹不為空: int comp = key.compareTo(x.key); if (comp>0){ //如果key大於x的key,繼續搜尋右子樹 return get(x.right,key); }else if (comp<0){ //如果key小於x的key,繼續搜尋左子樹 return get(x.left,key); }else { //如果key等於x的key,直接替換該結點value return x.value; } } //根據key,從樹中刪除對應的鍵值對 public void delete(K key){ root = delete(root, key); } //根據key,從指定的樹中刪除對應的鍵值對 private Node delete(Node x,K key){ //如果樹為空: if (x==null){ return null; } //如果樹不為空: int comp = key.compareTo(x.key); if (comp>0){ //如果key大於x的key,繼續搜尋右子樹 x.right = delete(x.right,key); }else if (comp<0){ //如果key小於x的key,繼續搜尋左子樹 x.left = delete(x.left,key); }else { //如果key等於x的key,則進行刪除操作:要刪除的結點為x //結點數-1 N--; //1.如果當前結點的左子樹不存在 if (x.left ==null){ return x.right; } //2.如果當前結點的右子樹不存在 if (x.right ==null){ return x.left; } //3.如果當前結點的左右子樹都存在 //3.1找到替換結點(右子樹中最小的結點)和其父結點 Node replaceParNode=x.right; while (replaceParNode.left != null && replaceParNode.left.left != null){ replaceParNode=replaceParNode.left; } Node replaceNode=(replaceParNode.left==null)?replaceParNode:replaceParNode.left; //3.2斷開替換結點和父結點的聯絡 replaceParNode.left=null; //3.3連線替換結點和刪除的父結點、左孩子、右孩子 replaceNode.left=x.left; replaceNode.right=x.right; //3.4**斷開刪除結點和關聯結點的聯絡 x=replaceNode; } return x; } }
//向樹中插入一個鍵值對 public void put(K key,V value){ put(root,key,value); } //給指定樹x上,插入一個鍵值對,並返回新增後的以x為根節點的新樹 //無返回值重複呼叫版 private void put(Node x,K key,V value){ //如果樹為空: if (root==null){ N++; root =new Node(key,value,null,null); return; } //如果樹不為空: int comp = key.compareTo(x.key); if (comp>0){ //如果key大於x的key,繼續搜尋右子樹 if (x.right != null) { put(x.right,key,value); }else { x.right=new Node(key,value,null,null); N++; } }else if (comp<0){ //如果key小於x的key,繼續搜尋左子樹 if (x.left != null) { put(x.left,key,value); }else { x.left=new Node(key,value,null,null); N++; } }else { //如果key等於x的key,直接替換該結點value x.value=value; } }
2.擴充套件功能:查詢二叉樹中最大/小的鍵
//找出整個樹最小的鍵 public K getMinKey(){ return getMinKey(root).key; } private Node getMinKey(Node n) { if (n.left!=null){ return getMinKey(n.left); }else { return n; } } //找出整個樹中最大的鍵 public K getMaxKey(){ return getMaxKey(root).key; } private Node getMaxKey(Node n) { if (n.right!=null){ return getMaxKey(n.right); }else { return n; } }
3.基礎遍歷
3.1前序遍歷:先訪問根結點,然後再訪問左子樹,最後訪問右子樹
前序遍歷結果:EBADCGFH
3.2中序遍歷:先訪問左子樹,中間訪問根節點,最後訪問右子樹
中序遍歷結果:ABCDEFGH
3.3後序遍歷:先訪問左子樹,再訪問右子樹,最後訪問根節點
後序遍歷結果:ACDBFHGE
3.4層序遍歷:從根節點(第一層)開始,依次向下,獲取每一層所有結點的值
層序遍歷結果:EBGADFHC
實現程式碼:
//前序遍歷,獲得整個樹中所有的鍵 public Queue<K> preOrderTraversal(){ Queue<K> keys=new LinkedList<>(); preOrderTraversal(root, keys); return keys; } private void preOrderTraversal(Node n, Queue<K> keys) { if (n!=null){ keys.add(n.key); preOrderTraversal(n.left,keys); preOrderTraversal(n.right,keys); } } //中序遍歷,獲得整個樹中所有的鍵 public Queue<K> middleOrderTraversal(){ Queue<K> keys=new LinkedList<>(); middleOrderTraversal(root, keys); return keys; } private void middleOrderTraversal(Node n, Queue<K> keys) { if (n!=null){ middleOrderTraversal(n.left,keys); keys.add(n.key); middleOrderTraversal(n.right,keys); } } //後序遍歷,獲得整個樹中所有的鍵 public Queue<K> postOrderTraversal(){ Queue<K> keys=new LinkedList<>(); postOrderTraversal(root, keys); return keys; } private void postOrderTraversal(Node n, Queue<K> keys) { if (n!=null){ postOrderTraversal(n.left,keys); postOrderTraversal(n.right,keys); keys.add(n.key); } } //層序遍歷,獲得整個樹中所有的鍵 public Queue<K> layerTraversal(){ Queue<K> keys=new LinkedList<>(); //建立佇列,儲存每一層的結點 Queue<Node> queue=new LinkedList<>(); queue.offer(root); while (!queue.isEmpty()){ //出隊一個元素 Node node = queue.poll(); //2.獲取當前結點的key; keys.offer(node.key); //如果當前結點的左子結點不為空,則把左子結點放入到佇列中 if (node.left!=null){ queue.offer(node.left); } //如果當前結點的右子結點不為空,則把右子結點放入到佇列中 if (node.right!=null){ queue.offer(node.right); } } return keys; }
4.獲得樹的最大深度
//獲取樹的最大深度 public int getMaxDepth(){ return getMaxDepth(root); } private int getMaxDepth(Node n) { //1.如果根結點為空:則最大深度為0 if (n==null){ return 0; } //2.如果根結點不為空: //設變數分別儲存樹、左子樹、右子樹的最大深度 int max=0,maxL=0,maxR=0; //2.1如果左子樹不為空,則計算左子樹的最大深度 if (n.left != null){ maxL = getMaxDepth(n.left); } //2.2如果右子樹不為空,則計算右子樹的最大深度 if (n.right != null) { maxR = getMaxDepth(n.right); } //2.3計算樹樹的最大深度:左右子樹中的較大者+1 max = maxL>maxR?maxL+1:maxR+1; return max; }