二分查找(折半查找)
-
在长度为n的 有序 顺序表List中顺序查找一个目标值 => O(n)
-
很慢,为什么慢?
-
因为每次比较判断只排除了一个元素,完全没有利用表的有序性
-
二分查找:在比较失效时,利用表的有序性排除 待查找元素 的一半
-
每次排除一半 => O(lg(n))
-
算法流程:
- 用 l 和 r 表示待查找范围的左右边界,初始时 l = 0,r = n - 1
- 循环以下操作直至 l > r:
- 找当前搜索范围的正中间: mid = (l + r) / 2
- 判断 target 和 List[mid] 的关系:
- ==:找到了结果,返回mid
- < :target只可能在mid位置左边,排除mid及其右侧所有元素 => 即更新 r = mid - 1
- > :target只可能在mid位置右边,排除mid及其左侧所有元素 => 即更新 l = mid + 1
- 循环能结束说明数组中不存在target,返回-1
Key 1:二分查找只能用于 有序数组,需要通过下标直接访问元素
Key 2:二分(折半)查找过程:①找中 ②折半
Key 3:二分查找每次排除一半,至多log₂(n) + 1次后结束 => 复杂度为O(lg(n))
AVL
二叉搜索树 / 二叉查找树 / 二叉排序树
- 二叉查找树中的任何一个结点都满足:
- 左子树如果不空,则左子树所有结点都 < 本结点
- 右子树如果不空,则右子树所有结点都 > 本结点
- 其左右子树都是二叉查找树
- 左小右大
- 查找目标值:
- 如果target < 当前结点,进入左子树
- 如果target >当前结点,进入右子树
- 如果进入空结点表明树中无target
给定序列构造二叉查找树
- 给定一个序列{31, 25, 57, 11, 44, 78, 2, 49, 19},将其依次插入一颗二叉查找树
- 方法:
- 第一个元素为根
- 从第二个元素开始,逐个按照 小往左大往右 的原则插至叶子结点
- 每次新插入的元素一定位于叶子
AVL树(平衡二叉查找树)
- 思考:从序列{1,2,3,4,5… n}依次插入构造出的二叉查找树长什么样?
- 退化成一个单链表,查找目标值的时间复杂度为O(n)
- AVL树:尽量保持一棵二叉查找树的 平衡性:
- 每一个结点的左右子树的高度差 <= 1
- 方法:
- 如果插入一个结点导致树不平衡,通过 旋转 调整回平衡
- 如果插入一个结点导致树不平衡,通过 旋转 调整回平衡
Key 1:二叉查找树中任意结点:>左子树 所有结点 ,<右子树 所有结点(not左孩子,右孩子)
Key 2:二叉查找树查找: 左小右大
Key 3:二叉查找树插入: 小往左,大往右
Key 4:AVL树:任意结点左右子树高度差 <= 1
Key 5:当插入或删除导致AVL树不平衡则需要进行 旋转 使其重新平衡
Key 6:二叉查找树的 中序 遍历就是其所有元素的有序序列
哈希表
哈希表(散列表)
- 前面介绍的两种查找都需要维护元素的有序性
- 但我们只是单纯地想知道:有 or 没有
- 所以可以建立 元素值 -> 数组下标 的 映射,直接判断目标值是否存在结构中
- 例:元素为{22,6,19,18,2},通过 h(x) = x % 7 的映射关系将其映射到数组对应位置上(-1表示空值)
- 查找19:直接计算19 % 7 = 5,访问nums[5] => 判断查找成功
- 查找24:直接计算24 % 7 = 3,访问nums[3] => 判断查找失败
- 这种映射关系就叫做哈希,这个结构就叫做哈希表, h(x) = x % 7 就是哈希函数,经过哈希函数算出来的值就叫做哈希值就是上图中的数组下标
哈希冲突
- 假设哈希函数 h(x) = x % 7
- 元素为{22,6,19,18,2},构造了哈希表:
- 现在插入元素29:
- 计算哈希值h(29) = 29 % 7 = 1
- 试图将29放入nums[1],发现nums[1]已经放了22 => 产生了 冲突
哈希表冲突解决方案1:开放地址法
- 假设哈希函数 h(x) = x % 7
- 元素为{22,6,19,18,2},构造了哈希表:
- 现在插入元素29,哈希值为1,产生了冲突
- 继续下一级哈希:h₁ = (1 + 1) % 7
- 仍然冲突?再哈希:h₂ = (1 + 2) % 7
- …
- 直至哈希至:h₆ = (1 + 6) % 7
- 29经过3次哈希,放入nums[3]
- 以上方法称为 线性探测法:H + 1 -> H + 2 -> … -> H + k (k <= m - 1,m为哈希表内置长度,也叫底层数组的长度,此处m = 7)
- 还有一种方法叫做 二次探测法:1² -> -1² -> 2² -> -2² -> … -> k² -> -k² (k <= m / 2) 了解即可,考试考试考得少,考到题目一般都会给介绍
- 开放地址法:产生冲突时使用增量di继续哈希,直至有空位或发现表满了
- hi = (H + di) % m
- 线性探测:di = 1, 2, 3, 4, 5 … k ( k <= m - 1)
(看似复杂,其实就是冲突时向后挪直至有空位,到末尾了就回到开头继续试) - 二次探测:di = 1², -1², 2², -2², 3², -3² … , k², -k² (k <= m / 2)
- 线性探测:di = 1, 2, 3, 4, 5 … k ( k <= m - 1)
哈希表冲突解决方法2:链地址法
- 链地址法:将哈希值相同的元素存放在同一个 单链表 中
- h(x) = x % 7,依次插入22,6,19,18,2,29,30,42,10,54,50
- 查找x:①根据 h(x) 找到链表头 ②然后在链表上顺序查找
哈希表查找时间复杂度
- 最理想情况下:哈希函数特别牛逼,没有冲突,所有目标值只需要1次哈希 => O(1)
- 一般情况下:取决于数据元素的分布,哈希函数的设计和冲突解决方法
Key 1:哈希函数是一个映射:元素值 -> 哈希值
Key 2:不同的元素拥有相同的哈希值:冲突
Key 3:哈希函数应尽量能较为均匀地映射元素 => 降低冲突概率
Key 4:冲突解决方法:①开放地址法 ②链地址法
Key 5:线性探测法就是向后逐个位置试