目录
1. 引入
-
1.1 搜索
-
—— 听起来是个比较抽象的词语。但其实,在我们的生活中就有很多搜索
- 生活中的搜索
- 1. 找出符合条件的集合(eg:找出班级中身高在160cm-180cm之间的同学)
- 2. 统计符合条件内容出现的次数(eg:一首歌中,出现了多少次“我”)
- 3. 确定集合中是否包含指定条件的内容(eg:一篇文章中是否包含有“搜索”)
- 将这些生活中的搜索与数据结构结合起来
- 前两种搜索可以利用Map<>实现(key-value)
- 第三种搜索可以利用Set<>实现(key)
- 生活中的搜索
-
-
1.2 常见容器
- 分类
- ArrayList<E>、LinkedList<E>
- HashMap<K,V>、HashSet<E>
- TreeMap<K,V>、TreeSet<E>
- 如何选择
- 使用键值对选择Map,若只查看是否存在用Set
- 一般情况下使用Hash,需要天生有序性使用Tree
- 分类
-
1.3 与搜索问题有关的数据结构
- 二分查找
- 适用
- 有序数组
- 已经有序且不会变更的数据
- 求n的平方根
- 适用
- 哈希表
- 优点
- 超快 时间复杂度为O(1)
- 优点
- 搜索树
- 优点
- 天生的有序性,中序遍历是有序的
- 优点
- 使用数据库中的查询举例
- 数据库中的索引
- 使用B+树,利用搜索树的天生有序性
- select * from table where id>10;
- 使用搜索树,查询范围
- select * from table where id=1;
- 使用哈希,查询确定值
- select * from table order by id;
- 使用搜索树,因为搜索树的天生有序性,无序再次排序
- 数据库中的索引
- 二分查找
2. 搜索树(二叉搜索树)
-
2.1 定义
- 任意节点的左子树的值都小于节点的值,右子树的值都大于节点的值。
- 搜索树中不存在相同的值
-
2.2 时间复杂度
- 当需要查找的数字大于根结点,只需要向右子树攻击;小于根结点,则只需要向左子树攻击。
- 因此,时间复杂度,即为二叉树的高度 -> O(log(n)) ~O(n)(当该搜索树为一个单支树时,树的高度为O(n))
-
2.3 查找一个指定数
- 利用递归实现
- 终止条件
- 树为空树 -> 返回false
- 找到指定值 -> 返回true
- 递推公式
- 若所给值小于根结点,在左子树中查找
- 若大于根结点,在右子树中查找
- 终止条件
- 利用递归实现
-
2.4 插入一个数
- 利用递归实现
- 终止条件
- 树为空树 -> 插入新的节点
- 若与根结点的值相同,抛出异常
- 递推公式
- 若所给值小于根结点,在左子树中插入
- 若大于根结点,在右子树中插入
- 注意
- ∵ 只有当node为空的时候才会插入,∴ 插入会改变当前节点的状态。
- -> 我们需要将当前节点从null 改为新节点
- ∵ 只有当node为空的时候才会插入,∴ 插入会改变当前节点的状态。
- 终止条件
- 利用递归实现
-
2.5 删除一个节点
- 难点
- 删除后,被删除的节点用谁替代
- 1. 被删除的节点为叶子节点,直接删除
- 2. 只有一个孩子的节点,子承父业
- 3. 两个孩子
- 替换删除法
- 找出左孩子中最大的或右孩子中最小的,替换被删除节点
- 左孩子中的最大的一定没有右孩子;右孩子中最小的一定没有左孩子
- 找出左孩子中最大的或右孩子中最小的,替换被删除节点
- 替换删除法
- 删除后,被删除的节点用谁替代
- 递推公式
- 若所给值小于根结点,在左子树中插入
- 若大于根结点,在右子树中插入
- 注意
- ∵ 只有当node为空的时候才会插入,∴ 插入会改变当前节点的状态。
- -> 我们需要将当前节点从null 改为新节点
- ∵ 只有当node为空的时候才会插入,∴ 插入会改变当前节点的状态。
- 难点
-
2.6 缺点
- 高度不确定 -> 时间复杂度不确定
3. 平衡树
-
3.1 定义
- 为了解决搜索树高度不确定的情况,平衡树的高度比较接近log(n)
-
3.2 AVL树
- 高度平衡二叉树,要求树中每一个左子树的高度和右子树的高度差,不超过1。
- 难点
- 随着插入的进行,树可能不平衡
- 解决方法
- 旋转
- 旋转
- 在每个节点中记录一项属性:平衡因子
- 平衡因子 = 左子树高度-右子树高度,值为-1、0、1
- 当插入一个节点时,平衡因子会出现3种情况
- -1 -> 0
- 0 -> 1
- 1 -> 2
- 只有平衡因子变为2时,才会触发旋转操作
- 何时旋转
- 向左子树插入节点,插入后左子树 - 右子树==2
- 向右子树插入节点,插入后右子树 - 左子树==2
- 旋转方式
- 向左子树插入节点
- node.left.left - node.left.right == 2 ,node右旋
- node.left.left - node.left.right == -2 ,node.left左旋,node右旋
- 向右子树插入节点
- node.right.left - node.risht.right == 2 ,node.right右旋,node左旋
- node.right.left - node.risht.right == -2 ,node左旋
- 向左子树插入节点
- 旋转的是常数时间,不影响插入和删除的时间复杂度,仍为O(log(n))
- 在每个节点中记录一项属性:平衡因子
- 难点
-
3.3 红黑树
- 不是绝对意义上的平衡树,但是接近于平衡树
- 性质
- 1. 每个结点有颜色(红或黑 0或1)
- 2. 红色不能与红色相邻
- 3. 根结点一定是黑色的
- 4. 叶子节点(null)是黑色的
- 5. 从根到每一个叶子,所有这样的路径上,黑色的数量一样多
- 特点
- 最长边一定是黑-红-黑-红-黑...
- 最短边一定是黑-黑-黑...
- 因此,最长边的长度一定不会超过最短边长度*2
- 插入
- 为了不破坏红黑树的性质,插入的点一定是红色的。
- 存在情况
- 父亲为黑色,没有破坏红黑树的性质,直接返回
- 父亲为红色,红红相邻,需调整
- 特点
- 这时,父亲定不是根结点(根节点是黑色)
- 因此,存在祖父节点,且祖父节点定为黑色
- 如何调整
- (1)存在红色叔叔节点
- 把祖父节点变为红色,父亲节点与叔叔节点都变为黑色
- (2)不存在叔叔节点或存在黑色叔叔节点,且当前节点是父节点的左孩子
- 右旋后,交换父亲与祖父颜色
- (3)不存在叔叔节点或存在黑色叔叔节点,且当前节点是父节点的右孩子
- 从父节点左旋,->(3)
- (1)存在红色叔叔节点
- 特点
- 3.2.3 Battle
- AVL 查找更好一点:高度更加平衡、更低
- 红黑树 插入/ 删除更好一点:调整次数更少
- 高度平衡二叉树,要求树中每一个左子树的高度和右子树的高度差,不超过1。
-
3.3 时间复杂度
- 所有平衡搜索树的 插入/删除/查找 的时间复杂度都是O(log(n))
4. B-树
-
4.1 引入
- 数据库中使用索引来提高检索效率,那么主要是利用了什么方法呢?
- 搜索树(B+树(多叉树))
- 哈希
- 为什么使用B+树而不使用红黑树呢???
- 因为B+树是多叉树,在同等数量数据的情况下,高度更低。使得访问磁盘次数更少,效率更高。
- 数据库中使用索引来提高检索效率,那么主要是利用了什么方法呢?
-
4.2 使用场景
- 多用于磁盘搜索
- 磁盘:读写效率低
- B树系列:孩子多,高度低 -> 读写次数少
- 多用于磁盘搜索
-
4.3 分类
- B-树:值除了在叶子节点,中间节点也保存
- B+树:值全部在叶子节点中