Как правильно вставить / удалить в двоичном дереве поиска в C? - PullRequest
2 голосов
/ 24 марта 2009

Я вроде бы отложил свои предыдущие вопросы по С, потому что этот вопрос сейчас важнее ...

Я уже закодировал функции вставки и удаления в моем бинарном дереве поиска, но функция удаления не завершена. Есть пара вещей, в которых мне нужна помощь ...

1) Хороша ли моя функция вставки или ее можно как-то улучшить?

2) В моей функции удаления отсутствует удаление узла с левым и правым потомками. За последние несколько часов я много искал, но не мог найти подходящий способ сделать это.

2.a) Как мне удалить узел с 2 дочерними узлами?

2.b) Как и в первом вопросе, хороша ли функция удаления или ее можно улучшить? Я знаю, что это возможно, потому что я повторяю много кода в этих случаях, но я не вижу, как его улучшить, мне тоже нужна помощь в этом.

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 () все время, я думаю, что я использую его правильно.
  • Я не использую рекурсию, я скорее передаю указатель (в данном случае указателя структуры) и работаю с этим. Он выглядит более чистым, и в будущем я хочу вернуть значение успеха, если узел был удален.

ОБНОВЛЕНИЕ НИЖЕ:
Я уже сделал свою итеративную версию функции удаления, но мне не нравятся некоторые вещи, возможно, они могут быть улучшены (или нет), но я не могу понять, как это сделать. Я также попытался закодировать случай, которого он пропустил, удалив узел с двумя дочерними элементами, но он не работает должным образом ...

Я прокомментировал весь код, где, я думаю, код можно улучшить и в чем проблема. Я также назвал эти проблемы как 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 ]

5 голосов
/ 24 марта 2009

Когда вы удаляете узел, вы должны что-то делать с его дочерними элементами.

Если нет детей - нет проблем. Вы просто удаляете узел.

Если есть оставленный ребенок, тоже нет проблем; Вы удаляете узел и перемещаете его левого потомка на его место.

То же самое для правильного ребенка; просто переместите ребенка на место удаленного узла.

Проблема возникает, когда вы хотите удалить узел, у которого есть левый и правый дочерние узлы. Вы можете переместить левого или правого дочернего элемента на место удаленного узла, но что вы тогда будете делать с другим дочерним элементом и его поддеревом?

Решение таково; вы находите логического преемника удаляемого узла. Под логическим преемником я имею в виду это; скажем, у вас есть дерево из целых чисел, и вы удаляете узел со значением 35, логический преемник является следующим по величине числом . Ya? если вы выполняли обход по порядку, это был бы элемент, к которому вы пришли после удаляемого элемента.

Теперь есть простое правило, чтобы найти логического преемника; Вы идете направо один (у вас всегда есть право, потому что это тот случай, когда у вас двое детей), а затем вы идете как можно левее.

Тот элемент, в котором вы оказались, является логическим преемником. Он больше, чем удаленный элемент (вы пошли в самом начале, помните?), Но это наименьший следующий по величине элемент .

Теперь, у этого элемента ВСЕГДА есть только один или нет детей - потому что вы ушли так далеко, как только можете, помните? так что вы больше не можете идти налево - потому что не осталось левого - так что у элемента нет дочерних элементов или просто правый дочерний элемент, и это означает, что он попадает в одну из легко отсоединяемых категорий (нет дочерних элементов или только один дочерний элемент) , Так что отсоединение этого элемента легко.

Теперь самое интересное - подумайте об этом; если этот следующий по величине элемент находится в том же месте в дереве, что и элемент, который вы хотите удалить, дерево все равно будет действительным и правильным - потому что все слева от каждого элемента меньше, все до право больше.

Так что вы делаете это; Вы копируете пользовательские данные в следующем по величине узле в удаляемый узел и удаляете этот следующий по величине узел (у него нет дочерних или просто правильный дочерний узел, поэтому его легко отсоединить и удалить).

И это все!

Итак, в основном - найдите своего логического преемника, отсоедините его от дерева и поместите его пользовательские данные в элемент, который вы фактически удаляете (который затем вы не удаляете, конечно, потому что он все еще физически является частью дерево).

2 голосов
/ 24 марта 2009

Прежде всего, вы упомянули, что вы не используете рекурсию, но у каждой функции есть логический путь, который вызывает сам себя.

На вопросы:

1) Удалить рекурсию. Это может доставить вам много хлопот, если ваше дерево достаточно велико, чтобы взорвать ваш стек. У Gcc ограниченная поддержка хвостовой рекурсии, но я бы на это не рассчитывал.

2) Как правило, когда вы удаляете дочерний узел с двумя узлами, вы переводите левый или правый узел в положение, в котором находился удаленный узел. (Это очень упрощенный случай, я предполагаю, что ваше дерево не сбалансировано)

2б) Ваш код удаления имеет некоторые проблемы. Я бы порекомендовал пройти через это с несколькими гипотетическими ситуациями. Для меня сразу же было очевидно, что нужно свободно указывать указатель и затем ссылаться на него:

free(cliPtr);

cliPtr->clientProfile = NULL;

Конечно, вы всегда можете беспокоиться о стиле, как только возьмете в сторону правильность.

1 голос
/ 11 февраля 2011

это двоичные коды: вставка, удаление, поиск и выход. Примеры:

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;
            }
        }
    }
}
1 голос
/ 24 марта 2009

В идеале есть три случая удаления узла в BST:

Дело 1:

X has no children: remove X

Случай 2:

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 и следуйте за случаем № 1 или № 2. Вы также можете заменить его предшественником, может быть хорошей альтернативой.

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

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

}

0 голосов
/ 17 октября 2010
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.
...