2025年8月28日 星期四

ZeroJudge 解題筆記:d526. Binary Search Tree (BST)

作者:王一哲
日期:2025年8月28日


ZeroJudge 題目連結:d526. Binary Search Tree (BST)

解題想法


這題要自訂二元樹類別,遞迴版比較簡潔。

Python 程式碼


遞迴版,使用時間約為 0.7 s,記憶體約為 4 MB,通過測試。
class Node:  # 自訂節點類別
    def __init__(self, data):
        self.data = data  # 資料
        self.left = None  # 左子樹
        self.right = None  # 右子樹

class BST:  # 自訂二元搜尋樹類別
    def __init__(self):
        self.root = None  # 根節點

    def insert(self, data):  # 插入節點
        if self.root is None:  # 如果沒有根節點
            self.root = Node(data)  # 設定根節點
        else:  # 如果有根節點
            self._insert(data, self.root)  # 遞迴,代入 data 及 self.root

    def _insert(self, data, current):  # 內部使用的插入方法
        if data < current.data:  # 如果要插入的資料小於目前節點的資料
            if current.left is None:  # 如果沒有左子樹
                current.left = Node(data)  # data 放在左子樹
            else:  # 如果有左子樹
                self._insert(data, current.left)  # 遞迴,代入 data 及左子樹
        elif data > current.data:  # 如果要插入的資料大於目前節點的資料
            if current.right is None:  # 如果沒有右子樹
                current.right = Node(data)  # data 放在右子樹
            else:  # 如果有右子樹
                self._insert(data, current.right)  # 遞迴,代入 data 及右子樹
    
    def delete(self, data):  # 刪除資料
        self.root = self._delete(self.root, data)
    
    def _delete(self, current, data):  # 內部使用的刪除資料方法
        if current is None:  # 如果 current 是空的
            return current  # 回傳 current 並離開函式
        # 找要刪除的節點
        if data < current.data:  # 如果要刪除的資料小於目前節點的資料
            current.left = self._delete(current.left, data)  # 遞迴,代入左子樹及 data
        elif data > current.data:  # 如果要刪除的資料大於目前節點的資料
            current.right = self._delete(current.right, data)  # 遞迴,代入右子樹及 data
        else:  # 找到要刪除的節點
            if current.left is None:  # 如果左子樹是空的
                return current.right  # 用右子樹取代 node
            elif current.right is None:  # 如果右子樹是空的
                return current.left  # 用左子樹取代 node
            # 有左、右子樹
            temp = self._findMin(current.right)  # 找右子樹中最小資料的節點
            current.data = temp.data  # current 的資料改成 temp 的資料
            current.right = self._delete(current.right, temp.data)  # 刪除右子樹中最小資料的節點
        return current
    
    def _findMin(self, current):  # 內部使用的找最小值方法
        while current.left is not None:  # 向下找左子樹直到空節點為止
            current = current.left
        return current
    
    def preorder(self):  # 前序走訪
        self._preorder(self.root)
        print()

    def _preorder(self, current):  # 內部使用的前序走訪
        if current:  # 如果 current 不是 None
            print(current.data, end=' ')  # 印出 current.data
            self._preorder(current.left)  # 遞迴,代入左子樹
            self._preorder(current.right)  # 遞迴,代入右子樹

import sys

for line in sys.stdin:
    n = int(line)  # 一行有 n 個數字
    tree = BST()  # 建立 BST 物件
    for data in map(int, input().split()):  # 依序讀取此行的數字
        tree.insert(data)  # 將資料插入 tree 之中
    tree.preorder()  # 前序走訪

非遞迴版,使用時間約為 0.5 s,記憶體約為 4 MB,通過測試。
class Node:  # 自訂節點類別
    def __init__(self, data):
        self.data = data  # 資料
        self.left = None  # 左子樹
        self.right = None  # 右子樹

class BST:  # 自訂二元搜尋樹類別
    def __init__(self):
        self.root = None  # 根節點

    def insert(self, data):  # 插入節點
        if self.root is None:  # 如果沒有根節點
            self.root = Node(data)  # 設定根節點
            return  # 離開函式
        # 如果有根節點
        current = self.root  # 目前走訪的節點,預設為根節點
        parent = None  # 父節點,預設為 None
        while current is not None:  # 往下找直到 current 是 None 為止
            parent = current  # 父節點設定為 current
            if data < current.data:  # 如果要插入的資料小於目前節點的資料
                current = current.left  # 走到左子樹
            elif data > current.data:  # 如果要插入的資料大於目前節點的資料
                current = current.right  # 走到右子樹
            else:  # BST 的節點值不能重複
                print("該值已存在於樹中")
                return  # 離開函式
        # 找到要插入的位置,與父節點的資料比大小
        if data < parent.data:  # data 較小,放在左子樹
            parent.left = Node(data)
        else:  # 反之,放在右子樹
            parent.right = Node(data)

    def preorder(self):  # 前序走訪
        if self.root is None: return  # 如果沒有根節點,離開函式
        st = [self.root]  # 堆疊,先放入根節點
        while st:  # 如果 st 有資料繼續執行
            current = st.pop()  # 讀取並移除堆疊最上方的節點
            print(current.data, end=' ')  # 印出 current.data
            # 先放入右子樹,再放入左子樹,從堆疊取出資料時才會先讀到左子樹
            if current.right:  # 如果有右子樹
                st.append(current.right)  # 右子樹加到 st
            if current.left:  # 如果有左子樹
                st.append(current.left)  # 左子樹加到 st
        print()  # 換行

import sys

for line in sys.stdin:
    n = int(line)  # 一行有 n 個數字
    tree = BST()  # 建立 BST 物件
    for data in map(int, input().split()):  # 依序讀取此行的數字
        tree.insert(data)  # 將資料插入 tree 之中
    tree.preorder()  # 前序走訪


C++ 程式碼


遞迴版,使用時間約為 44 ms,記憶體約為 356 kB,通過測試。
#include <iostream>
using namespace std;

class Node {  // 自訂節點類別
public:
    int data;  // 資料
    Node* left;  // 左子樹
    Node* right;  // 右子樹
    
    Node(int val) : data(val), left(nullptr), right(nullptr) {}  // 建構子
};

class BST {  // 自訂二元搜尋樹類別
private:
    Node* root;  // 根節點
    
    Node* insertHelper(Node* node, int data) {  // 內部使用的插入資料輔助函式
        if (node == nullptr) {  // 如果 node 是空的
            return new Node(data);  // 回傳以 data 為資料建立的新節點
        }
        
        if (data < node->data) {  // 如果要插入的資料小於目前節點的資料
            node->left = insertHelper(node->left, data);  // 遞迴,代入左子樹及 data
        } else if (data > node->data) {  // 如果要插入的資料大於目前節點的資料
            node->right = insertHelper(node->right, data);  // 遞迴,代入右子樹及 data
        } else {  // BST 的值不能重複
            cout << "該值已存在於樹中: " << data << "\n";
        }
        
        return node;  // 回傳 node 並離開函式
    }
    
    Node* findMin(Node* node) {  // 內部使用的找最小資料節點函式
        while(node->left != nullptr) {  // 向下找左子樹直到是空的為止
            node = node->left;
        }
        return node;
    }
    
    Node* deleteHelper(Node* node, int data) {  // 內部使用的刪除資料輔助函式
        if (node == nullptr) return node;  // 如果 node 是空的,回傳 node 並離開函式
        
        if (data < node->data) {  // 如果要刪除的資料小於目前節點的資料
            node->left = deleteHelper(node->left, data);  // 遞迴,代入左子樹及 data
        } else if (data > node->data) {  // 如果要刪除的資料大於目前節點的資料
            node->right = deleteHelper(node->right, data);  // 遞迴,代入右子樹及 data
        } else {  // 找到要刪除的資料
            if (node->left == nullptr) {  // 如果左子樹是空的
                Node* temp = node->right;  // 暫存右子樹
                delete node;  // 刪除 node
                return temp;  // 用原來的右子樹取代被刪除的 node
            } else if (node->right == nullptr) {  // 如果右子樹是空的
                Node* temp = node->left;  // 暫存左子樹
                delete node;  // 刪除 node
                return temp;  // 用原來的左子樹取代被刪除的 node
            }
            // 有兩個子樹的狀況
            Node* temp = findMin(node->right);  // 找左子樹中最小值的節點
            node->data = temp->data;  // node 的資料改成 temp 的資料
            node->right = deleteHelper(node->right, temp->data);  // 刪除右子樹中最小值的節點
        }
        return node;  // 回傳 node 並離開函式
    }
    
    void preorderHelper(Node* node) {  // 內部使用的前序走訪輔助函式
        if (node == nullptr) return;  // 如果 node 是空的,離開函式
        
        cout << node->data << " ";  // 印出 node->data
        preorderHelper(node->left);  // 遞迴,代入左子樹
        preorderHelper(node->right);  // 遞迴,代入右子樹
    }
    
    void clearHelper(Node* node) {  // 內部使用的刪除 BST 輔助函式
        if (node == nullptr) return;  // 如果 node 是空的,離開函式
        
        clearHelper(node->left);  // 遞迴,代入左子樹
        clearHelper(node->right);  // 遞迴,代入右子樹
        delete node;  // 刪除 node
    }
    
public:
    BST() : root(nullptr) {}  // 建構子
    
    ~BST() {  // 解構子
        clearHelper(root);
    }
    
    void insert(int data) {  // 插入資料
        root = insertHelper(root, data);
    }
    
    void preorder() {  // 前序走訪
        preorderHelper(root);
        cout << "\n";
    }
};

int main() {
    ios::sync_with_stdio(0); cin.tie(0);
    int n;  // 一行有 n 個數字
    while(cin >> n) {
        BST tree;  // 建立 BST 物件
        for(int i=0; i<n; i++) {  // 讀取 n 個數字
            int data; cin >> data;
            tree.insert(data);  // 將資料插入 tree 之中
        }
        tree.preorder();  // 前序走訪
    }
    return 0;
}

非遞迴版,使用時間約為 39 ms,記憶體約為 388 kB,通過測試。
#include <iostream>
#include <stack>
using namespace std;

class Node {  // 自訂節點類別
public:
    int data;  // 資料
    Node* left;  // 左子樹
    Node* right;  // 右子樹
    
    Node(int val) : data(val), left(nullptr), right(nullptr) {}  // 建構子
};

class BST {  // 自訂二元搜尋樹類別
private:
    Node* root;  // 根節點
    
public:
    BST() : root(nullptr) {}  // 建構子
    
    
    void insert(int data) {  // 插入資料
        Node* newNode = new Node(data);  // 建立新的節點
        
        if (root == nullptr) {  // 如果根節點是空的
            root = newNode;  // 以新節點作為根節點
            return;  // 離開函式
        }
        
        Node* current = root;  // 目前走訪的節點,預設為根節點
        Node* parent = nullptr;  // 父節點,預設為空的
        
        while(current != nullptr) {  // 找要插入的節點位置
            parent = current;
            if (data < current->data) {  // 如果要插入的資料小於目前節點的資料
                current = current->left;  // 走到左子樹
            } else if (data > current->data) {  // 如果要插入的資料右於目前節點的資料
                current = current->right;  // 走到右子樹
            } else {  // BST 的資料不能重複
                cout << "該值已存在於樹中: " << data << "\n";
                delete newNode; // 釋放記憶體
                return;  // 離開函式
            }
        }
        
        if (data < parent->data) {  // 如果要插入的資料小於父節點的資料
            parent->left = newNode;  // 新節點當作左子樹
        } else {  // 反之,當作右子樹
            parent->right = newNode;
        }
    }
    
    void preorder() {  // 前序走訪
        if (root == nullptr) return;  // 如果根節點是空的,離開函式
        
        stack<Node*> st;  // 待走訪的節點
        st.push(root);  // 先放入根節點
        
        while(!st.empty()) {  // 如果 st 還有節點繼續執行
            Node* current = st.top();  // 取出 st 最上面的節點
            st.pop();  // 移除 st 最上面的節點
            cout << current->data << " ";  // 印出目前節點的值
            // 先放入右子樹,再放入左子樹,取出節點時順序反過來
            if (current->right != nullptr) {
                st.push(current->right);
            }
            if (current->left != nullptr) {
                st.push(current->left);
            }
        }
        cout << "\n";
    }
    
    ~BST() {  // 解構子
        clear(root);
    }

private:
    void clear(Node* node) {  // 釋放記憶體
        if (node == nullptr) return;
        clear(node->left);
        clear(node->right);
        delete node;
    }
};

int main() {
    ios::sync_with_stdio(0); cin.tie(0);
    int n;  // 一行有 n 個數字
    while(cin >> n) {
        BST tree;  // 建立 BST 物件
        for(int i=0; i<n; i++) {  // 讀取 n 個數字
            int data; cin >> data;
            tree.insert(data);  // 將資料插入 tree 之中
        }
        tree.preorder();  // 前序走訪
    }
    return 0;
}


沒有留言:

張貼留言