Leetcode Lect4 二叉树中的分治法与遍历法

在这一章节的学习中,咱们将要学习一个数据结构——二叉树(Binary Tree),和基于二叉树上的搜索算法。html

在二叉树的搜索中,咱们主要使用了分治法(Divide Conquer)来解决大部分的问题。之因此大部分二叉树的问题可使用分治法,是由于二叉树这种数据结构,是一个自然就帮你作好了分治法中“分”这个步骤的结构。java

本章节的先修内容有:node

  • 什么是递归(Recursion)—— 请回到第二章节中复习
  • 递归(Recursion)、回溯(Backtracking)和搜索(Search)的联系和区别
  • 分治法(Divide and Conquer)和遍历法(Traverse)的联系和区别
  • 什么是结果类 ResultType,何时使用 ResultType
  • 什么是二叉查找树(Binary Search Tree)
  • 什么是平衡二叉树(Balanced Binary Tree)

本章节的补充内容有:面试

  • Morris 算法:使用 O(1) 的额外空间复杂度对二叉树进行先序遍历(Preorder Traversal)
  • 用非递归的方法实现先序遍历,中序遍历和后序遍历
  • 二叉查找树(Binary Search Tree)的增删查改
  • Java 自带的平衡排序二叉树 TreeMap / TreeSet 的介绍和面试中的应用

 

二叉树上的遍历法

定义

遍历(Traversal),顾名思义,就是经过某种顺序,一个一个访问一个数据结构中的元素。好比咱们若是须要遍历一个数组,无非就是要么从前日后,要么从后往前遍历。可是对于一棵二叉树来讲,他就有不少种方式进行遍历:算法

  1. 层序遍历(Level order)
  2. 先序遍历(Pre order)
  3. 中序遍历(In order)
  4. 后序遍历(Post order)

咱们在以前的课程中,已经学习过了二叉树的层序遍历,也就是使用 BFS 算法来得到二叉树的分层信息。经过 BFS 得到的顺序咱们也能够称之为 BFS Order。而剩下的三种遍历,都须要经过深度优先搜索的方式来得到。而这一小节中,咱们将讲一下经过深度优先搜索(DFS)来得到的节点顺序,数据库

先序遍历 / 中序遍历 / 后序遍历

先序遍历(又叫先根遍历、前序遍历)

首先访问根结点,而后遍历左子树,最后遍历右子树。遍历左、右子树时,仍按先序遍历。若二叉树为空则返回。api

该过程可简记为根左右,注意该过程是递归的。如图先序遍历结果是:ABDECF数组

1 // 将根做为root,空ArrayList做为result传入,便可获得整棵树的遍历结果
2 private void traverse(TreeNode root, ArrayList<Integer> result) {
3     if (root == null) {
4         return;
5     }
6     result.add(root.val);
7     traverse(root.left, result);
8     traverse(root.right, result);
9 }
View Code

中序遍历(又叫中根遍历)

首先遍历左子树,而后访问根结点,最后遍历右子树。遍历左、右子树时,仍按中序遍历。若二叉树为空则返回。简记为左根右
上图中序遍历结果是:DBEAFC
核心代码:
Java:markdown

1 private void traverse(TreeNode root, ArrayList<Integer> result) {
2     if (root == null) {
3         return;
4     }
5     traverse(root.left, result);
6     result.add(root.val);  // 注意访问根节点放到了遍历左子树的后面
7     traverse(root.right, result);
8 }
View Code

后序遍历(又叫后根遍历)

首先遍历左子树,而后遍历右子树,最后访问根结点。遍历左、右子树时,仍按后序遍历。若二叉树为空则返回。简记为左右根
上图后序遍历结果是:DEBFCA网络

1 private void traverse(TreeNode root, ArrayList<Integer> result) {
2     if (root == null) {
3         return;
4     }
5     traverse(root.left, result);
6     traverse(root.right, result);
7     result.add(root.val);  // 注意访问根节点放到了最后
8 }
View Code

一些有趣的题目:

http://www.lintcode.com/problem/construct-binary-tree-from-inorder-and-postorder-traversal/
http://www.lintcode.com/problem/construct-binary-tree-from-preorder-and-inorder-traversal/


二叉树上的分治法

定义

分治法(Divide & Conquer Algorithm)是说将一个大问题,拆分为2个或者多个小问题,当小问题获得结果以后,合并他们的结果来获得大问题的结果。

举一个例子,好比中国要进行人口统计。那么若是使用遍历(Traversal)的办法,作法以下:

人口普查员小张本身一我的带着一个本子,跑遍全中国挨家挨户的敲门查户口

而若是使用分治法,作法以下:

  1. 国家统计局的老板小李想要知道全国人口的总数,因而他找来全国各个省的统计局领导,下派人口普查任务给他们,让他们各自去统计本身省的人口总数。在小李这儿,他只须要最后将各个省汇报的人口总数结果累加起来,就获得了全国人口的数目。
  2. 而后每一个省的领导,又找来省里各个市的领导,让各个市去作人口统计。
  3. 市找县,县找镇,镇找乡。最后乡里的干部小王挨家挨户敲门去查户口。

在这里,把全国的任务拆分为省级的任务的过程,就是分治法中的这个步骤。把各个小任务派发给别人去完成的过程,就是分治法中的这个步骤。可是事实上咱们还有第三个步骤,就是将小任务的结果合并到一块儿的过程,这个步骤。所以若是我来取名字的话,我会叫这个算法:分治合算法

为何二叉树的问题适合使用分治法?

在一棵二叉树(Binary Tree)中,若是将整棵二叉树看作一个大问题的话,那么根节点(Root)的左子树(Left subtree)就是一个小问题,右子树(Right subtree)是另一个小问题。这是一个自然就帮你完成了“分”这个步骤的数据结构。

二叉树的最大深度

 

 

判断平衡二叉树

 

判断二叉搜索树

 

 

 


递归,分治法,遍历法的联系与区别

联系

分治法(Divide & Conquer)与遍历法(Traverse)是两种常见的递归(Recursion)方法。

分治法解决问题的思路

先让左右子树去解决一样的问题,而后获得结果以后,再整合为整棵树的结果。

遍历法解决问题的思路

经过前序/中序/后序的某种遍历,游走整棵树,经过一个全局变量或者传递的参数来记录这个过程当中所遇到的点和须要计算的结果。

两种方法的区别

从程序实现角度分治法的递归函数,一般有一个返回值,遍历法一般没有。

 

递归、回溯和搜索

什么是递归 (Recursion) ?

不少书上会把递归(Recursion)看成一种算法。事实上,递归是包含两个层面的意思的:

  1. 一种由大化小,由小化无的解决问题的算法。相似的算法还有动态规划(Dynamic Programming)。
  2. 一种程序的实现方式。这种方式就是一个函数(Function / Method / Procedure)本身调用本身。

与之对应的,有非递归(Non-Recursion)和迭代法(Iteration),你能够认为这两个概念是同样的概念(番茄和西红柿的区别)。不须要作区分。

什么是搜索 (Search)?

搜索分为深度优先搜索(Depth First Search)和宽度优先搜索(Breadth First Search),一般分别简写为 DFS 和 BFS。搜索是一种相似于枚举(Enumerate)的算法。好比咱们须要找到一个数组里的最大值,咱们能够采用枚举法,由于咱们知道数组的范围和大小,好比经典的打擂台算法:

int max = nums[0];
for (int i = 1; i < nums.length; i++) {
    max = Math.max(max, nums[i]);
}

枚举法一般是你知道循环的范围,而后能够用几重循环就搞定的算法。好比我须要找到 全部 x^2 + y^2 = K 的整数组合,能够用两重循环的枚举法:

// 不要在乎这个算法的时间复杂度
for (int x = 1; x <= k; x++) {
    for (int y = 1; y <= k; y++) {
        if (x * x + y * y == k) {
            // print x and y
        }
    }
}

而有的问题,好比求 N 个数的全排列,你可能须要用 N 重循环才能解决。这个时候,咱们就倾向于采用递归的方式去实现这个变化的 N 重循环。这个时候,咱们就把算法称之为搜索。由于你已经不能明确的写出一个不依赖于输入数据的多重循环了。

一般来讲 DFS 咱们会采用递归的方式实现(固然你强行写一个非递归的版本也是能够的),而 BFS 则无需递归(使用队列 Queue + 哈希表 HashMap就能够)。因此咱们在面试中,若是一个问题既可使用 DFS,又可使用 BFS 的状况下,必定要优先使用 BFS。由于他是非递归的,并且更容易实现。

什么是回溯(Backtracking)?

有的时候,深度优先搜索算法(DFS),又被称之为回溯法,因此你能够彻底认为回溯法,就是深度优先搜索算法。在个人理解中,回溯其实是深度优先搜索过程当中的一个步骤。好比咱们在进行全子集问题的搜索时,假如当前的集合是 {1,2} 表明我正在寻找以 {1,2}开头的全部集合。那么他的下一步,会去寻找 {1,2,3}开头的全部集合,而后当咱们找完全部以 {1,2,3} 开头的集合时,咱们须要把 3 从集合中删掉,回到 {1,2}。而后再把 4 放进去,寻找以 {1,2,4} 开头的全部集合。这个把 3 删掉回到 {1,2} 的过程,就是回溯。

subset.add(nums[i]);
subsetsHelper(result, subset, nums, i + 1);
subset.remove(list.size() - 1) // 这一步就是回溯

详情请参考:
http://www.jiuzhang.com/solutions/subsets/

 


递归三要素

咱们以《二叉树的最大深度》和《二叉树的前序遍历》两个题目为例子,来分析一下递归的三要素

相关题目连接:

http://www.lintcode.com/problem/maximum-depth-of-binary-tree/
http://www.lintcode.com/problem/binary-tree-preorder-traversal/

1. 递归的定义

每个递归函数,都须要有明确的定义,有了正确的定义之后,才可以对递归进行拆解。

例子:
Java:

int maxDepth(TreeNode root) 

Python:

def maxDepth(root): 

表明 以 root 开头的子树的最大深度是多少
Java:

void preorder(TreeNode root, List<TreeNode> result) 

Python:

def preorder(root, result): 

表明 将 root 开头的子树的前序遍历放到 result 里面

2. 递归的拆解

一个大问题如何拆解为若干个小问题去解决。

例子:
Java:

int leftDepth = maxDepth(root.left); int rightDepth = maxDepth(root.right); return Math.max(leftDepth, rightDepth) + 1; 

Python:

leftDepth = maxDepth(root.left)
rightDepth = maxDepth(root.right)
return max(leftDepth, rightDepth) + 1 

整棵树的最大深度,能够拆解为先计算左右子树深度,而后在左右子树深度中找到最大值+1来解决。
Java:

result.add(root);
preorder(root.left, result);
preorder(root.right, result);

Python:

result.append(root)
preorder(root.left, result)
perorder(root.right, result)

一棵树的前序遍历能够拆解为3个部分:

  1. 根节点本身(root)
  2. 左子树的前序遍历
  3. 右子树的前序遍历

因此对应的,咱们把这个递归问题也拆分为三个部分来解决:

  1. 先把 root 放到 result 里 --> result.add(root);
  2. 再把左子树的前序遍历放到 result 里 --> preorder(root.left, result)。回想一下递归的定义,是否是正是如此?
  3. 再把右子树的前序遍历放到 result 里 --> preorder(root.right, result)。

3. 递归的出口

何时能够直接知道答案,不用再拆解,直接 return

例子:
Java:

// 二叉树的最大深度 if (root == null) { return 0; } 

Python:

# 二叉树的最大深度 if not root: return 0 

一棵空的二叉树,能够认为是一个高度为0的二叉树。
Java:

// 二叉树的前序遍历 if (root == null) { return; } 

Python:

if not root: return 

一棵空的二叉树,天然不用往 result 里听任何的东西。

递归的定义

每个递归函数,都须要有明确的定义,有了正确的定义之后,才可以对递归进行拆解。

例子:
Java:

int maxDepth(TreeNode root) 

Python:

def maxDepth(root): 

表明 以 root 开头的子树的最大深度是多少
Java:

void preorder(TreeNode root, List<TreeNode> result) 

Python:

def preorder(root, result): 

表明 将 root 开头的子树的前序遍历放到 result 里面

 

递归的拆解

一个大问题如何拆解为若干个小问题去解决。

例子:
Java:

int leftDepth = maxDepth(root.left); int rightDepth = maxDepth(root.right); return Math.max(leftDepth, rightDepth) + 1; 

Python:

leftDepth = maxDepth(root.left)
rightDepth = maxDepth(root.right)
return max(leftDepth, rightDepth) + 1 

整棵树的最大深度,能够拆解为先计算左右子树深度,而后在左右子树深度中找到最大值+1来解决。
Java:

result.add(root);
preorder(root.left, result);
preorder(root.right, result);

Python:

result.append(root)
preorder(root.left, result)
preorder(root.right, result)

一棵树的前序遍历能够拆解为3个部分:

  1. 根节点本身(root)
  2. 左子树的前序遍历
  3. 右子树的前序遍历

因此对应的,咱们把这个递归问题也拆分为三个部分来解决:

  1. 先把 root 放到 result 里 --> result.add(root);
  2. 再把左子树的前序遍历放到 result 里 --> preorder(root.left, result)。回想一下递归的定义,是否是正是如此?
  3. 再把右子树的前序遍历放到 result 里 --> preorder(root.right, result)。

递归的出口

何时能够直接知道答案,不用再拆解,直接 return

例子:
Java:

// 二叉树的最大深度 if (root == null) { return 0; } 

Python:

# 二叉树的最大深度 if not root: return 0 

一棵空的二叉树,能够认为是一个高度为0的二叉树。
Java:

// 二叉树的前序遍历 if (root == null) { return; } 

Python:

if not root: return 

一棵空的二叉树,天然不用往 result 里听任何的东西。


使用 ResultType 返回多个值

什么是 ResultType

一般是咱们定义在某个文件内部使用的一个类。好比:
Java:

class ResultType { int maxValue, minValue; public ResultType(int maxValue, int minValue) { this.maxValue = maxValue; this.minValue = minValue; } } 

何时须要 ResultType

当咱们定义的函数须要返回多个值供调用者计算时,就须要使用 ResultType了。
因此若是你只是返回一个值就够用的话,就不须要。

其余语言须要 ResulType 么?

不是全部的语言都须要自定义 ResultType。
像 Python 这样的语言,天生支持你返回多个值做为函数的 return value,因此是不须要的。


什么是二叉搜索树

定义

二叉搜索树(Binary Search Tree,又名排序二叉树,二叉查找树,一般简写为BST)定义以下:
空树或是具备下列性质的二叉树
(1)若左子树不空,则左子树上全部节点值均小于或等于它的根节点值;
(2)若右子树不空,则右子树上全部节点值均大于根节点值;
(3)左、右子树也为二叉搜索树;
如图即为BST:
图片

BST 的特性

  • 按照中序遍历(inorder traversal)打印各节点,会获得由小到大的顺序。
  • 在BST中搜索某值的平均状况下复杂度为O(logN)O(logN)O(logN),最坏状况下复杂度为O(N)O(N)O(N),其中N为节点个数。将待寻值与节点值比较,若不相等,则经过是小于仍是大于,可判定该值只可能在左子树仍是右子树,继续向该子树搜索
  • 在balanced BST中查找某值的时间复杂度为O(logN)O(logN)O(logN)。

BST 的做用

  • 经过中序遍历,可快速获得升序节点列表。
  • 在BST中查找元素,平均状况下时间复杂度是O(logN)O(logN)O(logN);插入新节点,保持BST特性平均状况下要耗时O(logN)。(参考连接)。
  • 和有序数组的对比:有序数组查找某元素能够用二分法,时间复杂度是O(logN);可是插入新元素,维护数组有序性要耗时O(N)。

常见的BST面试题

http://www.lintcode.com/en/tag/binary-search-tree/

BST是一种重要基本的结构,其相关题目也十分经典,并延伸出不少算法。
在BST之上,有许多高级且有趣的变种,以解决各式各样的问题,例如:

  • 用于数据库或各语言标准库中索引的红黑树
  • 提高二叉树性能底线的伸展树
  • 优化红黑树的AA树
  • 随机插入的树堆
  • 机器学习kNN算法的高维快速搜索k-d树

什么是平衡二叉搜索树

定义

平衡二叉搜索树(Balanced Binary Search Tree,又称为AVL树,有别于AVL算法)是二叉树中的一种特殊的形态。二叉树当且仅当知足以下两个条件之一,是平衡二叉树:

  • 空树。
  • 左右子树高度差绝对值不超过1左右子树都是平衡二叉树

图片

如图(图片来自网络),节点旁边的数字表示左右两子树高度差。(a)是AVL树,(b)不是,(b)中5节点不知足AVL树,故4节点,3节点都再也不是AVL树。

AVL树的高度为 O(logN)O(logN)O(logN)

当AVL树有N个节点时,高度为O(logN)O(logN)O(logN)。为什么?
试想一棵满二叉树,每一个节点左右子树高度相同,随着树高的增长,叶子容量指数暴增,故树高必定是O(logN)O(logN)O(logN)。而相比于满二叉树,AVL树仅放宽一个条件,容许左右两子树高度差1,当树高足够大时,能够把1忽略。如图是高度为9的最小AVL树,若节点更少,树高毫不会超过8,也即为什么AVL树高会被限制到O(logN)O(logN)O(logN),由于树不可能太稀疏。严格的数学证实复杂,略去。
图片

为什么普通二叉树不是O(logN)O(logN)O(logN)?这里给出最坏的单枝树,若单枝扩展,则树高为O(N)O(N)O(N):
图片

AVL树有什么用?

最大做用是保证查找的最坏时间复杂度为O(logN)。并且较浅的树对插入和删除等操做也更快。

AVL树的相关练习题

判断一棵树是否为平衡树
http://www.lintcode.com/problem/balanced-binary-tree/
提示:能够自下而上递归判断每一个节点是否平衡。若平衡将当前节点高度返回,供父节点判断;不然该树必定不平衡。


补充内容:

用 Morris 算法实现 O(1) 额外空间遍历二叉树

https://www.jiuzhang.com/tutorial/algorithm/402

 

非递归的方式实现二叉树遍历

先序遍历

思路

遍历顺序为

  1. 若是根节点非空,将根节点加入到栈中。
  2. 若是栈不空,弹出出栈顶节点,将其值加加入到数组中。
    1. 若是该节点的右子树不为空,将右子节点加入栈中。
    2. 若是左子节点不为空,将左子节点加入栈中。
  3. 重复第二步,直到栈空。

    练习

    http://www.lintcode.com/problem/binary-tree-preorder-traversal/

public class Solution {
    public List<Integer> preorderTraversal(TreeNode root) {
        Stack<TreeNode> stack = new Stack<TreeNode>();
        List<Integer> preorder = new ArrayList<Integer>();
        
        if (root == null) {
            return preorder;
        }
        
        stack.push(root);
        while (!stack.empty()) {
            TreeNode node = stack.pop();
            preorder.add(node.val);
            if (node.right != null) {
                stack.push(node.right);
            }
            if (node.left != null) {
                stack.push(node.left);
            }
        }
        
        return preorder;
    }
}

 

中序遍历

思路

遍历顺序为

  1. 若是根节点非空,将根节点加入到栈中。
  2. 若是栈不空,取栈顶元素(暂时不弹出),
    1. 若是左子树已访问过,或者左子树为空,则弹出栈顶节点,将其值加入数组,若有右子树,将右子节点加入栈中。
    2. 若是左子树不为空,则将左子节点加入栈中。
  3. 重复第二步,直到栈空。

练习

http://www.lintcode.com/problem/binary-tree-inorder-traversal/

public class Solution {
    /**
     * @param root: The root of binary tree.
     * @return: Inorder in ArrayList which contains node values.
     */
    public ArrayList<Integer> inorderTraversal(TreeNode root) {
        Stack<TreeNode> stack = new Stack<>();
        ArrayList<Integer> result = new ArrayList<>();
        
        while (root != null) {
            stack.push(root);
            root = root.left;
        }
    
        while (!stack.isEmpty()) {
            TreeNode node = stack.peek();
            result.add(node.val);
            
            if (node.right == null) {
                node = stack.pop();
                while (!stack.isEmpty() && stack.peek().right == node) {
                    node = stack.pop();
                }
            } else {
                node = node.right;
                while (node != null) {
                    stack.push(node);
                    node = node.left;
                }
            }
        }
        return result;
    }
}

 

后序遍历

思路

遍历顺序为

  1. 若是根节点非空,将根节点加入到栈中。
  2. 若是栈不空,取栈顶元素(暂时不弹出),
    1. 若是(左子树已访问过或者左子树为空),且(右子树已访问过或右子树为空),则弹出栈顶节点,将其值加入数组,
    2. 若是左子树不为空,切未访问过,则将左子节点加入栈中,并标左子树已访问过。
    3. 若是右子树不为空,切未访问过,则将右子节点加入栈中,并标右子树已访问过。
  3. 重复第二步,直到栈空。

练习

http://www.lintcode.com/problem/binary-tree-postorder-traversal/

public ArrayList<Integer> postorderTraversal(TreeNode root) {
    ArrayList<Integer> result = new ArrayList<Integer>();
    Stack<TreeNode> stack = new Stack<TreeNode>();
    TreeNode prev = null; // previously traversed node
    TreeNode curr = root;

    if (root == null) {
        return result;
    }

    stack.push(root);
    while (!stack.empty()) {
        curr = stack.peek();
        if (prev == null || prev.left == curr || prev.right == curr) { // traverse down the tree
            if (curr.left != null) {
                stack.push(curr.left);
            } else if (curr.right != null) {
                stack.push(curr.right);
            }
        } else if (curr.left == prev) { // traverse up the tree from the left
            if (curr.right != null) {
                stack.push(curr.right);
            }
        } else { // traverse up the tree from the right
            result.add(curr.val);
            stack.pop();
        }
        prev = curr;
    }

    return result;
}

 

 

什么是二叉搜索树(Binary Search Tree)

二叉搜索树能够是一棵空树或者是一棵知足下列条件的二叉树:

  • 若是它的左子树不空,则左子树上全部节点值均小于它的根节点值。
  • 若是它的右子树不空,则右子树上全部节点值均大于它的根节点值。
  • 它的左右子树均为二叉搜索树(BST)。
  • 严格定义下BST中是没有值相等的节点的(No duplicate nodes)。
    根据上述特性,咱们能够获得一个结论:BST中序遍历获得的序列是升序的。以下述BST的中序序列为:[1,3,4,6,7,8,10,13,14]

BST

BST基本操做——增删改查(CRUD)

https://www.jiuzhang.com/tutorial/algorithm/401

 

 

平衡排序二叉树(Self-balancing Binary Search Tree)

定义

平衡二叉搜索树又被称为AVL树(有别于AVL算法),且具备如下性质:

  • 它是一棵空树或它的左右两个子树的高度差的绝对值不超过1
  • 左右两棵子树都是一棵平衡二叉搜索树
  • 平衡二叉搜索树一定是二叉搜索树,反之则不必定。

平衡排序二叉树 与 二叉搜索树 对比

也许由于输入值不够随机,也许由于输入顺序的缘由,还或许一些插入、删除操做,会使得二叉搜索树失去平衡,形成搜索效率低落的状况。
AVL
好比上面两个树,在平衡树上寻找15就只要2次查找,在非平衡树上却要5次查找方能找到,效率明显降低。

平衡排序二叉树节点定义

Java:

class TreeNode{ int val; TreeNode left; TreeNode right; pubic TreeNode(int val) { this.val = val; this.left = this.right = null; } } 

Python:

class TreeNode: def __init__(self, val): self.val = val self.left, self.right = None, None 

经常使用的实现办法

Java中的 TreeSet / TreeMap

TreeSet / TreeMap 是底层运用了红黑树的数据结构

对比 HashSet / HashMap

  • HashSet / HashMap 存取的时间复杂度为O(1),而 TreeSet / TreeMap 存取的时间复杂度为 O(logn) 因此在存取上并不占优。
  • HashSet / HashMap 内元素是无序的,而TreeSet / TreeMap 内部是有序的(能够是按天然顺序排列也能够自定义排序)。
  • TreeSet / TreeMap 还提供了相似 lowerBoundupperBound 这两个其余数据结构没有的方法
    • 对于 TreeSet, 实现上述两个方法的方法为:
      • lowerBound
        • public E lower(E e) --> 返回set中严格小于给出元素的最大元素,若是没有知足条件的元素则返回 null
        • public E floor(E e) --> 返回set中不大于给出元素的最大元素,若是没有知足条件的元素则返回 null
      • upperBound
        • public E higher(E e) --> 返回set中严格大于给出元素的最小元素,若是没有知足条件的元素则返回 null
        • public E ceiling(E e) --> 返回set中不小于给出元素的最小元素,若是没有知足条件的元素则返回 null
    • 对于 TreeMap, 实现上述两个方法的方法为:
      • lowerBound
        • public Map.Entry<K,V> lowerEntry(K key) --> 返回map中严格小于给出的key值的最大key对应的key-value对,若是没有知足条件的key则返回 null
        • public K lowerKey(K key) --> 返回map中严格小于给出的key值的最大key,若是没有知足条件的key则返回 null
        • public Map.Entry<K,V> floorEntry(K key) --> 返回map中不大于给出的key值的最大key对应的key-value对,若是没有知足条件的key则返回 null
        • public K floorKey(K key) --> 返回map中不大于给出的key值的最大key,若是没有知足条件的key则返回 null
      • upperBound
        • public Map.Entry<K,V> higherEntry(K key) --> 返回map中严格大于给出的key值的最小key对应的key-value对,若是没有知足条件的key则返回 null
        • public K higherKey(K key) --> 返回map中严格大于给出的key值的最小key,若是没有知足条件的key则返回 null
        • public Map.Entry<K,V> ceilingEntry(K key) --> 返回map中不小于给出的key值的最小key对应的key-value对,若是没有知足条件的key则返回 null
        • public K ceilingKey(K key) --> 返回map中不小于给出的key值的最小key,若是没有知足条件的key则返回 null
    • lowerBound 与 upperBound 均为二分查找(所以要求有序),时间复杂度为O(logn).

对比 PriorityQueue(Heap)

PriorityQueue是基于Heap实现的,它能够保证队头元素是优先级最高的元素,但其他元素是不保证有序的。

  • 方法时间复杂度对比:
    • 添加元素 add() / offer()
      • TreeSet: O(logn)
      • PriorityQueue: O(logn)
    • 删除元素 poll() / remove()
      • TreeSet: O(logn)
      • PriorityQueue: O(n)
    • 查找 contains()
      • TreeSet: O(logn)
      • PriorityQueue: O(n)
    • 取最小值 first() / peek()
      • TreeSet: O(logn)
      • PriorityQueue: O(1)

常见用法

好比滑动窗口须要保证有序,那么这时能够用到TreeSet,由于TreeSet是有序的,而且不须要每次移动窗口都从新排序,只须要插入和删除(O(logn))就能够了。

注:在 C++ 中相似的结构为 set / map。在Python中没有内置的TreeSet、TreeMap,须要使用第三方库或者本身实现。

练习

http://www.lintcode.com/problem/consistent-hashing-ii/

 

练习:链表转平衡排序二叉树

https://www.jiuzhang.com/tutorial/algorithm/33

相关文章
相关标签/搜索