00 数据结构

1. 数组

2. 链表

3. 栈

4. 队列

5. 树

  1. 红黑树(自平衡二叉树)
    增删改查时间复杂度 O(log n)
    二叉搜索树(BST):左子节点 < 父节点 < 右子节点。
  • 问题:若插入顺序不当(如从小到大插入),BST 会退化为链表,查找效率从 O(log n) 降为 O(n)。

平衡二叉树(Balanced Binary Tree)

  • 核心思想:通过调整树的结构,避免退化为链表,保证左右子树高度差不超过 1。
  • 目标:维持操作(插入、删除、查找)的时间复杂度为 O(log n)。
  • 常见类型:
    • AVL 树:通过旋转(左旋/右旋)保持平衡,高度差 ≤1。
    • 红黑树:通过颜色标记和规则约束,近似平衡(高度差 ≤2 倍)。

6. 哈希表

在理想情况下,插入、删除和查找的平均时间复杂度是O(1)。

  1. 主要:哈希函数:给定一个函数输入,将输入映射到某个值,得出当前元素如何存储(哈希函数示例?)
    哈希函数是将键(Key)转换为哈希值(size_t 类型整数)的核心组件。哈希值决定了键值对存储在哈希表的哪个“桶”(bucket)中,每个桶维护一个链表数据结构,解决哈希冲突问题。

  2. 哈希冲突:不同输入,得到同样哈希值,如何解决?

    • 链表存储,元素多了使用树存储(增删改查时间复杂度log(n))。
  3. c++ unorder_set 实现
    unordered_set是无序的,那么它应该也是基于哈希表的。每个元素本身就是键,不需要关联额外的值。因此,哈希表在这里存储的是单个元素,而不是键值对。当插入一个元素时,计算该元素的哈希值,找到对应的桶,然后将元素存入该桶中。如果发生哈希冲突,比如两个不同的元素有相同的哈希值,那么它们会被放在同一个桶里,通常通过链表连接。

  4. c++ unorder_map实现
    C++标准库中unordered_set的实现机制。哈希表的基本结构是将键通过哈希函数映射到桶(bucket),然后处理可能的冲突,通常使用链地址法(每个桶是一个链表)或者开放寻址法。对于unordered_map来说,每个键值对被存储在哈希表的某个桶中,键用于计算哈希值,值则与键关联存储。
    unordered_map和unordered_set都是基于哈希表,但unordered_map存储的是键值对,而unordered_set只存储键。

/**< Hash 函数实现 unorder_map 示例 **/
// std 库内实现的哈希函数对象模版,在<bits/functional_hash.h> 中定义
namespace std {
	template<typename T>
	struct hash {
		size_t operator()(const T& key) const {
			// hash函数实现(若未特化,编译报错)
			return __hash__impl<T>::hash(key);
		}
	};
	
	// 对 int 特化
	template<> // 表明是模版特化
	struct hash<int> {
		size_t operator()(const int& key) const {
			return static_cast<size_t>(key);
		}
	};
};

// unordered_map 实现示例,在<bits/unordered_map.h> 中定义
template<typename Key, 
		typename Value,
		typename Hash = std::hash<key>, // 默认使用 std::hash
		typename KeyEqual = std::equal_to<key>>
class unordered_map {
private:
	// 桶链表简化版
	struct Bucket {
		std::list<std::pair<kay, value>> entries;
	};
	std::vector<Bucket> buckets; // 哈希表桶数组
	Hash hasher;					// 哈希函数对象
	KeyEqual key_eq;		// 键相等比较

public:
	void insert(const Key& key, const Value& value) {
		size_t hash_value = hasher(key);
		size_t bucket_idx = hash_value % buckets.size();
	
		// 处理冲突(链表法)
		for (auto& entry : buckets[bucket_idx]) {
			if (entry.first == key) {
				entry.second = value;
				return;
			}
		}
	
		// 保存新元素
		buckets[bucket_idx].entries.push_back(std::pair<key, value>);
	}
};

对于自定义的键,需要特化hash函数模版或者自定义哈希函数,才能使用unordered_map
补充:c++模版特化

  • 在 C++ 中,模板(Template) 允许你编写通用的代码,适用于多种数据类型。但某些类型需要特殊处理,这时就需要 模板特化(Template Specialization) —— 为特定类型定制专门的实现。
  • 模板特化是指为特定的数据类型提供一个特定的实现,覆盖通用模板的默认行为。
  • 默认情况下可能没有为所有类型实现,特别是用户自定义的类型。当尝试使用一个没有特化std::hash的自定义类型作为unordered_mapunordered_set的键时,编译器找不到对应的哈希函数,因此会报错。
  • 类模板的特化,通常需要重新定义整个类,而不仅仅是某些成员函数。但如果是函数模板的特化,可以只特化某个函数。
  • 通过模板特化,你为自定义类型“赋能”,使其无缝融入 C++ 标准库的生态系统。
// 自定义键
/**
为什么需要定义 operator==?
哈希表处理冲突时(不同键映射到同一桶),需要比较键是否真正相等。
**/
class Person {
public:
	int age;
	std::string name;
	// 对于作为键的类,需要重载 ==(用于冲突解决)
	bool operator==(const Person& p) const {
		return age == p.age && name == p.name;
	}
}

// hash 模版特化方式定义哈希函数对象
namespace std {
	template<> // 表示这是模板特化
	hash<Person> {
		size_t operator()(const Person& person) const {
			return hash<int>()(person.age) ^ (hash<string>()(person.name) << 1);
		}
	};
};
std::unordered_map<Persion, int> mp;

// 自定义哈希函数对象
struct HashMy {
	size_t operator()(const Person& person) const {
		return hash<int>()(person.age) ^( hash<string>()(person.name) << 1);
	}
}

// 使用时显示指定哈希函数类型
std::unordered_map<Persion, int, HashMy> mp;
  • std::list 双向链表

7. 补充

  1. unordered_map中的负载因子和rehashing机制?

  2. 继承/模版

    • 继承:父类实现基本功能,子类对功能进行扩展(继承:基础 -> 扩展)
    • 模版:写框架的,对于个别独特的个体,可以通过特化模版,实现模版内功能 (模版特化:框架 -> 个例)
      • 如果模版类写的足够详细,可以实现处理不同数据类型的通用类。
    • 通俗讲:继承,由粗到细;模版:通用实现(个别特例通过模版特化解决)。
posted @ 2025-02-27 09:46  ldfm  阅读(26)  评论(0)    收藏  举报