【问题标题】:Insertion into AVL tree only replaces root node插入 AVL 树只替换根节点
【发布时间】:2020-07-04 11:49:22
【问题描述】:

我目前正在执行一项任务,其中必须打印一本书 (.txt) 中最常用的 N 个单词。我目前面临的问题是,当我将一个节点添加到我的一棵树时,它只是替换了根节点,因此,树仍然是单个节点。

代码 sn-p 将文件“stopwords.txt”中的单词添加到名为 stopwords 的树中:

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 进行调试时,我发现在递归插入函数 (doInsert) 运行之前根节点 (d->root) 已被替换,导致程序始终返回节点 n,结果,已经存在于树中。例如,如果文本文件包含以下内容:
一个
b
c
那么程序将首先插入"a"作为stopwords-&gt;root,然后"b"将替换"a"并成为新的stopwords-&gt;root,最后"c"将替换"b"作为stopwords-&gt;root,产生一棵树一个节点,"c"

【问题讨论】:

  • 关于; typedef struct _DictRep *Dict; struct _DictRep { Link root; };这一切完全没有必要,而只是使用:DictNode *root = NULL:

标签: c data-structures binary-search-tree avl-tree


【解决方案1】:

您的代码中有很多不一致之处。

这里有个错误:

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

每次插入新节点时,您都​​会无条件地重新分配根。

您应该从函数doInsert 返回新节点,并且仅当新节点已成为新根时才重新分配根。

但您犯的另一个错误是您从doInsert 返回一个局部变量n,它不是新分配的,而是初始化为指向前一个根。

doInsert内部你需要分配一个新的节点NEW并使用变量x从根向下走,直到找到一个地方插入一个新分配的节点NEW。如果 x 在 root 处停止,那么您重新初始化 d-&gt;root = NEW

【讨论】:

    【解决方案2】:

    您的函数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 &lt;string.h&gt; 以使用strlen()strcpy()(如果不是)。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2021-07-16
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多