数组查找

重点掌握折半查找的思想,需要能够手写相关代码。

顺序查找

顺序查找的 核心思想 是从数组(线性表)的第一个元素开始,逐个检查每个元素是否等于目标值,直到找到目标或检查完整个数组。

其代码实现如下,非常简单,遍历一次数组即可:

int SequentialSearch(int A[], int n, int value) {
    for (int i = 0; i < n; i++) {
        if (A[i] == value) {
            return i;  // 找到,返回索引
        }
    }
    return -1;  // 未找到
}

顺序查找的时间复杂度如下:

  • 最好情况:$O(1)$
  • 最坏情况:$O(n)$
  • 平均情况:$O(n)$

折半查找

折半查找 要求数组是有序的

核心思想 在于每次将查找范围折半,只保留目标可能存在的一半,直到范围为空或找到目标为止。

例如,在有序数组中查找一个元素时,可以先比较中间元素:

  • 如果目标值小于中间值,查左半部分;
  • 如果目标值大于中间值,查右半部分;
  • 如果相等,则查找成功。

下图给出了一个实例,从数组中折半查找元素 23:

2
5
8
12
16
23
38
56
72
91
0
1
2
3
4
5
6
7
8
9
2
5
8
12
16
23
38
56
72
91
L=0
1
2
3
M=4
5
6
7
8
H=9
2
5
8
12
16
23
38
56
72
91
0
1
2
3
4
L=5
6
M=7
8
2
5
8
12
16
23
38
56
72
91
0
1
2
3
4
L=M=5
H=6
7
8
9
H=9
查找 23
23>16,向右边查找
23<56,向左边查找
找到了23,返回下标5

折半查找可以基于迭代实现,也可以作为一个递归函数实现:

int BinarySearch(int A[], int n, int value) {
    int low = 0;
    int high = n - 1;

    while (low <= high) {
        int mid = low + (high - low) / 2;  // 防止溢出

        if (A[mid] == value) {
            return mid;  // 找到
        } else if (A[mid] < value) {
            low = mid + 1;
        } else {
            high = mid - 1;
        }
    }

    return -1;  // 未找到
}
int BinarySearchRecursive(int A[], int low, int high, int value) {
    if (low > high) {
        return -1;  // 区间无效,查找失败
    }

    int mid = low + (high - low) / 2;

    if (A[mid] == value) {
        return mid;  // 找到目标
    } else if (A[mid] < value) {
        return BinarySearchRecursive(A, mid + 1, high, value);  // 查右半部分
    } else {
        return BinarySearchRecursive(A, low, mid - 1, value);   // 查左半部分
    }
}

折半查找的时间复杂度如下:

  • 最好情况:$O(1)$
  • 最坏情况:$O(log_{2}n)$
  • 平均情况:$O(log_{2}n)$

折半查找判定树

折半查找判定树(Binary Search Decision Tree)是折半查找(Binary Search)过程的树形表示,用于清晰地 描述在查找过程中所做的每一次比较决策

所以简单地来说,如何根据一个有序序列得到一个折半查找判定树呢,就是将折半查找的过程走一遍,举两个实际的例子来说明一下:

✅ 假设数组为 [10, 20, 30, 40, 50, 60],元素个数为 6。

🌳 折半查找判定树构造原则

折半查找需要选一个“中点”作为根节点。偶数长度数组有两个中点,一般可以:

  • 选靠左的那个(常见做法):mid = (low + high) // 2
  • 或者选靠右的那个

假设按 常规左中点 来构造,则 构造过程如下

  1. [10, 20, 30, 40, 50, 60]mid = 2 → 30 为根;

  2. 左边 [10, 20]mid = 0 → 10 为左子树;

    • 右边是 [20] → 作为 10 的右子节点;
  3. 右边 [40, 50, 60]mid = 4 → 50 为右子树;

    • 左边 [40] → 左子节点;
    • 右边 [60] → 右子节点。

最后可以得到如下折半查找判定树:

         30
       /    \
     10      50
       \    /  \
       20  40  60

分块查找

线性表的分块查找通常应用于一种特定的情境:线性表(例如数组)被分为多个大小相等(或者最后一个块可能较小)的块,并且块内的元素是无序的,但是块与块之间是有序的。也就是说,每个块内的最大(或最小)元素小于下一个块的任意元素。

常用的分块查找策略如下:

  1. 先对块索引进行顺序或二分查找以确定所需元素可能所在的块。
  2. 然后在确定的块中进行顺序查找。
24
54
78
88
0
6
9
13
24
21
6
11
8
22
32
31
54
72
61
78
88
83
Index Table
Array