一、哈希表
哈希表的基本概念
-
哈希函数:
- 哈希函数是将输入(键)转换为固定大小的输出(哈希值)的函数。这个输出通常是一个整数,表示在哈希表中的索引位置。
- 理想的哈希函数应该能够均匀分布输入,以减少冲突的发生。
-
冲突:
- 当两个不同的键通过哈希函数计算得到相同的索引时,就会发生冲突。哈希表需要一种机制来处理这些冲突。
- 开放地址法:在发生冲突时,寻找下一个空闲的位置来存储元素。
- 链地址法:在每个索引位置维护一个链表,所有哈希到同一位置的元素都存储在这个链表中。
- 当两个不同的键通过哈希函数计算得到相同的索引时,就会发生冲突。哈希表需要一种机制来处理这些冲突。
哈希表的操作
- 插入:使用哈希函数计算键的索引,将值存储在该索引位置。
- 查找:使用哈希函数计算键的索引,直接访问该索引位置以获取值。
- 删除:使用哈希函数计算键的索引,找到对应的值并将其删除
哈希表的优缺点
优点:
- 平均情况下,插入、查找和删除操作的时间复杂度为 𝑂(1)O(1),非常高效。
- 可以快速访问数据,适合需要频繁查找的场景。
缺点:
- 在最坏情况下(例如,所有键都发生冲突),时间复杂度可能退化为 𝑂(𝑛)O(n)。
- 需要额外的内存来存储链表或处理冲突。
- 哈希函数的设计和负载因子的管理是实现哈希表的关键。
二、算法
算法是为解决特定问题而设计的一系列明确的、有限的步骤和规则。
1. 算法设计原则:
- 正确性:确保语法正确,合法输入有合理输出,非法输入有明确处理,能够通过各种测试(尤其是边界条件测试)。
- 可读性:算法设计应清晰易读,具备高内聚、低耦合的特性,便于维护与交流。
- 健壮性:应对非法数据,算法能处理异常而不会崩溃,可通过异常处理机制(如
try-catch
或if-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)
排序思想: 冒泡排序通过多次比较相邻的元素,并交换位置,将最大(或最小)的元素“冒泡”到数组的一端。每一轮都会将剩余的最大(或最小)元素逐渐放到正确位置。不断重复这个过程,直到整个数组有序。
步骤:
- 从数组的开头开始,比较相邻的两个元素。
- 如果前一个元素比后一个元素大,就交换它们。
- 完成一轮后,数组末端的元素是当前最大的。
- 继续对剩下的元素重复上述过程,直到没有元素需要交换。
时间复杂度:
- 最好情况:O(n)O(n)O(n)(如果数组已经有序,只需要一次遍历)
- 平均情况:O(n2)O(n^2)O(n2)
- 最坏情况:O(n2)O(n^2)O(n2)
2. 选择排序(Selection Sort)
排序思想: 选择排序通过反复选择剩余元素中的最小(或最大)元素,将它放在排序的正确位置。每次选择后,将该元素与当前排序范围中的第一个未排序元素交换位置。
步骤:
- 找到数组中最小的元素,和第一个元素交换位置。
- 再从剩下的未排序部分中找到最小元素,和第二个元素交换位置。
- 不断重复这个过程,直到整个数组有序。
时间复杂度:
- 最好情况: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)
排序思想: 插入排序通过逐步构建有序序列,每次将未排序的元素插入到已排序部分的正确位置。适合数据量小或部分有序的场景。
步骤:
- 从第二个元素开始,将它与前面的元素进行比较,找到合适的位置插入。
- 每插入一个元素后,已排序部分的长度增加一。
- 不断重复这个过程,直到所有元素都被插入正确位置。
时间复杂度:
- 最好情况:O(n)O(n)O(n)(如果数组已经有序,每次只需一次比较)
- 平均情况:O(n2)O(n^2)O(n2)
- 最坏情况:O(n2)O(n^2)O(n2)
(插入排序在数组基本有序时效率较高。)
4. 快速排序(Quick Sort)
排序思想: 快速排序通过分治法实现。它选择一个基准元素(通常是数组的第一个或最后一个元素),然后将数组划分为两个子数组:小于基准的元素放在左边,大于基准的放在右边。递归对左右子数组进行排序。
步骤:
- 选择一个基准元素(pivot)。
- 将数组划分为两部分:小于基准的在左边,大于基准的在右边。
- 对左右子数组递归执行快速排序。
- 最终,左右子数组有序时,整个数组也有序。
时间复杂度:
- 最好情况:O(nlogn)O(n \log n)O(nlogn)(每次划分时基准能均匀地将数组分成两半)
- 平均情况:O(nlogn)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(nlogn)O(n \log n)O(nlogn), 平均 O(nlogn)O(n \log n)O(nlogn), 最坏 O(n2)O(n^2)O(n2)
四、查找方法的思想及时间复杂度
1. 顺序查找(线性查找)
查找思想: 顺序查找是最简单的查找方法,逐个检查数组中的每个元素,直到找到目标元素或检查完整个数组。适用于无序数组。
步骤:
- 从数组的第一个元素开始,依次比较每个元素与目标元素是否相等。
- 如果找到相等的元素,返回该元素的下标;如果遍历到最后也没找到,返回失败或空值。
时间复杂度:
- 最好情况:O(1)O(1)O(1)(目标元素在数组的第一个位置)
- 平均情况:O(n)O(n)O(n)(需要检查一半的元素)
- 最坏情况:O(n)O(n)O(n)(目标元素在最后或不存在)
2. 二分查找(Binary Search)
查找思想: 二分查找只适用于有序数组。它通过每次将查找范围减半来寻找目标元素。该算法基于“分而治之”的思想,将查找问题递归地缩小至更小的子问题。
步骤:
- 先将数组中间元素与目标元素比较。
- 如果相等,查找成功;如果目标元素小于中间元素,则在左半部分继续查找;如果目标元素大于中间元素,则在右半部分查找。
- 重复上述过程,直到找到目标元素或查找范围为空。
时间复杂度:
- 最好情况:O(1)O(1)O(1)(目标元素正好位于中间位置)
- 平均情况:O(logn)O(\log n)O(logn)
- 最坏情况:O(logn)O(\log n)O(logn)
(二分查找的高效性来源于每次都将问题规模缩小一半,因此具有对数时间复杂度。)
3. 哈希查找(Hash Search)
查找思想: 哈希查找通过使用哈希函数将关键字直接映射到数组的一个位置。理想情况下,哈希查找的时间复杂度为常数时间。哈希查找需要使用哈希表结构,并通过哈希函数进行查找和插入。哈希查找能非常快速地找到元素,但前提是哈希函数设计得当,冲突较少。
步骤:
- 使用哈希函数计算出目标元素的哈希值。
- 在哈希表中查看该哈希值对应的位置是否存有目标元素。
- 如果有冲突(多个元素映射到同一位置),根据冲突解决方法继续查找(如链地址法、开放地址法等)。
时间复杂度:
- 最好情况: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(logn)O(\log n)O(logn), 最坏 O(logn)O(\log n)O(logn)
- 哈希查找: 最好 O(1)O(1)O(1), 平均 O(1)O(1)O(1), 最坏 O(n)O(n)O(n)