数据结构——查找算法:二分查找、二叉排序树、平衡二叉树

查找是计算机中最基础的操作之一——从有序数组中快速定位目标、在数据库中搜索记录、在搜索引擎中检索关键词。不同场景需要不同的查找算法。

一、顺序查找

最简单的查找方式,从头到尾逐个比较。

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/爬虫 实战干货,不让你白来。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值