问题描述
题目:
输入一棵二元查找树,将该二元查找树转换成一个排序的双向链表。要求不能创建任何新的结点,只调整指针的指向。
转换成双向链表
二元查找树节点的数据结构如下:
struct BSTreeNode
{
BSTreeNode():m_pLeft(0),m_pright(0){}
int m_nValue;
BSTreeNode *m_pLeft;
BSTreeNode *m_pright;
};
二元查找树的定义
二元查找树: 它首先要是一棵二元树,在这基础上它或者是一棵空树;或者是具有下列性质的二元树:
1. 若左子树不空,则左子树上所有结点的值均小于它的根结点的值;
2. 若右子树不空,则右子树上所有结点的值均大于它的根结点的值;
3. 左、右子树也分别为二元查找树
算法思想
根据二元查找树的定义,不难推出二元查找树的一种极端情况是等价与一个有序的单链表。
这与题目的要求的双向链表只有一步之遥。对于这个单向链表我们可以遍历一遍就可以将其改造成双向链表了。所以一个简单的实现就是先将其变成单向链表,然后再改造成双向链表。
如何将一棵普通的二元查找树改造成这种极端的二元查找树呢?很容易可以想到利用递归的方法来实现。算法如把大象装进冰箱那么简单一样:
1. 如果右子树不为空,将右子树变为单链表。
2. 如果左子树不为空,将左子树变为单链表。
3. 将左子树,当前结点及右子树变成一个单链表。
不难从上述算法描述中看出,关键将第三步实现即可。先来看看完成1,2步奏之后树的形状。
从上图中可以直观的看出,步奏3只需要将结点7和结点8串起来即可。代码如下:
BSTreeNode **it = &top->m_pLeft;
for ( ; *it; it = &(*it)->m_pRight )
{
;
}
*it = top;
算法实现
通过上述分析,算法实现代码如下:
struct BSTreeNode
{
BSTreeNode():m_pLeft(0),m_pright(0){}
int m_nValue;
BSTreeNode *m_pLeft;
BSTreeNode *m_pright;
};
void ConvertToSingleList( BSTreeNode **p )
{
if ( !*p )
{
return;
}
ConvertToSingleList( &(*p)->m_pright );
ConvertToSingleList( &(*p)->m_pLeft );
BSTreeNode **it = &(*p)->m_pLeft;
if ( *it )
{
for( ; *it; it = &(*it)->m_pright )
{
;
}
*it = *p;
*p = (*p)->m_pLeft;
}
}
void ConvertToDoubleList( BSTreeNode **p )
{
ConvertToSingleList(p);
BSTreeNode *it = *p;
for ( ; it; it = it->m_pright )
{
if ( it->m_pright )
{
it->m_pright->m_pLeft = it;
}
}
}
算法改进
上述算法需要遍历两次,第一次构建单链表,第二次构建双向链表,有没有办法将只一次遍历就构建好双向链表呢?答案是肯定的。假设对于当前的top结点的左右子结点都已经是双向链表了,如下图所示:
据上图所示,不难看出,若要将此树转换成双向链表,只需下面两步即可:
1. 将右子结点的left指向top。
2. 将左子结点的最后一个结点的rigt指向top,top的left指向它。
同时我们发现在前面算法的实现当中,需要通过遍历左子树找到最后一个结点。这种方法效率不高,我们可以稍作改进,在将左右子树转换为双向链表的同时,返回各自的最后一个结点,以避免遍历左子树。
改进的算法实现如下:
struct BSTreeNode
{
BSTreeNode():m_pLeft(0),m_pright(0){}
int m_nValue;
BSTreeNode *m_pLeft;
BSTreeNode *m_pright;
};
void ConvertToDoubleList( BSTreeNode **p, BSTreeNode **tail )
{
if( !(*p) )
{
return;
}
BSTreeNode *rtail = NULL;
if( (*p)->m_pright )
{
ConvertToDoubleList( &(*p)->m_pright, &rtail );
(*p)->m_pright->m_pLeft = *p;
}
else
{
//再没有右子树了,因此它就是末尾结点
*tail = *p;
}
BSTreeNode *ltail = NULL;
if ( (*p)->m_pLeft )
{
ConvertToDoubleList( &(*p)->m_pLeft, <ail );
ltail->m_pright = *p;
*p = (*p)->m_pLeft;
ltail->m_pright->m_pLeft = ltail;
//现在是一个完整的双向链表了,末尾结点自然是之前右子树的末尾结点。
*tail = rtail;
}
}