我有一个家庭作业来实现二元搜索树(创建,删除,搜索)。我使用了老师提供的示例,但我无法使其工作。
到目前为止,这是我的代码:
void insert_node(int k){
struct node *nodnou,*flow,*parent;
nodnou = (struct node*)malloc(sizeof(node));
nodnou->st = NULL;
nodnou->dr = NULL;
nodnou->nr = k;
if (root==NULL)
{
root = (struct node*)malloc(sizeof(node));
root = nodnou;
}
else
{
flow = (struct node*)malloc(sizeof(node));
parent = (struct node*)malloc(sizeof(node));
flow = root;
parent = root;
while (((flow->st!=NULL) || (flow->dr!=NULL)) && flow!=NULL)
{
if (k<flow->nr)
{
parent = flow;
flow = flow->st;
}
else
{
parent = flow;
flow = flow->dr;
}
}
if (k<flow->nr)
{
parent->st = nodnou;
}
else
{
parent->dr = nodnou;
}
}
}
思维方式:此函数获取我们要插入的节点的值作为k参数。该函数只插入树的根(root是全局变量)。
我认为我最大的问题是while
循环遍历树以找到新节点的位置。
如果我使用while (flow!=NULL)
它将无法工作,因为流指针获得了对不存在的东西的赋值。请帮我理解我错在哪里(作业)。
答案 0 :(得分:3)
您的代码有几个重要缺陷,其中最重要的是对动态内存分配在C中的工作原理的误解。永远不要遵循这样的模式:
Type *pointer = malloc(sizeof(Type));
pointer = <<something else>>
它实际上泄漏了内存并在两条短线中获得 nothing 。这不是基于对象引用的语言,如Java或C#。指针是保存内存地址的变量。就像int
可以保存整数一样,指针包含地址。就像下面的例子一样:
int n = 6;
n = 5; //Hmm. Where did the 6 go? Oh yeah, We overwrote it with 5.
您将丢失分配链接,使用指针执行相同的操作:
struct node *root = malloc(sizeof(*root));
root = nodnou; // memory allocated above is gone. forever leaked.
指针是变量。就像任何其他变量一样,它们具有值。但是,在指针的情况下,它的值是地址。你可以指向C中几乎任何东西,包括指针指针;包含指针变量地址的变量。我提出它们是因为它们为您的插入要求提供了一个特别优雅的解决方案。
以下是二叉树插入的一般实现,它不支持树中的重复(如果允许重复,代码会变得更短)。此外,它使用超出提供的函数参数的零局部变量来完成此操作,并且我要求您剖析它并确定它是如何工作的。它甚至适用于最初的NULL树根指针,无需特殊的套管if (root) {} else {}
逻辑:
void insert_node(struct node **pp, int k)
{
while (*pp)
{
if (k < (*pp)->nr) // move down left side?
pp = &(*pp)->st;
else if ((*pp)->nr < k) // move down right side?
pp = &(*pp)->dr;
else return; // found the key, no dupes. leave
}
// pp contains the address of the pointer we need to set.
*pp = malloc(sizeof(**pp));
(*pp)->st = (*pp)->dr = NULL;
(*pp)->nr = k;
}
如果您的树应该支持重复,那么您需要对它们插入的哪一侧保持一致,但它会大大缩短上述算法:
void insert_node(struct node **pp, int k)
{
while (*pp)
pp = (k < (*pp)->nr) ? &(*pp)->st : &(*pp)->dr;
// pp contains the address of the pointer we need to set.
*pp = malloc(sizeof(**pp));
(*pp)->st = (*pp)->dr = NULL;
(*pp)->nr = k;
}
在任何一种情况下,都在调用者方面调用如下:
struct node *root = NULL;
insert(&root, 5);
insert(&root, 10);
insert(&root, 7);
...etc...
答案 1 :(得分:0)
我认为您应该使用while(flow!= NULL)并在此之后将元素作为流插入。它现在的方式会在不应该停止的情况下停止并且在它停止时做奇怪的事情。尝试用笔和纸做一些例子。
答案 2 :(得分:0)
你几乎得到了它。保持紧张!
首先,您需要了解更好的内存分配。实际上,您只需要在函数中进行第一次malloc()
调用。这是您在每次insert_node()
调用期间为要附加到树的节点分配的内存。您正在执行的所有剩余malloc
都是不必要的。您似乎直觉地认为需要为正在使用的其他指针分配内存,但所有指针都是临时的,不需要任何分配,只需在尝试取消引用它们之前分配给有效节点。实际上,这些不必要的分配将在这样的代码中创建所谓的内存泄漏(您请求并且无法释放的内存):
root = (struct node*)malloc(sizeof(node));
root = nodnou;
第二个assignmet(root = nodnou
)会覆盖先前malloc()
调用的结果,并且由于您没有将覆盖的指针值保存在任何其他位置,因此您将无法再释放该值内存,它将被标记为用于应用程序的生命周期!
接下来,您可以简化在树中寻找插入点的代码。 您似乎担心流量变为NULL,但无关紧要。父节点是重要节点。 while循环结束后,它将指向插入节点需要链接的实际节点。以下是您的代码的修改版本。
void insert_node(int k) {
struct node *nodnou, *flow, *parent;
// this is the only memory allocation that should be done
nodnou = (struct node*)malloc(sizeof(node));
nodnou->st = NULL;
nodnou->dr = NULL;
nodnou->nr = k;
parent = NULL;
if( root == NULL ) {
root = nodnou;
} else {
flow = root;
// We will walk the tree in order until we reach the bottom of the
// tree (flow becomes null). What we are trying to do is to find out
// the node that will become the parent of the new node we are inserting
// It doesn't matter if flow becomes NULL, the important value after the
// while loop ends is parent
while( flow != NULL ) {
// update the current potential parent node
parent = flow;
if( k < flow->nr ) {
// the number we are inserting is lower than the flow node, walk to the left
flow = flow->st;
} else {
// the number we are inserting is greater or equal than the flow node,
// walk to the right
flow = flow->dr;
}
}
// We have reached the bottom, now compare number again with the node that
// will become parent, to find out if we need to link this node to the left
// or to the right of the parent node
if( k < parent->nr ) {
parent->st = nodnou;
} else {
parent->dr = nodnou;
}
}
}
就是这样。尝试编写其余树操作的代码,并毫不犹豫地询问您是否感到困惑。 =)