使用Java實現二叉樹的添加,刪除,獲取以及遍歷

  一段來自百度百科的對二叉樹的解釋:git

  在計算機科學中,二叉樹是每一個結點最多有兩個子樹的樹結構。一般子樹被稱做「左子樹」(left subtree)和「右子樹」(right subtree)。二叉樹常被用於實現二叉查找樹和二叉堆。
  一棵深度爲k,且有2^k-1個節點的二叉樹,稱爲滿二叉樹。這種樹的特色是每一層上的節點數都是最大節點數。而在一棵二叉樹中,除最後一層外,若其他層都是滿的,而且最後一層或者是滿的,或者是在右邊缺乏連續若干節點,則此二叉樹爲徹底二叉樹。具備n個節點的徹底二叉樹的深度爲floor(log2n)+1。深度爲k的徹底二叉樹,至少有2 k-1個葉子節點,至多有2k-1個節點。
  二叉樹的結構:

  二叉樹節點的聲明:github

static final class Entry<T extends Comparable<T>>{
        //保存的數據
        private T item;
        //左子樹
        private Entry<T> left;
        //右子樹
        private Entry<T> right;
        //父節點
        private Entry<T> parent;
        Entry(T item,Entry<T> parent){
            this.item = item;
            this.parent = parent;
        }
    }

  類屬性:測試

 //根節點
    private Entry<T> root;
    //數據量
    private int size = 0;

  二叉樹添加數據:this

/**
     * 添加元素
     * @param item 待添加元素
     * @return 已添加元素
     */
    public T put(T item){
        //每次添加數據的時候都是從根節點向下遍歷
        Entry<T> t = root;
  if (t == null){
            //當前的叉樹樹的爲空,將新節點設置爲根節點,父節點爲null
            root = new Entry<>(item,null);
       size++; 
return root.item; } //天然排序結果,若是傳入的數據小於當前節點返回-1,大於當前節點返回1,不然返回0 int ret = 0; //記錄父節點 Entry<T> p = t; while (t != null){ //與當前節點比較 ret = item.compareTo(t.item); p = t; //插入節點比當前節點小,把當前節點設置爲左子節點,而後與左子比較,以此類推找到合適的位置 if (ret < 0) t = t.left; //大於當前節點 else if (ret > 0) t = t.right; else { //相等就把舊值覆蓋掉 t.item = item; return t.item; } } //建立新節點 Entry<T> e = new Entry<>(item,p); //根據比較結果將新節點放入合適的位置 if (ret < 0) p.left = e; else p.right = e;
     size++;
return e.item; }

   在put的過程當中,使用Comparable<T>中的compareTo來比較兩個元素的大小的,因此在二叉樹中存儲的元素必需要繼承Comparable<T> 類,覆寫compareTo方法。spa

 

  二叉樹刪除數據code

/**
     * 刪除元素
     * 刪除元素若是細分的話,能夠分爲4中:沒有子節點,只有左節點,只有右節點,有兩個子節點
     * 1)沒有子節點這種狀況比較簡單,直接刪除就能夠了
     * 2)只有左節點或右節點,這兩種狀況處理方式是一致的,只是方向相反,因此在一塊兒講了,
     * 將刪除節點的父節點的左節點(右節點)指向刪除節點的子節點,將左節點(右節點)指向刪除節點的父節點
     * 3)有兩個子節點,這種狀況相對來講比較複雜一點:
     * 找到刪除節點的前驅節點或後繼節點,而後將前驅或後繼節點的值賦給刪除節點,而後將前驅或後繼節點刪除。本質是刪除前驅或後繼節點
     * 前驅節點的特色:
     * 1)刪除的左子節點沒有右子節點,那麼左子節點即爲前驅節點
     * 2)刪除節點的左子節點有右子節點,那麼最右邊的最後一個節點即爲前驅節點
     * 後繼節點的特色:
     * 與前驅節點恰好相反,老是右子節點,或則右子節點的最左子節點;例如:刪除節點爲c ,那麼前驅節點爲 m,後繼節點爲n
     *                                          a
     *                                       /     \
     *                                    b          c
     *                                  / \         /  \
     *                                d    e       f    g
     *                              /  \  / \     / \   / \
     * @param item 刪除元素          h   i  j  k   l   m n   o
     * @return 刪除結果
     */
    public boolean remove(T item){
        //獲取刪除節點
        Entry<T> delEntry = getEntry(item);
        if (delEntry == null) return false;
        //刪除節點的父節點
        Entry<T> p = delEntry.parent;
        size--;
        //狀況1:沒有子節點
        if (delEntry.left == null && delEntry.right == null){
            //刪除節點爲根節點
            if (delEntry == root){
                root = null;
            }else {//非根節點
                //刪除的是父節點的左節點
                if (delEntry == p.left){
                    p.left = null;
                }else {//刪除右節點
                    p.right = null;
                }
            }
            //狀況2:刪除的節點只有左節點
        }else if (delEntry.right == null){
            Entry<T> lc = delEntry.left;
            //刪除的節點爲根節點,將刪除節點的左節點設置成根節點
            if (p == null) {
                lc.parent = null;
                root = lc;
            } else {//非根節點
                if (delEntry == p.left){//刪除左節點
                    p.left = lc;
                }else {//刪除右節點
                    p.right = lc;
                }
                lc.parent = p;
            }
            //狀況3:刪除節點只有右節點
        }else if (delEntry.left == null){
            Entry<T> rc = delEntry.right;
            //刪除根節點
            if (p == null) {
                rc.parent = null;
                root = rc;
            }else {//刪除非根節點
                if (delEntry == p.left)
                    p.left = rc;
                else
                    p.right = rc;
                rc.parent = p;
            }
            //狀況4:刪除節點有兩個子節點
        }else {//有兩個節點,找到後繼節點,將值賦給刪除節點,而後將後繼節點刪除掉便可
            Entry<T> successor = successor(delEntry);//獲取到後繼節點
            delEntry.item = successor.item;
            //後繼節點爲右子節點
            if (delEntry.right == successor){
                //右子節點有右子節點
                if (successor.right != null) {
                    delEntry.right = successor.right;
                    successor.right.parent = delEntry;
                }else {//右子節點沒有子節點
                    delEntry.right = null;
                }
            }else {//後繼節點一定是左節點
                successor.parent.left = null;
            }
            return true;
        }
        //讓gc回收
        delEntry.parent = null;
        delEntry.left = null;
        delEntry.right = null;
        return true;
    }

/**
     * 獲取節點節點
     * @param item 獲取節點
     * @return 獲取到的節點,可能爲null
     */
    private Entry<T> getEntry(T item){
        Entry<T> t = root;
        int ret;
        //從根節點開始遍歷
        for (;t != null;){
            ret = item.compareTo(t.item);
            if (ret < 0)
                t = t.left;
            else if (ret > 0)
                t = t.right;
            else
                return t;
        }
        return null;
    }


    /**
     * 查找後繼節點
     * @param delEntry 刪除節點
     * @return 後繼節點
     */
    private Entry<T> successor(Entry<T> delEntry) {
        Entry<T> r = delEntry.right;//r節點一定不爲空
        while (r.left != null){
            r = r.left;
        }
        return r;
    }

   二叉樹獲取節點orm

    /**
     * 判斷是否存在該元素
     * @param item 查找元素
     * @return 結果
     */
    public boolean contains(T item){
        return getEntry(item) != null;
    }


    
    /**
     * 獲取節點節點
     * @param item 獲取節點
     * @return 獲取到的節點,可能爲null
     */
    private Entry<T> getEntry(T item){
        Entry<T> t = root;
        int ret;
        //從根節點開始遍歷
        for (;t != null;){
            ret = item.compareTo(t.item);
            if (ret < 0)
                t = t.left;
            else if (ret > 0)
                t = t.right;
            else
                return t;
        }
        return null;
    }

  由於我這個例子是存儲一個元素,獲取到的元素和傳進去的元素是一致的,因此我用contains方法來判斷返回true即表示獲取成功了,不返回獲取到的結果了。固然,若是將entry存儲的元素改成kv形式的話,就可使用get方法了。blog

 

  二叉樹的遍歷排序

  二叉樹的遍歷能夠分爲三種:前序遍歷、中序遍歷和後續遍歷,其中中序遍歷是最經常使用的遍歷方式,由於它遍歷出來的結果的升序的。繼承

  前序遍歷:

     /**
     * 前序遍歷
     * @param e 開始遍歷元素
     */
    public void prevIterator(Entry<T> e){
        if (e != null) {
            System.out.print(e.item + " ");
            prevIterator(e.left);
            prevIterator(e.right);
        }
    }

  中序遍歷:

   /**
     * 中序遍歷
     * @param e
     */
    public void midIterator(Entry<T> e){
        if (e != null){
            midIterator(e.left);
            System.out.print(e.item + " ");
            midIterator(e.right);
        }
    }

  後序遍歷:

    /**
     * 後續遍歷
     * @param e 開始遍歷元素
     */
    public void subIterator(Entry<T> e){
        if (e != null) {
            subIterator(e.left);
            subIterator(e.right);
            System.out.print(e.item + " ");
        }
    }

 

  demo完整的代碼:也能夠到個人github中下載代碼:https://github.com/rainple1860/MyCollection

package com.rainple.collections;

/**
 * 二叉樹
 * @param <T>
 */
public class BinaryTree<T extends Comparable<T>> {

    //根節點
    private Entry<T> root;
    //數據量
    private int size = 0;

    public BinaryTree(){}

    /**
     * 添加元素
     * @param item 待添加元素
     * @return 已添加元素
     */
    public T put(T item){
        //每次添加數據的時候都是從根節點向下遍歷
        Entry<T> t = root;
        size++;
        if (t == null){
            //當前的叉樹樹的爲空,將新節點設置爲根節點,父節點爲null
            root = new Entry<>(item,null);
            return root.item;
        }
        //天然排序結果,若是傳入的數據小於當前節點返回-1,大於當前節點返回1,不然返回0
        int ret = 0;
        //記錄父節點
        Entry<T> p = t;
        while (t != null){
            //與當前節點比較
            ret = item.compareTo(t.item);
            p = t;
            //插入節點比當前節點小,把當前節點設置爲左子節點,而後與左子比較,以此類推找到合適的位置
            if (ret < 0)
                t = t.left;
            //大於當前節點
            else if (ret > 0)
                t = t.right;
            else {
                //相等就把舊值覆蓋掉
                t.item = item;
                return t.item;
            }
        }
        //建立新節點
        Entry<T> e = new Entry<>(item,p);
        //根據比較結果將新節點放入合適的位置
        if (ret < 0)
            p.left = e;
        else
            p.right = e;
        return e.item;
    }

    public void print(){
        midIterator(root);
    }

    /**
     * 中序遍歷
     * @param e
     */
    public void midIterator(Entry<T> e){
        if (e != null){
            midIterator(e.left);
            System.out.print(e.item + " ");
            midIterator(e.right);
        }
    }

    /**
     * 獲取根節點
     * @return 根節點
     */
    public Entry<T> getRoot(){return root;}

    /**
     * 前序遍歷
     * @param e 開始遍歷元素
     */
    public void prevIterator(Entry<T> e){
        if (e != null) {
            System.out.print(e.item + " ");
            prevIterator(e.left);
            prevIterator(e.right);
        }
    }

    /**
     * 後續遍歷
     * @param e 開始遍歷元素
     */
    public void subIterator(Entry<T> e){
        if (e != null) {
            subIterator(e.left);
            subIterator(e.right);
            System.out.print(e.item + " ");
        }
    }

    /**
     * 獲取節點節點
     * @param item 獲取節點
     * @return 獲取到的節點,可能爲null
     */
    private Entry<T> getEntry(T item){
        Entry<T> t = root;
        int ret;
        //從根節點開始遍歷
        for (;t != null;){
            ret = item.compareTo(t.item);
            if (ret < 0)
                t = t.left;
            else if (ret > 0)
                t = t.right;
            else
                return t;
        }
        return null;
    }

    /**
     * 判斷是否存在該元素
     * @param item 查找元素
     * @return 結果
     */
    public boolean contains(T item){
        return getEntry(item) != null;
    }

    /**
     * 刪除元素
     * 刪除元素若是細分的話,能夠分爲4中:沒有子節點,只有左節點,只有右節點,有兩個子節點
     * 1)沒有子節點這種狀況比較簡單,直接刪除就能夠了
     * 2)只有左節點或右節點,這兩種狀況處理方式是一致的,只是方向相反,因此在一塊兒講了,
     * 將刪除節點的父節點的左節點(右節點)指向刪除節點的子節點,將左節點(右節點)指向刪除節點的父節點
     * 3)有兩個子節點,這種狀況相對來講比較複雜一點:
     * 找到刪除節點的前驅節點或後繼節點,而後將前驅或後繼節點的值賦給刪除節點,而後將前驅或後繼節點刪除。本質是刪除前驅或後繼節點
     * 前驅節點的特色:
     * 1)刪除的左子節點沒有右子節點,那麼左子節點即爲前驅節點
     * 2)刪除節點的左子節點有右子節點,那麼最右邊的最後一個節點即爲前驅節點
     * 後繼節點的特色:
     * 與前驅節點恰好相反,老是右子節點,或則右子節點的最左子節點;例如:刪除節點爲c ,那麼前驅節點爲 m,後繼節點爲n
     *                                          a
     *                                       /     \
     *                                    b          c
     *                                  / \         /  \
     *                                d    e       f    g
     *                              /  \  / \     / \   / \
     * @param item 刪除元素       h   i  j  k   l   m n   o
     * @return 刪除結果
     */
    public boolean remove(T item){
        //獲取刪除節點
        Entry<T> delEntry = getEntry(item);
        if (delEntry == null) return false;
        //刪除節點的父節點
        Entry<T> p = delEntry.parent;
        size--;
        //狀況1:沒有子節點
        if (delEntry.left == null && delEntry.right == null){
            //刪除節點爲根節點
            if (delEntry == root){
                root = null;
            }else {//非根節點
                //刪除的是父節點的左節點
                if (delEntry == p.left){
                    p.left = null;
                }else {//刪除右節點
                    p.right = null;
                }
            }
            //狀況2:刪除的節點只有左節點
        }else if (delEntry.right == null){
            Entry<T> lc = delEntry.left;
            //刪除的節點爲根節點,將刪除節點的左節點設置成根節點
            if (p == null) {
                lc.parent = null;
                root = lc;
            } else {//非根節點
                if (delEntry == p.left){//刪除左節點
                    p.left = lc;
                }else {//刪除右節點
                    p.right = lc;
                }
                lc.parent = p;
            }
            //狀況3:刪除節點只有右節點
        }else if (delEntry.left == null){
            Entry<T> rc = delEntry.right;
            //刪除根節點
            if (p == null) {
                rc.parent = null;
                root = rc;
            }else {//刪除非根節點
                if (delEntry == p.left)
                    p.left = rc;
                else
                    p.right = rc;
                rc.parent = p;
            }
            //狀況4:刪除節點有兩個子節點
        }else {//有兩個節點,找到後繼節點,將值賦給刪除節點,而後將後繼節點刪除掉便可
            Entry<T> successor = successor(delEntry);//獲取到後繼節點
            delEntry.item = successor.item;
            //後繼節點爲右子節點
            if (delEntry.right == successor){
                //右子節點有右子節點
                if (successor.right != null) {
                    delEntry.right = successor.right;
                    successor.right.parent = delEntry;
                }else {//右子節點沒有子節點
                    delEntry.right = null;
                }
            }else {//後繼節點一定是左節點
                successor.parent.left = null;
            }
            return true;
        }
        //讓gc回收
        delEntry.parent = null;
        delEntry.left = null;
        delEntry.right = null;
        return true;
    }

    /**
     * 查找後繼節點
     * @param delEntry 刪除節點
     * @return 後繼節點
     */
    private Entry<T> successor(Entry<T> delEntry) {
        Entry<T> r = delEntry.right;//r節點一定不爲空
        while (r.left != null){
            r = r.left;
        }
        return r;
    }

    public int size(){return size;}

    public boolean isEmpty(){return size == 0;}

    public void clear(){
        clear(getRoot());
        root = null;
    }

    private void clear(Entry<T> e){
        if (e != null){
            clear(e.left);
            e.left = null;
            clear(e.right);
            e.right = null;
        }
    }

    static final class Entry<T extends Comparable<T>>{
        //保存的數據
        private T item;
        //左子樹
        private Entry<T> left;
        //右子樹
        private Entry<T> right;
        //父節點
        private Entry<T> parent;
        Entry(T item,Entry<T> parent){
            this.item = item;
            this.parent = parent;
        }
    }

}

   

  測試代碼示例:

public static void main(String[] args) {
       BinaryTree<Integer> binaryTree = new BinaryTree<>();
        //放數據
        binaryTree.put(73);
        binaryTree.put(22);
        binaryTree.put(532);
        binaryTree.put(62);
        binaryTree.put(72);
        binaryTree.put(243);
        binaryTree.put(42);
        binaryTree.put(3);
        binaryTree.put(12);
        binaryTree.put(52);

        System.out.println("size: " + binaryTree.size());
        binaryTree.put(52);
        System.out.println("添加相同元素後的size: " + binaryTree.size());
        //判斷數據是否存在
        System.out.println("數據是否存在:" + binaryTree.contains(12));
        //中序遍歷
        System.out.print("中序遍歷結果: ");
        binaryTree.midIterator(binaryTree.getRoot());
        System.out.println();
        //前序遍歷
        System.out.print("前遍歷結果: ");
        binaryTree.prevIterator(binaryTree.getRoot());
        System.out.println();
        //後序遍歷
        System.out.print("後續遍歷結果: ");
        binaryTree.subIterator(binaryTree.getRoot());
        //刪除數據
        System.out.println();
        binaryTree.remove(62);
        System.out.println("刪除數據後判斷是否存在:" + binaryTree.contains(62));
        //清空二叉樹
        binaryTree.clear();
        System.out.print("清空數據後中序遍歷: ");
        binaryTree.midIterator(binaryTree.getRoot());
    }

  測試結果:

size: 10
添加相同元素後的size: 10
數據是否存在:true
中序遍歷結果: 3 12 22 42 52 62 72 73 243 532 
前遍歷結果: 73 22 3 12 62 42 52 72 532 243 
後續遍歷結果: 12 3 52 42 72 62 22 243 532 73 
刪除數據後判斷是否存在:false
清空數據後中序遍歷: 

  純手寫的demo,有什麼錯誤的地方歡迎指正,謝謝你們的閱讀!!!

相關文章
相關標籤/搜索