数据结构(6)哈希表和算法

一、哈希表

哈希表的基本概念

  1. 哈希函数

    • 哈希函数是将输入(键)转换为固定大小的输出(哈希值)的函数。这个输出通常是一个整数,表示在哈希表中的索引位置。
    • 理想的哈希函数应该能够均匀分布输入,以减少冲突的发生。
  2. 冲突

    • 当两个不同的键通过哈希函数计算得到相同的索引时,就会发生冲突。哈希表需要一种机制来处理这些冲突。
      • 开放地址法:在发生冲突时,寻找下一个空闲的位置来存储元素。
      • 链地址法:在每个索引位置维护一个链表,所有哈希到同一位置的元素都存储在这个链表中。

哈希表的操作

  • 插入:使用哈希函数计算键的索引,将值存储在该索引位置。
  • 查找:使用哈希函数计算键的索引,直接访问该索引位置以获取值。
  • 删除:使用哈希函数计算键的索引,找到对应的值并将其删除

哈希表的优缺点

优点

  • 平均情况下,插入、查找和删除操作的时间复杂度为 𝑂(1)O(1),非常高效。
  • 可以快速访问数据,适合需要频繁查找的场景。

缺点

  • 在最坏情况下(例如,所有键都发生冲突),时间复杂度可能退化为 𝑂(𝑛)O(n)。
  • 需要额外的内存来存储链表或处理冲突。
  • 哈希函数的设计和负载因子的管理是实现哈希表的关键。

二、算法

算法是为解决特定问题而设计的一系列明确的、有限的步骤和规则。

1. 算法设计原则
  • 正确性:确保语法正确,合法输入有合理输出,非法输入有明确处理,能够通过各种测试(尤其是边界条件测试)。
  • 可读性:算法设计应清晰易读,具备高内聚、低耦合的特性,便于维护与交流。
  • 健壮性 :应对非法数据,算法能处理异常而不会崩溃,可通过异常处理机制(如 try-catchif-else)。
  • 高效率:在时间复杂度上尽量优化,确保随着输入规模增长,运行时间不成倍增加。
  • 低存储:空间复杂度要尽量优化,减少对内存的占用。
2. 时间复杂度的计算规则
  • 用常数 1 取代运行时间中的所有加法常数。
  • 保留运行时间函数中的最高阶项。
  • 去除最高阶项中的常数系数。
3. 常见时间复杂度类型
  • O(1): 常数时间,执行时间不受输入规模影响。
  • O(log n): 对数时间,典型于二分查找等算法。
  • O(n): 线性时间,执行时间随输入规模线性增长。
  • O(n log n): 常见于归并排序和快速排序的平均时间复杂度。
  • O(n^2): 二次时间,常见于双重嵌套循环。
  • O(2^n): 指数时间,通常用于组合问题。
  • O(n!): 阶乘时间,常见于全排列问题。
4. 时间复杂度增长顺序

O(1) < O(log n) < O(n) < O(n log n) < O(n^2) < O(n^3) < O(2^n) < O(n!) < O(n^n)

5. 空间复杂度
  • 与时间复杂度类似,空间复杂度衡量算法在内存中的占用情况,应尽量减少占用空间。

三、排序思想及时间复杂度

1. 冒泡排序(Bubble Sort)

排序思想: 冒泡排序通过多次比较相邻的元素,并交换位置,将最大(或最小)的元素"冒泡"到数组的一端。每一轮都会将剩余的最大(或最小)元素逐渐放到正确位置。不断重复这个过程,直到整个数组有序。

步骤

  1. 从数组的开头开始,比较相邻的两个元素。
  2. 如果前一个元素比后一个元素大,就交换它们。
  3. 完成一轮后,数组末端的元素是当前最大的。
  4. 继续对剩下的元素重复上述过程,直到没有元素需要交换。

时间复杂度

  • 最好情况:O(n)O(n)O(n)(如果数组已经有序,只需要一次遍历)
  • 平均情况:O(n2)O(n^2)O(n2)
  • 最坏情况:O(n2)O(n^2)O(n2)

2. 选择排序(Selection Sort)

排序思想: 选择排序通过反复选择剩余元素中的最小(或最大)元素,将它放在排序的正确位置。每次选择后,将该元素与当前排序范围中的第一个未排序元素交换位置。

步骤

  1. 找到数组中最小的元素,和第一个元素交换位置。
  2. 再从剩下的未排序部分中找到最小元素,和第二个元素交换位置。
  3. 不断重复这个过程,直到整个数组有序。

时间复杂度

  • 最好情况:O(n2)O(n^2)O(n2)
  • 平均情况:O(n2)O(n^2)O(n2)
  • 最坏情况:O(n2)O(n^2)O(n2)

(选择排序的比较次数与输入无关,始终是 O(n2)O(n^2)O(n2),即使数组已排序。)


3. 插入排序(Insertion Sort)

排序思想: 插入排序通过逐步构建有序序列,每次将未排序的元素插入到已排序部分的正确位置。适合数据量小或部分有序的场景。

步骤

  1. 从第二个元素开始,将它与前面的元素进行比较,找到合适的位置插入。
  2. 每插入一个元素后,已排序部分的长度增加一。
  3. 不断重复这个过程,直到所有元素都被插入正确位置。

时间复杂度

  • 最好情况:O(n)O(n)O(n)(如果数组已经有序,每次只需一次比较)
  • 平均情况:O(n2)O(n^2)O(n2)
  • 最坏情况:O(n2)O(n^2)O(n2)

(插入排序在数组基本有序时效率较高。)


4. 快速排序(Quick Sort)

排序思想 : 快速排序通过分治法实现。它选择一个基准元素(通常是数组的第一个或最后一个元素),然后将数组划分为两个子数组:小于基准的元素放在左边,大于基准的放在右边。递归对左右子数组进行排序。

步骤

  1. 选择一个基准元素(pivot)。
  2. 将数组划分为两部分:小于基准的在左边,大于基准的在右边。
  3. 对左右子数组递归执行快速排序。
  4. 最终,左右子数组有序时,整个数组也有序。

时间复杂度

  • 最好情况:O(nlog⁡n)O(n \log n)O(nlogn)(每次划分时基准能均匀地将数组分成两半)
  • 平均情况:O(nlog⁡n)O(n \log n)O(nlogn)
  • 最坏情况:O(n2)O(n^2)O(n2)(当基准总是最小或最大元素,导致划分极不均匀)
总结时间复杂度对比:
  • 冒泡排序: 最好 O(n)O(n)O(n), 平均 O(n2)O(n^2)O(n2), 最坏 O(n2)O(n^2)O(n2)
  • 选择排序: 最好、平均、最坏均为 O(n2)O(n^2)O(n2)
  • 插入排序: 最好 O(n)O(n)O(n), 平均 O(n2)O(n^2)O(n2), 最坏 O(n2)O(n^2)O(n2)
  • 快速排序: 最好 O(nlog⁡n)O(n \log n)O(nlogn), 平均 O(nlog⁡n)O(n \log n)O(nlogn), 最坏 O(n2)O(n^2)O(n2)

四、查找方法的思想及时间复杂度

1. 顺序查找(线性查找)

查找思想: 顺序查找是最简单的查找方法,逐个检查数组中的每个元素,直到找到目标元素或检查完整个数组。适用于无序数组。

步骤

  1. 从数组的第一个元素开始,依次比较每个元素与目标元素是否相等。
  2. 如果找到相等的元素,返回该元素的下标;如果遍历到最后也没找到,返回失败或空值。

时间复杂度

  • 最好情况:O(1)O(1)O(1)(目标元素在数组的第一个位置)
  • 平均情况:O(n)O(n)O(n)(需要检查一半的元素)
  • 最坏情况:O(n)O(n)O(n)(目标元素在最后或不存在)

2. 二分查找(Binary Search)

查找思想 : 二分查找只适用于有序数组。它通过每次将查找范围减半来寻找目标元素。该算法基于"分而治之"的思想,将查找问题递归地缩小至更小的子问题。

步骤

  1. 先将数组中间元素与目标元素比较。
  2. 如果相等,查找成功;如果目标元素小于中间元素,则在左半部分继续查找;如果目标元素大于中间元素,则在右半部分查找。
  3. 重复上述过程,直到找到目标元素或查找范围为空。

时间复杂度

  • 最好情况:O(1)O(1)O(1)(目标元素正好位于中间位置)
  • 平均情况:O(log⁡n)O(\log n)O(logn)
  • 最坏情况:O(log⁡n)O(\log n)O(logn)

(二分查找的高效性来源于每次都将问题规模缩小一半,因此具有对数时间复杂度。)


3. 哈希查找(Hash Search)

查找思想 : 哈希查找通过使用哈希函数将关键字直接映射到数组的一个位置。理想情况下,哈希查找的时间复杂度为常数时间。哈希查找需要使用哈希表结构,并通过哈希函数进行查找和插入。哈希查找能非常快速地找到元素,但前提是哈希函数设计得当,冲突较少。

步骤

  1. 使用哈希函数计算出目标元素的哈希值。
  2. 在哈希表中查看该哈希值对应的位置是否存有目标元素。
  3. 如果有冲突(多个元素映射到同一位置),根据冲突解决方法继续查找(如链地址法、开放地址法等)。

时间复杂度

  • 最好情况:O(1)O(1)O(1)(没有冲突的情况下)
  • 平均情况:O(1)O(1)O(1)
  • 最坏情况:O(n)O(n)O(n)(当发生大量哈希冲突时,查找退化为线性查找)
查找方法总结与时间复杂度对比:
  • 顺序查找: 最好 O(1)O(1)O(1), 平均 O(n)O(n)O(n), 最坏 O(n)O(n)O(n)
  • 二分查找: 最好 O(1)O(1)O(1), 平均 O(log⁡n)O(\log n)O(logn), 最坏 O(log⁡n)O(\log n)O(logn)
  • 哈希查找: 最好 O(1)O(1)O(1), 平均 O(1)O(1)O(1), 最坏 O(n)O(n)O(n)
相关推荐
搬砖的小码农_Sky3 小时前
C语言:数组
c语言·数据结构
先鱼鲨生5 小时前
数据结构——栈、队列
数据结构
一念之坤5 小时前
零基础学Python之数据结构 -- 01篇
数据结构·python
IT 青年5 小时前
数据结构 (1)基本概念和术语
数据结构·算法
熬夜学编程的小王5 小时前
【初阶数据结构篇】双向链表的实现(赋源码)
数据结构·c++·链表·双向链表
liujjjiyun6 小时前
小R的随机播放顺序
数据结构·c++·算法
Reese_Cool8 小时前
【数据结构与算法】排序
java·c语言·开发语言·数据结构·c++·算法·排序算法
djk88888 小时前
.net将List<实体1>的数据转到List<实体2>
数据结构·list·.net
搬砖的小码农_Sky9 小时前
C语言:结构体
c语言·数据结构
飞升不如收破烂~9 小时前
redis的map底层数据结构 分别什么时候使用哈希表(Hash Table)和压缩列表(ZipList)
算法·哈希算法