【问题标题】:Red Black Tree insertion operation keeps on crashing红黑树插入操作不断崩溃
【发布时间】:2020-07-25 18:18:24
【问题描述】:

我正在尝试实现红黑树,但无法自己编写代码,所以我正在搜索它的代码,它是用 C 编写的,并在 github 上找到了以下代码,我对其进行了分析,它非常简单向前且简单,所以我做了一些修改(只是重命名了几个变量并添加了一个菜单),当我尝试运行它时,我在尝试旋转树时遇到了问题。插入工作正常,直到树变得不平衡,特别是当叔叔变成 "Black" 我的程序无法旋转树并且它只是崩溃。所以让我解释一下程序的流程:每当我们插入一个新节点时,都会调用一个名为 insert 的函数,在成功插入后,它会调用另一个名为 insertfixup 的函数,该函数检查是否有任何红黑树的属性如果违反,则根据问题节点的叔叔是 RED 还是 BLACK 如果叔叔是 Red 那么它可以正常工作,但是一旦叔叔变成 Black 那么它只是崩溃。有人可以检查我在下面给出的代码并指出究竟是什么导致了问题,我怀疑它与指针有关,但无法弄清楚实际发生了什么,

#include <stdio.h>
#include <stdlib.h>
#include <conio.h>

typedef struct RB_Tree {
    struct RB_Tree *left, *right, *parent;
    int info;
    char color;
} node;
int count = 0;

void left_rotate(node **root, node *par) {  
    node *child = par->right;

    par->right = child->left;

    if (child->left!=NULL)
        child->left->parent = par;

    child->parent = par->parent;

    if (par->parent == NULL)
        (*root) = child;
    else
    if (par == par->parent->left)
        par->parent->left = child;
    else
        par->parent->right = child; 

    child->left = par;
    par->parent = child;
}

void right_rotate(node **root, node *par) {
    node *child = par->left;

    par->left = child->right;

    if (child->right!=NULL)
        child->right->parent = par;

    child->parent = par->parent;

    if (par->parent == NULL)
        (*root) = child;
    else
    if (par = par->parent->left)
        par->parent->left = child;
    else
        par->parent->right = child;

    child->right = par;

    par->parent = child;
}

void insertFixup(node **root, node *new) {
    while (new != *root && new->parent->color == 'R') {
        node *uncle;
        //Find uncle and store uncle in "uncle" variable!

        if (new->parent == new->parent->parent->left) {
            uncle = new->parent->parent->right;
        } else {
            uncle = new->parent->parent->left;
        }

        if (uncle == NULL || uncle->color == 'B') {

            if (new->parent == new->parent->parent->left && new == new->parent->left) {
                printf("Inside ll case before rot");
                char col = new->parent->color;
                new->parent->color = new->parent->parent->color;
                new->parent->parent->color = col;

                right_rotate(root, new->parent->parent);
            }

            if (new->parent == new->parent->parent->right && new == new->parent->right) {
                char col = new->parent->color;
                new->parent->color = new->parent->parent->color;
                new->parent->parent->color = col;
                left_rotate(root, new->parent->parent);
            }

            if (new->parent == new->parent->parent->left && new == new->parent->right) {
                char col = new->color;
                new->color = new->parent->parent->color;
                new->parent->parent->color = col;
                //printf("\nHere we are in the left right!");
                left_rotate(root, new->parent);

                right_rotate(root, new->parent->parent); 
            }

            if (new->parent == new->parent->parent->right && new == new->parent->left) {
                char col = new->color;
                new->color = new->parent->parent->color;
                new->parent->parent->color = col;
                right_rotate(root, new->parent);
                left_rotate(root, new->parent->parent);
            }
        }

        if (uncle) {
            if (uncle->color == 'R') {
                uncle->color = 'B';
                new->parent->color = 'B';
                new->parent->parent->color = 'R';
                new = new->parent->parent;
            }
        }
    }

    (*root)->color = 'B';
}

void insert(node **root, int data) {
    node *new = (node*)malloc(sizeof(node));
    new->info = data;
    new->parent = new->left = new->right = NULL;

    if (*root == NULL) {
        (*root) = new;
        new->color = 'B';
    } else {
        node *par;
        node *temp = (*root);

        while (temp) {
            par = temp;
            if(new->info > temp->info)
                temp = temp->right;
            else
                temp = temp->left;
        }

        new->parent = par;

        if (par->info > new->info)
            par->left = new;
        else
            par->right = new;

        new->color = 'R';

        insertFixup(root, new);
    }
}

void main() {
    int men, c, data;
    node *root = NULL;

    while (1) {
        system("cls");
        printf("1.) Insert\n");
        printf("2.) exit\n");
        printf("Enter your choice : ");
        scanf("%d", &men);
        switch (men) {
          case 1:
            printf("Enter data : ");
            scanf("%d", &data);
            insert(&root, data);
            printf("%d successfully added!", data);
            break;

          case 2:
            exit(0);

          default:
            printf("Invalid choice!");
            while ((c = fgetc(stdin)) != '\n') {}
            break;       
        }
        getch();
    }
}

【问题讨论】:

  • 头文件:conio.h 是 windows 特定的(不可移植) 建议重写,将conio.h 和相关函数替换为 c 库函数
  • 关于:void main() { 尽管 Visual Studio(和其他一些相关编译器)允许 void 返回类型,但 main() 只有两个有效签名。它们是:int main( void )int main( int argc, char* argv[] )
  • OT:关于“菜单”printf("1.) Insert\n"); printf("2.) exit\n"); printf("Enter your choice : "); 函数printf() 在 CPU 周期中非常昂贵。建议:printf("%s", "1.) Insert\n" "2.) exit\n" "Enter your choice : ");
  • OT:system()cls 的参数不可移植建议“\f”(换页)或 ANSI 转义序列:printf( "\1b[2J\n" );
  • OT:关于:int count = 0; 全局变量:count 未在发布的代码中的任何地方使用。所以可以(并且可能应该)被淘汰

标签: c pointers data-structures tree red-black-tree


【解决方案1】:

编译时出现警告,第 53 行:

else if(par = par->parent->left)
    par->parent->left = child;

你正在分配,你想比较:

else if(par == par->parent->left)
    par->parent->left = child;

【讨论】:

  • 我在命令提示符下编译,所以这就是为什么没有收到警告,但即使在修复行号之后也是如此。 53 我仍然得到相同的结果
  • @ansme 是的 insertFixup 的定义不正确,还有一些其他的东西,看我的回答
【解决方案2】:

主要问题

正如已经说过的第 53 行

else if(par = par->parent->left)

必须

else if(par == par->parent->left)

insertFixup 中的算法不正确,例如插入数据 3 然后 2 然后 1 会导致崩溃,我在互联网上看到相同的定义,可能你明白了,但不幸的是你的源被窃听。该定义有效:

void insertFixup(node **root, node *new) {
  while (new != *root && new->parent->color == 'R') {
    if (new->parent == new->parent->parent->left) {
      if (new->parent->parent->right && new->parent->parent->right->color == 'R') {
        new->parent->color = 'B';
        new->parent->parent->right->color = 'B';
        new->parent->parent->color = 'R';
        new = new->parent->parent;
      }
      else {
        if (new == new->parent->right) {
          new = new->parent;
          left_rotate(root, new);
        }

        new->parent->color = 'B';
        new->parent->parent->color = 'R';
        right_rotate(root, new->parent->parent);
      }
    }
    else {
      if (new->parent->parent->left && new->parent->parent->left->color == 'R') {
        new->parent->color = 'B';
        new->parent->parent->left->color = 'B';
        new->parent->parent->color = 'R';
        new = new->parent->parent;
      }
      else {
        if (new == new->parent->left) {
          new = new->parent;
          right_rotate(root, new);
        }

        new->parent->color = 'B';
        new->parent->parent->color = 'R';
        left_rotate(root, new->parent->parent);
      }
    }
  }

  (*root)->color = 'B';
}

其他问题

*main*的签名错误,main返回一个int

如果用户没有输入 int 您的 scanf 行 160 不会设置 men 并且您可以测试从未初始化的值。 总是检查scanf的返回值,比如和后面的代码兼容:

if (scanf("%d", &men) != 1)
  men = -1;

while ((c = fgetc(stdin)) != '\n') {}

c 已设置但从未使用过,最糟糕的是,如果您到达 EOF,例如因为输入被重定向到文件上,您将循环不结束。比如做

while ((c = fgetc(stdin)) != '\n')
  if (c == EOF)
    exit(0);

第 10 行:

int count = 0;

定义一个从未使用过的全局变量,你可以删除该行


为了检查我的建议,我添加了该功能以显示树:

void display(node * x){
  if (x) {
    display(x->left);
    printf("%d ", x->info);
    display(x->right);
  }
}

我修改了你的 main 以便能够显示树:

int main()
{
  int men, c, data;
  node *root = NULL;

  while (1) {
    fputs("\n"
          "1.) Insert\n"
          "2.) exit\n"
          "3.) Display tree\n"
          "Enter your choice : ",
          stdout);
    if (scanf("%d", &men) != 1)
      men = -1;

    switch (men) {
    case 1:
      printf("Enter data : ");
      scanf("%d", &data);
      insert(&root, data);
      printf("%d successfully added!", data);
      break;

    case 2:
      exit(0);

    case 3:
      display(root);
      putchar('\n');
      break;

    default:
      puts("Invalid choice!");
      while ((c = fgetc(stdin)) != '\n')
        if (c == EOF)
          exit(0);
      break;       
    }
  }
}

编译和执行:

bruno@bruno-XPS-8300:/tmp/t$ gcc -Wall -Wextra t.c
bruno@bruno-XPS-8300:/tmp/t$ ./a.out

1.) Insert
2.) exit
3.) Display tree
Enter your choice : 1
Enter data : 3
3 successfully added!
1.) Insert
2.) exit
3.) Display tree
Enter your choice : 1
Enter data : 2
2 successfully added!
1.) Insert
2.) exit
3.) Display tree
Enter your choice : 1
Enter data : 1
1 successfully added!
1.) Insert
2.) exit
3.) Display tree
Enter your choice : 3
1 2 3 

1.) Insert
2.) exit
3.) Display tree
Enter your choice : 1
Enter data : 5
5 successfully added!
1.) Insert
2.) exit
3.) Display tree
Enter your choice : 1
Enter data : 4
4 successfully added!
1.) Insert
2.) exit
3.) Display tree
Enter your choice : 3
1 2 3 4 5 

1.) Insert
2.) exit
3.) Display tree
Enter your choice : 2
bruno@bruno-XPS-8300:/tmp/t$ 

【讨论】:

  • 是的,它工作得很好!但是你能解释一下关于检查 scanf 返回的内容吗?我没有得到您将其与 1 进行比较的部分,然后您将 men = -1?
  • @ansme scanf 返回它能够读取的元素数量,我们想要读取 scanf("%d", &men) 1 个元素,因此如果 scanf 不返回 1,则表示未读取 men。目标是检测用户输入的不是数字而是其他内容,例如“aze”,因为在这种情况下,我将 men 设置为 -1,该值不是 1 2 或 3,所以 switch 的 i>default 案例被执行并发出错误值的信号。例如,我使用 -1 而不是 4,以防为值 4 添加新的有效案例,如果使用负值,我不会冒险错误地使用未来的有效值。
  • 明白!非常感谢您提供简短的解释并修复我的代码。
猜你喜欢
  • 2015-01-07
  • 2018-11-06
  • 2013-11-17
  • 2017-01-26
  • 2017-03-16
  • 2018-05-30
  • 1970-01-01
  • 2017-12-23
相关资源
最近更新 更多