二叉搜索树转换成有序的双向循环链表(一)

来源:互联网 发布:小米查看支持什么网络 编辑:程序博客网 时间:2024/05/18 00:38

题目描述

给定一棵二叉排序树(BST),将该树转换成一棵双向循环链表。请看下面的图示说明,你可以更清楚的了解题意。

BST的结构定义如下:

struct node {    int data;    struct node* left;    struct node* right;};typedef struct node Node;


                                     图1)一棵存储1-5的二叉搜索树(BST)

 

        图2)根据上面的BST转换得到的有序循环链表。其中,树的左右孩子指针替换成了pre和next指针,分别指向链表的前一个和后一个结点。

 

 分析

看到这个题目,相信大多数人第一反应就是中序遍历这棵二叉树,同时改变树中结点的left和right指针。这里需要额外考虑的是如何将最后一个结点的right指针指向第一个结点,如下图所展示的那样。

                                             图3)一个双向循环链表
以中序遍历遍历一棵二叉树的时候,每遍历到一个结点,我们就可以修改该结点的left指针指向前一个遍历到的结点,因为在后续操作中我们不会再用到left指针;与此同时,我们还需要修改前一个遍历结点的right指针,让前一个遍历结点的right指针指向当前结点。比如我们遍历到结点2,则我们修改结点2的left指针指向结点1,同时需要修改结点1的right指针指向结点2。需要注意一点,这里的前一个遍历结点不是当前结点的父结点,而是当前结点的前一个比它小的结点。

看似问题已经解决,慢着,我们其实落下了重要的两步。1)我们没有对头结点head赋值。 2)最后一个结点的right指针没有指向第一个结点。

解决这两个问题的方案非常简单:在每次递归调用的时候,更新当前遍历结点的right指针让其指向头结点head,同时更新头结点head的left指针让其指向当前遍历结点。当递归调用结束的时候,链表的头尾结点会指向正确的位置。不要忘记只有一个结点的特殊情况,它的left和right指针都是指向自己。

      图4)只有一个结点的双向循环链表

这个解法非常的精巧,因为该算法是对中序遍历的一个改进,因此它的时间复杂度为O(N),N为结点数目。当然,相比中序遍历,我们在每次递归调用过程中增加了额外的赋值操作。
// 这是一个改进的中序遍历// prev (初始化为NULL) 用于跟踪前一个遍历结点// 当递归结束时,head指向链表的头部void treeToDoublyList(Node *p, Node *& prev, Node *& head) {  if (!p) return;  treeToDoublyList(p->left, prev, head);  // 当前结点p的left指向前一个结点prev  p->left = prev;  if (prev)    prev->right = p;  //前一个结点的right指向当前结点  else    head = p; //如果前面没有结点,则设置head为当前结点(当前结点为最小的结点)。  //递归结束后,head的left指针指向最后一个结点,最后一个结点的右指针指向head结点。注意保存p的right指针,因为在后面代码中会修改该指针。  Node *right = p->right;  head->left = p;  p->right = head;  prev = p;//更新前一个结点  treeToDoublyList(right, prev, head); } //主函数,初始化prev和head为NULLNode* treeToDoublyList(Node *root) {  Node *prev = NULL;  Node *head = NULL;  treeToDoublyList(root, prev, head);  return head;}

代码解析

这个递归算法挺复杂的,还是从函数功能上来理解会容易懂一点。函数treeToDoublyList(Node *p, Node *&prev, Node *&head)的功能是将以p为根结点的树转换成有序的双向循环链表。函数执行前,prev总是指向遍历到p之前的前一个结点。函数执行完成后,prev指向p,head指向链表头结点。

以上面图中的二叉树为例,treeToDoublyList(Node *p, Node *&prev, Node *&head)过程如下:根结点为4,调用treeToDoublyList(p->left, prev, head)遍历完左子树后,左子树构成有序双向循环链表1=2=3,prev指向的是结点3,head指向当前双向循环链表的头结点1。更新p->left=prev,即4的left指针指向3,设置prev->right=p,即3的right指针指向4。同时更新head结点1的left为节点4,4的right指针更新为1,则此时双向循环链表变成1=2=3=4,接下来递归遍历右子树结点,保持双向循环链表的性质。

原创粉丝点击