1. 程式人生 > >二叉樹(BST樹)內結點的刪除(3種情況全解)

二叉樹(BST樹)內結點的刪除(3種情況全解)

現在有一棵二叉樹查詢樹如下:
這裡寫圖片描述
如果我們需要刪除一個結點,而且在刪除之後,依然滿足二叉查詢樹的資料排序策略。此時刪除操作可分為一下三種情況。如下

情況1結點沒有左子樹
如圖:一棵沒有左子樹的二叉樹
這裡寫圖片描述
如果在此情況下刪除結點,按照結點的位置又可以分為三種處理方式。如下

  • 刪除根結點:刪除根結點也就是刪除結點 5,此時只需將根結點指標指向其右子樹結點。如下圖
    這裡寫圖片描述

  • 刪除葉子結點:如果刪除的結點是葉子結點也就是結點9,此時只需將父節點指向NULL,如下圖
    這裡寫圖片描述

  • 三處中間結點:如果刪除的結點是中間結點也就是結點6,此時只要將父節點指向右子結點即可。如下圖
    這裡寫圖片描述

情況2:結點沒有右子樹


如圖:一棵沒有右子樹的二叉樹
這裡寫圖片描述
如果在此情況下刪除結點,按照結點的位置也可以分為三種處理方式。它和上面將到的沒有左子樹的三種情況一樣只是左右指標的差異。這裡不做過多的贅述。大家自己理解一下。

情況3:結點具有左子樹也有右子樹
如圖:如果二叉樹是這種情況,其處理不會因為結點的位置不同而不同,但是處理過程就比較複雜。
這裡寫圖片描述
觀察上述二叉樹,如果需要刪除根結點5,我們若能找到一個結點也就是結點3和7之間,然後將它取代根節點的位置。這樣整棵二叉樹並不需要太對的搬動就可以完成結點刪除的操作。例如:結點4正符合上面的要求,等到刪除結點5 後其結構如圖
這裡寫圖片描述
我們可以發現真正刪除的結點是原來的結點4,然後將原來根結點的內容5替換為4,就完成了刪除的操作,問題是如何找到符合條件的結點4,其實我們觀察二叉查詢樹的特性:
其特性如下:

  • 每一個結點的值都不同,也就是整棵樹中的每一個結點都擁有不同的值
  • 每一個結點的資料大於左子樹結點(如果有的話)的資料,但是小於右子樹的結點(如果有的話)的資料。
  • 左右兩部分的子樹,也是一課二叉查詢樹。

如果想要刪除結點後二叉樹依然是一棵二叉查詢樹,可以發現符合這樣要求的結點只有兩個,那就是4和6.它們是從根結點5的左結點3一直從右子樹走到葉子結點和右結點7一直往左子樹走一直走到葉子結點。

現在我們使用從左子結點開始尋找替換的結點,如果刪除的結點是5,就從結點5的左子結點3開始往右子樹找,直到達到葉子結點,找到的是結點4。接著刪除此結點,其方法和情況1,2相同,最後取代原結點5成為4。

如果刪除的是結點3,此時左子結點2並沒有右子結點,所以符合條件的就是結點2,刪除的操作就是成為刪除一個沒有右子樹的結點2,然後將原結點3的值取代成為2.其完整的操作如下圖
這裡寫圖片描述
至於從右子結點開始尋找和替換刪除結點值的方法,和從左邊結點開始尋找類似…….

好,直接看程式碼

/*********************************************************
-  Copyright (C): 2016
-  File name    : deletetree.c
-  Author       : - Zhaoxinan -
-  Date         : 2016年08月05日 星期五 16時15分45秒
-  Description  : 使用遞迴建立一棵二叉樹,然後輸入一個結點值後
                  使用二叉查詢方法尋找節點,如果找到,就呼叫刪除
                  函式將節點刪除,最後輸出刪除後的結果
*  *******************************************************/
#include <stdio.h>
#include <stdlib.h>

struct tree                    //樹結構的宣告
{
    int data;                  //結點資料
    struct tree *left;         //指向左子樹的指標
    struct tree *right;        //指向右子樹的指標
};

typedef struct tree treenode;  //樹的結構新型別
typedef treenode *btree;       //宣告樹結點指標型別

/*
 遞迴建立二叉樹
 */
btree createtree(int *data, int pos)
{
    btree newnode;

    //遞迴終止條件
    if (data[pos] == 0 || pos > 15)
    {
        return NULL;
    }
    else
    {
        //給新節點分配記憶體
        newnode = (btree)malloc(sizeof(treenode));
        //建立新節點內容
        newnode->data = data[pos];
        //建立左子樹的遞迴呼叫
        newnode->left = createtree(data, 2*pos);
        //建立右子樹的遞迴呼叫
        newnode->right = createtree(data, 2*pos+1);
        return newnode;
    }
}

/* 
 二叉樹的查詢
 */
btree btreefind(btree ptr, int value, int *pos)
{
    btree backfather;

    backfather = ptr;          //設定父節點指標初值
    *pos = 0;                  //設定位置初值

    while (ptr != NULL)
    {
        if (ptr->data == value)
        {
            return backfather;  //找到了返回父節點
        }
        else
        {
            backfather = ptr;
            if (ptr->data > value)
            {
                ptr = ptr->left;    //左子樹
                *pos = -1;
            }
            else
            {
                ptr = ptr->right;   //右子樹
                *pos = 1;
            }
        }
    }
    return NULL;
}

/*
 二叉樹節點的刪除
 */
btree deletenode (btree root, int value)
{
    btree backfather;         //父結點指標
    btree ptr;                //刪除結點指標
    btree next;               //子結點指標
    int pos;                  //刪除位置

    backfather = btreefind(root, value, &pos);
    if (backfather == NULL)  //沒有找到
    {
        return root;
    }
    //刪除位置
    switch (pos)
    {
        case -1:
        {
            //左子節點
            ptr = backfather->left;
            break;
        }
        case 1:
        {
            //右子節點
            ptr = backfather->right;
            break;
        }
        case 0:
        {
            //跟節點
            ptr = backfather;
            break;
        } 
    }
    /* 第一種情況,沒有左子樹 */
    if (ptr->left == NULL)
    {
        if (backfather != ptr) //判斷是否是根節點
        {
            //不是根節點,那麼刪除操作為父節點的右子節點等於指向目前節點的右子節點
            //就是跳過當前節點
            backfather->right = ptr->right;
        }
        else
        {
            //是根節點,那麼根節點直接指向右節點
            root = root->right;
        }
        free(ptr);
        ptr = NULL;
        return root;
    }
    /* 第二種情況,沒有右子樹 */
    if (ptr->right == NULL)
    {
        if (backfather != ptr) //判斷是否是根節點
        {
            //不是根節點,那麼刪除操作為父節點的右子節點等於指向目前節點的右子節點
            //就是跳過當前節點
            backfather->left = ptr->left;
        }
        else
        {
            //是根節點,那麼根節點直接指向右節點
            root = root->left;
        }
        free(ptr);
        ptr = NULL;
        return root;
    }
    /* 第三種情況,有左子樹也有右子樹 */
    backfather = ptr;  //父節點指向當前節點
    next = ptr->left;   //設定子節點
    while (next->right != NULL)
    {
        backfather = next;
        next = next->right;
    }
    ptr->data = next->data;  //替換資料

    if (backfather->left == next) 
    {
        backfather->left = next->left;
    }
    else
    {
        backfather->right = next->right;
    }
    free(next);
    return root;
}

/* 
 二叉樹的前序遍歷輸出
 */
void preolder(btree ptr)
{
    if (ptr)
    {
        printf("%2d", ptr->data);
        preolder(ptr->left);
        preolder(ptr->right);
    }
}

/*
 二叉樹的中序遍歷輸出
 */
void inolder(btree ptr)
{
    if (ptr)
    {
        inolder(ptr->left);
        printf("%2d", ptr->data);
        inolder(ptr->right);
    }
}

/*
 二叉樹的後序遍歷輸出
 */
void postolder(btree ptr)
{
    if (ptr)
    {
        postolder(ptr->left);
        postolder(ptr->right);
        printf("%2d", ptr->data);
    }
}

int main()
{
    btree root = NULL;
    int value;

    int data[16] = {00,5,4,6,2,0,0,8,1,3,0,0,0,0,7,9};
    //遞迴建立二叉樹
    root = createbtree(data, 1);
    printf("\n-------前序遍歷-------\n");
    preolder(root);
    printf("\n-------中序遍歷-------\n");
    inolder(root);
    printf("\n-------後序遍歷-------\n");
    postolder(root);

    printf("\n請輸入想要刪除的節點(1-9):");
    scanf("%d", &value);

    root = deletenode(root, value);
    printf("刪除後樹的節點內容為\n");
    printf("\n-------前序遍歷-------\n");
    preolder(root);
    printf("\n-------中序遍歷-------\n");
    inolder(root);
    printf("\n-------後序遍歷-------\n");
    postolder(root);
    printf("\n");
    return 0;
}

原始二叉樹
這裡寫圖片描述

刪除結點2
這裡寫圖片描述
這裡寫圖片描述

刪除節點5
這裡寫圖片描述
這裡寫圖片描述

刪除結點6
這裡寫圖片描述
這裡寫圖片描述