输入一棵二元查找树,将该二元查找树转换成一个排序的双向链表。要求不能创建任何新的结点,只调整指针的指向。 比如将二元查找树
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的链接实现有序双向链表的操作就可以。