递归一题三解-将二分查找树(BST)转化成循环双链表(DLL)
来源:互联网 发布:码市和程序员客栈 编辑:程序博客网 时间:2024/06/06 05:19
题目来自leetcode: 已知一个BST(binary search tree), 将其原地转化成一个循环的排序过的双链表(circular sorted double linked list)。
说明:BST的节点有两个指针left, right, 分别指向比它小,和比它大的节点。变成DLL之后,由于DLL节点原本有prev 和 next 指针分别和之前和之后的节点,这里假定原left指针指向之前,原right 指向之后节点。关于题意可以参考下图:
图一. BST to DLL 示例(转)
如图一所示,黑线是原本的BST中left和right指针,红色箭头表示转化成DLL后的next指针(借用原right指针)。
可以感觉到,大体是个遍历BST的过程,天生适合递归。本文会介绍三种解法,虽然都使用递归,但思路各有不同。
方法一:这个方法是我原创的,借鉴了中序遍历(in-order)遍历的思想。
附中序遍历:
void InOrder(node* root){ //LRV InOrder(root->left); display(root->val); InOrder(root->right);}中序遍历中,递归的顺序依次是left, curr, right。考虑到当前节点(curr)对于后续节点(nextt)即意味着前向节点(prev),为了保证在每个递归函数中均匀处理,首先处理curr节点与prev节点间的关联,然后将curr作为前向节点传给右子树节点,最后返回curr所在子树的尾节点给后续。
沿用图一中的BST作为已知。图二展示了当前处理节点2的情况,在函数结束时,将自身作为前向节点去处理节点3。
图二. getTail()
对于DLL的头节点(head), 在递归函数的调用栈中,最底层调用(即最左边叶子节点)时,它的prev为空,因此它就是整个DLL的head。 对于DLL的尾节点(tail),这里最顶层递归函数返回的就是tail,所以这里等顶层递归函数返回后,再将头节点和尾节点链接起来。实现代码如下:
node* getTail(node* curr, node*& pPrev){ if(curr==0) return 0; node* tmp = getTail(curr->left, pPrev); if(tmp==0){ if(pPrev==0){ pPrev = curr; //head of sorted DLL }else{ pPrev->right = curr; curr->left = pPrev; } }else{ tmp->right = curr; curr->left = tmp; } tmp = getTail(curr->right, curr); return tmp==0 ? curr : tmp;}node* BST2SortedDLL_01(node* root){ node *head = 0, *tail = 0; tail = getTail(root, head); if(head==0 || tail==0){ return 0; } tail->right = head; head->left = tail; return head;}
方法二:来自leetcode网站。依然借鉴了中序遍历的思维,不过递归函数不再返回节点给后续,而是在函数体内部就将自身链接成一个闭环的DLL。这样每次都在尾部新插入一个节点,并将头节点跟它链接起来。
图三. bstToDLL(), 插入节点3,和插入节点4
void bstToDLL(node *p, node*& prev, node*& head){ if(!p) return; bstToDLL(p->left, prev, head); p->left = prev; //link p and its predecessor(prev) if(prev) prev->right = p; else head = p; node *right = p->right; //head stays as the real "head" of DLL, it linked to p in every statement call. as a result, it is linked to head->left = p; //real "tail" in final function call p->right = head; prev = p; //p as the prev of next function call bstToDLL(right, prev, head);}node* BST2SortedDLL_02(node* root){ node *prev = 0; node *head = 0; bstToDLL(root, prev, head); return head;}bstToDLL()的函数实现中,head作为整个双向链表的头节点,在第一次被赋值之后,作为引用永远不变的传递下去。每次将新插入的节点(即目前的尾节点)作为下一个新节点的前向传递下去。由于没有返回值,所以记得每次都要将头节点跟当前新插入的节点链接,以形成闭环。
方法三:来自leetcode转载,出处在此。这个方法的特点在于利用了分治(divide-and-conquer)的思维,而没有考虑中序遍历。每次把一个节点的左子树,自身节点,右子树都变成一个闭环的双向链表,然后一个一个再链接起来,最后形成一个全树的闭环双向链表。当然,递归是必不可少的。
图四. append() 和 join()
下面是完整代码实现。图四是我根据代码画的示意图,可以帮助理解有关函数。
void join(node* a, node* b){ //link a to b as predecessor of b a->right = b; b->left = a;}node* append(node* a, node* b){//convert alast->a,blast->b to alast->b, blast->a if(a==0) return b; if(b==0) return a; node *aLast = a->left; node *bLast = b->left; join(aLast, b); join(bLast, a); return a;}node* BST2SortedDLL_03(node* root){ if(root==0) return 0; node *aList = BST2SortedDLL_03(root->left); node *bList = BST2SortedDLL_03(root->right); root->left = root; //unlink root to append to left half, and append right half to left half seperately root->right = root; aList = append(aList, root); aList = append(aList, bList); return aList;}
不断的合并两个已有的闭环双向链表,需要更多的对于整个问题的大局观,这个解法的确很酷。
小结:
1. 二叉树相关问题,天生适用递归。事实上,树这个概念,就是用递归来定义的。
2. 递归方法,实质是将一个许多步的处理问题,按照某种方式分配成很多份,每一份由一次函数调用来实现。那么我们在设计递归函数中,首先需要考虑如何分配这些处理。比如方法一和方法二,每一次递归函数,仅仅处理(插入)一个新节点进双向链表;方法三中,将当前的左子树,右子树分别放进递归函数中处理。
3. 递归函数是否需要返回值因题而异。很多时候,返回值有助于简化递归函数内部的处理,如方法一。如果有返回值,记得在最顶层的递归函数返回后进行必要处理。如果没有返回值,记得在递归函数内部加以处理,如方法二。
4. 递归函数要特别注意边界情况。最可怕的就是缺乏退出条件从而造成无限循环,那简直是噩梦。
- 递归一题三解-将二分查找树(BST)转化成循环双链表(DLL)
- 二分查找树转化为排序的循环双链表
- 二分查找树(BST)
- 二分查找 递归实现 和 循环实现
- 二分查找的循环和递归方法
- 二分查找法(递归+循环)
- 递归将树转化成json字符串
- Java 二叉查找树转化为排序的循环双链表
- 二分查找法的C语言实现:递归与循环
- 二分查找法(循环与递归分别实现)
- 高性能二分查找BinarySearch循环取代递归版
- 二分查找的循环实现和递归实现
- 算法:两种方式(递归/循环)实现二分查找
- 二分查找法(递归与循环实现)
- 二分查找算法的递归、循环实现及其缺陷
- 二分查找的两种实现方式--循环和递归
- 二分查找的循环版本和递归版本
- 二分查找 (循环、递归两种方法)
- Linked List Cycle II
- 项目中导入ActionBar的android-support-v7-appcompat.jar包
- JAVA中发送邮件方法
- iOS程序完成后如何生成ipa进行真机测试
- dbcp重连问题排查
- 递归一题三解-将二分查找树(BST)转化成循环双链表(DLL)
- PHP_XML_Expat
- 黄豆
- IOCP 简单服务器和客户端
- C和指针
- 双路由端口映射
- NYOJ-456 邮票分你一半 AC
- linux时间函数总结
- 推荐一个Flash Builder格式化插件,非常强大