Вставка в дерево AVL заменяет только корневой узел

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

Фрагмент кода, добавляющий слова из файла stopwords.txt в дерево стоп-слов:

Dict stopwords = newDict();

if (!readFile("stopwords.txt"))
   {
      fprintf(stderr, "Can't open stopwords\n");
      exit(EXIT_FAILURE);
   }

   FILE *fp = fopen("stopwords.txt", "r");

   while (fgets(buf, MAXLINE, fp) != NULL)
   {
      token = strtok(buf, "\n");
      DictInsert(stopwords, buf); //the root is replaced here
   }
   fclose(fp);

Структуры данных определяются следующим образом:

typedef struct _DictNode *Link;

typedef struct _DictNode
{
   WFreq data;
   Link left;
   Link right;
   int height;
} DictNode;

typedef struct _DictRep *Dict;

struct _DictRep
{
   Link root;
};

typedef struct _WFreq {
   char  *word;  // word buffer (dynamically allocated)
   int    freq;  // count of number of occurences
} WFreq;

Код для вставки и перебалансировки дерева:

// create new empty Dictionary
Dict newDict(void)
{
   Dict d = malloc(sizeof(*d));
   if (d == NULL)
   {
      fprintf(stderr, "Insufficient memory!\n");
      exit(EXIT_FAILURE);
   }
   d->root = NULL;
   return d;
}

// insert new word into Dictionary
// return pointer to the (word,freq) pair for that word
WFreq *DictInsert(Dict d, char *w)
{
   d->root = doInsert(d->root, w); //the root is replaced here before doInsert runs
   return DictFind(d, w);
}

static int depth(Link n)
{
   if (n == NULL)
      return 0;
   int ldepth = depth(n->left);
   int rdepth = depth(n->right);
   return 1 + ((ldepth > rdepth) ? ldepth : rdepth);
}

static Link doInsert(Link n, char *w)
{
   if (n == NULL)
   {
      return newNode(w);
   }

   // insert recursively
   int cmp = strcmp(w, n->data.word);
   if (cmp < 0)
   {
      n->left = doInsert(n->left, w);
   }
   else if (cmp > 0)
   {
      n->right = doInsert(n->right, w);
   }
   else
   { // (cmp == 0)
      // if time is already in the tree,
      // we can return straight away
      return n;
   }

   // insertion done
   // correct the height of the current subtree
   n->height = 1 + max(height(n->left), height(n->right));

   // rebalance the tree
   int dL = depth(n->left);
   int dR = depth(n->right);

   if ((dL - dR) > 1)
   {
      dL = depth(n->left->left);
      dR = depth(n->left->right);
      if ((dL - dR) > 0)
      {
         n = rotateRight(n);
      }
      else
      {
         n->left = rotateLeft(n->left);
         n = rotateRight(n);
      }
   }
   else if ((dR - dL) > 1)
   {
      dL = depth(n->right->left);
      dR = depth(n->right->right);
      if ((dR - dL) > 0)
      {
         n = rotateLeft(n);
      }
      else
      {
         n->right = rotateRight(n->right);
         n = rotateLeft(n);
      }
   }

   return n;
}

static Link newNode(char *w)
{
   Link n = malloc(sizeof(*n));
   if (n == NULL)
   {
      fprintf(stderr, "Insufficient memory!\n");
      exit(EXIT_FAILURE);
   }

   n->data.word = w;
   n->data.freq = 1;
   n->height = 1;
   n->left = NULL;
   n->right = NULL;
   return n;
}

// Rotates  the  given  subtree left and returns the root of the updated
// subtree.
static Link rotateLeft(Link n)
{
   if (n == NULL)
      return n;
   if (n->right == NULL)
      return n;
   Link rightNode = n->right;
   n->right = rightNode->left;
   rightNode->left = n;

   n->height = max(height(n->left), height(n->right)) + 1;
   rightNode->height = max(height(rightNode->right), n->height) + 1;

   return rightNode;
}

// Rotates the given subtree right and returns the root of  the  updated
// subtree.
static Link rotateRight(Link n)
{
   if (n == NULL)
      return n;
   if (n->left == NULL)
      return n;
   Link leftNode = n->left;
   n->left = leftNode->right;
   leftNode->right = n;

   n->height = max(height(n->left), height(n->right)) + 1;
   leftNode->height = max(height(leftNode->right), n->height) + 1;

   return leftNode;
}

Я считаю, что большая часть кода функциональна, и просто вставка не работает. Когда я попытался отладить это с помощью gdb, я обнаружил, что корневой узел (d-›root) был заменен до того, как была запущена функция рекурсивной вставки (doInsert), в результате чего программа всегда возвращала узел n, который в результате уже существует в дереве. Например, если текстовый файл содержит следующее:
a
b
c
, то программа сначала вставит "a" как stopwords->root, затем "b" заменит "a" и станет новым stopwords->root. , наконец, "c" заменит "b" на stopwords->root, в результате получится дерево с одним узлом, "c".


person Ixidal    schedule 04.07.2020    source источник
comment
касательно; typedef struct _DictRep *Dict; struct _DictRep { Link root; }; Всё это совершенно не нужно, Лучше просто используйте: DictNode *root = NULL:   -  person user3629249    schedule 05.07.2020


Ответы (2)


В вашем коде много несоответствий.

Здесь одна ошибка:

d->root = doInsert(d->root, w);

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

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

Но другая ошибка, которую вы совершаете, заключается в том, что вы возвращаете из doInsert локальную переменную n, которая не была заново выделена, а была инициализирована, чтобы указывать на предыдущий корень.

Внутри doInsert вам нужно выделить новый узел NEW и использовать переменную x для перехода от корня до тех пор, пока вы не найдете место для вставки нового выделенного узла NEW. Если x останавливается в корневом каталоге, вы повторно инициализируете файл d->root = NEW.

person alinsoar    schedule 04.07.2020

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

Чтобы предотвратить это, вы должны копировать входную строку при вставке узла.

Чтобы заархивировать это,

    n->data.word = w;

должно быть

    n->data.word = malloc(strlen(w) + 1);
    if (n->data.word == NULL)
    {
        fprintf(stderr, "Insufficient memory!\n");
        exit(EXIT_FAILURE);
    }
    strcpy(n->data.word, w);

Добавьте #include <string.h>, чтобы использовать strlen() и strcpy(), если это не так.

person MikeCAT    schedule 04.07.2020