令你头疼的[树]

本文涉及的产品
云数据库 RDS MySQL,集群系列 2核4GB
推荐场景:
搭建个人博客
RDS MySQL Serverless 基础系列,0.5-2RCU 50GB
RDS MySQL Serverless 高可用系列,价值2615元额度,1个月
简介: 令你头疼的[树]

每日分享

The first step to accepting yourself is to stop comparing yourself to others.

接受自己的第一步就是停止将自己和别人进行比较。

小闫语录

每个人都喜欢将自己和其他人进行比较,换来的却是无尽的烦恼。因为在比较的过程中,往往迷失了最初的自己。将他人作为自己的榜样,为自己提供前进的动力,这样无可厚非。但请不要妄自菲薄,弄丢了真实的你。将自己的弱点与他人的优点比较,只能垂头丧气。深受一些鸡汤的毒害,也许你认为努力了就一定成功,奋斗了就一定能超越他人,他人能做到的你一定能做到。但是你忽略了一件事,一只小鸡,即使被人从悬崖上丢下去,它也不会像老鹰一样展翅翱翔。了解自己,正视自己,接受自己,成就自己。


一条插播

在进行树的讲解之前,先插播一个小的知识点(并不是广告,不要害怕)。这个知识点和树没有关系,是突然想到的一个知识点。那就是重载

重载存在于静态语言中,是面向对象编程中一个重要的概念。它指的是多个相同函数名的函数,根据传入的参数个数,参数类型而执行不同的功能。函数重载实质上是为了解决编程中参数可变不统一的问题。

那么作为动态语言的python有重载吗?各说纷纭。我们从概念入手,『相同函数名的函数』在python中是不存在的,函数会根据从上到下的执行顺序发生覆盖。『传入的参数个数』也由于python的传参方式,可以限定在一个函数实施。『参数类型』因为python中不需要声明变量类型,所以函数可以接受任何类型的参数,也就无法根据参数类型来支持重载。

python中的传参方式有默认参数/可变参数/可变关键字参数。我们可以通过设置缺省值,让原本两个参数,只传一个参数即可。

所以说,python中本身就不需要重载。如果非要用这个重载的话,也是有解决办法的。python3.4中就提供了一个转发机制可以实现重载。

from functools import singledispatch
@singledispatch
def function(obj):
    print('%r'%(obj))
@function.register(int)
def function_test(obj):
    print('Integer: %d' % (obj))
@function.register(str)
def function_test(obj):
    print('String: %s' % (obj))
@function.register(list)
def function_test(obj):
    print('List: %r' % (obj))
if __name__ == "__main__":
    function(1)
    function('hello')
    function([1,2,3])

结果为:

Integer: 1
String: hello
List: [1, 2, 3]


树的介绍

树分为无序树和有序树。树中任意节点的子节点之间没有顺序关系,称为无序树。相反,有顺序关系就是有序树。

1.有序树

二叉树:每个节点最多有两个子树。

  • 完全二叉树:二叉树,除最底层外,其他各层节点数目均已达到最大值,且最底层所有节点从左向右连续地紧密排列。不能左边的还没挂满,右边的已经挂了一些节点。
  • 满二叉树:非叶节点都挂满了元素。也就是叶节点都在最底层的完全二叉树。
  • 排序二叉树:根节点左边小于根节点,右边的大于根节点。也称为二叉查找树、二叉搜索树、有序二叉树。时间复杂度是 O(logn)
  • 平衡二叉树(AVL树):当且仅当任何节点的两棵子树的高度差不大于1的二叉树。

霍夫曼树:用于信息的编码和数据压缩。带权路径最短的二叉树称为哈夫曼树或最优二叉树。

B树:对读写操作进行优化的自平衡二叉查找树,能够保持数据有序,拥有的子树多于两个。(排序多叉平衡树)

2.树的存储

树的存储可以采用顺序存储链式存储。将数据结构存储在固定的数组中,虽然在遍历速度上有一定的优势,但是所占空间比较大,是非主流的存储方式。二叉树通常以链式存储。在链式存储时,有个小缺陷,就是指针域指针个数不定。由于对节点的个数无法掌握,常见的树的存储都将多叉树转换成二叉树进行处理,子节点个数最多为2。

链式存储数据时每个结点有两部分,一部分存储数据,一部分存储指针,即指向下一个元素存储位置。我们可以设计下面这样的二叉链表存储。

lchild data rchild
左孩子指针 数据域 右孩子指针

3.树的应用场景

1.路由协议使用了树的算法。

2.MySQL数据库索引使用了树结构。

3.文件系统的目录结构。

4.很多经典的AI算法都是树搜索。比如决策树。

4.MySQL索引

假如我们执行下面的一个SQL语句:

  1. select *from students where name = xxx;

它会进行全表扫描,对比名字是否相等,如果数据量大,IO量也会很大,IO影响比较大。但是通过索引字段来查询,就要快的多。MySQL索引多采用B+树,下面就来回答为什么。

索引是有序的;索引是单独的文件;如果通过索引字段查询会先扫描索引区。

首先说一下B树,网上有大量介绍B树的文章,因此本文只简单的做一个介绍。B树是一种多路搜索树,它的每个节点可以拥有多个子节点,M路的B树最多能拥有M个孩子节点。那么为什么设计成多路的呢?这是为了降低树的高度,而且路数越多,树的高度越低,查询效率就高,但是不能无限多路,那样B树会退化成一个有序的数组。B树在文件系统的索引上用的比较多,这是考虑到文件系统和数据库都存在硬盘上,涉及到IO。当数据量大的时候很难一次性将所有的数据加载进内存,就更别提查询了。那怎么办?多路存储优势就显现了,每次只需加载B树的一个节点。虽然内存中红黑树比B树效率高,但是涉及到磁盘操作,B树要更胜一筹。

再来说一下B+树。B+树是对B树的改良,它将所有的数据存在叶子节点,叶子节点之间还加了指针,构成了有序链表。这样一来呢,就有一个极大的优点,就是只需遍历叶子节点就能遍历所有的数据。

面试题:B+树的时间复杂度为log(n),hash时间复杂度为O(1),那么为什么MySQL还要使用B+树而不是hash呢?

答:这与使用场景有关,hash虽然快,但是适合查询单条数据。我们使用MySQL时,常常需要查询大量的数据,这时候由于B+树叶子节点保存了所有数据,还是有序链表,它的查询效率就要高的多了。而且数据库的索引保存在硬盘中,我们需要考虑到往内存中加载的情况。数据量大的时候,无法一次性加载入内存,就无法查询了。B+树可以分批加载,每次只加载一个节点,解决了上面的问题,而且由于树的高度很低,查询效率也高的多的多。


二叉树

二叉树上面也提到了,就是每个节点最多有两个子树的树结构,通常被称为“左子树”和“右子树”。

它有很多性质,我们需要掌握两个:

性质1:在二叉树的第i层上至多有 2^(i-1)个结点(i>0)

性质2:深度为k的二叉树至多有 2^k-1个结点(k>0)

深度就是树中节点的最大层次。也称为树的高度。

1.二叉树的实现

我们先定义一个节点类,用来创建节点对象。节点包含数据域和指针域。数据域用来保存数据,指针域保存左孩子和右孩子的指针。

class Node(object):
    def __init__(self,item):
        self.item = item
        self.lchild = None
        self.rchild = None

然后再定义二叉树,一开始为空,实现添加节点的方法。

添加元素的时候,我们可以先创建一个队列。然后从根节点开始检查。根节点为空则将元素添加到根节点位置,根节点不为空则将根节点添加进队列。然后由根节点检查左孩子和右孩子,执行同样的操作。其实就是按照由上至下,从左往右的顺序检查树,将节点挂在为空的位置。

1. class BinaryTree(object):
2.     def __init__(self):
3.         self.root = None
4. 
5.     def add(self,item):
6.         # 将数据保存成一个节点对象
7.         node = Node(item)
8.         # 如果根节点为空,将节点放在根节点位置
9.         if self.root is None:
10.             self.root = node
11.             return
12.         # 实现一个队列
13.         queue = [self.root]
14.         while queue:
15.             # 为了先进先出,弹出第一个元素
16.             cur = queue.pop(0)
17.             # 如果左孩子为空,将节点挂到左孩子位置
18.             if cur.lchild is None:
19.                 cur.lchild = node
20.                 return
21.             # 左孩子不为空,将左孩子添加进队列
22.             else:
23.                 queue.append(cur.lchild)
24.             # 如果右孩子为空,将节点挂在右孩子位置
25.             if cur.rchild is None:
26.                 cur.rchild = node
27.                 return
28.             # 右孩子不为空,将右孩子添加进队列中
29.             else:
30.                 queue.append(cur.rchild)
2.二叉树的遍历

二叉树因为是二维结构,具有纵向和横向两个方向,所以分为深度(高度)优先和广度(层次或者宽度)优先两种遍历方式。好像一张表,深度优先就是咱们按列遍历,广度优先则是按行遍历。

2.1广度优先遍历(层次优先遍历)

我们在上面二叉树类中定义一个广度优先的遍历方法。目的就是按照广度优先,将每一层的元素都遍历出来,先第一层再第二层。

def breadth_travel(self):
    # 如果根节点为空,树都没有,不用遍历了,直接返回即可。
    if self.root is None:
        return
    # 如果有树,我们就要实现一个队列,将所有的节点都添加都队列中。
    queue = [self.root]
    while queue:
        cur = queue.pop(0)
        print(cur.item)
        if cur.lchild is not None:
            queue.append(cur.lchild)
        if cur.rchild is not None:
            queue.append(cur.rchild)

traveltrævəl]:游历、旅行。我们引申为遍历。

breadth[brɛdθ]:宽度,广度。

2.2深度优先遍历

深度优先就是另一个维度优先的遍历方式。它按照根节点遍历的顺序分为三种方式:先序遍历、中序遍历和后序遍历。

先序遍历:根节点 --> 左子树 --> 右子树

中序遍历:左子树 --> 根节点 --> 右子树

后序遍历:左子树 --> 右子树 --> 根节点

2.2.1先序遍历
def pre_travel(self, node):
    # 设置递归退出条件
    if node is None:
        return
    print(node.item)
    self.pre_travel(node.lchild)
    self.pre_travel(node.rchild)

我们先遍历根节点,然后将左孩子作为根节点遍历,再将右孩子作为根节点遍历,采用递归的方法,直至节点为空。

2.2.2中序遍历
def mid_travel(self, node):
    # 设置递归退出的条件
    if node is None:
        return
    self.mid_travel(node.lchild)
    print(node.item)
    self.mid_travel(node.rchild)
2.2.3后序遍历
def post_travel(self, node):
    # 设置递归退出的条件
    if node is None:
        return
    self.post_travel(node.lchild)
    self.post_travel(node.rchild)
    print(node.item)

post:在后面

注意

像这么基础的二叉树实现以及遍历,必须达到手写的程度。所以,努力练习吧,面试中基本是必考的内容。之前我一直不以为然,直到被问到,才发现自己是多么的蠢。

相关实践学习
如何在云端创建MySQL数据库
开始实验后,系统会自动创建一台自建MySQL的 源数据库 ECS 实例和一台 目标数据库 RDS。
全面了解阿里云能为你做什么
阿里云在全球各地部署高效节能的绿色数据中心,利用清洁计算为万物互联的新世界提供源源不断的能源动力,目前开服的区域包括中国(华北、华东、华南、香港)、新加坡、美国(美东、美西)、欧洲、中东、澳大利亚、日本。目前阿里云的产品涵盖弹性计算、数据库、存储与CDN、分析与搜索、云通信、网络、管理与监控、应用服务、互联网中间件、移动服务、视频服务等。通过本课程,来了解阿里云能够为你的业务带来哪些帮助     相关的阿里云产品:云服务器ECS 云服务器 ECS(Elastic Compute Service)是一种弹性可伸缩的计算服务,助您降低 IT 成本,提升运维效率,使您更专注于核心业务创新。产品详情: https://www.aliyun.com/product/ecs
相关文章
|
算法 C# C++
|
6月前
|
存储 算法 关系型数据库
【面试普通人VS高手系列】b树和b+树的理解
【面试普通人VS高手系列】b树和b+树的理解
轻轻松松学递归
轻轻松松学递归
|
存储 关系型数据库 MySQL
浅浅谈一谈B树和B+树
浅浅谈一谈B树和B+树
|
前端开发 算法 API
[LeetCode算法]有了二叉树层序遍历,妈妈再也不用担心我不会做二叉树层级题了
博主最近在刷`leetcode`,做到二叉树套题的时候发现很多题的解题思路都是基于二叉树的层序遍历来完成的,因此写下这篇文章,记录一下二叉树层序遍历这件"神器"在实战的运用。
147 1
|
存储 关系型数据库 索引
B+树层数计算(面试官直呼内行)
首先搞清楚一个常识,我们都知道计算机在存储数据的时候,有最小存储单元,这就好比我们今天进行现金的流通最小单位是一毛 在计算机中磁盘存储数据最小单元是扇区,一个扇区的大小是 512 字节,而文件系统(例如XFS/EXT4)他的最小单元是块,一个块的大小是 4k
2104 0
|
存储 算法
学会二叉树不知道干啥?二叉树的深度优先搜索和广度优先搜索,我要打十个乃至二十个(打开你的LeetCode撸起来)学练并举
学会二叉树不知道干啥?二叉树的深度优先搜索和广度优先搜索,我要打十个乃至二十个(打开你的LeetCode撸起来)学练并举
学会二叉树不知道干啥?二叉树的深度优先搜索和广度优先搜索,我要打十个乃至二十个(打开你的LeetCode撸起来)学练并举
|
算法 NoSQL Redis
关于跳表,这么解释你肯定能听懂
如何用 30s 给面试官讲清楚什么是跳表
121 0
关于跳表,这么解释你肯定能听懂
|
C语言
排序会了递归,不学非递归太可惜了
排序会了递归,不学非递归太可惜了
86 0
排序会了递归,不学非递归太可惜了