非中序遍历思路把二元查找树转变成排序的双向链表的分析

(一)把二元查找树转变成排序的双向链表  

输入一棵二元查找树,将该二元查找树转换成一个排序的双向链表。要求不能创建任何新的结点,只调整指针的指向。 比如将二元查找树
                                            10
                                          /        \
                                        6         14
                                      /     \      /  \
                                   4      8   12  16
转换成双向链表 

4=6=8=10=12=14=16。

 分析:本题是微软面试100题的第一题,由于树本身定义就属于递归式定义,所以很多与树相关的题目都是用递归的思路来解决,本题亦是如此,

总体:当我们到达某一结点准备调整以该结点为根结点的子树时,先调整其左子树,将左子树转换成一个排好序的左子链表,再调整其右子树转换右子链表。最后链接左子链表的最右结点(左子树的最大结点)、当前结点和右子链表的最左结点(右子树的最小结点)。从树的根结点开始递归调整所有结点。

核心代码分析:

//head是空双链表的投结点,tail是尾结点,root是二叉搜索树的根结点,注意head与tail是引用的指针。

void Convert(BiTreeNode *& head, BiTreeNode *& tail, BiTreeNode *root) //H把二叉搜索树转化为双链表
{
      BiTreeNode *left, *right;
      if (root == NULL) 
      {
            head = NULL;
            tail = NULL;
            return;
      }
      Convert(head, left, root->left);//A
      Convert(right, tail, root->right);//B
      if (left!=NULL) //E
     {
             left->right = root;
             root->left = left;
     } 
    else 
    {
            head = root;//C
    }
    if (right!=NULL) //F
   {
         root->right=right;
         right->left = root;
   } 
   else 
    {
          tail = root;//D
    }


这块代码难以理解的在ABCD这四块,其他像EF处很自然。

先说下代码执行流程:A,B是找出同一父亲father结点的两个子结点,找好后,A中是把该父亲father的左结点赋给left,B中是把该父亲father的右结点赋给right。然后再把该父亲father结点分别与它的左、右子结点链接成双链表。

但为什么递归时,AB函数中第一,第二个参数是下面这样放置呢,即A中left与H中tail相对应,Bz中right与H中head对应,

即A中head与H中head相对应,B中tail与H中tail对应?

void Convert(BiTreeNode *& head, BiTreeNode *& tail, BiTreeNode *root) //H把二叉搜索树转化为双链表

 Convert(head, left, root->left);//A
 Convert(right, tail, root->right);//B

以下面二叉搜索树为例说明:

        a
                                          /        \
                                        b         e
                                      /     \      /  \
                                   c        d   f    g

最高层次的父亲结点为a,那么当Convert(head, left, a->left);与Convert(right, tail, a->right);这2个函数完全递归完,回到此时root为a时,执行后面代码E

时,left与right的结点又分别是多少呢?这时cbd与feg已形成两个有序双链表,left结点是d,right结点是f,所以根结点a链接d与f结点最后形成有序双链表,那么更细一步分析就是,在已形成的有序子双链表cbd,feg中,d结点是构成有序双链表cbd尾结点[tail结点],那么同样f结点构成有序双链表feg的头结点[head结点]。

所以在函数调用A,B处,head-->right,tail-->left相对应。

void Convert(BiTreeNode *& head, BiTreeNode *& tail, BiTreeNode *root) 

 Convert(head, left, root->left);//A
 Convert(right, tail, root->right);//B

这样从整体上确保了,根结点能与它的左右子结点(广义上的子结点,即根结点与它的所有左[右]子结点构成的有序双链表形成一个有序双链表)。

那么在同一个函数体内,为什么是right与tail,head与left作为参数传入呢?

 Convert(head, left, root->left);//A
 Convert(right, tail, root->right);//B

这就要理解这个Convert()函数的意义,该函数是把父亲结点的所有左结点与父亲结点形成有序双链表,父亲结点与它的所有右结点形成有序双链表。

以下面二叉搜索树为例说明:

         a
                                          /        \
                                        b         e
                                      /     \      /  \
                                   c        d   f    g

最高层次的父亲结点为a,那么当Convert(head, left, a->left);与Convert(right, tail, a->right);这2个函数完全递归完,回到此时root为a时,执行后面代码E

时,left与right的结点又分别是多少呢?这时cbd与feg已形成两个有序双链表,left结点是d,right结点是f,所以根结点a链接d与f结点最后形成有序双链表,把父亲结点a当成一个有序双向链表,那么更细一步分析就是,在已形成的有序子双链表cbd,a,feg中,d结点是构成总有序双链表cbdafeg的头结点[head][相对根结点a而言],那么同样f结点是构成总有序双链表cbdafeg的尾结点[相对根结点a而言][tail]。

所以在同一个函数内right与tail,head与left作为参数传入。


在递归调用函数时,如下面:

void Convert(BiTreeNode *& head, BiTreeNode *& tail, BiTreeNode *root) 

 Convert(head, left, root->left);//A
 Convert(right, tail, root->right);//B

是希望根结点的左[右]子结点形成的有序双向链表中,所以在一个左[右】结点形成的子有序双向链表中,根结点的left左结点必定是处于该子有序双向链表的尾部(tail)【因为根结点a的所有左子结点形成的有序双向链表中,父亲结点a的子结点left必定是最大的,所以应该排在该子有序双向链表的尾部】,根结点的right左结点必定是处于该子有序双向链表的头部(head)【因为根结点a的所有右子结点形成的有序双向链表中,父亲结点a的子结点lright必定是最小的,所以应该排在该子有序双向链表的头部】,这样下面的代码直接实现left,a,right的链接实现有序双向链表的操作就可以。









评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值