微软面试题解题笔记——二元查找树转排序双向链表

来源:互联网 发布:手机图片扫描软件 编辑:程序博客网 时间:2024/06/11 02:53

(题目来源于July的整理,十分感谢。July博客地址:http://blog.csdn.net/v_JULY_v。 本文后面附上了July整理提供的答案。小生第一次写博客,加上才疏学浅。所以文中错误和不足期望大家指正

题目:输入一棵二元查找树,将该二元查找树转换成一个排序的双向链表。要求不能创建任何新的结点,只调整指针的指向。 

           10          

          /     \         

       6       14       

       / \        / \     

     4  8   12 16

转换成双向链表4=6=8=10=12=14=16。

===================================================================================================================================

解题思想:在一棵二叉查找树中,对某个节点来说其左子树各节点的值都会比当前节点的值小,其右子树中的各节点的值都会比当前节点的大。所以先将左子树中的各节点排好后当前节点就可以直接插入到链表中。让后再将右子树以同样的方式排好。

实现方式:将树节点的左指针(left)作为链表的向前指针,将树节点的右指针(right)作为链表的向后指针。先访问一个树节点,如过该树节点有左子树则对左子树进行转换。转换完毕后将得到的链表尾指针和当前树节点进行双向连接。注意在此之前要保存该树节点的右子树指针否则会丢失右子树。链表连接完毕后开始转换右子树。

卡壳的地方:在使用递归函数的时候没有注意到对指针的处理。这里要保证每次转换的时候链表指针必须指向链表的末尾。否则就会发生先前已经排好的链表又被打乱。保证的方式是将链表的指针作为引用,这样当在递归某一层中链表被修改后在其他递归层的指针也会相应得到修改。

#include <iostream>using namespace std;typedef struct _Node {int data;_Node* left;_Node* right;}Node,*PNode;typedef _Node* Tree;typedef _Node* List; typedef List* PList;//根据输入的元素建立二元查找树void AddData(Tree& tree,const int data){if (tree == NULL){tree = new _Node;tree->data = data;tree->left = tree->right = NULL;return;}//如果插入的元素比当前节点的值大,则将元素插入到该节点的右子树中if (tree->data >= data){AddData(tree->left,data);}//否则插入到当前节点的左子树中else{AddData(tree->right,data);}}//打印二叉树,先序遍历void PrintTree(Tree tree){if (tree == NULL)return ;else{cout<<tree->data<<"->";PrintTree(tree->left);}PrintTree(tree->right);}//转换二元查找树void Turn(Tree tree,PList& plist){if (tree->left != NULL)Turn(tree->left,plist);List listtemp = *plist;if (*plist == NULL)*plist = tree;else(*plist)->left = tree;plist = &tree;if (tree->right != NULL)Turn(tree->right,plist);tree->right = listtemp;}List ChangeTree(Tree tree){List list = NULL,listtemp = NULL;PList plist = &list;Turn(tree,plist);return list;}void main(){cout<<"输入元素,-1结束:";Tree tree = NULL;bool bEnd = false;while(!bEnd){int data = 0;cin>>data;if (data == -1)bEnd = true;elseAddData(tree,data);}PrintTree(tree);cout<<endl;List list = ChangeTree(tree);for (;list;list = list->left){cout<<list->data<<"->";}system("pause");}
===================================================================================================================================

July提供的答案(July博客地址:http://blog.csdn.net/v_JULY_v):

#include <stdio.h>#include <iostream.h>struct BSTreeNode{int m_nValue; // value of nodeBSTreeNode *m_pLeft; // left child of nodeBSTreeNode *m_pRight; // right child of node};typedef BSTreeNode DoubleList;DoubleList * pHead;DoubleList * pListIndex;void convertToDoubleList(BSTreeNode * pCurrent);// 创建二元查找树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;}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);}}// 二叉树转换成listvoid convertToDoubleList(BSTreeNode * pCurrent){pCurrent->m_pLeft = pListIndex;if (NULL != pListIndex){pListIndex->m_pRight = pCurrent;}else{pHead = pCurrent;}pListIndex = pCurrent;cout<<pCurrent->m_nValue<<endl;}int main(){BSTreeNode * pRoot = NULL;pListIndex = NULL;pHead = 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);ergodicBSTree(pRoot);return 0;}


看答案后感悟:
①。在判断语句中将常量现在==的左边
②。对于二叉查找树,从小到大的顺序便是对这棵中序遍历的结果,只要意识到这一点问题会变得简单。
③。使用全局或者成员变量会减少编程的难度。这样保证链表当前指针都是统一的。
④。在使用递归函数时很多步骤是可以简化的。比如在建立二叉排序数时答案显得更简洁。

===================================================================================================================================

非递归二叉树中序遍历:由于在中序遍历二叉树时需要记录节点的返回信息。所以非递归式的中序遍历需要借助栈的帮助。在这里使用了STL中的stack这会减少重复开发的工作。
实现思想:
①。从根节点沿着树的左子树走并将经过的节点压入栈中,直到遇到某一节点其左子树为NULL,则访问该节点的数据。
②。然后进入到节点的回溯过程。检查该节点的右子树是否为NULL。如果为NULL则弹出栈顶。访问弹出的这个节点的数据,继续检查该节点的右子树是否为空。如此反复直到弹出的节点右子树非空进入到第①步。
③。注意遍历退出的条件,在第②步中如果某个节点右子树为NULL同时栈为空则表示已经遍历完所有节点。

#include <iostream>#include <stack>using namespace std;typedef struct _Node {int data;_Node* left;_Node* right;}Node,*PNode;typedef Node* Tree;Node* g_listHead = NULL;Node* g_currentNode = NULL;void AddNode(Tree& tree,int data){if (NULL == tree){tree = new Node;tree->left = tree->right = NULL;tree->data = data;return;}PNode* pCurNode = &tree;while(true){if ((*pCurNode)->data > data)pCurNode = &((*pCurNode)->left);elsepCurNode = &((*pCurNode)->right);if (NULL == *pCurNode){*pCurNode = new Node;(*pCurNode)->data = data;(*pCurNode)->left = (*pCurNode)->right = NULL;return;}}}//二叉树的非递归中序遍历需要借助栈来完成void PrintTree(Tree tree){stack<PNode> nodestack;if (NULL == tree)return;PNode pNode = tree;bool bEnd = false;do{if (NULL != pNode->left){nodestack.push(pNode);pNode = pNode->left;}else{while (true){cout<<pNode->data<<"->";if (NULL == pNode->right){if (nodestack.empty()){bEnd = true;break;}pNode = nodestack.top();nodestack.pop();}else{pNode = pNode->right;break;}}}}while(!bEnd);}void main(){Tree tree = NULL;AddNode(tree,10);AddNode(tree,6);AddNode(tree,14);AddNode(tree,4);AddNode(tree,8);AddNode(tree,12);AddNode(tree,16);PrintTree(tree);system("pause");}

===================================================================================================================================

总结:这题考察的点其实就是二叉树的中序遍历以及二元查找树的特性。比如我们在了解二元查找树的中序遍历便是按排序顺序遍历的结果。那么问题就转换到如何解决二叉树的中序遍历。在写算法时一定要思路清晰。万一运行不出结果试着放置断点单步调试看是否我们的代码跟着我们的思路在走。