Модераторы: bsa

Поиск:

Ответ в темуСоздание новой темы Создание опроса
> конструктор и деструктор бинарного дерева 
:(
    Опции темы
feodorv
Дата 15.5.2015, 01:12 (ссылка) | (нет голосов) Загрузка ... Загрузка ... Быстрая цитата Цитата


Эксперт
****


Профиль
Группа: Комодератор
Сообщений: 2214
Регистрация: 30.7.2011

Репутация: 12
Всего: 45



Вот, что получилось у меня:
Код

#include <Windows.h>
#include <iostream>
#include <fstream>
#include <string>
#include <iomanip>
#include <cctype>

class Node
{
private:
  Node *leftPtr, *rightPtr;

public:
  Node() : key(0), leftPtr(0), rightPtr(0) {}
  Node( int k, const std::string &p) : key(k), stroka(p), leftPtr(0), rightPtr(0) {}
  ~Node() { leftPtr = rightPtr = 0; }

  int key;
  std::string stroka;

  bool is_palindrome();
  void show();

  friend class Tree;
};

void Node::show()
{
  std::cout << key << " " << stroka << " " << is_palindrome() << std::endl;
}

char myToLower( char c )
{
  if( c >= 'A' && c <= 'Z' ) return c - 'A' + 'a';

  switch( c )
  {
    case 'А': return 'а';
    case 'Б': return 'б';
    case 'В': return 'в';
    case 'Г': return 'г';
    case 'Д': return 'д';
    case 'Е': return 'е';
    case 'Ж': return 'ж';
    case 'З': return 'з';
    case 'И': return 'и';
    case 'Й': return 'й';
    case 'К': return 'к';
    case 'Л': return 'л';
    case 'М': return 'м';
    case 'Н': return 'н';
    case 'О': return 'о';
    case 'П': return 'п';
    case 'Р': return 'р';
    case 'С': return 'с';
    case 'Т': return 'т';
    case 'У': return 'у';
    case 'Ф': return 'ф';
    case 'Х': return 'х';
    case 'Ц': return 'ц';
    case 'Ч': return 'ч';
    case 'Ш': return 'ш';
    case 'Щ': return 'щ';
    case 'Ъ': return 'ъ';
    case 'Ы': return 'ы';
    case 'Ь': return 'ь';
    case 'Э': return 'э';
    case 'Ю': return 'ю';
    case 'Я': return 'я';
  }

  return c;
}

bool Node::is_palindrome()
{
  size_t length = stroka.length();
  if( length == 0 ) return false;

  for( size_t i = 0, j = length-1; i < j; )
    if( stroka[i] == ' ' )
      ++i;
    else if( stroka[j] == ' ' )
      --j;
    else if( myToLower(stroka[i]) != myToLower(stroka[j]) )
      return false;
    else
      ++i, --j;

  return true;
}

class Tree
{
private:
  Node *root;
  int count;

  void cleanup( Node *node );
  void show_recursive( Node *node );
  void remove( Node *node, Node *parent);
  void remove_non_palindroms_recursive( Node *node, Node *parent);

public:
  Tree() : root(0), count(0) {}
  ~Tree() { cleanup( root ); }

  bool add( Node *node );
  void show();
  void remove_non_palindroms();
};

void Tree::cleanup( Node *node )
{
  if( node != 0 )
  {
    cleanup( node->leftPtr );
    cleanup( node->rightPtr );
    delete node;
  }
}

void Tree::show_recursive( Node *node )
{
  if( node != 0 )
  {
    show_recursive( node->leftPtr );
    node->show();
    show_recursive( node->rightPtr );
  }
}

void Tree::show()
{
  show_recursive( root );
  std::cout << std::endl;
}

bool Tree::add( Node *node )
{
  if( count >= 10 ) return false;

  Node *parent = 0, *current = root;

  while( current != 0 )
  {
    parent = current;
    if( node->key == current->key ) return false;

    if( node->key < current->key )
      current = current->leftPtr;
    else
      current = current->rightPtr;
  }

  if( parent == 0 )
    root = node;
  else if( node->key < parent->key )
    parent->leftPtr = node;
  else
    parent->rightPtr = node;

  count++;
  return true;
}

void Tree::remove( Node *node, Node *parent)
{
  if( node->rightPtr == 0 && node->leftPtr == 0 )
  {
    if( parent == 0 )
      root = 0;
    else if( parent->leftPtr == node )
      parent->leftPtr = 0;
    else
      parent->rightPtr = 0;
  }
  else if( node->rightPtr == 0 )
  {
    if( parent == 0 )
      root = node->leftPtr;
    else if( parent->leftPtr == node )
      parent->leftPtr = node->leftPtr;
    else
      parent->rightPtr = node->leftPtr;
  }
  else if( node->leftPtr == 0 )
  {
    if( parent == 0 )
      root = node->rightPtr;
    else if( parent->leftPtr == node )
      parent->leftPtr = node->rightPtr;
    else
      parent->rightPtr = node->rightPtr;
  }
  else
  {
    Node *c, *p = node;
    for( c = node->rightPtr; c->leftPtr != NULL; c = c->leftPtr) p = c;
    if( p != node )
    {
      p->leftPtr = c->rightPtr;
      c->rightPtr = node->rightPtr;
    }
    c->leftPtr = node->leftPtr;
    if( parent == 0 )
      root = c;
    else if( parent->leftPtr == node )
      parent->leftPtr = c;
    else
      parent->rightPtr = c;
  }

  delete node;
}

void Tree::remove_non_palindroms_recursive( Node *node, Node *parent)
{
  if( node == 0 ) return;

  if( node == parent->leftPtr )
  {
    while( parent->leftPtr != 0 && !parent->leftPtr->is_palindrome() )
      remove( parent->leftPtr, parent);

    if( (node = parent->leftPtr) != 0 )
    {
      remove_non_palindroms_recursive( node->leftPtr, node);
      remove_non_palindroms_recursive( node->rightPtr, node);
    }
  }
  else
  {
    while( parent->rightPtr != 0 && !parent->rightPtr->is_palindrome() )
      remove( parent->rightPtr, parent);

    if( (node = parent->rightPtr) != 0 )
    {
      remove_non_palindroms_recursive( node->leftPtr, node);
      remove_non_palindroms_recursive( node->rightPtr, node);
    }
  }
}

void Tree::remove_non_palindroms()
{
  while( root != 0 && !root->is_palindrome() ) remove( root, 0);

  if( root != 0 )
  {
    remove_non_palindroms_recursive( root->leftPtr, root);
    remove_non_palindroms_recursive( root->rightPtr, root);
  }
}

struct key_value
{
  int key;
  const char *value;
};

struct key_value kvTable[] =
{
  { 0,  "не палиндром" },
  { -10, "нет" },
  { -20, "тоже нет" },
  { 20, "А луна канула" },
  { 30, "а луна канула" },
  { -25, "Вол около колоколов" },
  { 25, "А роза упала на лапу Азора" },
  { -15, "ротор" },
  { 5, "Аргентина манит негра" },
  { 3, "Россия не манит негра" },
  { 0, NULL }
};

int main( void )
{
  Tree tree;

  for( struct key_value *kv = kvTable; kv->value != NULL; kv++)
  {
    std::string p = kv->value;
    Node *node = new Node( kv->key, p);
    if( !tree.add( node ) ) delete node;
  }

  tree.show();

  tree.remove_non_palindroms();
  tree.show();
}


PS Функцию myToLower() блондинка писала, что для данного задания даже в плюс)))


--------------------
Напильник, велосипед, грабли и костыли - основные инструменты программиста...
PM MAIL   Вверх
lenarano
Дата 15.5.2015, 02:16 (ссылка) | (нет голосов) Загрузка ... Загрузка ... Быстрая цитата Цитата


Шустрый
*


Профиль
Группа: Участник
Сообщений: 62
Регистрация: 17.4.2014

Репутация: нет
Всего: нет



решила проработать, алгоритм не из форума, взяла отсюда http://www.studfiles.ru/preview/1644497/
Код

void Tree::destroy(Node *node, void Tree::destroy(Node *node, Node *parent )
{
    if (!node) return; //    дерево пустое
    if (!node->leftPtr && !node->rightPtr)
    { 
        delete node;// если удаляемый узел не имеет потомков, допустимо просто убрать его из дерева
        if (parent) parent = 0;//эту строчку не поняла
    }
    else if (!node->leftPtr && node->rightPtr)//если есть только правый узел
    {
        if (parent) parent = node->rightPtr;//эту строчку не поняла
        delete node;
    }
    else if (node->leftPtr && !node->rightPtr)//если есть только левый узел
    {
        if (parent) parent = node->leftPtr;
        delete node;
    }
    else //у узла оба потомка
{//заменить удаленный узел крайним правым узлом дерева из левой ветви.
    Node * temp = node->rightPtr;
while (temp->leftPtr)
temp = temp->leftPtr;
temp->leftPtr = node->leftPtr; 
temp = node;
node = node->rightPtr;
parent = node;
delete temp;
}
}    parent )
{
    if (!node) return; //    дерево пустое
    if (!node->leftPtr && !node->rightPtr)
    { 
        delete node;// если удаляемый узел не имеет потомков, допустимо просто убрать его из дерева
        if (parent) parent = 0;//эту строчку не поняла
    }
    else if (!node->leftPtr && node->rightPtr)//если есть только правый узел
    {
        if (parent) parent = node->rightPtr;//эту строчку не поняла
        delete node;
    }
    else if (node->leftPtr && !node->rightPtr)//если есть только левый узел
    {
        if (parent) parent = node->leftPtr;
        delete node;
    }
    else //у узла оба потомка
{//заменить удаленный узел крайним правым узлом дерева из левой ветви.
    Node * temp = node->rightPtr;
while (temp->leftPtr)
temp = temp->leftPtr;
temp->leftPtr = node->leftPtr; 
temp = node;
node = node->rightPtr;
parent = node;
delete temp;
}
}    




Хоть ты тресни не понимаю правильно ли я выбрала параметры у функции и по какому принципу их нужно выбирать. Решила что void Tree::destroy(Node *node, Node *parent ), потому что наша другая функция по удалению не полинома должна передавать именно узел, который нужно удалить. Не понимаю зачем в этом алгоритме 2 параметра.

Вот эти строчки в коде мне не понятны
Код

if (parent) parent = 0;//эту строчку не поняла


Код

if (parent) parent = node->rightPtr;//эту строчку не поняла


Ну и ,конечно,по узлу с 2 потомками завал. Можно прокомментировать каждую строчку?
PM MAIL   Вверх
lenarano
Дата 15.5.2015, 02:46 (ссылка) | (нет голосов) Загрузка ... Загрузка ... Быстрая цитата Цитата


Шустрый
*


Профиль
Группа: Участник
Сообщений: 62
Регистрация: 17.4.2014

Репутация: нет
Всего: нет



Ой, не увидела, что свою написали. Я тогда вашу посмотрю и если что-то не пойму- спрошу.
PM MAIL   Вверх
feodorv
Дата 15.5.2015, 04:31 (ссылка) | (нет голосов) Загрузка ... Загрузка ... Быстрая цитата Цитата


Эксперт
****


Профиль
Группа: Комодератор
Сообщений: 2214
Регистрация: 30.7.2011

Репутация: 12
Всего: 45



Цитата(lenarano @  15.5.2015,  02:16 Найти цитируемый пост)
решила проработать, алгоритм не из форума, взяла отсюда

Гм. Смысл выполнения поставленной перед Вами задачи - не взять готовое и как-то приладить под свои нужды, а всё-таки разобраться с тем, что и как должно происходить. Меня удручает Ваша склонность брать чей-то неизвестно как работающий и с какими ошибками код и тупо его добавлять в свой. И на первый взгляд, код с ошибками. Как Вы их будете исправлять, ничего не понимая в коде, остаётся только догадываться.

Но картинки по ссылке годные, как раз под мой право-левый вариант кода. И хотя картинка, демонстрирующая самый сложный случай
user posted image
описывается как
Цитата
Чтобы решить эту проблему, вы должны заменить удаленный узел крайним правым узлом дерева из левой ветви. Другими словами, двигайтесь вниз от удаленного узла по левой ветви. Затем двигайтесь вниз по правым ветвям до тех пор, пока не найдете узел без правой ветви. Это и есть нужный узел (см. Рисунок 10).

на самом деле на рисунке всё происходит зеркально: удаляемый узел заменяется крайним левым узлом дерева из правой ветви и т.д. Выбор право-левого подхода или лево-правого подхода есть дело вкуса (иначе нужно вводить какой-то критерий выбора, например, можно оценить глубины левой и правой ветви поддерева). Но когда текст расходится с картинкой, лично у меня начинается когнитивный диссонанс. Уверен, что и в коде по ссылке есть недоработки. Стоит ли им безоглядно пользоваться?


Цитата(lenarano @  15.5.2015,  02:16 Найти цитируемый пост)
if (parent) parent = 0;//эту строчку не поняла

Я тоже не понимаю смысла этой строчки. Но учитывая, что функция удаления в коде определена как
Цитата
void Delet1(TREE* node, TREE*& parent) 
а не как у Вас (без ссылки на указатель)
Цитата(lenarano @  15.5.2015,  02:16 Найти цитируемый пост)
void Tree::destroy(Node *node, Node *parent )
то какой-то смысл должен быть.


Цитата(lenarano @  15.5.2015,  02:16 Найти цитируемый пост)
Ну и ,конечно,по узлу с 2 потомками завал. Можно прокомментировать каждую строчку? 

Каждую строчку - это сума сойти можно. Оно Вам точно нужно? 


Цитата(lenarano @  15.5.2015,  02:46 Найти цитируемый пост)
Я тогда вашу посмотрю и если что-то не пойму- спрошу. 

Давайте лучше по моему коду.  Спрашивайте  smile

Добавлено через 10 минут и 38 секунд
Цитата(lenarano @  15.5.2015,  02:16 Найти цитируемый пост)
Не понимаю зачем в этом алгоритме 2 параметра.

Потому что при удалении узла нужно поправить предка этого узла, который на него указывает (либо через leftPtr, либо через rightPtr). Если этого не сделать, то все дерево посыпется - в нём будут присутствовать нерелевантные указатели (то есть указывающие куда-то, но неизвестно куда).


--------------------
Напильник, велосипед, грабли и костыли - основные инструменты программиста...
PM MAIL   Вверх
lenarano
Дата 15.5.2015, 11:56 (ссылка) | (нет голосов) Загрузка ... Загрузка ... Быстрая цитата Цитата


Шустрый
*


Профиль
Группа: Участник
Сообщений: 62
Регистрация: 17.4.2014

Репутация: нет
Всего: нет



Разобрала удаление узла. Код закомментировала.
Код

void Tree::remove( Node *node, Node *parent)//удаление узла
{
    if( node->rightPtr == 0 && node->leftPtr == 0 )//если это лист
    {
        if( parent == 0 )//если он единственный в дереве
            root = 0;//дерево пустое
        else if( parent->leftPtr == node )//иначе если родитель есть и наш узел стоит слева от родителя
            parent->leftPtr = 0;//мы его выкидываем
        else
            parent->rightPtr = 0;//если был справа тоже выкидываем
    }
    else if( node->rightPtr == 0 )//если наш узел имеет только потомка слева
    {
        if( parent == 0 )//если это корень дерева
            root = node->leftPtr;//потомок слева становится корнем
        else if( parent->leftPtr == node )//иначе если наш узел расположен слева у своего родителя
            parent->leftPtr = node->leftPtr;//потомок узла становится на его место
        else
            parent->rightPtr = node->leftPtr;//значит узел находится справа у родителя, удаляем его
    }
    else if( node->leftPtr == 0 )//аналогично
    {
        if( parent == 0 )
            root = node->rightPtr;
        else if( parent->leftPtr == node )
            parent->leftPtr = node->rightPtr;
        else
            parent->rightPtr = node->rightPtr;
    }
    else //у него есть 2 потомка
    {
        Node *c, *p = node;//2 указателя на удаляемый узел
        for( c = node->rightPtr; c->leftPtr != NULL; c = c->leftPtr) p = c;//ищем в правой ветви крайний левый узел
        if( p != node )
        {
            p->leftPtr = c->rightPtr;
            c->rightPtr = node->rightPtr;
        }
        c->leftPtr = node->leftPtr;
        if( parent == 0 )//если это случай, когда узел -это корень
            root = c;//просто крайний левый ставим в корень
        else if( parent->leftPtr == node )//если есть родитель, то теперь родитель указывает слева на крайний левый узел
            parent->leftPtr = c;
        else
            parent->rightPtr = c;//аналогично
    }
    delete node;//до этого расставляли верно указатели, что бы не разрушить структуру дерева, а сейчас удаляем узел.
}



Вот этот кусочек кода не поняла
Код

if( p != node )
        {
            p->leftPtr = c->rightPtr;
            c->rightPtr = node->rightPtr;
        }
        c->leftPtr = node->leftPtr;

PM MAIL   Вверх
lenarano
Дата 15.5.2015, 12:22 (ссылка) | (нет голосов) Загрузка ... Загрузка ... Быстрая цитата Цитата


Шустрый
*


Профиль
Группа: Участник
Сообщений: 62
Регистрация: 17.4.2014

Репутация: нет
Всего: нет



само условие ставит в тупик 
Код

if( p != node )//теперь оба указателя и p и с указавают на крайний левый узел в правой ветке
        {//рассматривается самый тяжелый случай, т.е.это не просто "веточка с 2 вишенкам"
            p->leftPtr = c->rightPtr;
            c->rightPtr = node->rightPtr;
        }
        c->leftPtr = node->leftPtr;

по моей логике условие должно было бы примерно таким(parent->p !== node) Ведь p указывает на крайний левый узел, за ним ноль. Он не может быть нашим узлом с 2 потомками.

Посмотрела void Tree::remove_non_palindroms(). Так как корень нельзя обработать рекурсивно мы его выделяем отдельно. 

Посмотрела 
Код

void Tree::remove_non_palindroms_recursive( Node *node, Node *parent)
{
    if( node == 0 ) return;//если узел на каком-то этапе рекрусии становится пустой
    if( node == parent->leftPtr )//если наш узел находится слева от корня
    {
        while( parent->leftPtr != 0 && !parent->leftPtr->is_palindrome() )//узел есть,он не полиндром 1&&0=0 while(0)
            remove( parent->leftPtr, parent);//удаляем этот узел
        if( (node = parent->leftPtr) != 0 )//узел есть, он полиндром
        {
            remove_non_palindroms_recursive( node->leftPtr, node);//пускаем рекурсию
            remove_non_palindroms_recursive( node->rightPtr, node);
        }
    }
    else//узел находится справа от корня
    {
        while( parent->rightPtr != 0 && !parent->rightPtr->is_palindrome() )//аналогично
            remove( parent->rightPtr, parent);
        if( (node = parent->rightPtr) != 0 )
        {
            remove_non_palindroms_recursive( node->leftPtr, node);
            remove_non_palindroms_recursive( node->rightPtr, node);
        }
    }
}



Вот эта срока не понятна, почему так?
node = parent->rightPtr
node = parent->leftPtr


Еще в функции добавление узла. Вначале вроде поняла. Вот мы нашли место в дереве, где надо поставить узел. current хранит этот адрес. Потом про него нигде не упоминается. Мы начинаем опять с корня, проверяя значения ключа, и устаналиваем его в нужное место.Можно об этом поподробнее.
Код

bool Tree::add( Node *node )//функция добавления узла
{
    if( count >= 20 ) return false;
    Node *parent = 0, *current = root;
    while( current != 0 )//если дерево не пустое
    {
        parent = current;// теперь он указывает на корень
        if( node->key == current->key ) return false;//если значения ключа повторяются
        if( node->key < current->key )//находем место в дереве нашему узлу, исходя из значения в ключе
            current = current->leftPtr; 
        else
            current = current->rightPtr;
    }
    if( parent == 0 )//если дерево пустое
        root = node;
    else if( node->key < parent->key )//устанавливаем узел на нужное место
        parent->leftPtr = node;
    else
        parent->rightPtr = node;
    count++;
    return true;
}


Это сообщение отредактировал(а) lenarano - 15.5.2015, 14:34
PM MAIL   Вверх
feodorv
Дата 15.5.2015, 16:52 (ссылка) | (нет голосов) Загрузка ... Загрузка ... Быстрая цитата Цитата


Эксперт
****


Профиль
Группа: Комодератор
Сообщений: 2214
Регистрация: 30.7.2011

Репутация: 12
Всего: 45



Спасибо за вопросы! Давайте по порядку...
Цитата(lenarano @  15.5.2015,  11:56 Найти цитируемый пост)
Разобрала удаление узла. Код закомментировала.

Очень хорошо! Позволю себе внести правки.

Замечательно, что Вы поняли необходимость правки соответствующего указателя у предка (если он есть; если нет, то правим root) при удалении узла. Такая правка в коде удаления делается аж четыре раза, что намекает на выделение повторяющегося кода в отдельную функцию (лучше даже в inline-функцию):
Код

// Вспомогательная функция замены одного из потомков у предка parent (то есть parent->leftPtr или parent->rightPtr, 
// что ещё нужно определить), чей указатель совпадает с oldNode, на newNode
//
// parent может быть равен нулю только для одного узла дерева - root, поэтому если parent == 0, то на самом деле
// мы меняем сам root с oldNode на newNode (можно в этом случае даже проверить, что oldNode == root)

void Tree::parentFixup( Node *parent, Node *oldNode, Node *newNode)
{ 
// если заменяется сам root; можно проверить, что oldNode == root
  if( parent == 0 )
    root = newNode;

// если же заменяемый узел стоит слева от родителя
  else if( parent->leftPtr == oldNode )
    parent->leftPtr = newNode;

// иначе заменяемый узел стоит справа от родителя; можно проверить, что parent->rightPtr == oldNode
  else
    parent->rightPtr = newNode;
}
Все эти "можно проверить" легко реализуются через assert(). 

В результате введения в дело parentFixup() функция удаления приобретает такой вид:
Код

// Функция удаления узла из дерева, зная его предка. 
// Если предок есть 0, то удаляется корневой узел дерева

void Tree::remove( Node *node, Node *parent)
{
  if( node->rightPtr == 0 && node->leftPtr == 0 ) // у node нет потомков
    parentFixup( parent, node, 0); // меняем у предка node на 0
  else if( node->rightPtr == 0 ) // у node нет правого потомка
    parentFixup( parent, node, node->leftPtr); // меняем у предка node на node->leftPtr
  else if( node->leftPtr == 0 ) // у node нет левого потомка
    parentFixup( parent, node, node->rightPtr); // меняем у предка node на node->rightPtr
  else // у node есть и левый, и правый потомки; тяжёлый случай
  {
    Node *c, *p = node;
    for( c = node->rightPtr; c->leftPtr != NULL; c = c->leftPtr) p = c;
    if( p != node )
    {
      p->leftPtr = c->rightPtr;
      c->rightPtr = node->rightPtr;
    }
    c->leftPtr = node->leftPtr;
    parentFixup( parent, node, c); // меняем у предка node на найденный узел c
  }
  delete node;
}

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


Цитата(lenarano @  15.5.2015,  12:22 Найти цитируемый пост)
по моей логике условие должно было бы примерно таким(parent->p !== node) 

Простите, parent->что?

Это сообщение отредактировал(а) feodorv - 15.5.2015, 18:03


--------------------
Напильник, велосипед, грабли и костыли - основные инструменты программиста...
PM MAIL   Вверх
feodorv
Дата 15.5.2015, 18:02 (ссылка) | (нет голосов) Загрузка ... Загрузка ... Быстрая цитата Цитата


Эксперт
****


Профиль
Группа: Комодератор
Сообщений: 2214
Регистрация: 30.7.2011

Репутация: 12
Всего: 45



Что же делать в самом тяжелом случае удаления узла из бинарного дерева, когда у этого самого узла есть оба потомка?

Идея состоит в том, чтобы подменить удаляемый узел каким-то другим узлом из дерева, желательно без потомков. Абы каким первым попавшимся узлом мы удаляемый узел заменить не можем, дерево всё же отсортировано. Поэтому мы должны действовать строго в согласии с сортировкой - заменяющий узел должен быть или предыдущим, или следующим в последовательности сортировки узлов. В коде ищется следующий за удаляемым в последовательности узел -  то есть самый левый в правом поддереве удаляемого узла (см. картинку). И, ура, поскольку между удаляемым узлом и следующим никаких других узлов быть не может - на то он и следующий), то у следующего узла отсутствует левый потомок (это вне зависимости от алгоритма поиска следующего узла; по алгоритму - это очевидно). 

Однако даже найдя этот самый следующий узел с отсутствующим левым потомком, мы должны понимать, что этот узел принадлежит его предку. При выдёргивании следующего узла со своего места в дереве мы обязаны соответствующим образом поправить указатель левого потомка у предка этого следующего узла. Поскольку у следующего узла есть только правое поддерево, то оно и становится на место следующего узла, здесь всё просто:
Цитата(feodorv @  15.5.2015,  16:52 Найти цитируемый пост)
      p->leftPtr = c->rightPtr;
Выдернув следующий узел из своего места в дереве мы вставляем его на место удаляемого узла, поправив предка удаляемого узла, что мы уже с лёгкостью умеем:
Цитата(feodorv @  15.5.2015,  16:52 Найти цитируемый пост)
    parentFixup( parent, node, c); // меняем у предка node на найденный узел c
а так же заменив левого и правого потомка у следующего узла на те значения, какие были у удаляемого узла:
Цитата(feodorv @  15.5.2015,  16:52 Найти цитируемый пост)
     c->rightPtr = node->rightPtr;
Цитата(feodorv @  15.5.2015,  16:52 Найти цитируемый пост)
    c->leftPtr = node->leftPtr;


Осталось собственно найти следующий элемент по отношению к удаляемому, не забывая про необходимость помнить предка этого следующего элемента:
Код

    Node *c; // следующий элемент, который мы будем искать в правом поддереве узла node
                  // сокращение от c-urrent
    Node *p; // предок узла c; сокращение от p-arent

    // Начальные значения для узлов с и p:
    c = node->rightPtr; // ищем в правом поддереве
    p = node; // предок узла c (ведь у узла node->right предком является node)

    // Цикл поиска следующего узла. 
    // На каждом шаге цикла мы смотрим левого потомка от текущего узла c, 
    // сохраняя узел p предком c при изменении значения c
    while( c->leftPtr != 0 )
    {
       p = c;
       c = c->leftPtr;
    }

    // Теперь мы имеем следующий по отношению к node узел c 
    // (у него c->leftPtr равен 0) и его предка p


Весь выше приведённый код легко записывается в компактную двустрочную форму:
Цитата(feodorv @  15.5.2015,  16:52 Найти цитируемый пост)
    Node *c, *p = node;
    for( c = node->rightPtr; c->leftPtr != NULL; c = c->leftPtr) p = c;


Осталось поправить указатели:
Код
    p->leftPtr = c->rightPtr;
    c->rightPtr = node->rightPtr;
    c->leftPtr = node->leftPtr;
и предка удаляемого узла:
Код
    parentFixup( parent, node, c);



В теории всё просто. На практике Вы рано или поздно столкнётесь со случаем, когда p совпадает с node (то есть когда у node->rightPtr отсутствует левый потомок: node->rightPtr->leftPtr есть 0). Тогда вносимые в указатели правки приведут к хаосу, так как их планирование осуществлялось в предположении, что p != node (то есть что предок следующего узла не есть удаляемый узел). А что собственно меняется в коде в случае p == node? В этом случае мы должны внести всего одну правку в указатели:
  • p->leftPtr править бесполезно, так как Вы правите значение у удаляемого узла (это значение канет в Лету вместе с удаляемым узлом). Более того, поправив p->leftPtr, Вы безвозвратно испортите node->leftPtr, необходимое при правке c->leftPtr.
  • c->rightPtr править на node->rightPtr вообще нельзя, так как node->rightPtr есть собственно узел c. Более того, в таком случае при замене в дереве узла node на узел c значение c->rightPtr вообще править не нужно: оно какое было, таким и остаётся.
  • c->leftPtr (который есть 0), нужно заменить на node->rightPtr.
Вот отсюда и код
Цитата(lenarano @  15.5.2015,  11:56 Найти цитируемый пост)
Вот этот кусочек кода не поняла

if( p != node )
        {
            p->leftPtr = c->rightPtr;
            c->rightPtr = node->rightPtr;
        }
        c->leftPtr = node->leftPtr;

Здесь мы правим значения p->leftPtr и c->rightPtr только в том случае, если p не есть node.

Это сообщение отредактировал(а) feodorv - 15.5.2015, 23:23


--------------------
Напильник, велосипед, грабли и костыли - основные инструменты программиста...
PM MAIL   Вверх
feodorv
Дата 15.5.2015, 18:21 (ссылка) | (нет голосов) Загрузка ... Загрузка ... Быстрая цитата Цитата


Эксперт
****


Профиль
Группа: Комодератор
Сообщений: 2214
Регистрация: 30.7.2011

Репутация: 12
Всего: 45



В принципе, вариант, когда p совпадает с node, можно выделить в особый случай среди наших if-else (так как поиск следующего элемента в дереве не нужен - этот элемент уже известен):
Код

void Tree::remove( Node *node, Node *parent)
{
  if( node->rightPtr == 0 && node->leftPtr == 0 ) // у node нет потомков
    parentFixup( parent, node, 0); // меняем у предка node на 0
  else if( node->rightPtr == 0 ) // у node нет правого потомка
    parentFixup( parent, node, node->leftPtr); // меняем у предка node на node->leftPtr
  else if( node->leftPtr == 0 ) // у node нет левого потомка
    parentFixup( parent, node, node->rightPtr); // меняем у предка node на node->rightPtr
  else if( node->rightPtr->leftPtr == 0 )
  {
     node->rightPtr->leftPtr = node->leftPtr;
     parentFixup( parent, node, node->rightPtr);
  }
  else
  {
    Node *c, *p = node->rightPtr;
    for( c = p->leftPtr; c->leftPtr != NULL; c = c->leftPtr) p = c;
    p->leftPtr = c->rightPtr;
    c->rightPtr = node->rightPtr;
    c->leftPtr = node->leftPtr;
    parentFixup( parent, node, c); // меняем у предка node на найденный узел c
  }
  delete node;
}


Можно повыпендриваться дальше и проверить ещё и node->leftPtr->rightPtr (как будто мы смотрим предыдущий узел по отношению к node, а не следующий):
Код

void Tree::remove( Node *node, Node *parent)
{
  if( node->rightPtr == 0 && node->leftPtr == 0 ) // у node нет потомков
    parentFixup( parent, node, 0); // меняем у предка node на 0
  else if( node->rightPtr == 0 ) // у node нет правого потомка
    parentFixup( parent, node, node->leftPtr); // меняем у предка node на node->leftPtr
  else if( node->leftPtr == 0 ) // у node нет левого потомка
    parentFixup( parent, node, node->rightPtr); // меняем у предка node на node->rightPtr
  else if( node->rightPtr->leftPtr == 0 )
  {
     node->rightPtr->leftPtr = node->leftPtr;
     parentFixup( parent, node, node->rightPtr);
  }
  else if( node->leftPtr->rightPtr == 0 )
  {
     node->leftPtr->rightPtr = node->rightPtr;
     parentFixup( parent, node, node->leftPtr);
  }
  else
  {
    Node *c, *p = node->rightPtr; // точно знаем, что node->rightPtr->leftPtr не 0
    for( c = p->leftPtr; c->leftPtr != NULL; c = c->leftPtr) p = c;
    p->leftPtr = c->rightPtr;
    c->rightPtr = node->rightPtr;
    c->leftPtr = node->leftPtr;
    parentFixup( parent, node, c); // меняем у предка node на найденный узел c
  }
  delete node;
}


PS Этот код не проверял, так что если что, не обессудьте)))


--------------------
Напильник, велосипед, грабли и костыли - основные инструменты программиста...
PM MAIL   Вверх
feodorv
Дата 15.5.2015, 19:39 (ссылка) | (нет голосов) Загрузка ... Загрузка ... Быстрая цитата Цитата


Эксперт
****


Профиль
Группа: Комодератор
Сообщений: 2214
Регистрация: 30.7.2011

Репутация: 12
Всего: 45



Цитата(lenarano @  15.5.2015,  12:22 Найти цитируемый пост)
Еще в функции добавление узла. Вначале вроде поняла. Вот мы нашли место в дереве, где надо поставить узел. current хранит этот адрес. Потом про него нигде не упоминается. Мы начинаем опять с корня, проверяя значения ключа, и устаналиваем его в нужное место.Можно об этом поподробнее.

Место-то в дереве мы нашли. Но оно пустое, оно не занято ни каким другим узлом. Соответственно, current становится нулём. Посмотрите внимательно на цикл:
Цитата(lenarano @  15.5.2015,  12:22 Найти цитируемый пост)
    while( current != 0 )//если место для вставки ещё не найдено
    {
        if( node->key == current->key ) return false; // если значения ключа повторяются

        parent = current; // углубляемся в дерево ещё на один уровень
        if( node->key < current->key ) // выбираем ветку (левую или правую), исходя из значения ключа
            current = current->leftPtr; 
        else
            current = current->rightPtr;
    }

По его окончании current просто-напросто равен 0. Как его можно дальше использовать?


В чем же смысл этого цикла? В поиске узла (предка), в который мы будем осуществлять вставку нового узла. Смысл - в поиске parent, а не current; current здесь вспомогательная переменная. Но даже найдя этот узел, мы снова вынуждены определять ветку, в которую будем вставлять новый узел, либо в leftPtr, либо в rightPtr:
Цитата(lenarano @  15.5.2015,  12:22 Найти цитируемый пост)
    if( parent == 0 )//если дерево пустое
        root = node;
    else if( node->key < parent->key )//устанавливаем узел на нужное место
        parent->leftPtr = node;
    else
        parent->rightPtr = node;

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


Цитата(lenarano @  15.5.2015,  12:22 Найти цитируемый пост)
Посмотрела void Tree::remove_non_palindroms(). Так как корень нельзя обработать рекурсивно мы его выделяем отдельно. 

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

while( parent->leftPtr != NULL ...) ...

А вот почему мы проверяем значение root в цикле:
Цитата(feodorv @  15.5.2015,  01:12 Найти цитируемый пост)
  while( root != 0 && !root->is_palindrome() ) remove( root, 0);

Во-первых, изначально дерево может быть пустым, и казалось бы это легко проверяется через if( root != 0 ). Но ведь в процессе удаления узлов дерево действительно может стать пустым (когда в нём изначально нет ни одного палиндрома). Поэтому проверку на 0 нужно делать постоянно.
Во-вторых, само значение root меняется при удалении корня дерева. При изменении корня на его место может встать узел, опять-таки не являющийся палиндромным, и его тоже нужно удалить. И так далее, пока дерево не опустеет, либо корнем не станет палиндромный узел. Поэтому и проверку на палиндромность нужно делать всё время. В результате получается очень компактный while.


Аналогично и в функции remove_non_palindroms_recursive(). В ней узел node, на самом деле, служит лишь для определения ветки (правой или левой) предка, в которой он находится. Мы бы могли с тем же успехом использовать функцию вида:
Код

void Tree::remove_non_palindroms_recursive( bool isLeft, Node *parent)
{
  ...
}

вообще не передавая указатель на node (а зачем, если мы его легко найдём согласно значению isLeft). Предок нам существенно необходим (не только как аргумент функции удаления), так как в процессе удаления его непалиндромных потомков изменяются его leftPtr и/или rightPtr. А их (как и root'а) нужно будет повторно проверять на палиндромность:
Код

void Tree::remove_non_palindroms_recursive( bool isLeft, Node *parent)
{
...
  if( isLeft )
  {
    // удаляем узел parent->leftPtr до тех пор пока его не станет, либо он не окажется палиндромным
    // совершенно аналогично root, только через parent->leftPtr
    while( parent->leftPtr != 0 && !parent->leftPtr->is_palindrome() )
      remove( parent->leftPtr, parent);

    if( parent->leftPtr != 0 ) // рекурсия
    {
      remove_non_palindroms_recursive( true /* parent->leftPtr->leftPtr */, parent->leftPtr);
      remove_non_palindroms_recursive( false /* parent->leftPtr->rightPtr */, parent->leftPtr);
    }
  }
  else
  {
    // аналогично с правым потомком узла parent
    ...
  }
}

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


Для упрощения бесконечных -> можно ввести дополнительную переменную:
Код

void Tree::remove_non_palindroms_recursive( Node *node, Node *parent)
{
...
  if( parent->leftPtr == node )
  {
    // удаляем узел parent->leftPtr до тех пор пока его не станет, либо он не окажется палиндромным
    // совершенно аналогично root, только через parent->leftPtr
    while( parent->leftPtr != 0 && !parent->leftPtr->is_palindrome() )
      remove( parent->leftPtr, parent);

    Node *temp;
    if( (temp = parent->leftPtr) != 0 ) // рекурсия
    {
      remove_non_palindroms_recursive( temp->leftPtr, temp);
      remove_non_palindroms_recursive( temp->rightPtr, temp);
    }
  }
  else
  {
    // аналогично с правым потомком узла parent
    ...
  }
}

В ранее приведённом коде вместо temp использовалась переменная node, и только для упрощения кода, поэтому я надеюсь, что ответил на Ваш вопрос:
Цитата(lenarano @  15.5.2015,  12:22 Найти цитируемый пост)
Вот эта срока не понятна, почему так?
node = parent->rightPtr
node = parent->leftPtr



Это сообщение отредактировал(а) feodorv - 16.5.2015, 04:27


--------------------
Напильник, велосипед, грабли и костыли - основные инструменты программиста...
PM MAIL   Вверх
lenarano
Дата 16.5.2015, 00:53 (ссылка) | (нет голосов) Загрузка ... Загрузка ... Быстрая цитата Цитата


Шустрый
*


Профиль
Группа: Участник
Сообщений: 62
Регистрация: 17.4.2014

Репутация: нет
Всего: нет



Спасибо огроменное. smile 
По добавлению узла вопросов не осталось.
По удалению узла вот эти 2 строчки не понятны в случае, если узел и p не совпадают:
Код

p->leftPtr = c->rightPtr;
c->rightPtr = node->rightPtr;
 
Например, как на картинке из интернета, когда p=6, а с=4.
Предположим у нашего с (ключ4) есть справа потомок, а не ноль как на картинке. Разве у нас не теряются данные при этом? Вроде все при деле и 6 и 1, пристроены))) Если есть хвостик справа от 4, то всунули его в 4. А данные из 4 куда делись, разве при этом мы не теряем ключ и строку?


Это сообщение отредактировал(а) lenarano - 16.5.2015, 02:22
PM MAIL   Вверх
feodorv
Дата 16.5.2015, 02:21 (ссылка) | (нет голосов) Загрузка ... Загрузка ... Быстрая цитата Цитата


Эксперт
****


Профиль
Группа: Комодератор
Сообщений: 2214
Регистрация: 30.7.2011

Репутация: 12
Всего: 45



Цитата(lenarano @  16.5.2015,  00:53 Найти цитируемый пост)
Например, как на картинке из интернета, когда p=6, а с=4.

На картинке не нарисовано, но Вы представьте себе, что у 4-ки есть правое поддерево с ключом 5 (левого нет по условию нахождения самого левого узла). Что делать с 5-ой? Бросить на произвол судьбы? Нет, конечно. Перед тем, как править c->leftPtr и c->rightPtr, нужно бережно сохранить поддерево c->rightPtr, которое встаёт на место самого узла c:
Код

p->leftPtr = c->rightPtr;

Если поддерева нет (то есть c->rightPtr есть 0, то Вы просто правильно присвоите 0 левому потомку узла p). 


Вот Вам другая картинка, правда, с ошибкой (интересно, найдёте ли Вы её):
user posted image
Здесь в последнем случае удаляется узел 5 (node), на место которого встаёт узел 6 ( c ), а у него есть правый потомок 7 (c->rightPtr). Что делать с 7-ой?


Далее. Вы на место узла 5 (node) вставляете узел 6 ( c ), при этом Вы должны сохранить все старые связи удаляемого узла 5. То есть теперь parent должен иметь своим потомком узел c вместо узла node, а потомками узла c (как бы новым node) должны стать соответствующие потомки удаляемого узла node:
Код

    c->rightPtr = node->rightPtr;
    c->leftPtr = node->leftPtr;



В условных единицах этой картинки:
  • Отсоединяем замещающий узел 6 от дерева:
    Цитата
    10->leftPtr = 7;
  • Правим предка удаляемого узла:
    Цитата
    15->leftPtr = 6;
  • Правим потомков замещающего узла:
    Цитата
    6->leftPtr = 5->leftPtr;
    6->rightPtr = 5->rightPtr;
Всё, дерево свободно от узла 5, сохранив отсортированность своих узлов. Можно делать delete 5.


Я Вам специально выделил случай, когда p == node, чтобы подчеркнуть связность всех этих операций:
Цитата(feodorv @  15.5.2015,  18:21 Найти цитируемый пост)

  else if( node->rightPtr->leftPtr == 0 ) // p == node
  {
     node->rightPtr->leftPtr = node->leftPtr;
     parentFixup( parent, node, node->rightPtr);
  }
  else
  {
    Node *c, *p = node->rightPtr; // точно знаем, что node->rightPtr->leftPtr не 0
    for( c = p->leftPtr; c->leftPtr != NULL; c = c->leftPtr) p = c;
    p->leftPtr = c->rightPtr;
    c->rightPtr = node->rightPtr;
    c->leftPtr = node->leftPtr;
    parentFixup( parent, node, c);
 // меняем у предка node на найденный узел c
  }
Но Вы упорно выделяете только правки p->leftPtr и c->rightPtr, которые не нужно совершать в случае p == node. Почему?



Это сообщение отредактировал(а) feodorv - 16.5.2015, 04:39


--------------------
Напильник, велосипед, грабли и костыли - основные инструменты программиста...
PM MAIL   Вверх
lenarano
Дата 16.5.2015, 02:45 (ссылка) | (нет голосов) Загрузка ... Загрузка ... Быстрая цитата Цитата


Шустрый
*


Профиль
Группа: Участник
Сообщений: 62
Регистрация: 17.4.2014

Репутация: нет
Всего: нет



Ошибку нашла-художник оставил в итоге 5 на месте в (в). Я пристроила 7))) Я решила, что p->leftPtr это и есть с и боялась, что потеряем данные, а на этой картинке все поняла. 
PM MAIL   Вверх
feodorv
Дата 16.5.2015, 04:02 (ссылка) | (нет голосов) Загрузка ... Загрузка ... Быстрая цитата Цитата


Эксперт
****


Профиль
Группа: Комодератор
Сообщений: 2214
Регистрация: 30.7.2011

Репутация: 12
Всего: 45



Цитата(lenarano @  16.5.2015,  02:45 Найти цитируемый пост)
Ошибку нашла-художник оставил в итоге 5 на месте в (в).

 smile 

Цитата(lenarano @  16.5.2015,  02:45 Найти цитируемый пост)
Я решила, что p->leftPtr это и есть с

До выдёргивания узла c так и было. После - уже не должно быть)))


--------------------
Напильник, велосипед, грабли и костыли - основные инструменты программиста...
PM MAIL   Вверх
feodorv
Дата 18.5.2015, 00:08 (ссылка) | (нет голосов) Загрузка ... Загрузка ... Быстрая цитата Цитата


Эксперт
****


Профиль
Группа: Комодератор
Сообщений: 2214
Регистрация: 30.7.2011

Репутация: 12
Всего: 45



Цитата(feodorv @  15.5.2015,  19:39 Найти цитируемый пост)
В результате получается очень компактный while.

Мне тут в голову пришла простая, по сути, мысль: если сначала удалить непалиндромные узлы из потомков узла, а лишь потом проверять сам узел на непалиндромность, то while не нужен. Да и уменьшится количество операций (то есть быстрее работать будет) smile 


--------------------
Напильник, велосипед, грабли и костыли - основные инструменты программиста...
PM MAIL   Вверх
Ответ в темуСоздание новой темы Создание опроса
Правила форума "C/C++: Для новичков"
JackYF
bsa

Запрещается!

1. Публиковать ссылки на вскрытые компоненты

2. Обсуждать взлом компонентов и делиться вскрытыми компонентами

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


Если Вам понравилась атмосфера форума, заходите к нам чаще! С уважением, JackYF, bsa.

 
0 Пользователей читают эту тему (0 Гостей и 0 Скрытых Пользователей)
0 Пользователей:
« Предыдущая тема | C/C++: Для новичков | Следующая тема »


 




[ Время генерации скрипта: 0.0847 ]   [ Использовано запросов: 22 ]   [ GZIP включён ]


Реклама на сайте     Информационное спонсорство

 
По вопросам размещения рекламы пишите на vladimir(sobaka)vingrad.ru
Отказ от ответственности     Powered by Invision Power Board(R) 1.3 © 2003  IPS, Inc.