1. 程式人生 > >基本數據結構 -- 二叉查找樹的插入、刪除、查找和遍歷

基本數據結構 -- 二叉查找樹的插入、刪除、查找和遍歷

統一 -a delet 資料 實現 復雜度 let play 查找樹

一、什麽是二叉查找樹

  二叉查找樹(Binary Search Tree)是一種特殊的二叉樹,對於一個二叉查找樹,樹中的每個結點X,它的左子樹中所有關鍵字的值都小於X的關鍵字值;而它的右子樹中所有關鍵字的值大於X的關鍵字值。這意味著,該樹的所有元素可以使用一種統一的方式進行排序,因此,二叉查找樹又稱為二叉排序樹。下圖即為一個二叉查找樹:

技術分享圖片

二、如何在 BST 中查找一個結點

  二叉查找樹很適合進行查找操作。以上圖中的二叉查找樹為例:如果需要在該 BST 中查找值 10,先從根結點開始進行比較,10小於13,根據 BST 的性質,可以知道如果該 BST 中有結點值為10的話,那麽該結點必然位於根結點的左子樹中;再從結點 5 開始,可知 10 在結點 5 的右子樹;再從結點 8 開始,10 在結點 8 的右子樹,最終找到元素 10。

  用算法來描述,在一個 二叉查找樹 T(T 為二叉樹的根結點)中查找元素 n:

1)若 T 為空,則 n 不在 BST 中;

2)判斷 n 與根結點值(T->val)的關系;

3)如果 n 等於 T->val,則找到了元素 n;

4)如果 n 大於 T->val,則去根結點的右子樹 (T->right) 繼續查找。將 T->right 作為 T,回到第 1 步;

5)如果 n 小於 T->val,則去根結點的左子樹 (T->left ) 繼續查找。將 T->left 作為 T,回到第1步。

  算法實現如下:

typedef int
ElementType; typedef struct BinaryTreeNode_ { ElementType val; struct BinaryTreeNode_ *left; struct BinaryTreeNode_ *right; }BinaryTreeNode; typedef BinaryTreeNode *BiTreeNode; // 查找一個結點 BiTreeNode FindNode(BiTreeNode BiTree,ElementType n) { if (BiTree == NULL) //
如果頭結點為空,表明沒有找到元素 n return NULL; if (BiTree->val < n) // 結點值小於查找元素值,說明元素在結點的右邊 return FindNode(BiTree->right, n); // 遞歸,去右子樹繼續查找 else if (BiTree->val > n) return FindNode(BiTree->left, n); // 遞歸,去左子樹繼續查找 else return BiTree; }

  該查找算法的時間復雜度為 O(log2N)。

三、向 BST 中插入一個結點

  向 BST 中插入一個結點 n 時,若 BST 中已經存在與該結點值相等的結點,則判斷結點已存在,不做任何操作;如果 BST 中不存在該結點,則將該結點將作為一個新的葉子結點插入到 BST 中去(新結點總是 BST 的葉子結點),且需要保證插入後的樹是一個二叉查找樹。

  可以以類似查找算法的方式來遍歷二叉樹,從而找到插入的位置:

  用算法來描述,向一個二叉查找樹 T(T 為二叉樹的根結點)中插入一個結點,節點元素為 n:

1)若 T 為空,則將新結點插入到 T 所在的位置;

2)若 T 不為空,且 T->val 小於 n,則表明應該把 n 插入到 T 的右子樹。將 T->right 作為 T,回到第 1 步;

3)若 T 不為空,且 T->val 大於 n,則表明應該把 n 插入到 T 的左子樹。將 T->left 作為 T,回到第 1 步。

4)若 T->val 等於 n,則直接返回 T。

// 插入一個結點
BiTreeNode InsertNode(BiTreeNode BiTree, ElementType n)
{
    if (BiTree == NULL) {    // 樹為空,或已到達葉子結點
        // 為新結點分配內存
        BiTreeNode newNode = (BiTreeNode)malloc(sizeof(BinaryTreeNode));
        if (newNode == NULL) {
            perror("malloc failed!\n");
            exit(EXIT_FAILURE);
        }
        newNode->val = n;
        newNode->left = NULL;
        newNode->right = NULL;
        BiTree = newNode;
    }
    else if (BiTree->val < n) {    // 插入值大於當前結點的值,則將元素插入到結點的右子樹
        BiTree->right = InsertNode(BiTree->right, n);
    }
    else if (BiTree->val > n) {    // 插入值小於當前結點的值,則將元素插入到結點的左子樹
        BiTree->left = InsertNode(BiTree->left, n);
    }
    
    return BiTree;
    
}

四、遍歷 BST

  在上一篇博客中已經介紹了二叉樹的遍歷方法,主要有三種:先序遍歷、後序遍歷和中序遍歷。為了更好的體現二叉查找樹的特性,我們使用中序遍歷來遍歷它:

// 中序遍歷 BST
void TraverseTree(BiTreeNode BiTree)
{
    if (BiTree != NULL) {
        TraverseTree(BiTree->left);
        cout << BiTree->val << endl;
        TraverseTree(BiTree->right);
    }

}

五、刪除一個結點

  刪除操作比較復雜,分為三種情況:

1)被刪除結點為葉子結點:

  可以直接刪除該結點,如圖:

技術分享圖片

2)被刪除結點有一個左孩子或一個右孩子:

  將孩子結點設為該結點的父結點的孩子後,即可刪除該結點:

技術分享圖片

  如圖,結點 2 是結點 5 的左孩子,他有一個右孩子 4。刪除結點 2 後,其右孩子 4 替代原來的結點 2 成為結點 5 的左孩子。

技術分享圖片

  如圖,結點 16 是結點 18 的左孩子,他有一個左孩子 15。刪除結點 16 後,其左孩子 15 替代原來的結點 16 成為結點 18 的左孩子。

3)被刪除結點有兩個孩子結點:

  這種情況比較復雜,一般的刪除策略是:用被刪除結點的右子樹中的最小結點替代被刪除結點,並遞歸地刪除這個最小數據結點:

技術分享圖片

// 刪除一個結點
BiTreeNode DeleteNode(BiTreeNode BiTree, ElementType n)
{
    BiTreeNode tmpNode = (BiTreeNode)malloc(sizeof(BinaryTreeNode));
    if (tmpNode == NULL) {
        perror("malloc failed!\n");
        exit(EXIT_FAILURE);
    }

    // 先找到要刪除的結點在二叉樹中的位置
    if (BiTree == NULL) {    // 沒有找到元素
        perror("can`t find element\n");
        return NULL;
    }
        
    if (BiTree->val < n) {        // 元素值大於結點元素值,則去結點右子樹尋找
        BiTree->right = DeleteNode(BiTree->right, n);
        return BiTree;
    }
    else if (BiTree->val > n) {    // 元素值小於結點元素值,則去結點左子樹尋找
        BiTree->left = DeleteNode(BiTree->left, n);
        return BiTree;
    }
    // 找到結點
    else {
        // 若該結點有兩個孩子結點
        if (BiTree->left && BiTree->right) {
            tmpNode = FindMin(BiTree->right);    // 尋找右子樹最小結點
            tmpNode->right = DeleteMin(BiTree->right);    // 刪除右子樹的最小結點
            tmpNode->left = BiTree->left;        // 左子樹保持不變
            return tmpNode;
        }
        else {    // 只有1個或沒有孩子結點
            tmpNode = BiTree;
            if (BiTree->right == NULL) {    // 沒有右子樹,則直接返回左子樹
                BiTree = BiTree->left;
                return BiTree;
            }
            if (BiTree->left == NULL) {        // 沒有左子樹,則直接返回右子樹
                BiTree = BiTree->right;
                return BiTree;
            }
        }
    }
}

// 查找最左葉子結點(最小的結點)
BiTreeNode FindMin(BiTreeNode BiTree)
{
    if (BiTree == NULL)
        return NULL;
    if (BiTree->left == NULL)    // 結點沒有左子樹,即為最左葉子結點
        return BiTree;
    else        // 有左子樹,則繼續在左子樹中查找
        return FindMin(BiTree->left);
}

// 刪除最小結點
BiTreeNode DeleteMin(BiTreeNode BiTree)
{
    if (BiTree->left == NULL)
        return BiTree->right;
    else {
        BiTree->left = DeleteMin(BiTree->left);
        return BiTree;
    }

}

參考資料:

《數據結構與算法分析 C 語言描述》

基本數據結構 -- 二叉查找樹的插入、刪除、查找和遍歷