inline void insert(node *root, int value)
{
if(!root)
{
root = new node();
root->value = value;
}
else
{
node *itr = root;
while(1)
{
if(itr->value > value)
itr = itr->left;
else
itr = itr->right;
if(!itr)
{
itr = new node();
itr->value = value;
break;
}
}
}
}
//像这样调用插入函数
node *head = 0;
insert(head, 5);
insert(head, 10);
insert(head, 3);
insert(head, 1);
insert(head, 4);
我知道这段代码不起作用,因为 insert 函数中的'itr'是一个局部变量,因此,它不会反映方法之外的树。 但是,我不清楚为什么它不起作用。 虽然'itr'是局部变量,'itr'指向'root'指向的相同位置。此外,我取消引用它来移动'左'或'右'所以我认为它应该工作。
我认为这是通过值与指针传递指针的基本问题,但我无法找到为什么我不能使用指针的局部变量更改树的明确解释。
答案 0 :(得分:1)
是的,你真的需要在这个c ++代码中使用引用。
node * treeRoot = 0;
insert( treeRoot , 4 ); // treeRoot needs to be changed for this to work. So a reference is required.
在这种情况下,函数应声明为
void insert(node* &root, int value);
另一种方法是使用双指针。
node * treeRoot = 0;
insert( &treeRoot , 4 ); // treeRoot needs to be changed for this to work. So a pointer to the pointer will work.
在这种情况下,函数应声明为
void insert(node** root, int value);
这也是探索紧凑而优雅的递归解决方案的绝佳机会。 (虽然内联在这里不起作用)
void insert(node* &root, int value)
{
if(!root)
{
root = new node();
root->value = value;
}
else
{
if(root->value > value)
insert( root->left, value );
else
insert( root->right, value);
}
}
答案 1 :(得分:1)
这里有两个问题。首先,在您的示例用法中,您有:
node *head = 0;
如果你运行它,你将最终在每次调用时创建一个新节点,因为
if (!root) {}
永远都是真的。其次,正如您所指出的,当您更新树时,您正在创建一个新节点,但实际上并未将其插入树中。要纠正这两个问题,您可以执行以下操作:
node* insert(node *root, int value) {
if(!root) {
root = new node();
root->value = value;
} else {
node *itr = root;
node **where = 0;
while(1) {
if(itr->value > value) {
where = &itr->left;
itr = itr->left;
} else {
where = &itr->right;
itr = itr->right;
}
if(!itr) {
itr = new node();
itr->value = value;
// Insert the new node, to fix the second issue
*where = itr;
break;
}
prev = itr;
}
}
return root; // This always returns the tree, solves problem 1, which can also be solved using references or ptr-to-ptr as other people have mentioned
}
然后你的例子是:
node* head = insert(0, 5);
insert(head, 10);
insert(head, 3);
insert(head, 1);
insert(head, 4);
虽然仍然存在两次插入相同数字的问题,但这里处理不当。为了测试,避免在这样的函数中创建新节点是一种更好的做法。最好创建节点然后给节点添加要插入的新值,并让插入找出将其链接到树(包括头部)的位置。
答案 2 :(得分:1)
如果以正确的方式思考它们,C(和C ++)中的指针并不是那么困难。
让我用以下代码演示:
void foo(int i) {
i = i + 5;
}
int main()
{
int i = 5;
foo(i);
printf("i is %d\n", i);
return 0;
}
问。 i
被调用后,main()
中foo()
的价值是多少?
A。 i
按值传递给foo()
,因此i
中的main()
未被foo()
修改。 i
仍为5。
现在,让我们稍微改变一下代码:
void foo(int* i) {
i = malloc(sizeof(int));
}
int main()
{
int *i = 0;
foo(i);
printf("i is %p\n", i); /* printf a pointer with %p */
return 0;
}
问。 i
被调用后,main()
中foo()
的价值是多少?
A。 i
按值传递给foo()
,因此i
中的main()
未被foo()
修改。 i
仍为0。
换句话说,一切都没有改变! i
现在是指针的事实不会改变它是按值传递的。
实际上,在C中,所有函数参数都是按值计算的。那么,我们如何获得修改变量的函数?
如果要将变量传递给该函数的函数以对其进行修改,则必须将该变量的地址传递给该函数。 (对于C来说也是如此。在C ++中你也可以使用引用,但我这里只是指点指针。)
当您传递变量的地址时,您正在做两件事:
您正在计算变量的内存地址
您将内存地址按值传递给该功能。
内存地址可用于修改内存地址所指向的内存。由于函数内部的内存地址与函数调用之外的内存地址相同(因为它是按值传递的),因此它们指向的变量是同一个!
这是最棘手的概念,所以让我们画一些ascii。
| |
+------------+ <- 0x04762198
| |
| i |
| |
| |
+------------+ <- 0x0476219C
| |
让我向您介绍int i
。从内存地址i
开始,0x04762198
是4个字节(在此系统上)。所有变量都存储在内存中,并且位于内存地址中。
如果我们为i
分配一个值,该值将存储在上面的内存块中。
如果我们将i
传递给函数,i
的值将被复制到内存中的其他位置以供函数使用。该内存的值将与原始i
相同,但该变量的内存地址将位于其他位置。
这是巧妙的一点。如果我们将0x04762198
传递给函数,那么该函数现在可以访问原始i
的内存位置!这是一个指针,因为它指向到内存中的地址。如果我们想使用指针修改函数内的原始i
,我们取消引用它(例如*ptr = 5;
)。我们实际在做的是说“请将此值(5)存储在ptr
”指向的内存中。
让我们再次更改代码以实现此目的:
/*
* The address of an int* is int**
*/
void foo(int** i) {
/* dereference to access and modify the original `i` */
*i = malloc(sizeof(int));
}
int main()
{
int *i = 0;
/*
* Pass the address of i, so foo() can modify i
*/
foo(&i);
printf("i is %p\n", i); /* printf a pointer with %p */
return 0;
}
看到区别?
现在,您能否在自己的计划中看到自己做错了什么?
注意:为了简洁,我省略了通常的错误检查(例如,检查malloc()不返回NULL)。
答案 3 :(得分:1)
假设你有
int x = 0;
int y = x;
y = 3478;
您是否希望x还包含3478
?
我知道你不会,root
和itr
也是如此。
这是一个经典的铅笔纸问题(大多数指针问题都是),当你遇到这样的问题时,绝对值得拔出一些死树。
这是您的一个案例的ASCII版本,您想要向右插入,右边是NULL。
箭头表示各个变量指向的位置。
开始功能:
____
root ---> | |
------
/ \
/ \
left NULL
itr = root;
____
root ---> | | <--- itr
------
/ \
/ \
left NULL
itr = itr->right;
____
root ---> | |
------
/ \
/ \
left NULL <--- itr
if (!itr)
itr = new node();
____
root ---> | |
------
/ \
/ \ ____
left NULL itr ---> | |
----
正如您所看到的,输入树根本没有被修改,您只是在它之外分配了一个新节点并将其保留在那里。
这样可行:
____
root ---> | | <--- itr
------
/ \
/ \
left NULL
if (!itr->right)
{
itr->right = new node()
}
____
root ---> | | <--- itr
------
/ \
/ \
left ____
| |
----
铅笔和纸是找出指针的最好方法。
答案 4 :(得分:0)
您创建了一个节点,但没有将其插入树中。试试这段代码:
内联void插入(node * root,int value)
{
if(!root)
{
root = new node();
root->value = value;
}
else
{
node *itr = root , *prev;
while(itr)
{
prev=itr;
if(itr->value > value)
itr = itr->left;
else
itr = itr->right;
}
itr = new node();
itr->value = value;
if(value < prev->value)
prev->left=itr;
else
prev->right=itr;
}
}