二叉樹
什麼是樹,爲什麼使用樹
我們知道,對於數組,查找很快,有序的還可以通過二分法查找,但是插入數據卻需要移動一些數據的位置。而鏈表的插入和刪除很快,但是查找數據卻需要從head開始遍歷,那麼有沒有一種數據結構能同時具備數組查找快的優點以及鏈表插入和刪除快的優點呢?有,就是樹,Hello,樹先生!
樹(tree)是一種抽象數據類型(ADT),用來模擬具有樹狀結構性質的數據集合。一棵樹是一些節點的集合。這個集合可以是空集;若不是空集,則樹由根(root)的節點r以及0個或多個非空的(子)樹組成,這些紫薯中每一棵的根都被來自根r的一條有向的邊(edge)所連接。
樹的術語
- 路徑:順着節點的邊從一個節點走到另一個節點,所經過的節點的順序排列就稱爲“路徑”,比如從A到H的路徑
- 根:樹頂端的節點稱爲根。一棵樹只有一個根,如果要把一個節點和邊的集合稱爲樹,那麼從根到其他任何一個節點都必須有且只有一條路徑。A是根節點
- 父節點:若一個節點含有子節點,則這個節點稱爲其子節點的父節點;B是C的父節點
- 子節點:一個節點含有的子樹的根節點稱爲該節點的子節點;D是B的子節點
- 兄弟節點:具有相同父節點的節點互稱爲兄弟節點;比如上圖的D和E就互稱爲兄弟節點
- 葉節點:沒有子節點的節點稱爲葉節點,也叫葉子節點,比如上圖的D、E、F、H都是葉子節點
- 子樹:每個節點都可以作爲子樹的根,它和它所有的子節點、子節點的子節點等都包含在子樹中
- 節點的層次:從根開始定義,根爲第一層,根的子節點爲第二層,以此類推
- 深度:對於任意節點n,n的深度爲從根到n的唯一路徑長,根的深度爲0
- 高度:對於任意節點n,n的高度爲從n到一片樹葉的最長路徑長,所有樹葉的高度爲0
二叉樹
二叉樹:樹的每個節點最多只能有兩個子節點,比如上圖就是一個二叉樹
二叉搜索樹:若它的左子樹不空,則左子樹上所有結點的值均小於它的根結點的值; 若它的右子樹不空,則右子樹上所有結點的值均大於它的根結點的值; 它的左、右子樹也分別爲二叉排序樹。像下圖這樣
二叉搜索樹的實現
節點類
package myTree;
/**
* @Author fitz.bai
* @Date 2018/9/17 21:33
*/
public class Node {
// 節點內的值
int data;
// 左子節點
Node leftChild;
// 右子節點
Node rightChild;
// 懶惰刪除,可以使用邏輯刪除,該節點仍留在樹中,而知識標記爲被刪除
boolean isDelete;
public Node(int data) {
this.data = data;
}
public void value() {
System.out.println(data);
}
}
二叉樹的增刪改查
刪除
1、刪除葉子結點
2、刪除只有一個子節點節點
3、刪除有左右兩個子節點的節點
後繼節點也就是:比刪除節點大的最小節點。
算法:程序找到刪除節點的右節點,然後轉到該右節點的左子節點,依次順着左子節點找下去,最後一個左子節點即是後繼節點;如果該右節點沒有左子節點,那麼該右節點便是後繼節點。
然後將後繼節點的值替換到待刪除的節點位置,再將後繼節點刪除
package myTree;
/**
* @Author fitz.bai
* @Date 2018/9/17 21:35
*/
public class Tree {
public Node root;
//find
public Node find(int key) {
// 從root開始查找,如果比root大向右走,比root小向左走,依次往下循環進行
Node current = root;
while (current != null) {
if (key > current.data) {
current = current.rightChild;
} else if (key < current.data) {
current = current.leftChild;
} else {
return current;
}
}
return null;
}
public boolean put(int data) {
// 從root開始查找,如果比root大向右走,比root小向左走,依次往下循環進行,直到找到合適的位置,即找到某一節點合適子節點
Node newNode = new Node(data);
if (root == null) {
root = newNode;
return true;
} else {
Node current = root;
Node parentNode = null;
while (current != null) {
parentNode = current;
if (data > current.data) {
current = current.rightChild;
if (current == null) {
parentNode.rightChild = newNode;
return true;
}
} else {
current = current.leftChild;
if (current == null) {
parentNode.leftChild = newNode;
return true;
}
}
}
}
return false;
}
/**
* 刪除比較麻煩,分爲三種情況
* 1、刪除葉子結點:
* 2、刪除節點只有左子節點或右子節點
* 3、刪除節點有左右子樹
*
* @param key
* @return
*/
public boolean delete(int key) {
Node current = root;
Node parentNode = root;
boolean isLeftChild = false;
//無此key,返回false
while (current.data != key) {
parentNode = current;
if (current.data > key) {
isLeftChild = true;
current = current.leftChild;
} else {
isLeftChild = false;
current = current.rightChild;
}
if (current == null) {
return false;
}
}
//1、當前待刪除節點沒有子節點,葉子結點
// 判斷是否爲root節點,是,直接root置爲null,否,如果待刪除的節點是父節點的左子節點,
// 置賦節點的左子節點爲null,反之,置右子節點爲null
if (current.leftChild == null && current.rightChild == null) {
if (current == root) {
root = null;
} else if (isLeftChild) {
parentNode.leftChild = null;
} else {
parentNode.rightChild = null;
}
return true;
// 當前節點只有左子節點
// 判斷是否爲root節點,是,直接root置爲當前節點的左子節點
// 若否,判斷刪除的節點是左還是右子節點,若是左子節點,置刪除節點的父節點的左子節點爲刪除節點的左子節點
// 若是右子節點,置刪除節點的父節點的右子節點爲刪除節點的左子節點
} else if (current.leftChild != null && current.rightChild == null) {
if (current == root) {
root = current.leftChild;
} else if (isLeftChild) {
parentNode.leftChild = current.leftChild;
} else {
parentNode.rightChild = current.leftChild;
}
return true;
//當前節點只有右子節點
//同上面
} else if (current.leftChild == null && current.rightChild != null) {
if (current == root) {
root = current.rightChild;
} else if (isLeftChild) {
parentNode.leftChild = current.rightChild;
} else {
parentNode.rightChild = current.rightChild;
}
return true;
} else {
//刪除節點,既有左子節點,也有右子節點,詳細見前面的分析
//主要做法就是,找到刪除節點的後繼節點,將他的值賦給要刪除的節點,然後將後繼節點刪除
//原刪除的節點的左右子樹還掛在當前被改了值的節點
Node successor = getSuccessor(current);
if (current == root) {
successor = root;
} else if (isLeftChild) {
parentNode.leftChild = successor;
} else {
parentNode.rightChild = successor;
}
successor.leftChild = current.leftChild;
}
return false;
}
//獲取後繼節點:比刪除節點大的最小節點
private 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;
}
return successor;
}
}
二叉樹的遍歷
/**
* 遍歷分爲前序,中序,後序遍歷,都是通過遞歸實現,
* 比如從跟節點開始前序遍歷,先打印根節點,然後分別對root的
* 左右子節點在看做獨立的樹進行前序遍歷
*
* @param current
*/
//前序遍歷
public void preOrder(Node current) {
if (current != null) {
// 根節點-->左子節點-->右子節點
System.out.print(current.data + " ");
preOrder(current.leftChild);
preOrder(current.rightChild);
}
}
//中序遍歷
public void infixOrder(Node current) {
if (current != null) {
// 左子節點-->根節點-->右子節點
infixOrder(current.leftChild);
System.out.print(current.data + " ");
infixOrder(current.rightChild);
}
}
//後序遍歷
public void postOrder(Node current) {
if (current != null) {
// 左子節點-->右子節點-->根節點
postOrder(current.leftChild);
postOrder(current.rightChild);
System.out.print(current.data + " ");
}
}
findMax & findMin
//findMax
// 就是從根節點一直查找右子節點
public Node findMax() {
Node current = root;
Node maxNode = current;
while (current != null) {
maxNode = current;
current = current.rightChild;
}
return maxNode;
}
//findMin
// 就是從根節點一直查找左子節點
public Node findMin() {
Node current = root;
Node minNode = current;
while (current != null) {
minNode = current;
current = current.leftChild;
}
return minNode;
}
總結
網上找的有關二叉查找樹的時間複雜度http://www.cnblogs.com/yangecnu/p/Introduce-Binary-Search-Tree.html