二叉樹(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