1. 程式人生 > >二叉查詢樹的查詢、插入、刪除、釋放等基本操作的實現(C語言)

二叉查詢樹的查詢、插入、刪除、釋放等基本操作的實現(C語言)

二叉查詢樹是一種特殊性質的二叉樹,該樹中的任何一個節點,它的左子樹(若存在)的元素值小於節點的元素值,右子樹(若存在)的元素值大於節點的元素值。

實現了二叉樹查詢樹的實現以及基本操作,包括查詢、插入、刪除、初始化、釋放等。

原始碼下載地址:http://download.csdn.net/detail/mcu_tian/9548788

二叉查詢樹節點的結構定義

相比二叉樹,二叉查詢樹的元素結構定義稍微有點改動,即在元素結構體中添加了frequency成員,用來記錄重複的元素。

當為新的元素的時候,frequency成員為1,每當有重複的成員函式插入到樹種的時候,frequency加1。具體如下:

typedef char TreeElementType;
struct TreeNode
{
    TreeElementType element;
    unsigned int frequency;//當有重複的元素的時候的計數
    struct TreeNode *right;
    struct TreeNode *left;
};
typedef struct TreeNode *nodePtr;
typedef struct TreeNode *tree;
typedef struct TreeNode node;

二叉查詢樹MakeEmpty操作

該操作主要用於樹的初始化,即將一棵樹初始化為一顆空樹,遞迴釋放樹中的節點元素。

其實現如下;

/*以遞迴的方式傳遞進行樹節點的釋放*/
tree MakeEmptyTree(tree root)
{
    if(root != NULL)
    {
        MakeEmptyTree(root->left);
        MakeEmptyTree(root->right);
        free(root);
        root = NULL;
    }
    return root;
}

二叉查詢樹的插入操作SearchTreeInsert

進行插入操作的基本思路為

1:遞迴查詢插入元素的樹中位置,申請節點,插入到樹中

2:根據二叉查詢樹的性質,當插入的元素大於節點元素的時候,向右子樹確定元素插入位置,當小於某節點元素的時候,向左子樹確定元素插入位置

3:當樹中有與插入元素相等的節點,則將該節點結構中的frequency成員加1

其具體實現如下:

void SearchTreeInsert(TreeElementType x,tree *root)
{
    if((*root)==NULL)
    {
        (*root) =  (nodePtr) malloc(sizeof(node));

        if((*root) == NULL)
        {
            printf("can't malloc ,insert operation failed");
        }
        (*root)->frequency = 1;
        (*root)->element = x;
        (*root)->left = NULL;
        (*root)->right = NULL;
        return;
    }
    if(x > ((*root)->element))
    {
        SearchTreeInsert(x,&(*root)->right);
        return;
    }
    if(x < ((*root)->element))
    {
        SearchTreeInsert(x,&(*root)->left);
        return;
    }
    if(x == ((*root)->element))
    {
        ++((*root)->frequency);
    }
}

二叉查詢樹的查詢操作Find、FindMin、Findmax

二叉查詢樹的基本查詢操作有三種

nodePtr Find(TreeElementType x,tree root)函式的功能為查詢root樹中是否有x元素,若有則返回元素節點結構的指標,若沒有則返回NULL

nodePtr FindMax(tree root)函式的功能返回root查詢樹中的最大元的指標,若root為空,則返回NULL。

nodePtr FindMin(tree root)函式的功能返回root查詢樹中的最小元的指標,若root為空,則返回NULL。

以上三種函式的實現可以通過遞迴和迭代兩種方法。

Find

在本次的實現中Find操作的實現使用的是遞迴實現,程式碼如下:

nodePtr Find(TreeElementType x,tree root)
{
    if(root != NULL)
    {
        if(root->element < x)
        {
            return Find(x,root->right);
        }
        if(root->element > x)
        {
            return Find(x,root->left);
        }
    }
    return root;
}
FindMax和FindMin

FindMax可以用遞迴,也可以用迭代

下面通過迭代的方式實現進行最大值的查詢

遞迴的查詢的思路如下:

nodeptr FindMax(tree root)

{ if(root == NULL)

    { return NULL;}

    if(root->right != NULL)

    { return FindMax(root);}

    return root;

}

FindMin遞迴實現同上

其迭代實現的原始碼如下

FindMax

nodePtr FindMax(tree root)
{
    if(root == NULL)
    {
        return NULL;
    }
    while((root->right)!= NULL)
    {
        root = root->right;
    }
    return root;
}
FindMin
nodePtr FindMin(tree root)
{
    if(root == NULL)
    {
        return NULL;
    }
    while((root->left) != NULL)
    {
        root = root->left;
    }
    return root;
}
二叉查詢樹的刪除操作delete

二叉查詢樹的刪除操作是二叉查詢樹中最難的部分

刪除節點target有如下四種情況

1;刪除節點target的頻率大於1

2:刪除節點target為葉子節點

3:刪除節點target只有左孩子或者右孩子

4:刪除節點target只有既有右孩子也有左孩子

刪除節點target的頻率大於1在這種情況下,只需要將target結構體中的frequency成員的值減1

if(targetPtr->frequency > 1)
    {
        --targetPtr->frequency;
        return;
    }

刪除節點target為葉子節點

找到target節點的父節點,並刪除target節點

 if((targetPtr->left == NULL)&&(targetPtr->right == NULL))
    {
        if((*root) == targetPtr)
        {
            free(targetPtr);
            (*root) = NULL;
            return;
        }
        tmpPtr = *root;   //找到父親節點
        while(tmpPtr != NULL)
        {
            if(x < tmpPtr->element)
            {
                if(tmpPtr->left == targetPtr) break;
                    else tmpPtr = tmpPtr->left;
            }
            if(x > tmpPtr->element)
            {
                if(tmpPtr->right == targetPtr) break;
                    else tmpPtr = tmpPtr->right;
            }
        }
        free(targetPtr);
        tmpPtr->left = NULL;
        tmpPtr->right = NULL;
        return;
    }

刪除節點target只有左孩子或者右孩子

只有一個孩子節點的時候,只需要將孩子替換掉被刪除的節點即可

該方法中,具體實現為將目標節點target的孩子節點賦值到target中,然後刪除孩子節點

即 target = leftchild or target = rightchild   then delete righchild or leftchild

其實現程式碼如下

 if((targetPtr->left != NULL)&&(targetPtr->right == NULL))
    {
        tmpPtr = targetPtr->left;
        
        targetPtr->element = tmpPtr->element;
        targetPtr->left = tmpPtr->left;
        targetPtr->right = tmpPtr->right;
        targetPtr->frequency = tmpPtr->frequency;

        free(tmpPtr);
        return;
    }

    if((targetPtr->left == NULL)&&(targetPtr->right != NULL))
    {
        tmpPtr = targetPtr->right;

        targetPtr->element = tmpPtr->element;
        targetPtr->left = tmpPtr->left;
        targetPtr->right = tmpPtr->right;
        targetPtr->frequency = tmpPtr->frequency;

        free(tmpPtr);
        return;
    }

刪除節點target只有既有右孩子也有左孩子

當刪除節點有左右孩子的時候,用右子樹的最小值代替刪除的節點(也可以用左子樹的最大值代替),本次實現中,使用的是右子樹的最小值。

在找到最小值替換之後,就需要將最初的最小值的節點刪除,由於右子樹中的最小值只可能有右孩子,不可能有左孩子

當刪除最小節點的時候有如下兩種情況

1:最小值節點有右孩子節點,跟刪除節點target只有左孩子或者右孩子的思路類似,只需要將右孩子替換掉該節點。

2:最小值節點為葉子節點,找到該葉子節點的父節點,刪除該節點,並將父節點得右孩子指標設為NULL。

具體程式碼實現如下:

if((targetPtr->left != NULL)&&(targetPtr->right != NULL))
    {
        minPtr = FindMin(targetPtr->right); //最小值肯定沒有左子樹

        targetPtr->element = minPtr->element;

        if((minPtr->right == NULL))
        {
            tmpPtr = targetPtr;
            if(tmpPtr->right == minPtr) //最小值為左子樹的第一個節點
            {
                free(minPtr);
                tmpPtr->left = NULL;
                tmpPtr->right = NULL;
                return;
            }
            tmpPtr = tmpPtr->right;
            while(tmpPtr != NULL)
            {
                if(tmpPtr->left == minPtr)
                {
                    free(minPtr);
                    tmpPtr->left = NULL;
                    return;
                }
                tmpPtr = tmpPtr->left;
            }
        }
刪除還有其他的實現方法,如懶惰刪除。刪除操作不多的情況下,可以使用懶惰刪除:當一個元素要被刪除的時候,仍舊留在樹中,只是做了個被刪除的記號,同時頻率的數減1
測試:

在原始碼的main函式中

執行程式截圖如下


除了delete和makeEmpty之外,對於所有的操作都是花費O(logN)的時間,因為我們用的常數時間在書中降低一層,對樹的操作大概減少一半左右

二叉樹的平均深度是O(log N),但是當反覆進行刪除和插入操作時候,有可能會導致樹不平衡,比如左右子樹之間的深度有著很大的差。

這種情況就會導致之前樹的基本操作消耗,和深度的優勢就會大大的折扣,這種情況顯然不是樂於見到的。

對於上面,可以在進行刪除操作的時候,我們隨機選擇用右子樹的最小元素和左子樹的最大元素來替代被刪除的元素以緩解這種問題,但是不排除極端的問題。

還有一種方法,即使用平衡二叉樹,即AVL樹。

參考資料:資料結構與演算法分析:C語言描述(原書第2版)