數據結構和算法——二叉樹



1.樹的優點

有序數組: 查找很快,二分法實現的查找所需要的時間為O(logN),遍歷也很快,但是在有序數組中插入,刪除卻需要先 找到位置,
       在把數組部分元素后移,效率並不高。


鏈表: 鏈表的插入和刪除都是很快速的,僅僅需要改變下引用值就行了,時間僅為O(1),但是在鏈表中查找數據卻需要
       遍歷所有的元素, 這個效率有些慢了。

樹的優點: 樹結合了有序數組和鏈表的優點,可以實現快速的查找,也可以快速的刪除,查找。


樹的一些專用術語:
路徑:
  順着連接節點的邊從一個節點到另一個節點的,所經過的所有節點的順序排列就是路徑。
根:
  根即是樹的頂端,一個樹有且只有一個根,從根到所有節點的路徑有且只有一條。
父節點:
  每一個節點的連接的上一個節點即是該節點的父節點。
子節點;
  每一個節點的向下連接的節點即是改節點的子節點
子樹:
  每個節點都可以認為是一個樹的根,
葉節點:
  就是沒有子節點的節點
層:
  一個節點的層數,從根節點到該節點有多少代,就是多少層

二叉樹:
  樹種的節點可以有多個節點,二叉樹是最多只能有2個節點的樹。二叉樹的兩個節點被稱為左子節點和右子節點。
 二叉樹的節點是最多有2個子節點,但並不是必須有2個子節點。

平衡樹和非平衡樹:
如果一個樹中存在一個或多個的子樹,只有右子節點,或左子節點,那么這個樹就是非平衡樹。

2.二叉搜索樹:

根節點的左右2個節點,小於根節點在放在左側,大於根節點的放在右側。
 

<1>插入
<2>查找
<3>遍歷
1.中序遍歷
(1)調用自身遍歷節點的左子樹
(2)訪問這個節點
(3)調用自身遍歷節點的右子樹
如上圖遍歷過程:35,38,40,45,50,67,83
2.前序遍歷
(1)訪問這個節點
(2)調用自身遍歷節點的左子樹
(3)調用自身遍歷節點的右子樹
    如上圖遍歷過程:45,38,35,40,67,50,83

3.后序遍歷
(1)調用自身遍歷節點的左子樹
(2)調用自身遍歷節點的右子樹
(3)訪問這個節點
    如上圖遍歷過程:35,40,38,50,83,67,45

<4>刪除
     (1)刪除葉節點(沒有子節點的)
     (2)刪除節點(一個子節點的)
     (3)刪除節點(二個子節點的)
<5>查找最大最小值

3.二叉搜索樹的代碼實現
樹(Tree)的代碼實現:
  Tree
只需要有根節點,即可訪問所有的子節點,這里可以簡單的定義該類,只有一個變量Root.Root類型為Node(節點對象)
該類可以實現一些操作方法大致如下:
public class Tree {

    Node root;

    public Tree() {
    }

    /**
     *  刪除節點
     * @param key
     */
    public  void deldte( int key){

    }

    /**
     *  查找節點
     *
     * @param key
     */
    public Node find(int key){

      
        return null;
    }

    /**
     *
     *
     * @param key   插入值
     * @param otherdata  插入的其他數據
     */
    public void insert( int key,int otherdata){
       
    
    }


    /**
     * 遍歷二叉樹
     */

    public void disPlayTree(Node node){
      
}
 
        

  

 
 
        

  節點(Node)的代碼實現

  Node 需要有數據項,有該類對象的左節點,右節點,還可以包含其他的數據項。實現大致如下:

 

public class Node {
    /**
     * 數據項
     */
    int data;
    /**
     * 其他數據
     */
    int otherData;
    /**
     * 左節點
     */
    Node leftChild;
    /**
     * 右節點
     */
    Node rightChild;

    public Node() {
    }

}

  

 

  Tree和Node實現后,那么便可以實現里面的操作方法了。

 find查找過程如圖

根據上圖可以看出查找一個節點,最多比較次數為Tree的層數,其代碼如下:
 /**
     *  查找節點
     *
     * @param key 查找的值,在該代碼中為Node.data
     */
    public Node find(int key){

        Node current =root;
        while (current.data!=key){
            /**
             * 小於當前節點的值,去left查找,否則去right
             */
            if(key<current.data){
                current=current.leftChild;
            }else {
                current=current.rightChild;
            }
            /**
             * 沒查找到
             */
            if(current==null){
                return  null;
            }

        }
        return current;
    }
 
        

  

 
 
        

  insert插入,插入和查找基本過程差不多,仍然是比較數據項大小,小了放在左側,大了放其右側。

 

其代碼如:
 /**
     * @param key   插入值 node.data
     * @param otherdata  插入的其他數據  node.otherdata
     */
    public void insert( int key,int otherdata){
        Node newNode=new Node();
        newNode.data=key;
        newNode.otherData=otherdata;
        if(root==null){
            root=newNode;
        }else{
            Node current=root;
            Node parent;
            while (true)
            {
                parent=current;
                if(key<current.data){
                    current=current.leftChild;
                    if(current==null){
                        parent.leftChild=newNode;
                        return;
                    }
                }else{
                    current=current.rightChild;
                    if(current==null){
                        parent.rightChild=newNode;

                        return;
                    }
                }
            }

        }


    }
 
        

  

 
 
        

  遍歷二叉搜索樹

  二叉樹的中序遍歷過程是調用自身左子樹,然后訪問節點,在調用自身右子樹。遞歸代碼如下。而前序和后序的遍歷只需要把中序遍歷

  中的調用自身的遞歸和訪問節點(就是打印那一行)翻翻順序就ok了。

 

 /**
     * 遞歸遍歷二叉樹(中序)
     */

    public void disPlayTree(Node node){
        if(node!=null){
            if(node.leftChild!=null){
                disPlayTree(node.leftChild);
            }

            Log.d("", "二叉樹遍歷: "+node.data);
            if(node.rightChild!=null){
                disPlayTree(node.rightChild);
            }

        }
    }

 

  

 

 

  刪除delete節點。如果待刪除節點是葉節點(沒有子節點),值直接把刪除節點賦值為null即可。
  如果有一個子節點也簡單,待刪除的節點在刪除節點的左側(右側),則把待刪除節點的子樹賦值給待刪除節點父節點的
  左側(右側)。
  刪除:如果刪除節點右2個子節點,則需要先找到待刪除節點的后續節點,即是比待刪除節點次高的節點。
  如圖:

  刪除過程就是:87位后續節點,為50的右節點。62為87的左節點。89位87的右節點。


  刪除:后續節點在左側時:

查找到后續節點是77,則50的右節點為77,79變成87的左節點,93還是83的右節點。62變成77的左節點。


  刪除代碼實現:
  1.獲取后續節點
 /**
     * 獲取后序節點
     */
    public  Node  getSuccessor(Node delNode){
        Node successorParent =delNode;
        Node successor=delNode;
        Node current=delNode.rightChild;
        while(current!=null){
            successorParent=successor;
            successor=current;
            current=current.leftChild;
        }
        if(successor!=delNode.rightChild){
            successorParent.leftChild=successor.rightChild;
           successor.rightChild=delNode.rightChild;

        }
        Log.d("二叉樹遍歷", "getSuccessor: "+successor.data);
        return successor;
    }
 
        

  

 
 
        

  刪除節點:

 

  /**
     *  刪除節點
     * @param key
     */
    public  boolean deldte( int key){
        Node current=root;
        Node parent=root;
        boolean isLeftChild=true;
        /**
         * 先把刪除值的Node找出來
         */
        while(current.data!=key){
            parent=current;
            if(key<current.data){
                isLeftChild=true;
                current=current.leftChild;
            }else {
                isLeftChild=false;
                current=current.rightChild;
            }
            if(current==null){
                return  false;
            }

        }         // while結束,查找到刪除節點,就是current

        /**
         * 如果刪除節點是葉節點
         */

        if(current.leftChild==null&&current.rightChild==null){
            if(current==root){
                root=null;
            }else if(isLeftChild){
                parent.leftChild=null;
            }else{
                parent.rightChild=null;
            }

        }

        /**
         * 如果刪除的節點沒有rightChild,只有leftChild
         */

        else if(current.rightChild==null){
            if(current==root){
                root=current.leftChild;
            }
            else  if(isLeftChild){
                parent.leftChild=current.leftChild;
            }
            else {
                parent.rightChild=current.leftChild;
            }
        }

        /**
         * 如果刪除的節點只有rightChild
         */
        else if(current.leftChild==null){
            if(current==root){
                root=current.rightChild;
            }
            else if(isLeftChild){
                parent.leftChild=current.rightChild;
            }else{
                parent.rightChild=current.rightChild;
            }
        }

        /**
         * 如果刪除點有2個節點
         */


        else {

            /**
             * 獲取后續節點
             */
            Node successor=getSuccessor(current);
            if(current==root){
                root=successor;
            }else if( isLeftChild){
                parent.leftChild=successor;
            }else{
                parent.rightChild=successor;
            }
            successor.leftChild=current.leftChild;


        }






        return true;
    }

 

  

 

  找最大最小值:



  尋找最大最小值
這個就簡單了,從根節點一直找左節點直到沒有左子節點,那么這個值就是最小值,反之就是最大值。

4.小結:
代碼:
http://pan.baidu.com/s/1miz8ocC
View Code
 
        
https://github.com/galibujianbusana/MyErChaShu
View Code

 




免責聲明!

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



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