2

我有点不得不搁置我以前的 C 问题,因为这个问题现在更重要......

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

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

2)我的删除功能缺少删除左右子节点的节点。在过去的几个小时里,我进行了很多搜索,但找不到合适的方法。

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

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

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

        }
    }
}

你可能会注意到,但让我说两句:

  • 该树使用字符串而不是普通的 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;
}
4

5 回答 5

5

当你删除一个节点时,你必须对它的子节点做一些事情。

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

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

对的孩子也一样;只需将孩子移动到已删除节点的位置即可。

当您要删除同时具有左右子节点的节点时,问题就来了。您可以将左或右子节点移动到已删除节点的位置,但是您如何处理另一个子节点及其子树?

解决方案是这样的;您找到要删除的节点的逻辑继任者。我所说的逻辑继任者是指这个;假设你有一棵由整数组成的树,你删除了值为 35 的节点,逻辑后继是下一个最大的数字。是吗?如果您正在执行有序遍历,那么它将是您在删除元素之后到达的元素。

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

你最终得到的那个元素是合乎逻辑的继任者。它比被删除的元素大(你一开始就走对了,记得吗?)但它是最小的下一个最大的元素

现在,那个元素总是只有一个或没有孩子——因为你尽可能地向左走,记得吗?所以你不能再向左走 - 因为没有左边 - 所以该元素没有孩子或只有一个右孩子,这意味着它属于易于取消链接的类别之一(没有孩子或只有一个孩子) . 所以取消链接这个元素很容易。

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

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

就是这样!

所以,基本上 - 找到您的逻辑继任者,将他与树断开链接并将他的用户数据放入您实际删除的元素中(当然,您不会删除它,因为它仍然是树的物理部分)。

于 2009-03-24T20:42:52.890 回答
2

首先,您提到您没有使用递归,但每个函数都有一个调用自身的逻辑路径。

关于问题:

1)删除递归。如果你的树大到足以炸掉你的筹码,这会给你带来很多麻烦。Gcc 对尾递归的支持有限,但我不会指望它。

2)通常,当您删除具有两个节点的子节点时,您会将左侧或右侧节点提升到已删除节点所在的位置。(这是一个非常简单的情况,我假设您的树不平衡)

2.b)您的删除代码有一些问题。我建议通过一些假设的情况来遍历它。对我来说立即显而易见的是释放一个指针,然后将其推迟:

free(cliPtr);

cliPtr->clientProfile = NULL;

当然,一旦确定了正确性,您总是可以担心风格。

于 2009-03-24T03:17:54.757 回答
1

理想情况下,删除 BST 中的节点有以下三种情况:

情况1:

X has no children: remove X

案例二:

X has one children : Splice out X

案例3:

X has two children : swap X with its successor and follow case #1 or #2

所以对于丢失的删除案例:

当 X(要删除的节点)有两个子节点时,将 X 替换为 X 的后继节点,并遵循 case #1 或 case #2。您也可以用它的前身替换,可能是一个不错的选择。

if ( X->left && X->right) {

NODE *Successor = FindSuccessor(X);
X->data = Successor->data;
free(Successor);

}

于 2009-03-24T08:01:01.317 回答
1

这个二进制代码是插入、删除、搜索和退出。例子:

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.util.*;

public class Binary Tree {

    public static void main(String[] args) throws IOException {
        BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
        LinkedList ll = new LinkedList();

        ll.add("\n"+"mai    0020");
        ll.add("\n"+"king   0019");
        ll.add("\n"+"maan   0002");
        ll.add("\n"+"dimple 0024");
        ll.add("\n"+"eman   0004");
        ll.add("\n"+"lara   0005");
        ll.add("\n"+"cute   0008");
        ll.add("\n"+"irene  0011");
        ll.add("\n"+"sheena 0030");
        ll.add("\n"+"aisy   0003");

        System.out.println("display: " + ll);
        System.out.println("\n\n");

        for(int c=0; c<=10; c++) {
            System.out.println("select from: 1-insert, 2-delete," + 
                               " 3-display, 4-search, 5-quit");

            String x = br.readLine();
            int y = Integer.parseInt(x);
            switch (y) {
                case 1: //inserting
                    System.out.println("input name");
                    String n= br.readLine();
                    System.out.println("input a list number");
                    String o = br.readLine();
                    int z = Integer.parseInt(o);
                    ll.add("\n"+n+" "+z);
                    break;
                case 2: // delete   
                    ll.removeFirst();
                    break;
                case 3: //Display
                    System.out.println("\n\n"+"List of employee: " + ll);       
                    System.out.println("\n\n");
                    break;
                case 4: //search
                    System.out.println("\n");
                    System.out.println("Search");

                    System.out.println("first element of the Linkedlist is: "
                                       + ll.getFirst());
                    System.out.println("\n\n");
                    System.out.println("last element of linkedlist:"
                                       + ll.getLast());     
                    break;
                case 5: //quit
                    System.out.println("\n\n\n\n\n" 
                                       + " Thank You Very Much!!!!!!!\n\n\n");
                    System.exit(0);
                    break;
            }
        }
    }
}
于 2011-02-11T00:59:30.410 回答
0
int delete_value(Tree*&root,Tree*&find,Tree*&ptr,int numb){
    if(find->value==number){
//the number exist in the root,so we should find the highest value inside the right brache and replace it with the root.
    Tree*ptr2=NULL;
    Tree*ptr3=NULL;//pointer pointing to the parent of the last element of ptr2.
    ptr2=root->right;
    while(ptr2!=NULL){
        ptr3=ptr2;
        ptr2=ptr2->left;
    }
    if(ptr2->right!=NULL){
        ptr3->left=ptr2->right;
    }
    swap(ptr2->value,root->value);
    delete ptr2;
    ptr2=ptr3=NULL;
    }

    else{
        while(find->value!=numb){
            if(find->value!=numb){
                ptr=find;
            }
            if(find->value < numb){
                find=find->right;
                return delete_value(root,find,ptr,numb);
            }
            else{
                find=find->left;
                return delete_value(root,find,ptr,numb);
            }
        }//end of while
    }//end of else
//the pointer find is pointing at the element we want to delete.
//the pointer ptr  is pointing at the element before the one's we want to delete.
//case 1:the element to delete don't have any children
    if(find->right==NULL && find->left==NULL){
        if(ptr->left=find){
            ptr->left=NULl;
            delete find;
        }
        else{
            ptr->right=NULL;
            delete find;
        }
    }//end of the first case.
//case 2:the element has one child it could be to the left or to the right.
    //a-child to the right.
    if( find->right!=NULL && find->left==NULL ){
        Tree*ptr2=find->right;
        while(ptr2!=NULL){
            ptr2=ptr2->left;//find the highest value in the right branche and replace it with the delete element
        }
        swap(find->value,ptr2->value);
        delete ptr2;
        ptr2=NULL;
    }
    //b-child to the left.
    if(find->right==NULL && find->left!=NULL){
        Tree*ptr2=find->left;
        //check wether the find element is to the right or to the left of ptr.
        if(ptr->left==find){
            ptr->left=ptr2;
            delete find;
        }
        else{
            ptr->right=ptr2;
            delete find;
        }
    }//end of the second case.

//case3: the element has to children.
    if(find->right!=NULL&&find->left!=NULL){
        Tree*ptr2=find->left;
        while(ptr2->right!=NULL){
            ptr2=ptr2->right;
        }
        swap(ptr2->value,find->value);
        delete ptr2;
        ptr2=NULL;
    }//end of case 3.
}//end of the function.
于 2010-10-17T07:07:57.097 回答