剑指Offer--027-二叉搜索树与双向链表
2016-04-24 17:18
337 查看
链接
牛客OJ:二叉搜索树与双向链表九度OJ:http://ac.jobdu.com/problem.php?pid=1503
GitHub代码: 027-二叉搜索树与双向链表
CSDN题解:剑指Offer–027-二叉搜索树与双向链表
牛客OJ | 九度OJ | CSDN题解 | GitHub代码 |
---|---|---|---|
二叉搜索树与双向链表 | 1503-二叉搜索树与双向链表 | 剑指Offer–027-二叉搜索树与双向链表 | 027-二叉搜索树与双向链表 |
题意
题目描述输入一棵二叉搜索树,将该二叉搜索树转换成一个排序的双向链表。要求不能创建任何新的结点,只能调整树中结点指针的指向。
分析
二叉排序树的每个节点均由两个指针指向其两个孩子,双向链表中每个节点又都有两个指针指向其前驱和后继二叉排序树的左节点的值 < 根结点的值 < 右子节点的值,其中序遍历就是一个排序好的信息串
因此我们可以通过如下两种方法来实现
* 中序遍历来实现二叉搜索树向双向链表的转换,访问过程需修改为链接操作
* 把左子树和右子树都转换成排序的双向链表之后再和根节点链接起来,整棵二叉搜索树就转换成了排序的双向链表
中序递归
采用中序遍历,而中序遍历中当前结点的前一个节点要么是当前结点的左子树的的最右孩子
要么是当前结点其前一个节点的右孩子
对于第二种,我们好判断,但是对于第一种方式,无法快速的找到其左子树的最右孩子,因此我们链接的时候需要保存其前驱节点,我们称之为lastNode节点
用递归函数的参数保存lastNode节点
在我们的递归函数中,我们用lastNode保存前一个访问的节点,我们采用中序访问的方式,将访问当前结点currNode的操作转换为将当前节点currNode与前一个节点lastNode链接成链表的方式。由于我们每次需要更新lastNode的值,因为我们传递的参数是指向lastNode的指针pLastNode
#include <iostream> using namespace std; // #define __tmain main #ifdef __tmain #define debug cout #else #define debug 0 && cout #endif // __tmain #ifdef __tmain struct TreeNode { int val; struct TreeNode *left; struct TreeNode *right; // TreeNode(int x) // :val(x), left(NULL), right(NULL) // { // } }; #endif // __tmain class Solution { public: /** * [Convert description] * @AuthorHTL * @DateTime 2016-04-23T21:02:08+0800 * @param pRootOfTree [二叉排序树的根] * @return [转换成的链表的头结点] */ TreeNode* Convert(TreeNode* pRootOfTree) { if(pRootOfTree == NULL) { return NULL; } TreeNode *pLastNode = NULL; ConvertRecursion(pRootOfTree, &pLastNode); // 当递归结束后,*pLastNode指向了双向链表的尾结点 TreeNode *node = pLastNode; while(pLastNode != NULL && pLastNode->left != NULL) { pLastNode = pLastNode->left; } return pLastNode; } /** * [ConvertRecursion 递归的将root为根的二叉排序树专户才能成双向链表] * @AuthorHTL * @DateTime 2016-04-23T22:33:00+0800 * @param root [当前递归的二叉排序树的根节点] * @param pLastNode [中序遍历中指向前一个节点的指针] */ void ConvertRecursion(TreeNode *root, TreeNode **pLastNode) { if(root == NULL) { return; } TreeNode *currNode = root; ////////////////////////// /// 首先中序 递归左子树 ////////////////////////// if(currNode->left != NULL) { ConvertRecursion(root->left, pLastNode); } ////////////////////////// /// 接着中序开始访问根节点 /// 在这里访问的过程演变为将左子树的根与根节点链接成一个链表 ////////////////////////// /// 开始链接左子树和当前根节点 // *pLastNode -=> currNode currNode->left = *pLastNode; // if(*pLastNode != NULL) { (*pLastNode)->right = currNode; } else { debug <<"lastNode is NULL" <<endl; } debug <<((*pLastNode == NULL) ? -1 : (*pLastNode)->val) <<", " <<currNode->val <<endl; system("PAUSE"); // 更新上一个访问的指针域 // 由于要更新前一个指针的指针域 // 因此需要使用其指针来修改其指向 *pLastNode = currNode; // 中序遍历 if(currNode->right != NULL) { ConvertRecursion(currNode->right, pLastNode); } } }; int __tmain( ) { // 4 // 3 5 //2 TreeNode tree[4]; tree[0].val = 4; tree[0].left = &tree[1]; tree[0].right = &tree[2]; tree[1].val = 3; tree[1].left = &tree[3]; tree[1].right = NULL; tree[2].val = 5; tree[2].left = NULL; tree[2].right = NULL; tree[3].val = 2; tree[3].left = NULL; tree[3].right = NULL; Solution solu; TreeNode *head = solu.Convert(tree); while(head != NULL) { cout <<head->val <<" "; head = head->right; } return 0; }
递归的过程中,用static的error的问题
还记得剑指Offer–025-二叉树中和为某一值的路径中递归方法另外一种写法的时候,递归的参数path我们用了一个static的局部变量来存储,但是在这里我们的尝试没有成功,因为这里当一次转换完成,lastNode并没有被清掉,因此进行下一次转换之前我们的结果会跟上一次的结果串在一起
#include <iostream> using namespace std; // #define __tmain main #ifdef __tmain #define debug cout #else #define debug 0 && cout #endif // __tmain #ifdef __tmain struct TreeNode { int val; struct TreeNode *left; struct TreeNode *right; /* TreeNode(int x) :val(x), left(NULL), right(NULL) { }*/ }; #endif // __tmain class Solution { public: /** * [Convert description] * @AuthorHTL * @DateTime 2016-04-23T21:02:08+0800 * @param pRootOfTree [二叉排序树的根] * @return [转换成的链表的头结点] */ TreeNode* Convert(TreeNode* pRootOfTree) { if(pRootOfTree == NULL) { return NULL; } TreeNode *pLastNode = ConvertRecursion(pRootOfTree); // 当递归结束后,*pLastNode指向了双向链表的尾结点 TreeNode *node = pLastNode; // debug <<pLastNode->val <<endl; while(pLastNode != NULL && pLastNode->left != NULL) { // debug <<pLastNode->val <<" "; pLastNode = pLastNode->left; } return pLastNode; } /** * [ConvertRecursion 递归的将root为根的二叉排序树专户才能成双向链表] * @AuthorHTL * @DateTime 2016-04-23T22:33:00+0800 * @param root [当前递归的二叉排序树的根节点] * @param pLastNode [中序遍历中指向前一个节点的指针] */ TreeNode* ConvertRecursion(TreeNode *root) { if(root == NULL) { return NULL; } TreeNode *currNode = root; static TreeNode *lastNode = NULL; ////////////////////////// /// 首先中序 递归左子树 ////////////////////////// if(currNode->left != NULL) { lastNode = ConvertRecursion(root->left); } ////////////////////////// /// 接着中序开始访问根节点 /// 在这里访问的过程演变为将左子树的根与根节点链接成一个链表 ////////////////////////// /// 开始链接左子树和当前根节点 // *pLastNode -=> currNode currNode->left = lastNode; // if(lastNode != NULL) { lastNode->right = currNode; } else { debug <<"lastNode is NULL" <<endl; } debug <<((lastNode == NULL) ? -1 : lastNode->val) <<", " <<currNode->val <<endl; // 更新上一个访问的指针域 // 由于要更新前一个指针的指针域 // 因此需要使用其指针来修改其指向 if(currNode != NULL) { lastNode = currNode; } // 中序遍历 if(currNode->right != NULL) { lastNode = ConvertRecursion(currNode->right); } return lastNode; } }; int __tmain( ) { // 4 // 3 //2 TreeNode tree[3]; tree[0].val = 4; tree[0].left = &tree[1]; tree[0].right = NULL; tree[1].val = 3; tree[1].left = &tree[2]; tree[1].right = NULL; tree[2].val = 2; tree[2].left = NULL; tree[2].right = NULL; Solution solu; TreeNode *head = solu.Convert(tree); while(head != NULL) { cout <<head->val <<" "; head = head->right; } TreeNode tree2[4]; tree2[0].val = 4; tree2[0].left = &tree2[1]; tree2[0].right = &tree2[2]; tree2[1].val = 3; tree2[1].left = &tree2[3]; tree2[1].right = NULL; tree2[2].val = 5; tree2[2].left = NULL; tree2[2].right = NULL; tree2[3].val = 2; tree2[3].left = NULL; tree2[3].right = NULL; Solution solu2; head = solu2.Convert(tree2); while(head != NULL) { cout <<head->val <<" "; head = head->right; } return 0; }
当然关于这个问题,我们也有其他的解决方法,比如将lastnode保存成一个成员对象
但是我们通过参数保存的方法,这种方式更具有一般性,对于那些面向过程性的语言,也能更好的移植
非递归中序遍历实现
用栈来模拟递归栈我们实现中序遍历的非递归版本,将输出的过程更改为双向链表的链接,主要是将前驱节点preNode和当前访问节点currNode链接起来,这个时候需要注意的是第一个节点的处理(head的前驱节点为NULL)
class Solution { public: TreeNode* Convert(TreeNode* root) { if(root == NULL) { debug <<"The tree is NULL..." <<endl; } stack<TreeNode *> nstack; TreeNode *currNode = root; TreeNode *preNode = NULL; TreeNode *head = NULL; // 开始遍历整个二叉树 while(currNode != NULL || nstack.empty() != true) { // 不输出当前根节点,但是递归直至当前根节点node的最左端 while(currNode != NULL) { nstack.push(currNode); currNode = currNode->left; } // 此时栈顶的元素是当前最左元素 // 它应该被输出 if(nstack.empty( ) != true) { currNode = nstack.top( ); debug <<currNode->val; nstack.pop( ); // 第一个节点 if(preNode == NULL) { head = currNode; preNode = currNode; } else { preNode->right = currNode; currNode->left = preNode; preNode = currNode; } currNode = currNode->right; } } return head; } };
递归将左右子树变换为链表后,将他们与根链接成链表
这个算法的流程就是1 递归root左子树,并且与root与左子树链接起来
递归将root的左子树变换为双向链表,并且返回左子树根在链表中的位置节点
通过左子树的根的最后的最右子节点,即左子树链表的最后一个节点是root的前驱
2 递归root右子树,并且与root与右子树链接起来
递归将root的右子树变换为双向链表,并且返回左子树根在链表中的位置节点
通过右子树的根的最后的最左子节点,即右子树链表的第一个节点是root的后继
#include <iostream> using namespace std; // #define __tmain main #ifdef __tmain #define debug cout #else #define debug 0 && cout #endif // __tmain #ifdef __tmain struct TreeNode { int val; struct TreeNode *left; struct TreeNode *right; // TreeNode(int x) // :val(x), left(NULL), right(NULL) // { // } }; #endif // __tmain class Solution { public: /** * [Convert description] * @AuthorHTL * @DateTime 2016-04-23T21:02:08+0800 * @param pRootOfTree [二叉排序树的根] * @return [转换成的链表的头结点] */ TreeNode* Convert(TreeNode* pRootOfTree) { if(pRootOfTree == NULL) { return NULL; } TreeNode *pLastNode = ConvertRecursion(pRootOfTree); // 当递归结束后,*pLastNode指向了双向链表的尾结点 TreeNode *node = pLastNode; while(pLastNode != NULL && pLastNode->left != NULL) { pLastNode = pLastNode->left; } return pLastNode; } /** * [ConvertRecursion 递归的将root为根的二叉排序树专户才能成双向链表] * @AuthorHTL * @DateTime 2016-04-23T22:33:00+0800 * @param root [当前递归的二叉排序树的根节点] */ TreeNode* ConvertRecursion(TreeNode *root) { if(root == NULL) { debug <<"NULL" <<endl; return NULL; } ////////////////////////// /// 首先 递归左子树 ////////////////////////// TreeNode *lastNode = NULL; if(root->left != NULL) { /// 递归将左子树转换为双向链表, 并且返回左子树的根节点 lastNode = ConvertRecursion(root->left); // 递归左子树完成后 // lastNode节点指向了左子树的根节点 // 但是root的前驱应该是原来左子树的最右子节点, 即转换成链表的最后一个节点, while(lastNode != NULL && lastNode->right != NULL) { lastNode = lastNode->right; } root->left = lastNode; // lastNode->right = root; /// 无需判断lastNode是否为NULL, 因为我们的函数为root = NULL的是不进行递归的 debug <<lastNode->val <<", " <<root->val <<endl; } ////////////////////////// /// 接着 递归右子树 ////////////////////////// TreeNode *nextNode = NULL; if(root->right != NULL) { // 递归将左子树转换为双向链表, 并且返回左子树的根节点 nextNode = ConvertRecursion(root->right); // 递归左子树完成后 // lastNode节点应该是其左子树的最右节点 // 即转换成链表的最后一个节点 while(nextNode->left != NULL) { nextNode = nextNode->left; } nextNode->left = root; root->right = nextNode; debug <<root->val <<", " <<nextNode->val <<endl; } return root; } }; int __tmain( ) { // 4 // 3 5 //2 TreeNode tree[4]; tree[0].val = 4; tree[0].left = &tree[1]; tree[0].right = &tree[2]; tree[1].val = 3; tree[1].left = &tree[3]; tree[1].right = NULL; tree[2].val = 5; tree[2].left = NULL; tree[2].right = NULL; tree[3].val = 2; tree[3].left = NULL; tree[3].right = NULL; Solution solu; TreeNode *head = solu.Convert(tree); while(head != NULL) { cout <<head->val <<" "; head = head->right; } return 0; }
相关文章推荐
- 查看github pages文档的方式
- 使用BAE的基于Web.py的简单博客程序
- [C/C++]反转链表
- 只有程序员看的懂的面试圣经|如何拿下编程面试
- 下一次技术面试时要问的 3 个重要问题
- 书评:《算法之美( Algorithms to Live By )》
- 动易2006序列号破解算法公布
- C#实现基于链表的内存记事本实例
- Ruby实现的矩阵连乘算法
- C#插入法排序算法实例分析
- C#模拟链表数据结构的实例解析
- PHP程序员面试 切忌急功近利(更需要注重以后的发展)
- 超大数据量存储常用数据库分表分库算法总结
- C#数据结构与算法揭秘二
- C#冒泡法排序算法实例分析
- 算法练习之从String.indexOf的模拟实现开始
- C#算法之关于大牛生小牛的问题
- C#实现的算24点游戏算法实例分析
- c语言实现的带通配符匹配算法