1. 程式人生 > >c語言實現二叉樹的插入、查詢、刪除、列印樹

c語言實現二叉樹的插入、查詢、刪除、列印樹

目錄:

二叉樹的關鍵概念:

  • 每個節點是一個自引用結構體,形式如下:
struct TreeNode {
    struct TreeNode *leftPtr;  /* pointer to left subtree */
    int data;                  /* node data */
    struct TreeNode *rightPtr; /* pointer to right subtree */
};
  • 從根部節點開始,每個節點擁有兩個子節點(NULL或者一個節點),稱為左節點與右節點,每個節點的左部分與右部分又分別稱為該節點的左子樹與右子樹。
  • 每個節點的鍵值大於左節點,小於右節點;每個節點的鍵值大於左子樹所有節點的鍵值,小於右子樹所有節點的鍵值。所以二叉樹是按節點鍵值排序的資料結構。
  • 二叉樹的某個節點,如果不是葉節點,則有左子樹或右子樹,是一個更小的樹,因此可以遞迴地處理關於樹的一些問題。

二叉樹的插入

  • 思路:將要插入節點的鍵值與根節點鍵值比較,如果小於根節點鍵值,則插入根節點的左子樹,如果大於根節點的鍵值,則插入根節點的右子樹,插入子樹相當於插入一個更小的樹,因此可以用遞迴方法實現,直到找到沒有子樹的節點,將新節點插到其下面。注意,新節點插入後,最終只會成為葉節點。

函式程式碼如下(測試插入、刪除、列印功能的原始碼在最後面,此處只給出插入函式程式碼):

void insertNode(TreeNodePtr *treePtr, int value)
{

    /* if treePtr is NULL */
    if (*treePtr == NULL) {

        *treePtr = malloc(sizeof(TreeNode));

        if (*treePtr != NULL) {
            (*treePtr)->data = value;
            (*treePtr)->leftPtr = NULL;
            (*treePtr)->rightPtr = NULL;
        }
        else
{ printf("%d not inserted. No memory available.\n", value); } } else { /* insert node in left subtree */ if (value < (*treePtr)->data) { insertNode(&((*treePtr)->leftPtr), value); } else { /* insert node in right subtree */ if (value >(*treePtr)->data) { insertNode(&((*treePtr)->rightPtr), value); } else { printf("dup"); } } } }

二叉樹的查詢

  • 思路:與插入類似,從根節點開始,將查詢的鍵值與根節點鍵值比較,若相等,則返回指向該節點的指標,若查詢的鍵值比它大,則從根節點的右子樹開始查詢,若查詢的鍵值比它小,則從根節點的左子樹開始查詢。可以用遞迴方法實現,類似於插入。這裡我用迭代實現,能用迭代還是用迭代,因為遞迴開銷比較大。

函式程式碼如下:

TreeNode *binaryTreeSereach(TreeNode * const treePtr, int value)
{
    TreeNode *tempPtr = treePtr;

    while (tempPtr != NULL && tempPtr->data != value)
    {
        if (value > tempPtr->data)
            tempPtr = tempPtr->rightPtr;
        else
            tempPtr = tempPtr->leftPtr;
    }

    return tempPtr;
}

二叉樹的刪除

  • 相比於二叉樹的插入和查詢,刪除一個節點要複雜一些,原因是要保證二叉樹的排序性質。二叉樹刪除有如下三種情況:

    1. 刪除節點是葉節點,即沒有子節點,或者說左右子節點都是NULL。這種情況下,只需要把刪除節點的父節點中對應的指標指向NULL即可。然後釋放掉刪除節點的空間。

    2. 刪除節點有一個子節點(左子節點或右子節點),這種情況下,把刪除節點的父節點中對應的指標指向刪除節點的子節點即可。然後釋放掉刪除節點的空間

    3. 刪除節點有兩個子節點,這種情況下,必須要找到一個替代刪除節點的替代節點,並且保證二叉樹的排序性。根據二叉樹的排序性,可知替代節點的鍵值必須最接近刪除節點鍵值。比刪除節點鍵值小的所有鍵值中最大那個,或者是比刪除節點鍵值大的所有鍵值中最小的那個,是符合要求的。這兩個鍵值所在的節點分別在刪除節點的左子樹中最右邊的節點,刪除節點右子樹中最左邊的節點。以從左子樹中找最大鍵值節點為例,演算法如下:

    1. 找到刪除節點以及它的父節點
    2. 在刪除節點的左子樹中,向下向右遍歷,找到替代節點以及它的父節點
    3. 刪除節點的父節點中對應的指標指向替代節點
    4. 替代節點中的右子節點指標指向刪除節點的右子樹
    5. 如果替代節點的父節點不是刪除節點,則將替代節點的左子節點指標指向刪除節點的左子樹,並且替代節點的父節點中對應的指標指向替代節點的左子節點
    6. 釋放刪除節點的空間
      注意:第二步中找到的替代節點,可能會有左子樹,但一定沒有右子樹。第五步要判斷替代節點的父節點不是刪除節點後,才將替代節點的左子節點指標指向刪除節點的左子樹,否則會出現替代節點左子節點指標指向自己的情況,從而丟失替代節點的左子樹。

另外,還有一種實現相同效果的的方法,即將替代節點中的資料賦給刪除節點,然後釋放替代節點的空間。這種方法其實是刪除了替代節點,並沒有真正刪除想要刪除的節點。而且如果節點包括一個鍵值和很多其他的資料,則賦值語句會很多。在最後面的測試過程中,我也給出了這個函式的實現,void deleteNode2(TreeNode **treePtrP, int value)

程式碼如下:

void deleteNode(TreeNode **treePtrP, int value)
{
    TreeNode *deleteNodePtr = *treePtrP;
    TreeNode *parentNodeOfDeletePtr = NULL;
    TreeNode *substituteNodePtr;
    TreeNode *parentNodeOfSubstitutePtr;

    //find deleNode and its parentNode
    while (deleteNodePtr != NULL && value != deleteNodePtr->data)
    {
        parentNodeOfDeletePtr = deleteNodePtr;

        if (deleteNodePtr->data > value)
        {
            deleteNodePtr = deleteNodePtr->leftPtr;
        }
        else
        {
            deleteNodePtr = deleteNodePtr->rightPtr;
        }
    }

    //case that can't find such Node
    if (deleteNodePtr == NULL)
    {
        printf("no such Node, delete fail\n\n");
        return;

    }

    //delete a leafNode
    if (deleteNodePtr->leftPtr == NULL && deleteNodePtr->rightPtr == NULL)
    {
        //delete Node is root
        if (parentNodeOfDeletePtr == NULL)
        {
            *treePtrP = NULL;
        }
        else if (parentNodeOfDeletePtr->leftPtr == deleteNodePtr)
        {
            parentNodeOfDeletePtr->leftPtr = NULL;
        }
        else
        {
            parentNodeOfDeletePtr->rightPtr = NULL;
        }

    }
    //delete a Node which has a left child Node
    else if (deleteNodePtr->leftPtr != NULL && deleteNodePtr->rightPtr == NULL)
    {
        //delete Node is root
        if (parentNodeOfDeletePtr == NULL)
        {
            *treePtrP = deleteNodePtr->leftPtr;
        }
        else if (parentNodeOfDeletePtr->rightPtr == deleteNodePtr)
            parentNodeOfDeletePtr->rightPtr = deleteNodePtr->leftPtr;
        else
            parentNodeOfDeletePtr->leftPtr = deleteNodePtr->leftPtr;

    }

    //delete a Node which has a right child Node
    else if (deleteNodePtr->leftPtr == NULL && deleteNodePtr->rightPtr != NULL)
    {
        //delete Node is root
        if (parentNodeOfDeletePtr == NULL)
        {
            *treePtrP = deleteNodePtr->rightPtr;
        }
        else if (parentNodeOfDeletePtr->rightPtr == deleteNodePtr)
            parentNodeOfDeletePtr->rightPtr = deleteNodePtr->rightPtr;
        else
            parentNodeOfDeletePtr->leftPtr = deleteNodePtr->rightPtr;

    }
    //delete a Node which has a left and a right child Node
    else
    {
        parentNodeOfSubstitutePtr = deleteNodePtr;
        substituteNodePtr = deleteNodePtr->leftPtr;

        //search down and right to find substituteNode and its parentNode
        while (substituteNodePtr->rightPtr != NULL)
        {
            parentNodeOfSubstitutePtr = substituteNodePtr;
            substituteNodePtr = substituteNodePtr->rightPtr;

        }

        //delete Node is root
        if (parentNodeOfDeletePtr == NULL)
        {
            *treePtrP = substituteNodePtr;
        }
        else if (parentNodeOfDeletePtr->leftPtr == deleteNodePtr)
        {
            parentNodeOfDeletePtr->leftPtr = substituteNodePtr;
        }
        else
        {
            parentNodeOfDeletePtr->rightPtr = substituteNodePtr;
        }

        substituteNodePtr->rightPtr = deleteNodePtr->rightPtr;

        if (parentNodeOfSubstitutePtr != deleteNodePtr)
        {
            substituteNodePtr->leftPtr = deleteNodePtr->leftPtr;

            if (parentNodeOfSubstitutePtr->leftPtr == substituteNodePtr)
            {
                parentNodeOfSubstitutePtr->leftPtr = substituteNodePtr->leftPtr;
            }
            else
            {
                parentNodeOfSubstitutePtr->rightPtr = substituteNodePtr->leftPtr;
            }
        }

    }

    free(deleteNodePtr);
}

二叉樹的列印

  • 從根節點開始,先輸出右子樹,再輸出節點鍵值,再輸出左子樹。採用遞迴法
    程式碼如下:
void outputTree(TreeNodePtr treePtr, int spaces)
{
    int loop;

    while (treePtr != NULL) {

        outputTree(treePtr->rightPtr, spaces + 4);

        for (loop = 1; loop <= spaces; loop++) {
            printf(" ");
        } 

        printf("%d\n", treePtr->data);

        outputTree(treePtr->leftPtr, spaces + 4);
        treePtr = NULL;
    } 
} 

測試結果截圖

這裡寫圖片描述

這裡寫圖片描述

測試插入、刪除、列印樹原始碼

#include <stdio.h>
#include <stdlib.h>
#include <time.h>

struct TreeNode {
    struct TreeNode *leftPtr;  /* pointer to left subtree */
    int data;                  /* node data */
    struct TreeNode *rightPtr; /* pointer to right subtree */
};

typedef struct TreeNode TreeNode;

void insertNode(TreeNode **treePtr, int value);
TreeNode * binaryTreeSereach(TreeNode * const treePtr, int value);
void deleteNode(TreeNode **treePtrP, int value);
void outputTree(TreeNode *treePtr, int spaces);
void deleteNode2(TreeNode **treePtrP, int value);

int main(void)
{
    int arr[] = { 45, 83, 28, 97, 71, 40, 18, 77, 99, 92, 72, 69, 44, 32, 19, 11 };
    int i;                      /* loop counter */
    int item;                   /* value to deal with */
    int totalSpaces = 0;        /* spaces preceding output */
    TreeNode *rootPtr = NULL; /* points to the tree root */

    srand(time(NULL)); /* randomize */
    printf("The numbers being placed in the tree are:\n\n");

    for (i = 0; i < sizeof(arr) / sizeof(int); i++) {
        item = arr[i];
        printf("%3d", item);
        insertNode(&rootPtr, item);
    }

    printf("\n\n\nnow the tree is:\n\n");

    if (rootPtr == NULL)
        printf("empty tree\n");
    else
        outputTree(rootPtr, totalSpaces);

    //random delete Nodes, then output the tree
    while (rootPtr != NULL)
    {
        item = rand() % 16;
        printf("\n\nafter delete %d:\n\n", arr[item]);
        deleteNode2(&rootPtr, arr[item]);

        if (rootPtr == NULL)
            printf("empty tree\n");
        else
            outputTree(rootPtr, totalSpaces);

    }

    return 0; 
}

void insertNode(TreeNode **treePtr, int value)
{

    /* if treePtr is NULL */
    if (*treePtr == NULL) {

        *treePtr = malloc(sizeof(TreeNode));

        if (*treePtr != NULL) {
            (*treePtr)->data = value;
            (*treePtr)->leftPtr = NULL;
            (*treePtr)->rightPtr = NULL;
        }
        else {
            printf("%d not inserted. No memory available.\n", value);
        }

    }
    else {

        /* insert node in left subtree */
        if (value < (*treePtr)->data) {
            insertNode(&((*treePtr)->leftPtr), value);
        }
        else {

            /* insert node in right subtree */
            if (value >(*treePtr)->data) {
                insertNode(&((*treePtr)->rightPtr), value);
            }
            else {
                printf("dup");
            }
        }

    }

}

TreeNode *binaryTreeSereach(TreeNode * const treePtr, int value)
{
    TreeNode *tempPtr = treePtr;

    while (tempPtr != NULL && tempPtr->data != value)
    {
        if (value > tempPtr->data)
            tempPtr = tempPtr->rightPtr;
        else
            tempPtr = tempPtr->leftPtr;
    }

    return tempPtr;
}

void deleteNode(TreeNode **treePtrP, int value)
{
    TreeNode *deleteNodePtr = *treePtrP;
    TreeNode *parentNodeOfDeletePtr = NULL;
    TreeNode *substituteNodePtr;
    TreeNode *parentNodeOfSubstitutePtr;

    //find deleNode and its parentNode
    while (deleteNodePtr != NULL && value != deleteNodePtr->data)
    {
        parentNodeOfDeletePtr = deleteNodePtr;

        if (deleteNodePtr->data > value)
        {
            deleteNodePtr = deleteNodePtr->leftPtr;
        }
        else
        {
            deleteNodePtr = deleteNodePtr->rightPtr;
        }
    }

    //case that can't find such Node
    if (deleteNodePtr == NULL)
    {
        printf("no such Node, delete fail\n\n");
        return;

    }

    //delete a leafNode
    if (deleteNodePtr->leftPtr == NULL && deleteNodePtr->rightPtr == NULL)
    {
        //delete Node is root
        if (parentNodeOfDeletePtr == NULL)
        {
            *treePtrP = NULL;
        }
        else if (parentNodeOfDeletePtr->leftPtr == deleteNodePtr)
        {
            parentNodeOfDeletePtr->leftPtr = NULL;
        }
        else
        {
            parentNodeOfDeletePtr->rightPtr = NULL;
        }

    }
    //delete a Node which has a left child Node
    else if (deleteNodePtr->leftPtr != NULL && deleteNodePtr->rightPtr == NULL)
    {
        //delete Node is root
        if (parentNodeOfDeletePtr == NULL)
        {
            *treePtrP = deleteNodePtr->leftPtr;
        }
        else if (parentNodeOfDeletePtr->rightPtr == deleteNodePtr)
            parentNodeOfDeletePtr->rightPtr = deleteNodePtr->leftPtr;
        else
            parentNodeOfDeletePtr->leftPtr = deleteNodePtr->leftPtr;

    }

    //delete a Node which has a right child Node
    else if (deleteNodePtr->leftPtr == NULL && deleteNodePtr->rightPtr != NULL)
    {
        //delete Node is root
        if (parentNodeOfDeletePtr == NULL)
        {
            *treePtrP = deleteNodePtr->rightPtr;
        }
        else if (parentNodeOfDeletePtr->rightPtr == deleteNodePtr)
            parentNodeOfDeletePtr->rightPtr = deleteNodePtr->rightPtr;
        else
            parentNodeOfDeletePtr->leftPtr = deleteNodePtr->rightPtr;

    }
    //delete a Node which has a left and a right child Node
    else
    {
        parentNodeOfSubstitutePtr = deleteNodePtr;
        substituteNodePtr = deleteNodePtr->leftPtr;

        //search down and right to find substituteNode and its parentNode
        while (substituteNodePtr->rightPtr != NULL)
        {
            parentNodeOfSubstitutePtr = substituteNodePtr;
            substituteNodePtr = substituteNodePtr->rightPtr;

        }

        //delete Node is root
        if (parentNodeOfDeletePtr == NULL)
        {
            *treePtrP = substituteNodePtr;
        }
        else if (parentNodeOfDeletePtr->leftPtr == deleteNodePtr)
        {
            parentNodeOfDeletePtr->leftPtr = substituteNodePtr;
        }
        else
        {
            parentNodeOfDeletePtr->rightPtr = substituteNodePtr;
        }

        substituteNodePtr->rightPtr = deleteNodePtr->rightPtr;

        if (parentNodeOfSubstitutePtr != deleteNodePtr)
        {
            substituteNodePtr->leftPtr = deleteNodePtr->leftPtr;

            if (parentNodeOfSubstitutePtr->leftPtr == substituteNodePtr)
            {
                parentNodeOfSubstitutePtr->leftPtr = substituteNodePtr->leftPtr;
            }
            else
            {
                parentNodeOfSubstitutePtr->rightPtr = substituteNodePtr->leftPtr;
            }
        }

    }

    free(deleteNodePtr);
}

void outputTree(TreeNode *treePtr, int spaces)
{
    int loop;

    while (treePtr != NULL) {

        outputTree(treePtr->rightPtr, spaces + 4);

        for (loop = 1; loop <= spaces; loop++) {
            printf(" ");
        }

        printf("%d\n", treePtr->data);

        outputTree(treePtr->leftPtr, spaces + 4);
        treePtr = NULL;
    }
}

void deleteNode2(TreeNode **treePtrP, int value)
{
    TreeNode *deleteNodePtr = *treePtrP;
    TreeNode *parentNodeOfDeletePtr = NULL;
    TreeNode *substituteNodePtr;
    TreeNode *parentNodeOfSubstitutePtr;

    //find deleNode and its parentNode
    while (deleteNodePtr != NULL && value != deleteNodePtr->data)
    {
        parentNodeOfDeletePtr = deleteNodePtr;

        if (deleteNodePtr->data > value)
        {
            deleteNodePtr = deleteNodePtr->leftPtr;
        }
        else
        {
            deleteNodePtr = deleteNodePtr->rightPtr;
        }
    }

    //case that can't find such Node
    if (deleteNodePtr == NULL)
    {
        printf("no such Node, delete fail\n\n");
        return;

    }

    // delete a leafNode
    if (deleteNodePtr->leftPtr == NULL && deleteNodePtr->rightPtr == NULL)
    {
        //delete Node is root
        if (parentNodeOfDeletePtr == NULL)
        {
            *treePtrP = NULL;
        }
        else if (parentNodeOfDeletePtr->leftPtr == deleteNodePtr)
        {
            parentNodeOfDeletePtr->leftPtr = NULL;
        }
        else
        {
            parentNodeOfDeletePtr->rightPtr = NULL;
        }

    }
    //delete a Node which has a left child Node
    else if (deleteNodePtr->leftPtr != NULL && deleteNodePtr->rightPtr == NULL)
    {
        //delete Node is root
        if (parentNodeOfDeletePtr == NULL)
        {
            *treePtrP = deleteNodePtr->leftPtr;
        }
        else if (parentNodeOfDeletePtr->rightPtr == deleteNodePtr)
            parentNodeOfDeletePtr->rightPtr = deleteNodePtr->leftPtr;
        else
            parentNodeOfDeletePtr->leftPtr = deleteNodePtr->leftPtr;
    }

    //delete a Node which has a right child Node
    else if (deleteNodePtr->leftPtr == NULL && deleteNodePtr->rightPtr != NULL)
    {
        //delete Node is root
        if (parentNodeOfDeletePtr == NULL)
        {
            *treePtrP = deleteNodePtr->rightPtr;
        }
        else if (parentNodeOfDeletePtr->rightPtr == deleteNodePtr)
            parentNodeOfDeletePtr->rightPtr = deleteNodePtr->rightPtr;
        else
            parentNodeOfDeletePtr->leftPtr = deleteNodePtr->rightPtr;
    }
    //delete a Node which has a left and a right child Node
    else
    {
        //find substituteNode and its parentNode
        parentNodeOfSubstitutePtr = deleteNodePtr;
        substituteNodePtr = deleteNodePtr->leftPtr;

        //search down and right
        while (substituteNodePtr->rightPtr != NULL)
        {
            parentNodeOfSubstitutePtr = substituteNodePtr;
            substituteNodePtr = substituteNodePtr->rightPtr;

        }

        if (parentNodeOfSubstitutePtr->leftPtr == substituteNodePtr)
        {
            parentNodeOfSubstitutePtr->leftPtr = substituteNodePtr->leftPtr;
        }
        else
        {
            parentNodeOfSubstitutePtr->rightPtr = substituteNodePtr->leftPtr;
        }

        deleteNodePtr->data = substituteNodePtr->data;
        deleteNodePtr = substituteNodePtr;
    }

    free(deleteNodePtr);
}