1. 程式人生 > >【ADT】 第四章 樹

【ADT】 第四章 樹

樹,大部分操作的執行時間平均為O(log N)
樹是按照大小順序儲存的,不可重複的集合
Collection中基於二叉查詢樹實現了TreeSet和TreeMap類

二叉樹是基於排序的,因此需要比較,二叉樹中儲存的物件需要實現Comparable介面

1 樹

1.1 基礎知識

1 沒有兒子的節點稱為樹葉
2 沒有父親的節點(最頂點)稱為根
3 從根節點向下,第幾層稱為深度,根節點深度為0
4 從葉節點向上,距離葉節點幾層稱為高,葉節點高度為0

1.2 樹節點的宣告

存放的元素 + 第一個子節點 + 自己的下一個兄弟

class TreeNode<T
>{
T data; TreeNode firstChild; TreeNode nextSibling; }

如此獲取自己的第二個子節點:X.firstChild.nextSibling

1.3 樹的遍歷

依據順序分為先序遍歷、中序遍歷、後序遍歷

1.3.1 先序遍歷

對節點的處理工作是在他的諸多兒子節點處理之前進行
對於二叉樹:依據 根、左節點、右節點的順序遍歷

1.3.2 中序遍歷

對節點的處理工作是在他的諸多兒子節點處理中進行
對於二叉樹:依據 左節點、根、右節點的順序遍歷

1.3.3 後序遍歷

對節點的處理工作是在他的諸多兒子節點處理之後進行
對於二叉樹:依據 左節點、右節點、根的順序遍歷

2 二叉樹

我們運用最多的還是二叉樹
二叉樹是每個節點都不多於兩個兒子的樹
故我們可以直接宣告它的左右兒子節點

class BinaryNode<T> {
        private T data;
        private BinaryNode<T> left;
        private BinaryNode<T> right;
 }

2.1 二叉查詢樹

二叉查詢樹的特性是:對於樹的每個節點X,其左子樹的所有節點都小於X中的項,其右子樹的所有節點都大於X中的項
二叉查詢樹要求所有的項都能夠排序
二叉樹中的操作最基本的一點:遞迴

查詢操作

二叉樹的查詢規則:如果比當前節點大,則前往右子節點(x.right);如果比當前節點小,則前往左子節點(x.left)

private boolean contains (T t , BinaryNode<T> node) {
        if(node == null) {
            return false;
        }
        int index = t.compareTo(node.data);
        //理解一下樹的概念,有點像二分查詢
        if(index < 0) {//t比較小,應該找node的左子樹(這個節點的左節點)
            return contains(t,node.left);
        }else if(index > 0) {//右子樹
            return contains(t,node.right);
        }else{ //相等
            return true;
        }
    }

insert

二叉查詢樹是不能儲存重複資料的,首先查詢該元素,如果樹上沒有這個元素,直接將其插入到便利路徑上的最後一點上即可(insert操作不改變二叉樹樹結構,因為我就是按照這個規則插入的)

 private BinaryNode<T> insert(T t, BinaryNode<T> node) {
        if(node == null) { //查詢到最後的時候
            return new BinaryNode<T>(t,null,null);
        }
        //不是第一個數,就需要查找了,樹的作用是按順序儲存
        //如果查到相同的,不做處理,因為不存放相同元素
        //沒有相同的,那麼最後查到位置node==null的位置就是應該插入的位置
        //然後返回一個只儲存Data的node,再修改與之相連的left 或 right指向這個new node
        //因為insert不會改變結構,add到的一定是葉節點
        int index = t.compareTo(node.data);
        if(index < 0) { //left
            node.left = insert(t,node.left);
        }else if(index > 0) { //right
            node.right = insert(t,node.right);
        }
        return node;
    }

remove

remove操作就比較繁瑣:
1 如果刪除葉節點,直接刪除
2 如果是一個子節點的父節點X,需要將X的子節點連線到X的父節點
3 如果是兩個子節點的父節點X,需要查詢到右子樹(注意是右子樹中所有節點)中最小值代替這個父節點[因為二叉查詢樹要求節點的值小於右子樹所有值],再去刪除那個最小值(這個最小值如何刪除又分成了三種情況。。。)這樣就會改變二叉查詢樹的結構。。。

//刪除
    //如果是葉節點 直接刪除
    //如果只有一個子節點,刪除之後將被刪除節點的子節點連線到被刪除節點的父節點上
    //如果是兩個子節點,要去找右子節點的最小值,將他代替被刪除節點,這樣又相當於刪除了最小值的原節點
    private  BinaryNode<T> remove(T t,BinaryNode<T> node) {
        if(node == null) { //找不到
            return node;
        }
        int index = t.compareTo(node.data);
        if(index < 0) {
            node.left = remove(t,node.left);
        }else if(index > 0) {
            node.right = remove(t,node.right);
        }else { //找到了這個節點node
            if(node.right != null && node.left != null) { //兩個子節點
                BinaryNode<T> minNode = findMin(node.right);//去找右子樹中最小值
                node.data = minNode.data;//替換成minNode
                node.right = remove(minNode.data,node.right);
            }else{
                node = (node.left == null)? node.right:node.left;
            }
        }
        return node;
    }

劣勢

二叉查詢樹依據插入元素的順序會生成不同的樹結構
尤其在於按大小順序插入時會生成二叉樹深度最差情況O(N)
O(N)

這時候引入平衡樹

2.2 AVL樹

AVL樹是一種經典的帶有平衡條件的二叉查詢樹
平衡條件要求每個節點的左子樹和右子樹的高度差最多為1
如此保證了樹的深度為O(log N )
[即對於儲存了N個元素的AVL樹來講,有log N層,遍歷時按照一層一層遍歷,便是遍歷logN次]

AVL和二叉樹查詢樹的唯一不同在於平衡條件,對於查詢操作與二叉查詢樹一致,不同在於對AVL樹的插入、刪除都有可能改變樹的平衡結構,AVL樹多了一個balance函式,每次return的是return balance(node);

故每次操作都要通過balance()函式,通過旋轉重新平衡

旋轉平衡

將需要重新平衡的樹結構分為四種情況:

單次旋轉 一字型

1 左節點

這裡我就直接拍筆記了,畢竟還要畫圖
我這裡寫錯了。。。。這個應該叫右旋轉,因為旋轉的過程像順時針向右旋轉,但過程確實是令左節點代替父節點。。。
所以下面的都寫反了。。。。
那就不記左右旋轉了,直接左節點右節點好了

單次左旋轉

單次左旋轉的精髓在於令左節點代替破壞平衡的點成為父節點,,將注意力放在該節點和左子節點

private AVLNode<T> singleLeft(AVLNode<T> node) {
        AVLNode<T> left = node.left;
        //改變t.left和t之間的關係
        left.right = node;
        node.left = left.right;
        node.height = Math.max(height(node.left),height(node.right)) + 1;
        left.height = Math.max(height(left.left),height(left.left)) + 1;
        //返回父節點 left
        return left;
    }
2 右節點

單次右旋轉

單次左旋轉的精髓在於令右節點代替破壞平衡的點成為父節點,將注意力放在該節點和右子節點

private AVLNode<T> singRight(AVLNode<T> node) {
        AVLNode<T> right = node.right;
        node.right = right.left;
        right.left = node;
        node.height = Math.max(height(node.left),height(node.right)) + 1;
        right.height = Math.max(height(right.left),height(right.left)) + 1;
        return right;
    }

雙次旋轉 之字型

1 左節點

雙次左旋

 private AVLNode<T> doubleLeft(AVLNode<T> node) {
        //先右旋再左旋
        node.left = singRight(node.left);
        return singleLeft(node);
    }
2 右節點

雙次右旋

   private AVLNode<T> doubleRight(AVLNode<T> node) {
       //先左旋後右旋
        node.right = singleLeft(node.right);
        return singleLeft(node);
    }

2.3 自頂向下的紅黑樹

和AVL樹同樣屬於平衡二叉查詢樹
但優於AVL樹效能
Java中TreeMap TreeSet實現的基本資料結構
佔坑。。。。

2.4 伸展樹

佔坑。。。

2.5 B樹

B樹常用來做索引,例如MySQL的索引,佔坑,日後再看

3 TreeSet TreeMap

Java中對TreeSet、TreeMap的基本實現方法是平衡二叉查詢樹,但是不是AVL樹,而是另外一種:自頂向下的紅黑樹

3.1 TreeSet

保持有序狀態下、不允許重複元的Collection
有add、remove等方法

那是按照什麼排序呢?
1 預設情況下,假定TreeSet中的項實現了Comparable介面,按照實現的compareTo方法排序
2 同樣也可以利用Comparator例項化TreeSet
(一個類AComparator類實現Comparator介面,在例項化TreeSet時傳參一個AComparator物件)
按照AComparator中的compareTo方法排序

Set<String> set = new TreeSet<String>(new AComparator());

3.2 TreeMap

基於鍵值對,保證關鍵字key在邏輯上有序狀態