基于算法思想
比较排序
- 冒泡排序(
Bubble Sort
):- 反复交换相邻的逆序元素
- 快速排序(
Quick Sort
):- 通过分区交换来排序,递归地对分区进行排序
- 堆排序(
Heap Sort
):- 利用堆结构进行排序,构建最大堆,然后依次将最大元素移到数组末尾
- 归并排序(
Merge Sort
):- 分治法,将数组分成两部分分别排序,然后合并有序的部分
非比较排序
- 计数排序(
Counting Sort
):- 统计每个元素出现的次数,然后根据计数进行排序
- 桶排序(
Bucket Sort
):- 将元素分配到多个桶中,每个桶内部进行排序,然后合并所有桶中的元素
- 基数排序(
Radix Sort
):- 按位或数位对元素进行排序,从最低位到最高位逐步排序
基于时间复杂度
n方
- 冒泡排序(
Bubble Sort
):- 简单直观,适用于小规模数据
- 选择排序(
Selection Sort
):- 逐步选择最小(或最大)元素放到已排序部分的末尾
- 插入排序(
Insertion Sort
):- 逐步将元素插入到已排序部分的适当位置
n log n
- 快速排序(
Quick Sort
):- 平均情况下性能较好,最坏情况下为
O(n^2)
- 平均情况下性能较好,最坏情况下为
- 堆排序(
Heap Sort
):- 稳定的
O(n log n)
性能
- 稳定的
- 归并排序(
Merge Sort
):- 稳定的
O(n log n)
性能,适用于大规模数据
- 稳定的
n
- 计数排序(
Counting Sort
):- 适用于范围较小的整数排序
- 桶排序(
Bucket Sort
):- 适用于均匀分布的数据
- 基数排序(
Radix Sort
):- 适用于固定长度的整数或字符串排序
基于空间复杂度
原地排序
- 冒泡排序(
Bubble Sort
):- 原地排序
- 快速排序(
Quick Sort
):- 原地排序(有些实现可能需要辅助栈空间)
- 堆排序(
Heap Sort
):- 原地排序
非原地排序
- 归并排序(
Merge Sort
):- 需要额外的存储空间用于合并
- 计数排序(
Counting Sort
):- 需要额外的数组空间
- 桶排序(
Bucket Sort
):- 需要额外的桶空间
- 基数排序(
Radix Sort
):- 需要额外的数组或桶空间
稳定性
稳定
- 冒泡排序(
Bubble Sort
):稳定 - 归并排序(
Merge Sort
):稳定 - 计数排序(
Counting Sort
):稳定 - 桶排序(
Bucket Sort
):稳定(桶内部使用稳定排序算法) - 基数排序(
Radix Sort
):稳定
不稳定
- 快速排序(
Quick Sort
):不稳定 - 堆排序(
Heap Sort
):不稳定
哈希表
概述
- 散列表(
Hash Table
)和哈希表是同一种数据结构 - 它们都是一种用于实现关联数组(或字典)的数据结构,可以通过键来快速查找值
核心概念
- 哈希函数
- 将输入的键转换为数组中的索引
- 好的哈希函数能均匀地分布键,减少冲突
- 冲突处理
- 由于不同的键可能会映射到相同的索引,需要处理这种情况冲突,方法如下。
- 常见的冲突处理方法有:
- 链地址法(
Chaining
):将映射到相同索引的所有键值对存储在一个链表中 - 开放地址法(
Open Addressing
):当冲突发生时,通过寻找数组中的下一个空闲位置来存储键值对
- 链地址法(
插入
- 通过哈希函数计算键的初始哈希值,作为数组的下标
- 如果该下标位置没有存数据,则直接存储键值对
- 如果该下标位置已经存有数据,比较存储数据的键与待插入数据的键
- 如果键值相同,可以根据需要更新值(如果允许更新)
- 如果键值不同,则发生冲突
使用开放地址法中的探查方法(如线性探查、二次探查或双重哈希)找到下一个空闲位置,将新的键值对存储在那里
查找
- 通过哈希函数计算键的初始哈希值,作为数组的下标
- 如果该下标位置的键与要查找的键匹配,则返回相应的值
- 如果不匹配,使用开放地址法中的探查方法继续查找下一个位置,直到找到匹配的键或遇到空位置:
- 如果找到匹配的键,返回相应的值
- 如果遇到空位置,则说明哈希表中不存在该键
删除
- 删除过程在开放地址法中稍微复杂一些,因为简单地将元素删除并标记为空可能会破坏查找的正确性
- 为了解决这个问题,通常使用一个特殊的标记,表示某个位置曾经存储过数据,但现在已经删除(通常称为“懒删除”或“墓碑标记”)
- 通过哈希函数计算键的初始哈希值,作为数组的下标
- 使用与查找过程相同的探查方法,找到要删除的键所在的位置
- 将找到的位置标记为删除状态,而不是简单地标记为空
时间复杂度
- 平均情况下,插入、查找和删除操作的时间复杂度为
O(1)
- 最坏情况下,如果所有键都映射到同一个索引(极端情况),时间复杂度为
O(n)
使用场景
- 数据缓存
- 哈希表用于实现缓存机制,如内存缓存(例如
LRU
缓存)和数据库缓存 - 通过哈希表可以快速查找到缓存的数据,提高访问速度
- 哈希表用于实现缓存机制,如内存缓存(例如
- 关联数组(字典)
- 哈希表广泛应用于实现关联数组(或字典),即通过键快速查找对应的值
- 常见的编程语言如
Python
、JavaScript
、Java
和C++
都提供了哈希表的内置实现(如Python
的dict
,Java
的HashMap
,C++
的unordered_map
)
- 数据去重
- 哈希表可以用于检测和删除数据中的重复项
- 例如,从一个列表中去除重复的元素,可以将元素插入哈希表,如果元素已经存在,则跳过插入
- 计数
- 哈希表用于统计元素出现的频率
- 例如,统计一段文本中每个单词出现的次数,可以使用哈希表存储每个单词及其对应的出现次数
- 数据库索引
- 哈希表用于实现数据库的索引结构,以便快速查找特定记录
- 哈希索引通过计算记录键的哈希值来确定记录存储的位置,从而加快查询速度
- 字符串匹配
- 哈希表用于字符串匹配算法,如
Rabin-Karp
算法,通过哈希值快速比较子串和模式串,提高字符串匹配的效率
- 哈希表用于字符串匹配算法,如
- 密码学
- 哈希表用于密码学中的哈希函数实现
- 如哈希表中存储密码的哈希值,用于快速验证用户输入的密码是否正确
- 负载均衡算法
- 哈希表用于负载均衡算法
- 如一致性哈希(
consistent hashing
),将请求均匀分配到不同的服务器或资源上,提高系统的可扩展性和容错性
- 路由表
- 在网络路由中,路由器使用哈希表存储路由信息,根据目标 IP 地址的哈希值快速查找下一跳地址
- 词法分析器
- 编译器中的词法分析器使用哈希表存储关键字、标识符等符号,通过哈希表快速识别和查找符号
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 |
#include <iostream> #include <unordered_map> #include <string> #include <sstream> void countWordFrequency(const std::string &text) { std::unordered_map<std::string, int> wordCount; std::stringstream ss(text); std::string word; while (ss >> word) { wordCount[word]++; } for (const auto &pair : wordCount) { std::cout << pair.first << ": " << pair.second << std::endl; } } int main() { std::string text = "this is a test this is only a test"; countWordFrequency(text); return 0; } |
示例代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 |
#include <iostream> #include <list> #include <vector> #include <string> class HashTable { private: int bucketCount; std::vector<std::list<std::pair<int, std::string>>> table; int hashFunction(int key) { return key % bucketCount; } public: HashTable(int buckets) : bucketCount(buckets), table(buckets) {} void insertItem(int key, const std::string &value) { int index = hashFunction(key); table[index].push_back({key, value}); } std::string searchItem(int key) { int index = hashFunction(key); for (auto &item : table[index]) { if (item.first == key) { return item.second; } } return "Not Found"; } void deleteItem(int key) { int index = hashFunction(key); table[index].remove_if([key](const std::pair<int, std::string> &item) { return item.first == key; }); } }; int main() { HashTable ht(7); ht.insertItem(1, "Value1"); ht.insertItem(2, "Value2"); ht.insertItem(3, "Value3"); std::cout << ht.searchItem(2) << std::endl; ht.deleteItem(2); std::cout << ht.searchItem(2) << std::endl; return 0; } |
问题
- 如果解决冲突用的开放地址法,会通过寻找数组中的下一个空闲位置来存储键值对,那么,这个索引是怎么处理的?
- 上面的插入过程
本文为原创文章,版权归Aet所有,欢迎分享本文,转载请保留出处!
你可能也喜欢
- ♥ 一些变量值交换的方法08/10
- ♥ 数据结构模板03/09
- ♥ 排序_归并排序09/04
- ♥ 匹配_朴素字符串匹配算法10/14
- ♥ 基础算法模板03/09
- ♥ 排序_堆排序05/08