二叉树系列 - 二叉搜索树 - 线性时间内把有序链表转化为BST

引言

本文来自于Google的一道题目:

how to merge two binary search tree into balanced binary search tree. how to merge two binary search tree into balanced binary search tree.. Let there be m elements in first tree and n elements in the other tree. Your merge function should take O(m+n) time and do it in place.

http://www.careercup.com/question?id=5261732222074880

要线性时间内完成,而且要求in place,不难想到把BST转换为Double linked list。

然后将两个dll merge。但是,存在线性时间复杂度的算法,把dll转化成BST吗?

下面就是本文要记述的内容,算法参考自

http://www.geeksforgeeks.org/in-place-conversion-of-sorted-dll-to-balanced-bst/

已排序的 Double linked list 转化为 BST

因为要求线性时间,所以必须保证DLL上的每个节点只被访问 consant 次,最好是一次。

既然要求每个节点只能被访问一次,那么从根节点构造肯定是不行的。这种算法让BST从叶节点开始被构造,通过灵活地运用递归,巧妙地实现了自底向上构造BST的过程,而且还能保证BST是平衡的。

#include<iostream>
#include<stack>
using namespace std;

struct BSTNode{
    int val;
    BSTNode *left;
    BSTNode *right;
    BSTNode(int v): val(v), left(NULL), right(NULL){}
};

class BSTtoDLL{
    public:
        BSTNode *func(BSTNode *root){
            BSTtoDLLCore(root);
            return head;
        }

    private:
        BSTNode* pre = NULL;
        BSTNode* head = NULL;
        void BSTtoDLLCore(BSTNode *root){
            if(!root) return;
            BSTtoDLLCore(root -> left);
            if(pre){
                pre -> right = root;
                root -> left = pre;
            }else{
                head = root;
            }
            pre = root;
            BSTtoDLLCore(root -> right);
        }
};

class DLLtoBalancedBST{
    public:
        BSTNode* func(BSTNode* head){
            if(!head) return head;
            int n = 0;
            for(BSTNode *p = head; p; ++n, p = p -> right);
            return DLLtoBalancedBSTCore(&head, n);
        }
    private:
        //DLL to BST in place, Time O(N), Space O(LogN) for stack, N is the amount of nodes.
        //DLL needs to be sorted.
        BSTNode* DLLtoBalancedBSTCore(BSTNode** headref, int n){
            if(n == 0) return NULL;
            BSTNode* left = DLLtoBalancedBSTCore(headref, n/2);
            BSTNode *root = *headref;
            root -> left = left;
            *headref = root -> right;
            root -> right = DLLtoBalancedBSTCore(headref, n-n/2-1);
            return root;
        }

};

void InorderPrint(BSTNode* root){
    if(!root) return;
    stack<BSTNode *> st;
    while(!st.empty() || root){
        if(!root){
            root = st.top();
            st.pop();
            cout << root -> val << ‘ ‘;
            root = root -> right;
        }else{
            st.push(root);
            root = root -> left;
        }
    }
    cout << endl;
}

int main(){

    //Construct oringal BST
    BSTNode *root = new BSTNode(5);
    BSTNode *left3 = new BSTNode(3);
    BSTNode *left1 = new BSTNode(1);
    BSTNode *left2 = new BSTNode(2);
    BSTNode *right6 = new BSTNode(6);
    BSTNode *right7 = new BSTNode(7);

    root -> left = left2;
    left2 -> left = left1;
    left2 -> right = left3;
    root -> right = right7;
    right7 -> left = right6;

    cout << "-------Inorder print BST-------\n";
    InorderPrint(root);

    //Convert BST to DLL
    BSTtoDLL bstdll;
    BSTNode *head = bstdll.func(root);
    BSTNode *p = head;
    cout << "-------print converted double linked list----------\n";
    for(; p->right != NULL; cout << p -> val << ‘ ‘, p = p -> right);
    cout << endl;
    for(; p != NULL; cout << p -> val << ‘ ‘, p = p -> left);
    cout << endl;

    //Convert DLL back to Balenced BST
    DLLtoBalancedBST dllbst;
    BSTNode *con_root = dllbst.func(head);
    cout << "-------Inorder print converted BST-------\n";
    InorderPrint(con_root);

    return 0;
}

高亮部分为转化过程。

每次递归,headaddr这个指向节点的指针向末尾移动一次。因此每个节点只被访问一次,时间复杂度是线性的。

我们可以发现,这种算法对单向链表也适用。当然单链表不能保证in place,必须新申明节点,但是时间复杂度依然是线性的。

再推而广之,对于给定一个只能向 next 移动的iterator,通过这个算法也能构造将 iterator 经过的节点构造为BST。不过我们需要存下iterator的起始位置,因为算法需要先遍历一边记下节点的数量。

下面给出单向链表上的实现。

已排序的单向Linked list 转化为BST

#include<iostream>
#include<stack>
using namespace std;

struct ListNode{
    int val;
    ListNode* next;
    ListNode(int v): val(v), next(NULL){}
};

struct BSTnode{
    int val;
    BSTnode* left;
    BSTnode* right;
    BSTnode(int v): val(v), left(NULL), right(NULL){}
};

BSTnode *LLtoBSTCore(ListNode **headaddr, int n){
    if(n <= 0) return NULL;
    BSTnode *left = LLtoBSTCore(headaddr, n/2);
    BSTnode *root = new BSTnode((*headaddr) -> val);
    root -> left = left;
    *headaddr = (*headaddr) -> next;
    root -> right = LLtoBSTCore(headaddr, n-n/2-1);
    return root;
}

BSTnode *LLtoBST(ListNode *head){
    if(!head) return NULL;
    int n = 0; ListNode *p = head;
    for(; p; ++n, p = p -> next);
    return LLtoBSTCore(&head, n);
}

int main(){
    ListNode *head = new ListNode(1);
    ListNode *end = head;
    for(int i = 2; i <= 9; end -> next = new ListNode(i++), end = end -> next);
    cout << "List: \n";
    for(ListNode *p = head; p; cout << p -> val << ‘ ‘, p = p -> next);
    cout << endl;

    BSTnode *root = LLtoBST(head);

    cout << "BST inorder: " << endl;
    stack<BSTnode *> st;
    while(!st.empty() || root){
        if(!root){
            root = st.top();
            st.pop();
            cout << root -> val << " ";
            root = root -> right;
        }else{
            st.push(root);
            root = root -> left;
        }
    }
    cout << endl;

}

高亮部分为转化过程。

时间: 2024-10-23 03:42:34

二叉树系列 - 二叉搜索树 - 线性时间内把有序链表转化为BST的相关文章

二叉树、二叉搜索树、AVL树的java实现

数据结构一直都是断断续续的看,总是觉得理解的不够深入,特别是对树的理解,一直都很浅显,今儿又看了一遍,来做个总结吧. 首先,树中的一些概念: 1.树的节点包含一个数据元素,以及若干指向其子树的分支.节点拥有的子树的数量称为节点的度.节点的最大层次称为树的深度或高度. 2.二叉树是一种树形结构,其特点是每个节点至多有两棵子树,且子树有左右之分,次序不能随意颠倒. 3.满二叉树:一棵深度为k且有2^k - 1个节点的二叉树,称之为满二叉树. 4.完全二叉树:对一个深度为k,节点个数为n的二叉树,当且

数据结构学习笔记04树(二叉树、二叉搜索树、平衡二叉树)

一.树 树的基本术语 ①结点的度(Degree):结点的子树个数 ②树的度:树的所有结点中最大的度数 ③叶结点(Leaf):度为0的结点 ④父结点(Parent):有子树的结点是其子树的根结点的父结点 ⑤子结点(Child):若A结点是B结点的父结点,则称B结点是A结点的子结点:子结点也称孩子结点. ⑥兄弟结点(Sibling):具有同一父结点的各结点彼此是兄弟结点. ⑦路径和路径长度:从结点n1到nk的路径为一个结点序列n1 , n2 ,… , nk , ni是 ni+1的父结点.路径所包含边

二叉树之二叉搜索树(BSTree)

二叉搜索树(Binary Search Tree) 二叉搜索树是一种二叉树,子结点数量上限为2:为了方便搜索,结点是有序的,左结点的key不大于父节点,右节点的key不小于父节点,且左右子树也是二叉搜索树. 下面是一个二叉搜索树的样图,来自维基 一棵树一般都要提供一些函数,例如遍历.搜索.最大最小值.插入,删除,销毁等 代码含注释,下面是输出效果(msys2) 代码 开发环境:Qt Creator 4.8.2 Mingw64 7.3 windows 8.1 完整代码:https://github

剑指Offer对答如流系列 - 二叉搜索树的后序遍历序列

面试题33:二叉搜索树的后序遍历序列 题目描述 输入一个整数数组,判断该数组是不是某二叉搜索树的后序遍历的结果.如果是则返回true,否则返回false.假设输入的数组的任意两个数字都互不相同. 例如,输入数组{5.7.6.9.11.10.8},则返回true,因为这个整数序列是下图二叉搜索树的后序遍历结果. 如果输入的数组是{7.4.6.5},则由于没有哪棵二叉搜索树的后序遍历是这个序列,因此返回false. 问题分析 后序遍历 遍历顺序是 :左子树 -> 右子树 -> 根结点(最后遍历根节

【二叉树】二叉搜索树

二叉搜索树: 1.每个节点都有一个关键码(key)作为搜索依据,关键码互不相同. 2.左子树的所有关键码都小于根节点的关键码. 3.右子树的所有关键码都大于根节点的关键码. 4.左右子树都是二叉搜索树. 删除key:左为空,右为空,左右都不空 1)左为空:cur的右树链到父节点 2)右为空:cur的左树链到父节点 3)左右都不空:找右树最左节点或左树最右节点,将找到的节点与cur交换后删除它. 二叉搜索树的增.删.查(非递归及递归)程序代码如下: #pragma once #include<st

剑指Offer对答如流系列 - 二叉搜索树的第k个结点

面试题54:二叉搜索树的第k个结点 题目描述 给定一棵二叉搜索树,请找出其中的第k小的结点. 例如,图中的二叉搜索树,按节点值大小顺序,第三大节点的值是4. 二叉搜索树的节点定义 public class Node { int val = 0; Node left = null; Node right = null; public Node(int val) { this.val = val; } } 问题分析 二叉搜索树中序遍历的结果是顺序的.我们可以设置一个全局变量index,对二叉搜索树进

把二叉搜索树转化成更大的树 &#183; Convert BST to Greater Tree

[抄题]: 给定二叉搜索树(BST),将其转换为更大的树,使原始BST上每个节点的值都更改为在原始树中大于等于该节点值的节点值之和(包括该节点). Given a binary search Tree `{5,2,13}`: 5 / 2 13 Return the root of new tree 18 / 20 13 [暴力解法]: 时间分析: 空间分析: [思维问题]: [一句话思路]: 反向求和并把和赋给root.val [输入量]:空: 正常情况:特大:特小:程序里处理到的特殊情况:异常

二叉树之二叉搜索树的基本操作实现

这篇文章用来回顾二叉搜索数的以下操作: 遍历 前序遍历 中序遍历 后序遍历 层序遍历 查找 查找最大值 查找最小值 查找指定值 获取指定属性 获取总节点/叶节点数量 获取二叉树的高度(根的高度为1) 行为操作 插入 删除 二叉树的结构定义: 1 struct TreeNode{ 2 TreeNode():data(),left(nullptr),right(nullptr){} 3 ELEMENT data; 4 SearchTree left; 5 SearchTree right; 6 };

B-Tree 漫谈 (从二叉树到二叉搜索树到平衡树到红黑树到B树到B+树到B*树)

关于B树的学习还是需要做点笔记. B树是为磁盘或者其他直接存取辅助存储设备而设计的一种平衡查找树.B树与红黑树的不同在于,B树可以有很多子女,从几个到几千个.比如一个分支因子为1001,高度为2的B树,他可以存储超过10亿个关键字,尽管如此,因为根节点(只有一个)保留在主存中,故这可书中,寻找某一个关键字之多需要两次磁盘存取. 关于磁盘的结构,以及写入,读取数据的原理,这里就略过了. 一.概述: 1) 对于B树的每个节点x有: a)n[x],当前存储在结点x中的关键字数, b)关键字以非降序存放