题目:
输入一棵二元查找树,将该二元查找树转换成一个排序的双向链表。要求不能创建任何新的结点,只调整指针的指向。
10
/ \
6 14
/ \ / \
4 8 12 16
转换成双向链表4<=>6<=>8<=>10<=>12<=>14<=>16。
分析:
要明白二元查找树和双向链表的各自特点
- 二元查找树有左右子树,左节点永远小于根节点,右子树永远大于(等于)根节点。
- 二元查找树的中序遍历,会将树按照从小到大有序排列。所以该题目应该考虑从中序遍历着手,在中序遍历的同时修改二元查找树的左右子树链接指向,从而构造一个排序的双向链表。
- 二元查找树的左右子节点可以作为双向链表的左链接和右链接的候选,只不过需要合理的调整它们使之指向正确的左右链接结点。
解一:
利用中序遍历,递归求解
- /*Title: 1.把二元查找树转变成排序的双向链表:解一
- Author: gocode
- Date: 2012-09-27*/
- /* 10
- / \
- 6 14
- / \ / \
- 4 8 12 16 */
- #include <stdio.h>
- #include <iostream>
- using namespace std; //如果不加std,则cout需要写成std::cout
- // 定义双向链表的节点
- typedef struct BSTreeNode
- {
- int m_nValue; // 结点值
- BSTreeNode *m_pLeft; // 左节点
- BSTreeNode *m_pRight; // 右节点
- } DoubleList;
- // 定义全局变量
- DoubleList *pHead;
- DoubleList *pListIndex; // 辅助节点指针,指示双向链表的当前节点
- //void convertToDoubleList(BSTreeNode * pCurrent); // 如果该函数放在ergodicBSTree函数之后,则此处必须声明convertToDoubleList
- // 二叉树转换成双向链表
- void convertToDoubleList(BSTreeNode * pCurrent)
- {
- // 当前pCurrent结点是后加入的节点,它永远把pListIndex作为左节点,
- // 因为pListIndex是作为双向链表的当前节点指示器,所以pListIndex在pCurrent的左边,相应的pListIndex的右节点就是pCurrent
- pCurrent->m_pLeft = pListIndex;
- if (NULL != pListIndex)
- pListIndex->m_pRight = pCurrent;
- else
- pHead = pCurrent; // 建立双向链表的头节点
- pListIndex = pCurrent; // 移动pListIndex指示到最新加入的节点
- cout<<pCurrent->m_nValue<<" ";
- }
- // 创建二元查找树
- void addBSTreeNode(BSTreeNode *& pCurrent, int value)
- {
- // 当前节点为空,则创建新节点并赋值
- if (NULL == pCurrent)
- {
- BSTreeNode * pBSTree = new BSTreeNode();
- pBSTree->m_pLeft = NULL;
- pBSTree->m_pRight = NULL;
- pBSTree->m_nValue = value;
- pCurrent = pBSTree;
- }
- // 当前节点不为空,则与value比较大小,决定放在左还是右
- else
- {
- if ((pCurrent->m_nValue) > value)
- addBSTreeNode(pCurrent->m_pLeft, value);
- else if ((pCurrent->m_nValue) < value)
- addBSTreeNode(pCurrent->m_pRight, value);
- else
- {
- // 假定不允许有重复值节点存在
- // cout<<"重复加入节点"<<endl;
- }
- }
- }
- // 遍历二元查找树中序将按照从小到大排序,符合要求
- void ergodicBSTree(BSTreeNode * pCurrent)
- {
- if (NULL == pCurrent)
- return;
- // 左子树不为空,则遍历左子树
- if (NULL != pCurrent->m_pLeft)
- ergodicBSTree(pCurrent->m_pLeft);
- // 节点接到链表尾部
- convertToDoubleList(pCurrent);
- // 右子树不为空,则遍历右子树
- if (NULL != pCurrent->m_pRight)
- ergodicBSTree(pCurrent->m_pRight);
- }
- // 打印双向链表
- void displayDoubleList(DoubleList * pHead)
- {
- pListIndex = pHead;
- while(NULL != pListIndex)
- {
- cout<<pListIndex->m_nValue<<" ";
- pListIndex= pListIndex->m_pRight;
- }
- cout<<endl;
- }
- int main()
- {
- BSTreeNode * pRoot = NULL;
- addBSTreeNode(pRoot, 10);
- addBSTreeNode(pRoot, 4);
- addBSTreeNode(pRoot, 6);
- addBSTreeNode(pRoot, 8);
- addBSTreeNode(pRoot, 12);
- addBSTreeNode(pRoot, 14);
- addBSTreeNode(pRoot, 15);
- addBSTreeNode(pRoot, 16);
- cout<<"List the converting result: "<<endl;
- ergodicBSTree(pRoot);
- cout<<endl<<"List the converting DoublList: "<<endl;
- displayDoubleList(pHead);
- cout<<"End!"<<endl;
- getchar();
- return 0;
- }
结果:
解二:
递归求解
- /*Title: 1.把二元查找树转变成排序的双向链表:解二
- Author: gocode
- Date: 2012-09-27*/
- /* 10
- / \
- 6 14
- / \ / \
- 4 8 12 16 */
- #include <iostream>
- using namespace std;
- // Node类
- class Node{
- public:
- int data;
- Node *left;
- Node *right;
- Node(int d = 0, Node *lr = NULL, Node *rr = NULL):data(d), left(lr), right(rr){}
- };
- // 创建二叉查找树
- // 先创建左右节点,然后创建父节点
- Node *create()
- {
- Node *root;
- /*Node *p7 = new Node(7);
- Node *p9 = new Node(9);
- Node *p8 = new Node(8, p7, p9);*/
- Node *p4 = new Node(4);
- Node *p8 = new Node(8);
- Node *p6 = new Node(6, p4, p8);
- Node *p12 = new Node(12);
- Node *p16 = new Node(16);
- Node *p14 = new Node(14, p12, p16);
- Node *p10 = new Node(10, p6, p14);
- root = p10;
- return root;
- }
- // 改变链接
- Node *change(Node *p, bool asRight)
- {
- if (!p)
- return NULL;
- // 遍历左子树
- // 把左孩子与父节点的关系调整成双向链表的左右链接
- Node *pLeft = change(p->left, false);
- if (pLeft)
- pLeft->right = p;
- p->left = pLeft;
- // 遍历右子树
- // 把右孩子与父节点的关系调整成双向链表的左右链接
- Node *pRight = change(p->right, true);
- if (pRight)
- pRight->left = p;
- p->right = pRight;
- // 最关键的一段程序,需要仔细理解
- // 如果是右孩子,并且有左子孩子,则一直移动当前指针到左叶子节点
- // 如果是左孩子,并且有右子孩子,则一直移动当前指针到右叶子结点
- // 比如在构建12<=>14<=>16的时候,节点14是根节点10的右孩子asRight==true,它有左子孩子,此时当前指针一直向左移动到最左子孩子12上,12是根节点的后继
- // 比如在构建4<=>6=>8的时候,节点6是根节点10的左孩子asRight==false,它有右子孩子,此时当前指针一直向右移动到最右子孩子8上,8是根节点的前驱
- // 比如在构建6<=>10<=>14的时候,节点10是根节点asRight可为ture或false,此时asRight==false,有右子孩子,此时当前指针一直向右移动到最右子孩子16上
- Node *r = p;
- if (asRight)
- {// 右子树的开头节点是根节点的后继,即左叶子结点
- while (r->left)
- r = r->left;
- }else{// 左子树的最末尾节点是根节点的前驱,即右叶子结点
- while (r->right)
- r = r->right;
- }
- return r;
- }
- void main(){
- Node *root = create();
- Node *tail = change(root, false); // 给出root节点,但是asRight==false,则返回二叉查找树最末尾节点
- while (tail)
- {
- cout << tail->data << " ";
- tail = tail->left;
- }
- cout << endl;
- root = create();
- Node *head = change(root, true); // 给出root节点,但是asRight==ture,则返回二叉查找树最开头节点
- while (head)
- {
- cout << head->data << " ";
- head = head->right;
- }
- cout << endl;
- getchar();
- }
总结:
解一在构建二叉查找树使用了addBSTreeNode函数,此方法可以方便的自动建立二叉查找树的关系。这比解二一个节点一个节点手工建立要省事,而且可以复用。解一利用中序遍历时,拆解重构左右链接从而构造出排序的双向链表。解二代码量少,可以通过改变asRight变量控制双向链表的输出。这充分利用了链表的灵活性,更是建立在对二叉查找树的遍历和链表构建有充分的正确认识。从理解难易程度看,解一更好理解些。