【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)
這時候引入平衡樹
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在邏輯上有序狀態