二叉搜索樹Java實現(增刪改查遍歷等操作)

是一種特殊結構的二叉樹

二叉排序樹(BinarySortTree),又稱二叉查找樹、二叉搜索樹。

二叉搜索樹需滿足以下四個條件:

  • 若任意節點的左子樹不空,則左子樹上所有結點的值均小於它的根結點的值;
  • 若任意節點的右子樹不空,則右子樹上所有結點的值均大於它的根結點的值;
  • 任意節點的左、右子樹也分別爲二叉查找樹;
  • 沒有鍵值相等的節點。

二叉排序樹性質

按中序遍歷二叉排序樹,所得到的中序遍歷序列是一個遞增有序序列。

二叉搜索樹就是具備上述四種性質的二叉樹。

下面進行的二叉排序樹的增刪改查操作都是基於節點Node中key的大小構建二叉排序樹

二叉排序樹的插入

在二叉排序樹中插入新結點,要保證插入後的二叉樹仍符合二叉排序樹的定義。   
插入過程:
- 若二叉排序樹爲空,則待插入結點*S作爲根結點插入到空樹中;
- 當非空時,將待插結點關鍵字S->key和樹根關鍵字t->key進行比較,若s->key = t->key,則無須插入,若s->key< t->key,則插入到根的左子樹中,若s->key> t->key,則插入到根的右子樹中。而子樹中的插入過程和在樹中的插入過程相同,如此進行下去,直到把結點*s作爲一個新的樹葉插入到二叉排序樹中,或者直到發現樹已有相同關鍵字的結點爲止。
- 插入完成

二叉排序樹的查找

假定二叉排序樹的根結點指針爲 root ,給定的關鍵字值爲 K ,則查找算法可描述爲:

  • ① 置初值: q = root ;
  • ② 如果 K = q -> key ,則查找成功,算法結束;
  • ③ 否則,如果 K < q -> key ,而且 q 的左子樹非空,則將 q 的左子樹根送 q ,轉步驟②;否則,查找失敗,結束算法;
  • ④ 否則,如果 K > q -> key ,而且 q 的右子樹非空,則將 q 的右子樹根送 q ,轉步驟②;否則,查找失敗,算法結束。

二叉排序樹的刪除

假設被刪結點是*p,其雙親是*f,不失一般性,設*p*f的左孩子,下面分三種情況討論:   
- ⑴ 若結點*p是葉子結點,則只需修改其雙親結點*f的指針即可。  
- ⑵ 若結點*p只有左子樹PL或者只有右子樹PR,則只要使PL或PR 成爲其雙親結點的左子樹即可。   
- ⑶ 若結點*p的左、右子樹均非空,先找到*p的中序後繼(或前驅)節點*s(注意*s*p的右子樹中的key最小的結點,它的左子樹爲空),然後步驟如下:
- 找到 *p 的節點的直接中序後繼節點(即其右子樹中key值最小的節點 ),並刪除此直接中序後繼節點。
- 將此後繼節點的 key、value 值賦給待刪除節點的 key,value值。

例如刪除上圖一中 key 值爲 10 的節點,這時就需要用 key 值爲 10 的節點的中序後繼節點(節點 11)來代替 key 值爲 10 的節點,並刪除 key 值爲 10 的節點的中序後繼節點,由中序遍歷相關規則可知, key 值爲 10 的節點的直接中序後繼節點一定是其右子樹中 key 值最小的節點,所以此中序後繼節點一定不含子節點或者只含有一個右孩子,刪除此中序後繼節點就屬於上述 1,2 所述情況。圖一中 key 值爲 10 的節點的直接中序後繼節點 爲 11,節點 11 含有一個右孩子 12。

步驟:

a、找到 key 值爲 10 的節點的直接中序後繼節點(即其右子樹中值最小的節點 11),並刪除此直接中序後繼節點。

b、將此後繼節點的 key、value 值賦給待刪除節點的 key,value值。

實例demo

節點類


package org.vincent.strategy.binarysearch;

class Node {
    int key;
    int value;
    Node leftChild;
    Node rightChild;

    public Node(int key, int value) {
        this.key = key;
        this.value = value;
        this.leftChild = null;
        this.rightChild = null;
    }

    public Node(int key, int value, Node leftChild, Node rightChild) {
        super();
        this.key = key;
        this.value = value;
        this.leftChild = leftChild;
        this.rightChild = rightChild;
    }

    public Node() {

    }

    @Override
    public String toString() {
        return "Node [key=" + this.key + ", value=" + this.value + ", leftChild=" + this.leftChild + ", rightChild=" + this.rightChild + "]";
    }

    public int getKey() {
        return this.key;
    }

    public void setKey(int key) {
        this.key = key;
    }

    public int getValue() {
        return this.value;
    }

    public void setValue(int value) {
        this.value = value;
    }

    public Node getLeftChild() {
        return this.leftChild;
    }

    public void setLeftChild(Node leftChild) {
        this.leftChild = leftChild;
    }

    public Node getRightChild() {
        return this.rightChild;
    }

    public void setRightChild(Node rightChild) {
        this.rightChild = rightChild;
    }

}

二叉排序樹接口方法


package org.vincent.strategy.binarysearch;

public abstract class AbsBinaryTree {
    public abstract Node find(int key);// 查找指定節點

    public abstract boolean update(int key, int value);

    public abstract void insert(int key, int value); // 插入節點

    public abstract boolean delete(int key); // 刪除指定節點

    public abstract Node getDirectPostNode(Node delNode); // 得到待刪除節點的直接後繼節點

    public abstract void preOrder(Node rootNode); // 先序遍歷樹

    public abstract void inOrder(Node rootNode); // 中序遍歷樹

    public abstract void postOrder(Node rootNode); // 後序遍歷樹
}

二叉排序樹具體實現


package org.vincent.strategy.binarysearch;

public class BinaryTree extends AbsBinaryTree {

    private Node root;

    public Node getRoot() {
        return this.root;
    }

    public void setRoot(Node root) {
        this.root = root;
    }

    // 二叉排序樹查找節點
    // 找到和key相等則返回相應節點,否則返回 null。
    @Override
    public Node find(int key) {
        // TODO Auto-generated method stub
        Node currentNode = this.root;
        // currentNode.key和 key不等才需要循環
        while ((currentNode != null) && (currentNode.key != key)) {
            if (key < currentNode.key) {
                currentNode = currentNode.leftChild;
            } else if (key > currentNode.key) {
                currentNode = currentNode.rightChild;
            }
        }
        return currentNode;

    }

    @Override
    public void insert(int key, int value) {
        // TODO Auto-generated method stub
        if (this.root == null) {
            this.root = new Node(key, value);
            return;
        }
        Node currentNode = this.root;
        Node parentNode = this.root;// 指向currentNode節點的父節點
        boolean isLeftChild = true;
        // 尋找插入位置
        while (currentNode != null) {
            parentNode = currentNode;
            if (key < currentNode.key) {
                currentNode = currentNode.leftChild;
                isLeftChild = true;
            } else if (key > currentNode.key) {
                currentNode = currentNode.rightChild;
                isLeftChild = false;
            } else {
                // 插入的節點key和二叉樹中節點key相等無需插入
                // parentNode 和 currentNode兩個引用指向相同Node對象,引用變量相等,只需要更改value
                break;
            }
        }
        // 插入節點
        if (parentNode != currentNode) {
            Node newNode = new Node(key, value);
            if (isLeftChild) {
                parentNode.leftChild = newNode;
            } else {
                parentNode.rightChild = newNode;
            }

        } else {
            // 如果待插入節點和二叉樹中節點一樣;則只要更改值
            currentNode.setValue(value);
        }
    }

    @Override
    public boolean delete(int key) {
        // TODO Auto-generated method stub
        Node currentNode = this.root;// 用來保存待刪除節點
        Node parentNode = this.root;// 用來保存待刪除節點的父親節點
        boolean isLeftChild = true;// 用來保存待刪除節點是父親節點的左孩子還是右孩子
        // 尋找刪除節點並記錄刪除節點的父節點以及他是父節點的左孩子還是右孩子
        while ((currentNode != null) && (currentNode.key != key)) {
            parentNode = currentNode;
            if (key < currentNode.key) {
                currentNode = currentNode.leftChild;
                isLeftChild = true;
            } else {
                currentNode = currentNode.rightChild;
                isLeftChild = false;
            }
        }
        if (currentNode == null) return false;// 空樹
        // 要刪除的節點爲葉子節點,刪除的第一種情況
        if ((currentNode.leftChild == null) && (currentNode.rightChild == null)) {
            if (currentNode == this.root) {
                this.root = null;
            } else if (isLeftChild) {
                parentNode.leftChild = null;
            } else {
                parentNode.rightChild = null;
            }
            // 要刪除的節點只有左孩子 第二種情況
        } else if ((currentNode.rightChild == null) && (currentNode.leftChild != null)) {
            if (currentNode == this.root) {
                this.root = currentNode.leftChild;
            } else if (isLeftChild) {
                parentNode.leftChild = currentNode.leftChild;
            } else {
                parentNode.rightChild = currentNode.leftChild;
            }
            // 要刪除的節點只有右孩子 第三種情況
        } else if ((currentNode.leftChild == null) && (currentNode.rightChild != null)) {
            if (currentNode == this.root) {
                this.root = currentNode.rightChild;
            } else if (isLeftChild) {
                parentNode.leftChild = currentNode.rightChild;
            } else {
                parentNode.rightChild = currentNode.rightChild;
            }
        } // 最後一種情況,待刪除節點既有左子樹又有右子樹
        else {
            // 將待刪除節點的右子樹最小節點賦值給刪除節點的key,value,那麼刪除後新的二叉樹也是二叉排序樹
            // 思路:刪除右子樹中key值最小的節點,並返回,然後用這個節點的值賦值刪除節點的key和value
            // 右子樹中key最小的節點一定不含左子樹,所以刪除這個key最小的節點一定是屬於葉子節點或者只有右子樹的節點
            Node directPostNode = this.getDirectPostNode(currentNode);
            currentNode.key = directPostNode.key;
            currentNode.value = directPostNode.value;
        }

        return true;
    }

    // 獲取到待刪除節點的中序直接後繼節點。將該後繼節點從二叉樹中刪除並返回
    @Override
    public Node getDirectPostNode(Node delNode) {
        // TODO Auto-generated method stub
        // 方法作用爲得到待刪除節點的直接後繼節點

        Node parentNode = delNode;// 用來保存待刪除節點的直接後繼節點的父親節點
        Node direcrPostNode = delNode;// 用來保存待刪除節點的直接後繼節點
        Node currentNode = delNode.rightChild;// 待刪除節點右子樹
        while (currentNode != null) {
            parentNode = direcrPostNode;
            direcrPostNode = currentNode;
            currentNode = currentNode.leftChild;
        }
        if (direcrPostNode != delNode.rightChild) {// 從樹中刪除此直接後繼節點
            parentNode.leftChild = direcrPostNode.rightChild;// 後繼節點的父節點指向後繼節點的右孩子
            direcrPostNode.rightChild = null;// 直接後繼節點右孩子爲空
        }
        return direcrPostNode;// 返回此直接後繼節點
    }

    @Override
    public void preOrder(Node rootNode) {
        // TODO Auto-generated method stub
        if (rootNode != null) {
            System.out.println(rootNode.key + " " + rootNode.value);
            this.preOrder(rootNode.leftChild);
            this.preOrder(rootNode.rightChild);
        }
    }

    @Override
    public void inOrder(Node rootNode) {
        // TODO Auto-generated method stub
        if (rootNode != null) {
            this.inOrder(rootNode.leftChild);
            System.out.println(rootNode.key + " " + rootNode.value);
            this.inOrder(rootNode.rightChild);
        }
    }

    @Override
    public void postOrder(Node rootNode) {
        // TODO Auto-generated method stub
        if (rootNode != null) {
            this.postOrder(rootNode.leftChild);
            this.postOrder(rootNode.rightChild);
            System.out.println(rootNode.key + " " + rootNode.value);
        }
    }

    /**
     * // 基於二叉排序樹查找find查找節點,然後通過Node的setValue將新值賦值過去。
     */
    @Override
    public boolean update(int key, int value) {
        // TODO Auto-generated method stub
        Node node = this.find(key);
        node.setValue(value);
        return true;
    }

}

測試用例

package org.vincent.strategy.binarysearch;

public class BinaryTreeTest {

    public static void main(String[] args) {
        // TODO Auto-generated method stub
        BinaryTree tree = new BinaryTree();
        tree.insert(6, 6);// 插入操作,構造圖一所示的二叉樹
        tree.insert(3, 3);
        tree.insert(14, 14);
        tree.insert(16, 16);
        tree.insert(10, 10);
        tree.insert(9, 9);
        tree.insert(13, 13);
        tree.insert(11, 11);
        tree.insert(12, 12);
        System.out.println("刪除前中序遍歷結果,結果是一個遞增有序序列");
        tree.inOrder(tree.getRoot());// 中序遍歷操作
        tree.update(12, 200);
        System.out.println("更新節點值中序遍歷結果  key=12的值");
        tree.inOrder(tree.getRoot());
        System.out.println("刪除節點10之後遍歷結果");

        tree.delete(10);// 刪除操作
        tree.inOrder(tree.getRoot());
    }

}

參考文獻:

參考博文

Java二叉排序樹

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章