1. 程式人生 > 實用技巧 >資料結構之二叉樹

資料結構之二叉樹

一、概述

1.樹的基本定義

樹是由n(n>=1)個有限結點構成的一個具有層次關係的集合。之所以叫做“樹”,是因為看起來像是一顆倒掛的、根在上葉在下的樹。

樹具有如下特徵:

  沒有父結點的結點為根結點

  沒有子結點的結點為葉節點

  每個非根結點有且僅有一個父結點

  每個結點有零或多個子結點

2.樹的相關術語

  • 孩子結點:一個結點的直接後繼結點稱為該結點的孩子結點。
  • 雙親結點(父結點):一個結點的直接前驅稱為該結點的雙親結點。
  • 兄弟結點:同一雙親結點的孩子結點間互稱兄弟結點。
  • 葉結點:度為0的結點稱為葉結點,也可以叫做終端結點
  • 分支結點:度不為0的結點稱為分支結點,也可以叫做非終端結點

  • 結點的度:一個結點含有的子樹的個數稱為該結點的度。
  • 樹的度:樹中所有結點的度的最大值。
  • 結點的層次:從根結點開始,根結點的層次為1,根的直接後繼層次為2,以此類推。
  • 樹的高度(深度):樹中結點的最大層次。
  • 結點的層序編號:將樹中的結點,按照從上層到下層,同層從左到右的次序排成一個線性序列,把他們編成連續的自然數。
  • 森林:m(m>=0)個互不相交的樹的集合,將一顆非空樹的根結點刪去,樹就變成一個森林;給森林增加一個統一的根結點,森林就變成一棵樹。

3.二叉樹的分類

  • 二叉樹:就是度不超過 2的樹(每個結點最多有兩個子結點)。

  

  • 滿二叉樹:一個二叉樹,如果每一個層的結點樹都達到最大值,則這個二叉樹就是滿二叉樹。

  

  • 完全二叉樹:葉節點只能出現在最下層和次下層,並且最下面一層的結點都集中在該層最左邊的若干位置的二叉樹。

  

二、二叉查詢樹的實現

1.基本功能:新增、獲取、刪除

  • 結構:可以用連結串列或線性表,下面以取連結串列為例。Node類的成員變數包括鍵值對的key和value,以及左孩子結點和右孩子結點。

  • 特點:對於每一個子樹,左子樹的元素都小於其根結點,右子樹的元素都大於其根結點。
  • 實現思路:
    • 插入元素:
      • 有返回值遞迴版:從根結點開始。如果是往空子樹中插入,只需建立新結點返回即可,上層會將返回的新結點設為根結點;如果是往非空子樹中插入,就需要根據key值的大小選擇左子樹或右子樹進行遞迴遍歷,並將新增元素後的新左/右子樹賦值給原左/右子樹。即,會統一
        遍歷到空結點,在這裡建立N++和結點結束返回
      • 無返回值重複呼叫版:分為兩種情況,一種是往空子樹中插入,建立新結點設為根結點即可,一種是往非空子樹中插入,根據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;

    }