当前位置:  开发笔记 > 编程语言 > 正文

如何在C中的二叉搜索树中正确插入/删除?

如何解决《如何在C中的二叉搜索树中正确插入/删除?》经验,为你挑选了1个好方法。

我有点不得不把我以前的C问题暂停,因为现在这个问题更重要了......

我已经在二叉搜索树上编写了插入和删除函数,但删除函数不完整.我需要帮助的一些事情......

1)我的插入功能是好还是可以以某种方式改进?

2)我的删除功能没有删除具有左右子节点的节点.我在过去的几个小时里搜索过很多但是找不到合适的方法.

2.a)我应该如何删除具有2个子节点的节点?

2.b)与第一个问题一样,删除功能是好还是可以改进?这个我知道它可以因为我在那些ifs中重复了很多代码,但是我不知道如何改进它,我也需要帮助.

typedef struct sClientProfile *ClientProfile;
typedef struct sClientTree *ClientTree;

typedef struct sClientProfile {
    char *clientName;
    int clientAge;
    int clientNIF;
} nClientProfile;

typedef struct sClientTree {
    ClientProfile clientProfile;
    char *clientName;

    ClientTree leftTree;
    ClientTree rightTree;
} nClientTree;

void addClientToTree(ClientTree *cTree, ClientProfile cProfile) {
    if(!*cTree) {
        ClientTree new = (ClientTree)malloc(sizeof(nClientTree));

        if(!new) {
            perror("malloc");
        }

        new->clientName = strdup(cProfile->clientName);
        new->clientProfile = cProfile;

        new->leftTree = NULL;
        new->rightTree = NULL;

        *cTree = new;
    } else {
        if(strcmp((*cTree)->clientName, cProfile->clientName) > 0) {
            addClientToTree(&(*cTree)->leftTree, cProfile);
        } else {
            addClientToTree(&(*cTree)->rightTree, cProfile);
        }
    }
}

void deleteClientFromTree(ClientTree *cTree, char *cName) {
    if(!cTree) return;

    int nCompare = strcmp((*cTree)->clientName, cName);

    if(nCompare > 0) {
        deleteClientFromTree(&(*cTree)->leftTree, cName);
    } else if(nCompare < 0) {
        deleteClientFromTree(&(*cTree)->rightTree, cName);
    } else {
        if(!(*cTree)->leftTree && !(*cTree)->rightTree) {
            ClientTree cliPtr = *cTree;

            free(cliPtr->clientProfile);
            free(cliPtr);

            cliPtr->clientProfile = NULL;
            cliPtr = NULL;

            *cTree = NULL;
        } else if(!(*cTree)->leftTree) {
            ClientTree cliPtr = *cTree;

            free(cliPtr->clientProfile);
            free(cliPtr);

            cliPtr->clientProfile = NULL;

            *cTree = (*cTree)->rightTree;
        } else if(!(*cTree)->rightTree) {
            ClientTree cliPtr = *cTree;

            free(cliPtr->clientProfile);
            free(cliPtr);

            cliPtr->clientProfile = NULL;

            *cTree = (*cTree)->leftTree;
        } else {

            // MISSING DELETE CASE

        }
    }
}

您可能会注意到,但我只想发表2条评论:

此树使用字符串而不是普通的int表示.这就是我一直使用strcmp()的原因,我想我正在使用它.

我没有使用递归,我宁愿传递指针(在这种情况下是结构指针)并使用它.它看起来更干净,将来我希望在删除节点时返回成功值.

更新下面:
我已经完成了删除功能的迭代版本,但我不喜欢它的一些事情,也许它们可以改进(或不改进),但我看不出如何.我还试图编写它缺少的情况,删除一个有2个孩子的节点,但它不能正常工作......

我已经评论了整个代码,我认为代码可以改进,问题出在哪里.我还将这些问题命名为A,B(不再有B),C和D,以便我们可以轻松地引用它们.

bool deleteClientFromTree(ClientTree *cTree, char *cName) {
    if(!cTree) return FALSE;

    ClientTree currPtr = *cTree;
    ClientTree prevPtr = NULL;
    int nCompare;

    while(currPtr) {
        nCompare = strcmp(currPtr->clientName, cName);

        if(nCompare > 0) {
            prevPtr = currPtr;
            currPtr = currPtr->leftTree;
        } else if(nCompare < 0) {
            prevPtr = currPtr;
            currPtr = currPtr->rightTree;
        } else {
            /*
             * A)
             * 
             * The following cases have 3 lines in common, the free()
             * calls and return statement. Is there anyway to improve
             * this code and make it more compact?
             * 
             * Of course, the printf's are to be removed...
             */
            if(!prevPtr && !currPtr->leftTree && !currPtr->rightTree) {
                printf("CASE #1\n");

                *cTree = NULL;

                free(currPtr->clientProfile);
                free(currPtr);

                return TRUE;
            } else if(!currPtr->leftTree || !currPtr->rightTree) {
                printf("CASE #2\n");

                if(prevPtr->leftTree == currPtr) {
                    prevPtr->leftTree = currPtr->rightTree;
                } else {
                    prevPtr->rightTree = currPtr->leftTree;
                }

                free(currPtr->clientProfile);
                free(currPtr);

                return TRUE;
            } else {
                printf("CASE #3\n");

                ClientTree tempPtr = currPtr->rightTree;

                while(tempPtr->leftTree) {
                    tempPtr = tempPtr->leftTree;
                }

                /*
                 * C)
                 * 
                 * This has a big problem...
                 * 
                 * If you take a look at the ClientProfile structure,
                 * in the first post, you'll see two ints
                 * (clientNIF/clientAge) and one char* (clientName).
                 * 
                 * The problem is that the following code line is only
                 * copying the integer data, not the string. For some
                 * reason, the string remains the old one.
                 * 
                 * I tried to use strdup() directly on clientName like:
                 * currPtr->clientProfile->clientName = strdup(tempPtr->clientProfile->clientName);
                 * but it still doesn't work.
                 * 
                 * Why everything is being copied but the strings?
                 */
                currPtr->clientProfile = tempPtr->clientProfile;

                /*
                 * D)
                 * 
                 * Is there anyway to not call the function itself
                 * and make the while loop once again and delete the
                 * corresponding leaf?
                 */
                return deleteClientFromTree(&currPtr->rightTree, tempPtr->clientProfile->clientName);
            }
        }
    }

    return FALSE;
}

小智.. 5

删除节点时,必须对其子节点执行某些操作.

如果没有孩子 - 没问题.您只需删除该节点.

如果有一个孩子,也没问题; 删除节点并将其左子节点移动到其位置.

适合正确的孩子; 只需将子项移动到已删除节点的位置即可.

当您要删除具有左右子节点的节点时,会出现问题.您可以将左侧或右侧子项移动到已删除节点的位置,但是您对另一个子项及其子树又做了什么?

解决方案是这样; 找到要删除的节点的逻辑后继.通过逻辑继承者,我的意思是这个; 假设你有一个由整数组成的树,你删除了值为35的节点,逻辑后继是下一个最大的数字.雅?如果你正在进行有序行走,它将是你删除元素后的元素.

现在,有一个简单的规则来找到逻辑继承者; 你是对的(你总是有权利,因为这是你有两个孩子的情况),然后你尽可能地向左走.

你最终得到的那个元素是合乎逻辑的继承者.它比删除的元素大(你在开始时就去了,记得吗?)但它是最小的下一个最大的元素.

现在,那个元素总是只有一个或没有孩子 - 因为你尽可能地离开了,还记得吗?所以你不能再离开了 - 因为没有左 - 所以这个元素没有孩子或只有一个正确的孩子,这意味着它属于一个易于取消链接的类别(没有孩子或只有一个孩子) .因此,取消链接元素很容易.

现在来吧很酷 - 考虑一下; 如果下一个最大元素与树中要删除的元素位于树中相同的位置,则树仍然有效且正确 - 因为每个元素左侧的所有内容都较小,右侧的所有内容都较大.

所以你做的就是这个; 您将下一个最大节点中的用户数据复制到正在删除的节点中,并删除下一个最大的节点(它没有子节点或只有正确的子节点,因此很容易取消链接和删除).

就是这样!

所以,基本上 - 找到你的逻辑继承者,取消他与树的链接,并将他的用户数据放入你实际上最初删除的元素中(当然,你不会删除它,因为它仍然是树的一部分).



1> 小智..:

删除节点时,必须对其子节点执行某些操作.

如果没有孩子 - 没问题.您只需删除该节点.

如果有一个孩子,也没问题; 删除节点并将其左子节点移动到其位置.

适合正确的孩子; 只需将子项移动到已删除节点的位置即可.

当您要删除具有左右子节点的节点时,会出现问题.您可以将左侧或右侧子项移动到已删除节点的位置,但是您对另一个子项及其子树又做了什么?

解决方案是这样; 找到要删除的节点的逻辑后继.通过逻辑继承者,我的意思是这个; 假设你有一个由整数组成的树,你删除了值为35的节点,逻辑后继是下一个最大的数字.雅?如果你正在进行有序行走,它将是你删除元素后的元素.

现在,有一个简单的规则来找到逻辑继承者; 你是对的(你总是有权利,因为这是你有两个孩子的情况),然后你尽可能地向左走.

你最终得到的那个元素是合乎逻辑的继承者.它比删除的元素大(你在开始时就去了,记得吗?)但它是最小的下一个最大的元素.

现在,那个元素总是只有一个或没有孩子 - 因为你尽可能地离开了,还记得吗?所以你不能再离开了 - 因为没有左 - 所以这个元素没有孩子或只有一个正确的孩子,这意味着它属于一个易于取消链接的类别(没有孩子或只有一个孩子) .因此,取消链接元素很容易.

现在来吧很酷 - 考虑一下; 如果下一个最大元素与树中要删除的元素位于树中相同的位置,则树仍然有效且正确 - 因为每个元素左侧的所有内容都较小,右侧的所有内容都较大.

所以你做的就是这个; 您将下一个最大节点中的用户数据复制到正在删除的节点中,并删除下一个最大的节点(它没有子节点或只有正确的子节点,因此很容易取消链接和删除).

就是这样!

所以,基本上 - 找到你的逻辑继承者,取消他与树的链接,并将他的用户数据放入你实际上最初删除的元素中(当然,你不会删除它,因为它仍然是树的一部分).

推荐阅读
放ch养奶牛
这个屌丝很懒,什么也没留下!
DevBox开发工具箱 | 专业的在线开发工具网站    京公网安备 11010802040832号  |  京ICP备19059560号-6
Copyright © 1998 - 2020 DevBox.CN. All Rights Reserved devBox.cn 开发工具箱 版权所有