1. 程式人生 > >(二叉樹)二叉搜尋樹的查詢、插入和刪除

(二叉樹)二叉搜尋樹的查詢、插入和刪除

1.二叉搜尋樹簡介

二叉搜尋樹或者是一棵空樹,或者是具有下列性質的二叉樹: 若它的左子樹不空,則左子樹上所有結點的值均小於它的根結點的值; 若它的右子樹不空,則右子樹上所有結點的值均大於它的根結點的值; 它的左、右子樹也分別為二叉搜尋樹。二叉搜尋樹的特點之一即是其中序遍歷為升序。

2.二叉搜尋樹的查詢

根據二叉搜尋樹的性質,每個結點的值都是大於其左子結點值(左子結點非空)並且小於其右子結點值(右子結點非空),因此可以考慮二分查詢的方法:比較目標值與當前結點值,如果目標值大於當前結點值,那麼說明目標值不可能存在於當前結點的左子樹中,應當再從其右子樹中去查詢;如果目標值小於當前結點值,那麼說明目標值不可能存在於當前結點的右子樹中,應當再從其左子樹中去查詢;如果目標值等於當前結點值,就返回當前結點,因此就不難寫出程式碼了:

TreeNode* searchBST(TreeNode* root, int val) {
        if(!root)return NULL;   //如果到了空結點依然沒找到,說明不存在
        
        if(root->val==val)return root;   //目標值等於當前結點值則返回當前結點
        if(root->val>val)    //目標值小於當前結點值,就從左子樹中去查詢
            return searchBST(root->left,val);
        
        return searchBST(root->right,val);   //目標值大於當前結點值,就從右子樹中去查詢

3.二叉搜尋樹的插入

要想將結點插入到二叉搜尋樹中,那麼首先就需要找到插入的位置,那插入的位置怎麼找呢?實際上,將結點插入到二叉搜尋樹中的方法並不唯一,舉個例子:
向下面二叉搜尋樹中插入結點2:
在這裡插入圖片描述
插入的結果並不唯一:
在這裡插入圖片描述
在這裡插入圖片描述
當然不止這兩種插入方式,這裡介紹一種比較簡單的方法,具體的方法如下:如二叉查詢,比較待插入結點值(目標值)與當前結點值的大小,如果目標值大於當前結點值,那麼說明應當將目標結點插入到右子樹中;如果目標值小於當前結點值,那麼說明應當將目標結點插入到左子樹中,如果搜尋到空結點,說明目標結點就應該插入這個空結點的位置了,這種方法實現結果就如上面第二種插入方式,程式碼如下:

TreeNode* insertIntoBST(TreeNode* root, int val) {
        
        if(!root)    //如果當前結點值為空,那麼說明應當將目標結點插入到該空結點位置,就直接新建
        {
            TreeNode* newNode=new TreeNode(val);
            return newNode;
        }
        
        if(root->val==val)return root;   //如果目標結點值與當前結點值相同,就直接返回當前結點
        
        if(root->val<val)  //如果目標結點值大於當前結點值,就在右子樹中進行查詢
            root->right=insertIntoBST(root->right,val);
        
        else root->left=insertIntoBST(root->left,val);//如果目標結點值小於當前結點值,就在左子樹中進行查詢
        
        return root;   //返回結點
    }

4.二叉搜尋樹的刪除

二叉搜尋樹的刪除是比較麻煩的,刪除結點還是需要先找到待刪除結點的位置,找到待刪除結點實際上就是二叉搜尋樹的查找了,這裡就不多說了,主要說一下找到待刪除結點後該怎麼操作。這裡一共有以下幾種情況:
①待刪除結點沒有子結點;
②待刪除結點左子樹為空,右子樹非空;
③待刪除結點右子樹為空,左子樹非空;
④待刪除結點左右子樹皆非空。
第①種情況比較簡單,直接返回空結點即可,現在來說下其他情況:
對於第②種情況,舉個例子:刪除下面二叉搜尋樹中的1
在這裡插入圖片描述
那麼刪除後的結果就應該將2結點移到1結點,如下:
在這裡插入圖片描述
由此也可總結出第②種情況的刪除方法:從當前結點的右子結點開始,一直遍歷左子結點,來在當前結點的右子樹中找到最小值結點,然後將最小值賦值給當前結點值,再刪除最小值結點。

需要注意的是,最小值結點可能是葉子結點,也可能還有右子樹,如果是葉子節點,那麼直接讓該最小值結點的雙親結點的左子樹指標指向NULL即可;

如果最小值結點還有右子樹的話,如在上圖中繼續刪除3結點,就還需要再處理3結點的右子樹,那麼就只需要將最小值結點的右子樹接在最小值結點雙親結點的左子樹上即可,換句話說,就是將圖中的結點4接在結點3的雙親結點5的左子樹上。

除此之外,還有一種特殊情況,即是當前結點的右子結點就是最小值結點了,那麼說明其右子結點的左子樹必定為空了,此時只需將其右子結點的值賦值給當前結點,並將其右子結點的右子樹接到當前結點的右子樹上即可。

實現程式碼如下:

                //在右子樹中找到最小結點值來替換當前根結點值
                TreeNode* rightMin=root->right;
                TreeNode* lastMin=root;   //儲存雙親結點
                while(rightMin->left)
                {
                    lastMin=rightMin;   //儲存上一個結點
                    rightMin=rightMin->left;  //最小結點值必定是在根結點右子結點的左子樹中找
                }
                
                root->val=rightMin->val;   //將找到的最小結點值賦值給根結點
                
                if(lastMin==root)lastMin->right=rightMin->right;  //如果根結點的右子結點就是右子樹中最小的了,即右子結點
                                                          的左子結點為空,就直接將右子結點的右子樹接到根結點右子樹上即可
                else lastMin->left=rightMin->right;   //否則就將最小值結點的右子樹接到最小結點的上一個結點的左子樹上

第③種情況,左子樹非空,右子樹為空,刪除的方法與上述類似,就不多說了;
第④種情況,對於左右子樹均非空的情況,其實就既可以從左子樹中找到最大結點來取代當前結點,也可以從右子樹中找到最小結點來取代當前結點,兩種方法都是可以的。

綜合以上,程式碼如下:

TreeNode* deleteNode(TreeNode* root, int key) {
        
        if(!root)return NULL;
        
        if(root->val==key)   //根結點即是要刪除的
        {
            if(!root->left&&!root->right)//左右皆空
                return NULL;
            
            else if(!root->left&&root->right)   //左子樹空,右子樹非空
            {
                //在右子樹中找到最小結點值來替換當前根結點值
                TreeNode* rightMin=root->right;
                TreeNode* lastMin=root;
                while(rightMin->left)
                {
                    lastMin=rightMin;   //儲存上一個結點
                    rightMin=rightMin->left;  //最小結點值必定是在根結點右子結點的左子樹中找
                }
                
                root->val=rightMin->val;   //將找到的最小結點值賦值給根結點
                
                if(lastMin==root)lastMin->right=rightMin->right;  //如果根結點的右子結點就是右子樹中最小的了,即右子結點的左子結點為空,就直接將右子結點的右子樹接到根結點右子樹上即可
                else lastMin->left=rightMin->right;   //否則就將最小值結點的右子樹接到最小結點的上一個結點的左子樹上
                
            }
            else    //左子樹非空,右子樹空  或者 左右子樹均非空   均非空的情況下即可以在左子樹中找最大的也可以在右子樹中找最小的
            {
                //在左子樹中找到最大結點值來替換當前根結點值
                TreeNode* leftMax=root->left;
                TreeNode* lastMax=root;
                while(leftMax->right)
                {
                    lastMax=leftMax;   //儲存上一個結點
                    leftMax=leftMax->right;  //最大結點值必定是在根結點左子結點的右子樹中找
                }
                
                root->val=leftMax->val;   //將找到的最小結點值賦值給根結點
                
                if(lastMax==root)lastMax->left=leftMax->left;   //如果根結點的左子結點就是左子樹中最大的了,即左子結點的右子結點為空,就直接將左子結點的左子樹接到根結點左子樹上即可
                else lastMax->right=leftMax->left;  //否則就將最大值結點的左子樹接到最大結點的上一個結點的右子樹上
            }
        }
        else if(root->val>key)
            root->left=deleteNode(root->left,key);   //如果當前結點值大於目標值,就從左子樹中刪除目標值
        else root->right=deleteNode(root->right,key);   //如果當前結點值小於目標值,就從右子樹中刪除目標值
        
        
        return root;
        
    }