Проблемы с расшифровкой псевдокода моего учителя

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

Ссылка на псевдокод здесь: https://imgur.com/a/rhjhEIa

SUBROUTINE insert(current, parent, node)
    IF current is null
        IF parent is null
            root = node
        ELSE
            ID node.value < parent.value
                parent.left = node
            ELSE
                parent.right = node
            END IF
            RETURN true
        END IF
    ELSE IF node.value = current.=value
        RETURN false
    ELSE IF ode.value < current.value
        insert(current.left, current, node)
    ELSE
        insert(current.right, current, node)
    END IF
END SUBROUTINE

Вместо node я попробовал, по-видимому, большинство разрешенных переменных, включая текущую, родительскую (и даже значение, что не сработало. Шокер).

bool recursiveInsert(Node* current, Node* parent, double value)
{

    if (current == NULL)
    {
        if (parent == NULL)
        {
        }
        else
        {
            if (current->value < parent->value)
            {
                parent->left = current;
            }
            else
            {
                parent->right = current;
            }
            return true;
        }
    }
    else if (parent->value == current->value)
    {
        return false;
    }
    else if (parent->value < current->value)
    {
        insert(current->left, current->value, current);
    }
    else
    {
        insert(current->right, current->value, current);
    }   
}

Я ожидаю, что вывод добавит значение в двоичное дерево поиска и вернет true, но в настоящее время программа просто выдает ошибку всякий раз, когда я добираюсь до частей, требующих «узел».

Если current==NULL что это делает --> current->value < parent->value ?

MFisherKDX 31.05.2019 02:37

Ваша функция всегда возвращает значение?

MFisherKDX 31.05.2019 02:39

Почему вы чувствуете, что должны заменить node чем-то другим? В качестве альтернативы, на какой именно вопрос вы хотите получить ответ? (И это тот же вопрос, который вы задали своему профессору?)

rici 31.05.2019 02:42

Похоже, псевдокод вашего учителя отсутствует return перед рекурсивными вызовами функции.

Jonathan Leffler 31.05.2019 02:46

@rici Если вы посмотрите на кодекс пуэдо учителей 1975 года, это будет иметь смысл.

Bailey Kocin 31.05.2019 02:48

Вы должны включить псевдокод в виде текста: это легко сделать. Кажется, у вас нет аргумента, соответствующего node в псевдокоде, что затрудняет соответствие вашей функции псевдокоду вашего учителя.

Jonathan Leffler 31.05.2019 02:48

@bailey: мне кажется, псевдокод подходит. Но в посте по-прежнему отсутствует вопрос.

rici 31.05.2019 02:50

@rici Псевдо в порядке. Гэвин вообще не следил за ним. Он перепутал параметры, но я не могу набрать полный ответ на своем телефоне!!!!

Bailey Kocin 31.05.2019 02:51

Кажется, у вас тоже нет переменной root. Предположительно, это глобальная переменная — она не определена в псевдокоде. И аргумент node, похоже, является структурным типом. Вы на самом деле не указали, какой вопрос вы задали своему учителю; как мы должны угадать, что вы спросили? И вы не показали, как вы ожидаете, что функция будет вызываться. Это может иметь значение.

Jonathan Leffler 31.05.2019 02:56

@bailey: я думаю, мы все знаем, что не так. Но правила здесь таковы, что вы должны на самом деле задать вопрос. «Исправьте мой код для меня» — это не вопрос. Научиться задавать точные вопросы — важный навык.

rici 31.05.2019 02:56
Стоит ли изучать PHP в 2023-2024 годах?
Стоит ли изучать PHP в 2023-2024 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
0
10
93
2
Перейти к ответу Данный вопрос помечен как решенный

Ответы 2

Как вы упомянули, это функция для вставки узла в двоичное дерево поиска. Параметры следующие

  1. parent — родитель исследуемого узла. Это будет вызвано с корень дерева.
  2. current — слева или справа от исследуемого родительского узла. При первом вызове функции следует использовать root->left, если значение текущего узла меньше корня, или root->right, если значение больше root. Если root равно нулю, то current также должно быть NULL

    if (root == NULL)
    {
        ret = recursiveInsert(NULL, NULL, node);
    } 
    else if (root->value < node->value)     
    {
        ret = recursiveInsert(root->left, root, node);
    }
    else if (root-> value > node->value)
    {
        ret = recursiveInsert(root->right, root, node);
    }
    else 
    {
        //same value, handle error
    }
    
  3. node — это новый узел, который нужно добавить в дерево. Выделение памяти для этого узла должно быть выполнено до вызова recursiveinsert, и значение должно быть указано.

Теперь давайте посмотрим на код, который вы написали.

Первая ошибка — использовать третий параметр как double. Это должен быть параметр типа node, который уже должен быть выделен ранее.

Из условия проверьте, что

ELSE IF node.value = current.=value
    RETURN false

кажется, что node->value имеет целочисленный тип.

Принимая все это во внимание, обновленный код приведен ниже.

Node* root = NULL;  //global variable
...
bool recursiveInsert(Node* current, Node* parent, Node* node)
{

    if (current == NULL)
    {
        if (parent == NULL)
        {
            root = node;
        }
        else
        {
            if (current->value < parent->value)
            {
                parent->left = node;
            }
            else
            {
                parent->right = node;
            }
            return true;
        }
    }
    else if (node->value == current->value)
    {
        return false;
    }
    else if (parent->value < current->value)
    {
        recursiveInsert(current->left, current, node);
    }
    else
    {
        recursiveInsert(current->right, current, node);
    }   
}
Ответ принят как подходящий

node в контексте псевдокода — ранее выделенный узел, содержащий вставляемые в дерево данные. Первоначальный вызывающий объект выделяет его (что бессмысленно и никогда не делается в коде RW). В действительности крайне маловероятно, что на самом деле будет реализован этот шаблон, если только вы не рассматриваете библиотеку, которая потенциально движется узлов из одного дерева в другое, и вы хотите избежать затрат на установку/демонтаж самих узлов.

Что касается алгоритма, он довольно прост, хотя и не очень красив:

  1. Если и current, и parent равны нулю, это означает, что это должен быть первый узел в дереве, отслеживаемый некоторым глобальным указателем root. Следовательно, root назначается непосредственно входящему узлу.

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

  3. В противном случае, если current не является нулевым, мы просто проверяем, равны ли значения или меньше (предполагается большее, если ни одно из них не верно), и входим в поддерево слева или справа, если это оправдано. В этом случае current.left или current.right становятся рекурсивными current, а current становятся parent того же самого рекурсивного вызова.

Вот и все. Так работает этот алгоритм. И, честно говоря, это маргинал.

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

bool recursiveInsert(Node* current, Node* parent, double value)
{
    bool result = false;

    if (current == NULL)
    {
        if (parent == NULL)
        {
            root = malloc(sizeof *root);
            root->value = value;
            root->left = root->right = NULL;
        }
        else
        {
            Node *p = malloc(sizeof *p);
            p->value = value;
            p->left = p->right = NULL;

            if (value < parent->value)
            {
                parent->left = p;
            }
            else
            {
                parent->right = p;
            }
            result = true;
        }
    }
    else if (value < parent->value)
        result = recursiveInsert(current->left, current, value);

    else if (parent->value < value)
        result = recursiveInsert(current->right, current, value);

    return result;
}

При вставке значения в дерево вызов будет выглядеть примерно так:

recursiveInsert(root, NULL, value);

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


Другой подход

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

Рекурсивная вставка

bool treeInsert(Node **pp, double value)
{
    bool result = false;
    if (!*pp)
    {
        *pp = malloc(sizeof **pp);
        (*pp)->value = value;
        (*pp)->left = (*pp)->right = NULL;
        result = true;
    }
    else if (value < (*pp)->value)
    {
        result = recursiveInsert(&(*pp)->left, value);
    }
    else if ((*pp)->value < value)
    {
        result = recursiveInsert(&(*pp)->right, value);
    }
    return result;
}

Итеративная вставка

bool treeInsert(Node **pp, double value)
{
    bool result = false;

    while (*pp)
    {
        if (value < (*pp)->value)
            pp = &(*pp)->left;

        else if ((*pp)->value < value)
            pp = &(*pp)->right;

        else break;
    }

    if (!*pp)
    {
        *pp = malloc(sizeof **pp);
        (*pp)->value = value;
        (*pp)->left = (*pp)->right = NULL;
        result = true;
    }

    return result;
}

в любом случае вы вызываете, передавая адрес корневого указателя (таким образом, указатель на указатель), где пустая попытка обозначается нулевым корнем:

treeInsert(&root, value);

Любая функция выполнит поставленную задачу. Я оставляю защиту от ошибок как задачу для вас (проверьте свои mallocs).

Другие вопросы по теме