造輪子之二叉樹
阿新 • • 發佈:2020-12-24
在二叉樹中,每個根節點只能有左右兩個子節點,左子節點比根節點小,右子節點比根節點大(TreeSet中不允許出現相同元素。一些場景下,如果子節點和根節點相同會將子節點放到根節點的右邊)。
二叉搜尋樹作為一種經典的資料結構,它既有連結串列的快速插入與刪除操作的特點,又有陣列快速查詢的優勢;所以應用十分廣泛,例如在檔案系統和資料庫系統一般會採用這種資料結構進行高效率的排序與檢索操作。
//二叉搜尋樹
public class BinarySearchTree<T extends Comparable> {
private int size;
private Node root;
//新增元素
public boolean add(T data) {
if (size == 0) {
root = new Node(data, null);
} else {
add(data, root);
}
size++;
return true;
}
//遞迴新增子節點
private void add(T data, Node root) {
//呼叫元素的compareto方法,檢視根節點和data的大小關係
int f = root.data.compareTo(data);
//如果f大於等於0說明節點大,那應該把資料放在根節點左邊。
//如果f小於0說明節點小資料大,應該把資料放到根節點右邊
Node node = f > 0 ? root.left : root.right;
//如果根節點的左邊或者右邊==null表示可以直接往根節點放子節點
if (node == null) {
//建立子節點
Node newNode = new Node(data, root);
if (f > 0) {
//放到左邊
root.left = newNode;
} else {
//放到右邊
root.right = newNode;
}
} else {
//如果根據點的左邊或者右邊有元素
//需要將改元素在次重複判斷以上全部步驟。
add(data, node);
}
}
//列印二叉樹檢視結果
public Object[] list() {
ArrayList<T> list = new ArrayList<>();
toArray(root, list);
return list.toArray();
}
//遞迴函式
private void toArray(Node node, ArrayList list) {
Node left = node.left;
//如果有左節點
if (left != null) {
toArray(left, list);
}
System.out.println(node.data);//列印自身節點
list.add(node.data);
//如果有右邊節點,一樣遞迴
Node right = node.right;
if (right != null) {
toArray(right, list);
}
return;
}
public int size() {
return size;
}
//判斷節點是否存在
public boolean contains(T data) {
if (query(data, root) == null) {
System.out.println("沒有找到");
return false;
} else {
System.out.println("一共尋找了" + x + "次");
return true;
}
}
int x = 0;//記錄樹的遍歷次數,以檢驗是否是最優解
//查詢節點
private Node query(T data, Node node) {
x++;
//先找到這個元素的位置
int i = node.data.compareTo(data);
//如果==0就是找到了。
if (i == 0) {
return node;
}
Node left = node.left;
Node right = node.right;
//向左查詢
if (i > 0 && left != null) {
return query(data, left);
} else if (i < 0 || right != null) {//向右查詢
return query(data, right);
} else {
return null;
}
}
/*
* 刪除節點有三種情況
* 1 被刪除的節點無子孫節點,直接刪除。
* 2 被刪除的節點只有左子節點或者只有右子節點,需要將左或者右子節點頂替被刪除的節點。
* 3 被刪除的節點有左子節點或者右子節點,這種情況有兩種方案。
* 1 從左節點族群中找到最右邊的子孫節點。
* 2 從右節點族群中找到最左邊的子孫節點。
* 二叉樹的規則是節點左邊的小於節點,節點右邊的大於或等於節點。所以找到左邊的最右邊或者右邊
* 的最左邊這兩個值是最接近於被刪除節點的。我這裡取值是左邊的最右邊
* */
public T remove(T data) {
Node removeNode = query(data, root);
if (removeNode == null) {
return null;
}
//如果只有一個節點,那麼肯定是root節點
if (size == 1) {
root = null;
} else if (removeNode.left == null && removeNode.right == null) {//第一種情況
//拿到父節點,如果當前節點沒有父節點也沒有子節點,那麼size就是1不會走到這裡
Node removeNodeParent = removeNode.parent;
//判斷當前節點在父節點的左邊還是右邊,如果父節點比被刪除節點大
//表示被刪除的節點在父節點的左邊,如果相等或者小於那麼被刪除的
//元素在父節點的右邊
int c = removeNodeParent.data.compareTo(removeNode.data);
if (c > 0) {
//父節點左孩子置為null
removeNodeParent.left = null;
} else {
//父節點右孩子置為null
removeNodeParent.right = null;
}
} else if (removeNode.left == null || removeNode.right == null) {//第二種情況
//替代被刪除的節點
Node newChild;
//被刪除的節點有左節點族群
if (removeNode.left != null) {
//被刪除節點的左節點替代被刪除的節點
newChild = removeNode.left;
//斷開被刪除節點與子節點的指標
removeNode.left = null;
} else {
//被刪除的節點有右節點族群
//被刪除節點的右節點替代被刪除的節點
newChild = removeNode.right;
//斷開被刪除節點與子節點的指標
removeNode.right = null;
}
//如果被刪除的節點沒有父節點,說明這個節點是根節點
Node removeNodeParent = removeNode.parent;
//如果不是根節點。
if (removeNodeParent != null) {
//判斷當前節點在父節點的左邊還是右邊,如果父節點比被刪除節點大
//表示被刪除的節點在父節點的左邊,如果相等或者小於那麼被刪除的
//元素在父節點的右邊
int c = removeNodeParent.data.compareTo(removeNode.data);
if (c > 0) {
//將替代節點設為父節點的左孩子
removeNodeParent.left = newChild;
} else {
//將替代節點設為父節點的右孩子
removeNodeParent.right = newChild;
}
//斷開被刪除節點與父節點的指標
removeNode.parent = null;
} else {//如果被刪除的節點是根節點,那麼被刪除節點無論是擁有左節點還是右節點這個節點都是根節點
root = newChild;
//根節點不需要父節點
root.parent = null;
}
} else {//第三種情況
Node newChild = queryLeftInRight(removeNode.left);
//拿到替換節點的父節點
Node newChildParent = newChild.parent;
//查詢替換節點在它原本父節點的哪邊
int i = newChildParent.data.compareTo(newChild.data);
//此操作為了將替換節點從原有的父節點刪掉。
if (i > 0) {
newChildParent.left = null;
} else {
newChildParent.right = null;
}
//被刪除節點的父節點
Node removeNodeParent = removeNode.parent;
//被刪除的節點不是根節點
if (removeNodeParent != null) {
//判斷當前節點在父節點的左邊還是右邊,如果父節點比被刪除節點大
//表示被刪除的節點在父節點的左邊,如果相等或者小於那麼被刪除的
//元素在父節點的右邊
int c = removeNodeParent.data.compareTo(removeNode.data);
if (c > 0) {
removeNodeParent.left = newChild;
} else {
removeNodeParent.right = newChild;
}
//如果被刪除節點的左節點不是替代節點
//則需要將被刪除節點的左節點交給替代節點的左節點
if (removeNode.left != newChild) {
newChild.left = removeNode.left;
}
//被刪除元素的右節點交給替代節點的右節點
newChild.right = removeNode.right;
//埠被刪除節點與子節點和父節點的指標
removeNode.parent = null;
removeNode.left = null;
removeNode.right = null;
} else {//被刪除的是根節點
//如果根節點的左邊節點不是替代節點則
//將父節點的左邊交給替代節點的左邊
if (root.left != newChild) {
newChild.left = root.left;
}
//將根節點的右子節點交給替代節點的右邊
newChild.right = root.right;
//替代節點成為根節點
root = newChild;
}
}
//長度-1
size--;
//返回被刪除節點的資料
return removeNode.data;
}
//獲取左節點族中最右邊的節點
private Node queryLeftInRight(Node node) {
if (node.right != null) {
return queryLeftInRight(node.right);
}
return node;
}
class Node {
private T data;//節點資料
private Node left;//左邊的孩子
private Node right;//右邊的孩子
private Node parent;//父節點
public Node(T data, Node parent) {
this.data = data;
this.parent = parent;
}
public void setLeft(Node left) {
this.left = left;
}
public void setRight(Node right) {
this.right = right;
}
}
}
View Code