数据结构之查找的方法

1.顺序查找

您描述的是顺序查找(Sequential Search),也称为线性查找。

查找过程

从表的一端(如第一个元素)开始,依次将每个元素的关键字与目标关键字 key 进行比较,直到:

  • 找到相等的元素 → 返回该元素的位置(查找成功)。
  • 查找到表的末尾仍未找到 → 返回失败标志。

查找长度

1. 平均查找长度(ASL)

假设表中有 ( n ) 个元素,每个元素被查找的概率相等(( \frac{1}{n} ))。

查找成功

  • 最好情况:第 1 次就找到,比较次数 = 1
  • 最坏情况:最后 1 次才找到(或没找到),比较次数 = ( n )
  • 平均比较次数(成功时):

    ASL_{\\text{成功}} = \\frac{1+2+ \\cdots + n}{n} = \\frac{n+1}{2}

查找失败

必须比较完所有 ( n ) 个元素才能确定失败,比较次数 = ( n )。

2. 时间复杂度

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

特点

  1. 适用于任何顺序存储或链式存储的线性表(无序表也可)。
  2. 无需事先对数据排序。
  3. 查找效率低,不适合大数据量查找。

示例

数组 [3, 8, 2, 5, 1],查找 key = 5

  1. 比较 3,不等
  2. 比较 8,不等
  3. 比较 2,不等
  4. 比较 5,相等 → 返回索引 3(查找成功,比较 4 次)

查找 key = 7

比较完所有 5 个元素后仍没找到 → 返回失败(比较 5 次)。

1 + 2 + ... + n 是一个等差数列求和。


公式

1 + 2 + \\cdots + n = \\frac{n(n+1)}{2}


推导 (高斯小时候的方法):

S = 1 + 2 + 3 + \\cdots + n

倒过来写:

S = n + (n-1) + (n-2) + \\cdots + 1

两式相加:

2S = (1+n) + (2+(n-1)) + (3+(n-2)) + \\cdots + (n+1)

每项都是 ( n+1 ),共有 ( n ) 项:

2S = n(n+1)

所以

S = \\frac{n(n+1)}{2}

2.折半查找

您描述的是 折半查找(二分查找,Binary Search) 的基本思想和步骤。

我帮你整理并补充完整。


一、适用条件

  • 必须是有序序列(递增或递减),通常假设递增。
  • 存储结构常用一维数组(下标从 1 或 0 开始)。

二、查找过程

设数组 r[1..n] 按关键字递增排列。

  1. 初始化查找区间:

    \\text{low} = 1, \\quad \\text{high} = n

  2. low <= high 时:
    • 计算中间位置:

      \\text{mid} = \\lfloor (\\text{low} + \\text{high}) / 2 \\rfloor

      (向下取整,C/Java 中整数除法自动向下取整)
    • 比较 keyr[mid].key
      • 若相等 → 查找成功,返回 mid
      • key > r[mid].key → 在后半段继续查找:

        \\text{low} = \\text{mid} + 1

      • key < r[mid].key → 在前半段继续查找:

        \\text{high} = \\text{mid} - 1

        (注意:已经比较过 mid,所以下次查找时排除 mid
  3. low > high,说明查找区间为空,查找失败。

三、关键要点

  1. 中间值位置小数向下取整(不是四舍五入):

    • 例如 (4+5)/2 = 4.5 → 取 mid = 4
    • 这样做保证查找区间划分均匀,且整数下标合法。
  2. 排除中间值

    • 因为 mid 位置已经比较过且不相等,所以下一步区间是:
      • 前半段:[low, mid-1]
      • 后半段:[mid+1, high]
  3. 查找结束条件

    • 成功:在循环内找到 key == r[mid].key
    • 失败:low > high 时结束。

四、查找长度与复杂度

  • 时间复杂度:( O(\log n) )

  • 平均查找长度(成功)

    • 假设每个元素等概率被查找,有序表的二分查找 ASL ≈ ( \log_2 (n+1) - 1 )(近似于 ( O(\log n) ))。
    • 具体精确公式与查找判定树的深度有关。
  • 查找失败:也会在 ( O(\log n) ) 次比较后确定。


五、示例

数组 r[1..6] = {5, 16, 20, 27, 39, 46},查找 key=27

  1. low=1, high=6, mid=⌊(1+6)/2⌋=3r[3]=2027>20low=4
  2. low=4, high=6, mid=⌊(4+6)/2⌋=5r[5]=3927<39high=4
  3. low=4, high=4, mid=⌊(4+4)/2⌋=4r[4]=27,相等 → 查找成功,返回 4。
    以下是使用 JavaScript 实现的折半查找(二分查找)代码:
javascript 复制代码
/**
 * 折半查找(二分查找)
 * @param {Array} arr - 有序数组(升序)
 * @param {number} target - 要查找的目标值
 * @return {number} - 找到则返回索引,否则返回 -1
 */
function binarySearch(arr, target) {
    let low = 0;                    // 查找区间左边界
    let high = arr.length - 1;      // 查找区间右边界
    
    while (low <= high) {
        // 计算中间位置(向下取整)
        let mid = Math.floor((low + high) / 2);
        
        if (arr[mid] === target) {
            // 查找成功
            return mid;
        } else if (arr[mid] < target) {
            // 目标值在右半部分
            low = mid + 1;
        } else {
            // 目标值在左半部分
            high = mid - 1;
        }
    }
    
    // 查找失败
    return -1;
}

/**
 * 递归版本的折半查找
 * @param {Array} arr - 有序数组
 * @param {number} target - 要查找的目标值
 * @param {number} low - 查找区间左边界
 * @param {number} high - 查找区间右边界
 * @return {number} - 找到则返回索引,否则返回 -1
 */
function binarySearchRecursive(arr, target, low = 0, high = arr.length - 1) {
    if (low > high) {
        return -1;  // 查找失败
    }
    
    let mid = Math.floor((low + high) / 2);
    
    if (arr[mid] === target) {
        return mid;
    } else if (arr[mid] < target) {
        return binarySearchRecursive(arr, target, mid + 1, high);
    } else {
        return binarySearchRecursive(arr, target, low, mid - 1);
    }
}

// 测试示例
const sortedArray = [2, 5, 8, 12, 16, 23, 38, 56, 72, 91];
const targets = [23, 5, 91, 100];

console.log("有序数组:", sortedArray);
console.log("--- 迭代版本 ---");
targets.forEach(target => {
    const index = binarySearch(sortedArray, target);
    console.log(`查找 ${target}: ${index !== -1 ? `找到,索引为 ${index}` : "未找到"}`);
});

console.log("\n--- 递归版本 ---");
targets.forEach(target => {
    const index = binarySearchRecursive(sortedArray, target);
    console.log(`查找 ${target}: ${index !== -1 ? `找到,索引为 ${index}` : "未找到"}`);
});

// 性能测试(查找所有元素)
console.log("\n--- 验证所有元素都能找到 ---");
sortedArray.forEach((value, expectedIndex) => {
    const foundIndex = binarySearch(sortedArray, value);
    console.assert(foundIndex === expectedIndex, 
        `查找 ${value} 失败: 预期 ${expectedIndex}, 实际 ${foundIndex}`);
});
console.log("所有元素验证通过!");

// 边界情况测试
console.log("\n--- 边界情况测试 ---");
const emptyArray = [];
console.log("空数组查找 5:", binarySearch(emptyArray, 5));  // -1

const singleArray = [10];
console.log("单元素数组查找 10:", binarySearch(singleArray, 10));  // 0
console.log("单元素数组查找 5:", binarySearch(singleArray, 5));   // -1

// 查找过程演示
function binarySearchWithSteps(arr, target) {
    console.log(`\n查找 ${target} 的过程:`);
    let low = 0;
    let high = arr.length - 1;
    let steps = 0;
    
    while (low <= high) {
        steps++;
        let mid = Math.floor((low + high) / 2);
        console.log(`步骤 ${steps}: low=${low}, high=${high}, mid=${mid}, arr[mid]=${arr[mid]}`);
        
        if (arr[mid] === target) {
            console.log(`找到 ${target},位置: ${mid},共 ${steps} 步`);
            return mid;
        } else if (arr[mid] < target) {
            low = mid + 1;
        } else {
            high = mid - 1;
        }
    }
    
    console.log(`未找到 ${target},共 ${steps} 步`);
    return -1;
}

// 演示查找过程
binarySearchWithSteps(sortedArray, 23);

代码说明

1. 迭代版本 (binarySearch)

  • 使用 while 循环实现
  • 不断更新查找区间 [low, high]
  • 中间位置使用 Math.floor() 向下取整
  • 时间复杂度:O(log n)
  • 空间复杂度:O(1)

2. 递归版本 (binarySearchRecursive)

  • 递归实现,逻辑更简洁
  • 需要传递查找区间边界参数
  • 时间复杂度:O(log n)
  • 空间复杂度:O(log n)(递归栈空间)

3. 关键点

  • 数组必须有序(升序)
  • 中间位置向下取整:确保索引为整数
  • 排除已比较元素mid+1mid-1
  • 循环条件low <= high(允许相等,此时区间还有一个元素)

4. 测试示例输出

复制代码
有序数组: [2, 5, 8, 12, 16, 23, 38, 56, 72, 91]
--- 迭代版本 ---
查找 23: 找到,索引为 5
查找 5: 找到,索引为 1
查找 91: 找到,索引为 9
查找 100: 未找到

--- 递归版本 ---
查找 23: 找到,索引为 5
查找 5: 找到,索引为 1
查找 91: 找到,索引为 9
查找 100: 未找到

5. 性能特点

  • 每次比较将查找范围缩小一半
  • 仅适用于顺序存储结构(如数组)
  • 不适合链表(因为需要随机访问)
  • 比顺序查找快得多,但需要先排序

此实现完整展示了折半查找的核心思想和具体实现方法。

问得非常好!这是一个很关键的理解点。让我详细解释为什么折半查找的时间复杂度是 (O(\log n)),以及 "(\log)" 是怎么来的。

一、直观理解:每次砍掉一半

核心思想 :每次比较后,剩余需要查找的元素数量都减半

举例:假设数组有 (n=16) 个元素。

复制代码
第1次比较:剩余 16 个元素 → 比较一次 → 剩余 8 个
第2次比较:剩余 8 个元素 → 比较一次 → 剩余 4 个
第3次比较:剩余 4 个元素 → 比较一次 → 剩余 2 个
第4次比较:剩余 2 个元素 → 比较一次 → 剩余 1 个
第5次比较:剩余 1 个元素 → 比较一次 → 找到或确认不存在

从 16 到 1,只需要 5 次比较


二、数学推导

1. 设最坏情况下的比较次数为 (k)

每次比较后,剩余元素数变化:

n \\rightarrow \\frac{n}{2} \\rightarrow \\frac{n}{4} \\rightarrow \\cdots \\rightarrow 1

这个过程的次数 (k) 满足:

\\frac{n}{2\^k} = 1

解方程:

2\^k = n

k = \\log_2 n

所以最坏情况下需要比较 (\log_2 n) 次


2. 为什么是 "(\log_2 n)"?

因为每次除以 2(折半):

  • 如果你每次除以 3,复杂度就是 (O(\log_3 n))
  • 但计算机科学中,对数底数在渐进记号中不重要((\log_a n = \frac{\log_b n}{\log_b a}),常数倍差异)
  • 所以我们通常简写为 (O(\log n))(默认底数为 2)

三、用具体数字感受一下

数据规模 (n) (\log_2 n)(近似) 比较次数(最坏)
10 3.32 4
100 6.64 7
1,000 9.97 10
1,000,000 19.93 20
1,000,000,000 29.90 30

惊人的效率

  • 10 亿个元素中查找,最多只需约 30 次比较
  • 而顺序查找需要最多 10 亿次比较。

这就是 (O(\log n)) 的威力:输入规模翻倍,操作次数只增加 1 次


四、与顺序查找的对比

顺序查找 (线性查找):

\\text{比较次数} \\propto n \\quad (\\text{成正比})

  • (n) 增加 10 倍 → 比较次数也增加 10 倍

折半查找

\\text{比较次数} \\propto \\log n

  • (n) 增加 10 倍 → 比较次数只增加约 3-4 次
    • 因为 (\log(10n) = \log n + \log 10 \approx \log n + 3.32)

五、完整公式推导

更精确地说,对于长度为 (n) 的有序表,折半查找的最坏情况比较次数 为:

\\lfloor \\log_2 n \\rfloor + 1

\\lceil \\log_2 (n+1) \\rceil

推导

设比较次数为 (k),则:

  • 查找判定树的高度为 (h = \lceil \log_2 (n+1) \rceil)
  • 最坏情况就是沿着树的一条路径走到叶子,比较次数 = 树的高度

六、通俗比喻

想象你在猜数字游戏中猜 1-100 之间的数字:

  • 你问:"比 50 大吗?" → 是 → 范围缩小到 51-100
  • 你问:"比 75 大吗?" → 否 → 范围缩小到 51-75
  • ...

每次提问都能排除一半的可能性,所以最多只需要 (\log_2 100 \approx 7) 次提问。

这就像对数的定义

  • 对数问的是:"2 的多少次方等于 n?"
  • 在折半查找中问的是:"需要折半多少次,才能从 n 个元素减少到 1 个?"

总结

查找算法 时间复杂度 增长速率
顺序查找 (O(n)) 线性增长
折半查找 (O(\log n)) 对数增长(非常慢的增长)

对数增长是计算机算法中非常理想的复杂度,因为它意味着即使数据量极大,操作次数也增加得非常缓慢。这正是折半查找高效的核心原因。

3.哈希查找

是的,您描述得非常准确。您这段话精炼地总结了哈希表(Hash Table)的核心原理和查找过程。

让我们将其拆解成几个关键部分,并做一点延伸说明:

1. 核心原理:地址 = 哈希函数(关键字)

这是哈希表最本质的思想。它不通过比较关键字的大小来定位,而是用一个函数直接进行计算。

  • 输入 :记录的唯一标识,即 关键字(Key)
  • 处理 :通过一个预设的 哈希函数(Hash Function) 进行计算。
  • 输出 :一个整数,通常作为记录在数组中存储的 地址(索引)

示例: 假设有一个存储员工信息(关键字为工号)的哈希表。

  • 哈希函数设计为:地址 = 工号 % 100(取工号的最后两位)。
  • 那么,工号为 12345 的员工,其存储地址就是 45

2. 查找操作的过程

您描述的查找过程非常标准:

  1. 计算哈希地址 :对要查找的 待查关键字(Search Key) ,使用建表时同一个哈希函数计算,得到其理论存储地址。
  2. 访问存储单元:直接去到哈希表(通常是一个数组)的这个地址位置。
  3. 获取信息并判定
    • 情况A:命中 。该位置存储的记录关键字与待查关键字完全匹配。查找成功
    • 情况B:冲突位置不匹配 。该位置有记录,但关键字不匹配。这说明当初插入时发生了哈希冲突,当前记录是通过冲突解决策略(如链地址法、开放定址法)存放在这里的。此时需要根据冲突解决策略,继续"探测"下一个可能的位置,直到找到匹配的记录或确认不存在。
    • 情况C:空单元 。该位置为空,说明没有任何记录被哈希到这个地址。查找失败(对于开放定址法,可能需要继续探测直到遇到空单元才能最终确认失败)。

3. 重要的补充概念

您的话引出了哈希表的几个关键特性和需要处理的问题:

  • 哈希冲突 :不同的关键字可能被哈希函数映射到同一个地址(例如,工号 1234522345%100 计算后地址都是 45)。这是哈希表设计时必须处理的核心问题。
  • 冲突解决策略
    • 链地址法(开散列法):每个地址位置不是一个记录,而是一个链表(或其他容器)。冲突的记录都放在这个链表里。查找时,在计算出的地址的链表中进行顺序查找。
    • 开放定址法(闭散列法):所有记录都放在主数组里。发生冲突时,按照某种探测规则(如线性探测、二次探测)寻找数组中的下一个空位。查找时也需要遵循同样的规则进行探测。

总结流程图

根据您的描述,查找操作的流程图可以概括如下:

复制代码
开始查找
    ↓
输入待查关键字 Key
    ↓
使用哈希函数 H 计算地址: Addr = H(Key)
    ↓
访问哈希表 Table[Addr]
    ↓
          ┌─────────────────┐
          │ Table[Addr]状态?│
          └────────┬────────┘
                   │
        ┌──────────┴──────────┐
        ▼                     ▼                     ▼
     [为空]           [关键字匹配]          [关键字不匹配]
        │                     │                     │
        │               查找成功!            发生哈希冲突
        │                     │                     │
        ▼                     ▼                     ▼
    查找失败             返回记录信息          根据冲突解决策略
    (对于开放定址法,      (或指针)             寻找下一个可能位置
     可能需继续探测)                           (回到"访问..."步骤循环)

您这段话完美地点明了哈希表之所以能实现 O(1) 平均时间复杂度查找的奥妙:通过一次函数计算直接定位,避免了大量的比较操作。其性能高度依赖于哈希函数的质量和冲突解决策略的效率。

解决冲突的方法

哈希函数产生了冲突的解决方法如下:

  • 1.开放定址法: Hi=(H(key)+di) % m i=1, 2, ..., k(k<m一1) 其中,H(key)为哈希函数; m 为哈希表表长;di为增量序列。

    常见的增量序列有以下3 种。

  • 2.链地址法。它在查找表的每一个记录中增加一个链域,链域中存放下一个具有

    相同哈希函数值的记录的存储地址。利用链域,就把若工个发生冲突的记录链

    接在一个链表内。当链域的值为NULL 时,表示已没有后继记录了。因此,对于

    发生冲突时的查找和插入操作就跟线性表一样了。

  • 3.再哈希法: 在同义词发生地址冲突时计算另一个哈希函数地址,直到冲突不再

    发生。这种方法不易产生聚集现象,但增加了计算时间。

  • 4.建立一个公共溢出区。无论由哈希函数得到的哈希地址是什么,一旦发生冲突

    都填入到公共溢出区中。

线性探测法详细介绍

一、基本概念

线性探测法 是开放定址法中最简单 的一种冲突解决策略。当发生哈希冲突时,它按照线性顺序探测哈希表中的下一个位置。

二、基本规则

1. 核心探测序列

当关键字K的哈希地址H(K)发生冲突时,线性探测法依次检查以下位置:

复制代码
探测序列:H(K), H(K)+1, H(K)+2, ..., H(K)+i (mod m)

其中:

  • H(K)是初始哈希地址
  • i是探测次数(从0开始)
  • m是哈希表的大小
  • mod m确保探测在表内循环

2. 数学表达式

i次探测的地址为:

复制代码
h_i(K) = [H(K) + i] mod m,其中 i = 0, 1, 2, ..., m-1

三、插入操作规则

1. 插入算法步骤

复制代码
1. 计算初始地址:addr = H(key) mod m
2. 循环探测直到找到空位或已满:
   while (table[addr] 不为空 AND table[addr] 不是已删除标记):
       if (table[addr].key == key): // 关键字已存在
           更新值或报错
           return
       addr = (addr + 1) mod m // 线性探测到下一位置
       if (addr == H(key) mod m): // 已循环一圈
           表满,需要扩容
           return
3. 将记录插入 table[addr]

2. 插入示例

假设哈希表大小m=10,哈希函数H(key)=key mod 10

插入序列47, 67, 43, 87, 91

步骤 关键字 H(key) 冲突处理 最终位置 表状态
1 47 7 直接插入 7 [...7:47]
2 67 7 冲突,探测8 8 [...7:47, 8:67]
3 43 3 直接插入 3 [...3:43, 7:47, 8:67]
4 87 7 冲突,探测8,再冲突,探测9 9 [...3:43, 7:47, 8:67, 9:87]
5 91 1 直接插入 1 [1:91, 3:43, 7:47, 8:67, 9:87]

最终哈希表

复制代码
索引:  0   1   2   3   4   5   6   7   8   9
内容: [ ] [91][ ] [43][ ] [ ] [ ] [47][67][87]

四、查找操作规则

1. 查找算法步骤

复制代码
1. 计算初始地址:addr = H(key) mod m
2. 起始地址保存:start = addr
3. 循环探测:
   while (table[addr] 不为空):
       if (table[addr] 是有效记录 AND table[addr].key == key):
           查找成功,返回记录
       addr = (addr + 1) mod m // 线性探测到下一位置
       if (addr == start): // 已循环一圈
           退出循环
4. 查找失败,返回空

2. 查找示例

继续使用上面的哈希表,查找关键字:

情况1:查找91(无冲突)

复制代码
H(91) = 1 mod 10 = 1
table[1] = 91 → 匹配成功!
查找长度 = 1

情况2:查找87(有冲突)

复制代码
H(87) = 7 mod 10 = 7
table[7] = 47 → 不匹配,探测下一个
table[8] = 67 → 不匹配,探测下一个
table[9] = 87 → 匹配成功!
查找长度 = 3

情况3:查找52(不存在)

复制代码
H(52) = 2 mod 10 = 2
table[2] = 空 → 查找失败!
查找长度 = 1

五、删除操作的特殊处理

1. 删除的问题

如果简单地将元素置为空,会破坏查找链,导致后续元素无法被查找到。

2. 解决方案:使用删除标记

复制代码
删除记录时,不真正清空位置,而是标记为"已删除"(DELETED)

3. 修改后的查找算法

复制代码
while (table[addr] 不为空 AND table[addr] 不是删除标记):
    // ...原有逻辑
// 如果遇到删除标记,需要继续探测
// 查找时需要跳过删除标记继续探测
// 插入时可以将记录插入到删除标记的位置

六、性能分析

1. 平均查找长度(ASL)

查找成功时的平均查找长度:

复制代码
ASL_success ≈ (1 + 1/(1-α)) / 2
其中α = n/m(装填因子)

查找失败时的平均查找长度:

复制代码
ASL_unsuccess ≈ (1 + 1/(1-α)²) / 2

2. 性能随装填因子的变化

装填因子α 成功查找ASL 失败查找ASL
0.5 1.5 2.5
0.7 2.17 8.5
0.8 3.0 13.0
0.9 5.5 50.5

七、优缺点

优点:

  1. 实现简单:算法逻辑简单直观
  2. 空间局部性好:探测序列连续,缓存友好
  3. 无需额外空间:所有数据都在主数组中

缺点:

  1. 聚集现象(主要问题):

    • 初级聚集:哈希到同一地址的记录形成长簇
    • 次级聚集:不同哈希值的记录互相阻塞,形成更大的簇
  2. 删除复杂:需要特殊处理删除标记

  3. 性能下降快:当装填因子较高时,性能急剧下降

八、实际示例演示

完整示例:哈希表大小m=7,哈希函数H(key)=key mod 7

操作序列

  1. 插入:8, 15, 22, 29, 36
  2. 查找:22, 30
  3. 删除:15
  4. 插入:43

详细过程

复制代码
初始:table[0..6] = [ ] [ ] [ ] [ ] [ ] [ ] [ ]

1. 插入8:H(8)=1 → table[1]=8
   [ ] [8] [ ] [ ] [ ] [ ] [ ]

2. 插入15:H(15)=1 → 冲突!探测2 → table[2]=15
   [ ] [8] [15] [ ] [ ] [ ] [ ]

3. 插入22:H(22)=1 → 冲突!探测2 → 冲突!探测3 → table[3]=22
   [ ] [8] [15] [22] [ ] [ ] [ ]

4. 插入29:H(29)=1 → 探测2,3,4 → table[4]=29
   [ ] [8] [15] [22] [29] [ ] [ ]

5. 插入36:H(36)=1 → 探测2,3,4,5 → table[5]=36
   [ ] [8] [15] [22] [29] [36] [ ]

6. 查找22:
   H(22)=1 → table[1]=8≠22
   探测2 → table[2]=15≠22
   探测3 → table[3]=22 ✓ 找到!查找长度=3

7. 查找30:
   H(30)=2 → table[2]=15≠30
   探测3 → table[3]=22≠30
   探测4 → table[4]=29≠30
   探测5 → table[5]=36≠30
   探测6 → table[6]=空 ✗ 失败!查找长度=5

8. 删除15:
   H(15)=1 → 探测到table[2]=15 → 标记为DELETED
   [ ] [8] [DEL] [22] [29] [36] [ ]

9. 插入43:
   H(43)=1 → table[1]=8≠空
   探测2 → table[2]=DELETED(可插入!)→ table[2]=43
   [ ] [8] [43] [22] [29] [36] [ ]

九、实践建议

  1. 控制装填因子:通常建议α < 0.7,超过时应考虑扩容
  2. 表大小选择:最好选择质数作为表大小,减少聚集
  3. 监控性能:当平均查找长度显著增加时,考虑优化或扩容
  4. 替代方案考虑:对于高装填因子的场景,可以考虑二次探测或双散列法

线性探测法以其简单性在装填因子较低的场景下表现良好,但在高负载时性能下降明显,这是由其固有的聚集特性决定的。

相关推荐
fengxin_rou2 小时前
Redis 核心数据结构:跳表实现、层高设计解析
数据结构·数据库·redis
穿过锁扣的风2 小时前
决策树:从入门到实战,解锁 AI 分类预测的核心利器
数据结构·python·决策树
数智工坊2 小时前
【数据结构-线性表】2.2单链表
数据结构
牛马大师兄2 小时前
数据结构复习 | 单向链表
c语言·数据结构·笔记·链表
青桔柠薯片2 小时前
数据结构:单向链表和双向链表
数据结构·算法·链表
2401_841495642 小时前
【LeetCode刷题】对称二叉树
数据结构·python·算法·leetcode·二叉树··递归
Hello World . .3 小时前
排序算法:常用排序算法
c语言·数据结构·算法·vim·排序算法
虢薪3 小时前
双向链表与循环链表基础操作&进阶操作
数据结构·链表
寄存器漫游者3 小时前
数据结构 单向链表进阶
数据结构·链表