二叉搜索树删除结点时指针问题(邓俊辉数据结构)

Owl丶 2020-03-02 12:38:57
新手学习数据结构时遇到了些问题,想请教各位大佬。
在看清华大学邓俊辉网课数据结构 二叉搜索树删除结点时 看到这样一段代码:

#define BinNodePosi(T) BinNode<T>*//节点位置

template <typename T> BinNodePosi(T) & BST<T>::search ( const T & e ) { //在BST中查找关键码e
if ( !_root || e == _root->data ) {
_hot = NULL;
return _root;
} //在树根v处命中
for ( _hot = _root; ; ) { //自顶而下
BinNodePosi(T) & c = ( e < _hot->data ) ? _hot->lc : _hot->rc; //确定方向
if ( !c || e == c->data )
return c;
_hot = c; //命中返回,或者深入一层
} //无论命中或失败,hot均指向v之父亲(或为NULL)
} //返回目标节点位置的引用,以便后续插入、删除操作

template <typename T> bool BST<T>::remove ( const T& e ) { //从BST树中删除关键码e
BinNodePosi(T) & x = search ( e );
if ( !x ) return false; //确认目标存在(留意_hot的设置)
removeAt ( x, _hot );
_size--; //实施删除
updateHeightAbove ( _hot ); //更新_hot及其历代祖先的高度
return true;
} //删除成功与否,由返回值指示

template <typename T>
static BinNodePosi(T) removeAt ( BinNodePosi(T) & x, BinNodePosi(T) & hot ) {
BinNodePosi(T) w = x; //实际被摘除的节点,初值同x
BinNodePosi(T) succ = NULL; //实际被删除节点的接替者
if ( !HasLChild ( *x ) ) //若*x的左子树为空,则可
succ = x = x->rc; //直接将*x替换为其右子树
else if ( !HasRChild ( *x ) ) //若右子树为空,则可
succ = x = x->lc; //对称地处理——注意:此时succ != NULL
else { //若左右子树均存在,则选择x的直接后继作为实际被摘除节点,为此需要
w = w->succ(); //(在右子树中)找到*x的直接后继*w
swap ( x->data, w->data ); //交换*x和*w的数据元素
BinNodePosi(T) u = w->parent;
( ( u == x ) ? u->rc : u->lc ) = succ = w->rc; //隔离节点*w
}
hot = w->parent; //记录实际被删除节点的父亲
if ( succ )
succ->parent = hot; //并将被删除节点的接替者与hot相联
release ( w->data );
release ( w );
return succ; //释放被摘除节点,返回接替者
} //release()负责释放复杂结构,与算法无直接关系,具体实现详见代码包

仅讨论第三部分remove_At函数第6行 删除的结点x只有右子树的情况,假设y为x的父节点,并且x是y的右子树,那么第6行只做了x=x->rc仅用子树将其覆盖,为什么不需要将y->rc=x->rc?
x的父节点的成员rc,保存的不应该是x的 值 吗?x为要删除的二叉树节点指针, 给x赋值,x变化了,但是y->rc保存的值并没有变吧?
...全文
250 10 打赏 收藏 转发到动态 举报
写回复
用AI写文章
10 条回复
切换为时间正序
请发表友善的回复…
发表回复
Owl丶 2020-03-02
  • 打赏
  • 举报
回复
问题大概可以简单概括为,假设y为x右孩子节点(x,y,z为节点指针),z为y右孩子节点。
如果用y=y->rchild,那此时 x->rchild到底变没变?
我的理解是x->rchild应该是原y的值,y被重新赋值后,x->child还是原来的值,没有变。
但这个理解好像错了,请问错在哪?
寻开心 2020-03-02
  • 打赏
  • 举报
回复
别说没用的, 觉得有帮助,就给结贴, 来点儿实在的,
没看满论坛都是20分的提问,还一个比一个难回答, 好不容易有你这个大款上来了个100分的,赶紧买单
Owl丶 2020-03-02
  • 打赏
  • 举报
回复
引用 11 楼 寻开心 的回复:
如果search(e) 返回的是y->lc或者y->rc 那就圆满了
如果不是,你就还要去再看看
updateHeightAbove(_hot )这个函数又干了些什么, 毕竟hot就是x的父节点; 但是在这里实现更新是不合理的,应该不太可能!

突然发现第一个函数输出的确实是对父节点的子节点的引用!谢谢大佬!!
感激不尽
寻开心 2020-03-02
  • 打赏
  • 举报
回复
如果search(e) 返回的是y->lc或者y->rc 那就圆满了
如果不是,你就还要去再看看
updateHeightAbove(_hot )这个函数又干了些什么, 毕竟hot就是x的父节点; 但是在这里实现更新是不合理的,应该不太可能!
寻开心 2020-03-02
  • 打赏
  • 举报
回复
RemoveAt是执行了删除操作但是没有地方更改x的父节点啊

Remove函数都疑问,还有继续向上去看
BinNodePosi(T) & x = search ( e );
这是x的获取方式, 看到了吧, 是引用

所以要再去看search(e)这个函数到底返回的是什么
Owl丶 2020-03-02
  • 打赏
  • 举报
回复
引用 8 楼 寻开心 的回复:
所以楼主的问题
那么第6行只做了x=x->rc仅用子树将其覆盖,为什么不需要将y->rc=x->rc?x的父节点的成员rc,保存的不应该是x的 值 吗? x为要删除的二叉树节点指针, 给x赋值,x变化了,但是y->rc保存的值并没有变吧?

根源上是调用RemoveAt函数都代码,要负责把y->rc处理好
要么是 RemoveAt(y->rc, hot);
要么 y->rc = RemoveAt(temo, hot);
就是调用者负责的方式

RemoveAt函数能不能内部实现y->rc正确处理呢,显然是能
w->parent 就是y, 要给它赋值的时候需要注意判断,y是有可能为NULL, 还要判断x是y的lc还是rc
但是调用RemoveAt的人更清楚y是什么情况,x是哪一个子树, 归位调用者处理更合适一些

谢谢大佬的分析!一楼我发的第二个函数就是调用RemoveAT的Remove主函数,主函数好像并没有进行任何操作,只是简单的调用,那么他的代码是有问题的对吧= =
寻开心 2020-03-02
  • 打赏
  • 举报
回复
所以楼主的问题
那么第6行只做了x=x->rc仅用子树将其覆盖,为什么不需要将y->rc=x->rc?x的父节点的成员rc,保存的不应该是x的 值 吗? x为要删除的二叉树节点指针, 给x赋值,x变化了,但是y->rc保存的值并没有变吧?

根源上是调用RemoveAt函数都代码,要负责把y->rc处理好
要么是 RemoveAt(y->rc, hot);
要么 y->rc = RemoveAt(temo, hot);
就是调用者负责的方式

RemoveAt函数能不能内部实现y->rc正确处理呢,显然是能
w->parent 就是y, 要给它赋值的时候需要注意判断,y是有可能为NULL, 还要判断x是y的lc还是rc
但是调用RemoveAt的人更清楚y是什么情况,x是哪一个子树, 归位调用者处理更合适一些
寻开心 2020-03-02
  • 打赏
  • 举报
回复
把其他都if分枝都删掉,连起来看代码
BinNodePosi(T) w = x; // 实际被摘除的节点,初值同x
BinNodePosi(T) succ = NULL; // 实际被删除节点的接替者
// x的左子树为空,而右侧子树不空,此时目标是用x的右子树替代[color=#0000FF]树上的x的位置[/color]
succ = x = x->rc;
// 代码执行到这里,看一下树的情况
// 原来的x的父节点,还可以通过w->parent来获得(removeAt函数第一行就保存了x到w变量当中)
// 树本身到这儿还没有任何变化
// 这里的succ = x = x->rc 只是把三个指针处理成同一个值了,仅此而已
// 根据succ的定义,它删除x后,取代x的节点,所以succ指向原来的x->rc没有疑惑吧
// 此时的x这个变量还有什么用?
// 因为是传递进来的参数, x是可以返回的,让他成为新的替代节点返回了

hot = w->parent; // w是原来得的x, 所以hot就是原来的x的父节点;
if ( succ ) // 此时succ是原来的x->roc
succ->parent = hot; // 原来的x->rc的父节点,要改为原来的x都父节点,这样才把删掉x后的x->rc的节点关系理顺

// 整个代码下来,到此实际上还有一个疑问, w是w->prarnt的那一个子节点, 对w->parent的对应的改变在哪里呢

// 下面的代码是释放内存的,不解释了
release ( w->data );
release ( w );
return succ; // 返回了原来的x->rc

前面的代码,理顺了如何删除成功的,下面说第二层意思
也是楼主的那个关键的问题,也是前面解释代码过程中留下的那个疑问, 被删除的节点对父节点如何知道它的子树被替换了
这要看RemoveAt函数是如何调用的
先假定y是要删除的x节点的父节点, x可以是y的左子树(y->lc) 也可以是 y的右子树(y->rc);
那么有两种方法,来调用RemoveAt函数
方法一
RemoveAt(y->lc, // 也可以是y->rc, 都可以; 那么 RemoveAt函数里面的 succ = x = x->rc; 就替代了y->lc这个属性
hot);
方法二
传参x是一个中间变量, 比如 temp = y->lc;
y->lc = RemoveAt(temp, hot); // 用RemoveAt的返回值succ, 在RemoveAt里面 succ = x = x->rc; 这里获得到返回值

从这里,就可以看明白, removeat里面
succ = x = x->rc;
这句话的作用————为调用的RemoveAt的y(被删除的节点对父节点)正确设置它的子节点服务的




Owl丶 2020-03-02
  • 打赏
  • 举报
回复
引用 2 楼 qybao 的回复:
问题大概可以简单概括为,假设y为x右孩子节点(x,y,z为节点指针),z为y右孩子节点。
如果用y=y->rchild,那此时 x->rchild到底变没变?

首先你要了解赋值语句。int a = 5, b=a; 这里a和b是两个变量没问题吧?也就是说有两个内存地址,一个是a的内存地址,一个是b的内存地址,这个能理解吧?那么a的内存信息是什么?是5的二进制,这个也没问题吧?那b的内存信息是什么?也是5的二进制,对吧?这个5的二进制信息怎么来的(b没有跟5直接打交道)?这里是把a的内存信息复制给了b的内存地址,所以a的内存信息和b的内存信息相同,这就是赋值语句。

那么好了,根据你的问题前提可知, x->rchild=y, y->rchild=z 也就是 x->rchild指向y的地址,y->rchild指向z的地址,这里没问题吧?
那么y=y->rchild,此时y的内存信息是什么?是y->rchild的内存信息,即z的地址(y->rchild指向z的地址),也就是说此时y指向z,那么继续x->rchild=y,x->rchild的内存信息是什么?是y的内存信息,而y的内存信息是z的地址,所以x->rchild的内存信息就是z的地址,即x->rchild指向z,那么,问题的结论就是,x->rchild变了,变成指向z了(也就是变成了y的右子树),所以达到了删除右子树的效果。

所以问题的关键,还是在于理解赋值语句

大佬,你好像理解错了,我的问题前提x->rchild=y, y->rchild=z 这两句是赋值语句,并不是说=前后永远等价,x->rchild指向的是*y的地址。
y->rchild=z ,y=y->child 等价于y=z。所以原问题可以写成x->rchild=y,y=z 不就相当于a=b,b=c吗?后面一句b改变 并不会影响前面的a的值。如果按你的理解,应该是*y=*z,而不是y=z吧?
qybao 2020-03-02
  • 打赏
  • 举报
回复
问题大概可以简单概括为,假设y为x右孩子节点(x,y,z为节点指针),z为y右孩子节点。
如果用y=y->rchild,那此时 x->rchild到底变没变?

首先你要了解赋值语句。int a = 5, b=a; 这里a和b是两个变量没问题吧?也就是说有两个内存地址,一个是a的内存地址,一个是b的内存地址,这个能理解吧?那么a的内存信息是什么?是5的二进制,这个也没问题吧?那b的内存信息是什么?也是5的二进制,对吧?这个5的二进制信息怎么来的(b没有跟5直接打交道)?这里是把a的内存信息复制给了b的内存地址,所以a的内存信息和b的内存信息相同,这就是赋值语句。

那么好了,根据你的问题前提可知, x->rchild=y, y->rchild=z 也就是 x->rchild指向y的地址,y->rchild指向z的地址,这里没问题吧?
那么y=y->rchild,此时y的内存信息是什么?是y->rchild的内存信息,即z的地址(y->rchild指向z的地址),也就是说此时y指向z,那么继续x->rchild=y,x->rchild的内存信息是什么?是y的内存信息,而y的内存信息是z的地址,所以x->rchild的内存信息就是z的地址,即x->rchild指向z,那么,问题的结论就是,x->rchild变了,变成指向z了(也就是变成了y的右子树),所以达到了删除右子树的效果。

所以问题的关键,还是在于理解赋值语句

64,649

社区成员

发帖
与我相关
我的任务
社区描述
C++ 语言相关问题讨论,技术干货分享,前沿动态等
c++ 技术论坛(原bbs)
社区管理员
  • C++ 语言社区
  • encoderlee
  • paschen
加入社区
  • 近7日
  • 近30日
  • 至今
社区公告
  1. 请不要发布与C++技术无关的贴子
  2. 请不要发布与技术无关的招聘、广告的帖子
  3. 请尽可能的描述清楚你的问题,如果涉及到代码请尽可能的格式化一下

试试用AI创作助手写篇文章吧