查找是计算机中最基础的操作之一——从有序数组中快速定位目标、在数据库中搜索记录、在搜索引擎中检索关键词。不同场景需要不同的查找算法。
一、顺序查找
最简单的查找方式,从头到尾逐个比较。
public int sequentialSearch(int[] arr, int target) {
for (int i = 0; i < arr.length; i++) {
if (arr[i] == target) {
return i;
}
}
return -1;
}
时间复杂度: O(n)
空间复杂度: O(1)
二、二分查找
二分查找的前提:数组必须有序。
查找 target = 7
[1, 3, 5, 7, 9, 11, 13]
↑ ↑ ↑
low mid high
arr[mid]=5 < 7 → 目标在右半部分
low = mid + 1
[1, 3, 5, 7, 9, 11, 13]
↑ ↑ ↑
low mid high
arr[mid]=9 > 7 → 目标在左半部分
high = mid - 1
[1, 3, 5, 7, 9, 11, 13]
↑
low=mid=high
arr[mid]=7 == 7 → 找到!
1. 非递归实现
public int binarySearch(int[] arr, int target) {
int left = 0;
int right = arr.length - 1;
while (left <= right) {
int mid = left + (right - left) / 2; // 防止溢出
if (arr[mid] == target) {
return mid;
} else if (arr[mid] < target) {
left = mid + 1; // 目标在右半部分
} else {
right = mid - 1; // 目标在左半部分
}
}
return -1;
}
2. 递归实现
public int binarySearchRecursive(int[] arr, int target, int left, int right) {
if (left > right) return -1;
int mid = left + (right - left) / 2;
if (arr[mid] == target) return mid;
if (arr[mid] < target) return binarySearchRecursive(arr, target, mid + 1, right);
return binarySearchRecursive(arr, target, left, mid - 1);
}
3. 时间复杂度
每次查找排除一半数据 → 最多需要 log₂(n) 次比较
时间复杂度:O(log n)
当 n = 100 万时:
顺序查找最多 100 万次,二分查找最多 20 次
三、二叉排序树(BST)
1. 定义
二叉排序树(Binary Search Tree):
左子树所有节点 < 根节点
右子树所有节点 > 根节点
左右子树也都是二叉排序树
8
/ \
3 10
/ \ \
1 6 14
/ \
4 7
2. 查找
public TreeNode searchBST(TreeNode root, int val) {
if (root == null || root.val == val) {
return root;
}
if (val < root.val) {
return searchBST(root.left, val); // 去左子树找
} else {
return searchBST(root.right, val); // 去右子树找
}
}
3. 插入
public TreeNode insertBST(TreeNode root, int val) {
if (root == null) {
return new TreeNode(val);
}
if (val < root.val) {
root.left = insertBST(root.left, val);
} else if (val > root.val) {
root.right = insertBST(root.right, val);
}
return root;
}
4. 删除
public TreeNode deleteBST(TreeNode root, int val) {
if (root == null) return null;
if (val < root.val) {
root.left = deleteBST(root.left, val);
} else if (val > root.val) {
root.right = deleteBST(root.right, val);
} else {
// 找到要删除的节点
// 情况1:叶子节点或只有一个子节点
if (root.left == null) return root.right;
if (root.right == null) return root.left;
// 情况2:有两个子节点 → 找右子树的最小节点替换
TreeNode min = findMin(root.right);
root.val = min.val;
root.right = deleteBST(root.right, min.val);
}
return root;
}
private TreeNode findMin(TreeNode root) {
while (root.left != null) root = root.left;
return root;
}
5. 性能分析
最好情况:树平衡 → O(log n)
最坏情况:树退化成链表 → O(n)
(比如按 1,2,3,4,5 的顺序插入 → 全在右子树 → 链表)
四、平衡二叉树(AVL)
1. 为什么需要 AVL
二叉排序树在最坏情况下退化成链表,查找变成 O(n)。AVL 树通过旋转保持平衡,保证查找始终是 O(log n)。
2. 平衡因子
平衡因子 = 左子树高度 - 右子树高度
|平衡因子| ≤ 1 → 平衡
|平衡因子| > 1 → 需要旋转
3. 四种旋转
LL 型(左左):右旋
RR 型(右右):左旋
LR 型(左右):左旋 → 右旋
RL 型(右左):右旋 → 左旋
LL 型(左左)——右旋:
y x
/ \ 右旋 → / \
x C A y
/ \ / \
A B B C
RR 型(右右)——左旋:
x y
/ \ 左旋 → / \
A y x C
/ \ / \
B C A B
LR 型(左右)——先左旋再右旋:
y y z
/ \ 左旋 x → / \ 右旋 y → / \
x C z C x y
/ \ / \ / \ / \
A z x B A B C ?
/ \ / \
B ? A ?
4. AVL 插入代码
class AVLTree {
private int height(TreeNode node) {
return node == null ? 0 : node.height;
}
private int getBalance(TreeNode node) {
return node == null ? 0 : height(node.left) - height(node.right);
}
// 右旋
private TreeNode rightRotate(TreeNode y) {
TreeNode x = y.left;
TreeNode T2 = x.right;
x.right = y;
y.left = T2;
y.height = Math.max(height(y.left), height(y.right)) + 1;
x.height = Math.max(height(x.left), height(x.right)) + 1;
return x;
}
// 左旋
private TreeNode leftRotate(TreeNode x) {
TreeNode y = x.right;
TreeNode T2 = y.left;
y.left = x;
x.right = T2;
x.height = Math.max(height(x.left), height(x.right)) + 1;
y.height = Math.max(height(y.left), height(y.right)) + 1;
return y;
}
public TreeNode insert(TreeNode node, int val) {
// 1. 普通 BST 插入
if (node == null) return new TreeNode(val);
if (val < node.val) node.left = insert(node.left, val);
else if (val > node.val) node.right = insert(node.right, val);
else return node;
// 2. 更新高度
node.height = Math.max(height(node.left), height(node.right)) + 1;
// 3. 获取平衡因子
int balance = getBalance(node);
// 4. 四种旋转
// LL
if (balance > 1 && val < node.left.val)
return rightRotate(node);
// RR
if (balance < -1 && val > node.right.val)
return leftRotate(node);
// LR
if (balance > 1 && val > node.left.val) {
node.left = leftRotate(node.left);
return rightRotate(node);
}
// RL
if (balance < -1 && val < node.right.val) {
node.right = rightRotate(node.right);
return leftRotate(node);
}
return node;
}
}
五、查找算法对比
| 算法 | 平均时间复杂度 | 最坏时间复杂度 | 空间复杂度 | 要求 |
|---|---|---|---|---|
| 顺序查找 | O(n) | O(n) | O(1) | 无 |
| 二分查找 | O(log n) | O(log n) | O(1)或O(log n) | 有序数组 |
| 二叉排序树 | O(log n) | O(n) | O(n) | 可比较大小 |
| AVL 树 | O(log n) | O(log n) | O(n) | 可比较大小 |
六、408 考研常见考题
题1:二分查找判定树
数组 [1, 3, 5, 7, 9, 11, 13]
二分查找的判定树(mid 为根):
7
/ \
3 11
/ \ / \
1 5 9 13
题2:二叉排序树查找长度
ASL(平均查找长度)= 比较次数之和 / 元素总数
查找成功:
每个节点比较次数 = 节点深度
ASL = (1×1 + 2×2 + 3×3 + ...) / n
查找失败:
比较到叶子节点的空指针时发现不存在
补上空节点后计算
题3:判断 AVL 旋转类型
插入 3, 2, 1:
插 3 → [3]
插 2 → [3(左2)] 平衡
插 1 → [3(左2(左1))] 平衡因子=2 → LL 型右旋
插入 1, 2, 3:
插 1 → [1]
插 2 → [1(右2)] 平衡
插 3 → [1(右2(右3))] 平衡因子=-2 → RR 型左旋
总结
顺序查找 → 无要求,但慢
二分查找 → 有序数组,快但不支持动态插入
二叉排序树 → 动态查找,但可能退化成链表
平衡二叉树 → 动态查找,始终保持 O(log n)
408 重点:二分查找判定树、二叉排序树的插入/删除、AVL 的四种旋转。
💡 觉得有用的话,点赞 + 关注【张老师技术栈】吧!每周更新 Java/Python/爬虫 实战干货,不让你白来。
3569

被折叠的 条评论
为什么被折叠?



