VB.net 2010 视频教程 VB.net 2010 视频教程 python基础视频教程
SQL Server 2008 视频教程 c#入门经典教程 Visual Basic从门到精通视频教程
当前位置:
首页 > 编程开发 > 数据结构 >
  • 第13章 集合、映射与跳表 13.1 引言 集合(Set)和映射(Map)是两种常用的数据

第13章 集合、映射与跳表
13.1 引言
集合(Set)和映射(Map)是两种常用的数据结构,广泛应用于去重、查找、键值对存储等场景。集合的核心特性是元素唯一性(无重复元素),映射则是键值对(Key-Value)的集合(键唯一,值可重复)。跳表(Skip List)是一种有序数据结构,通过多级索引实现高效的查找、插入和删除操作,性能可与平衡树媲美,但实现更简单。本章将系统介绍集合、映射的基本概念与实现,以及跳表的结构与操作。
13.2 集合
13.2.1 基本概念
集合是由互不相同的元素组成的无序集合,其基本特性包括:
唯一性:集合中不存在重复元素;
无序性:元素之间没有固定顺序(与列表的有序性不同)。
基本操作:
add(e):向集合中添加元素e(若e已存在,则不添加);
remove(e):从集合中删除元素e(若e不存在,抛出异常或返回False);
contains(e):判断集合是否包含元素e,返回bool值;
size():返回集合中元素的个数;
is_empty():判断集合是否为空,返回bool值。
13.2.2 集合的实现
集合的实现需高效支持上述操作,常见实现方式包括基于数组、链表、哈希表等。其中,哈希表实现因平均时间复杂度为O(1),是实际应用中的首选。
哈希集合(HashSet)的实现
哈希集合通过哈希表存储元素,利用哈希函数将元素映射到表中的索引,通过链地址法处理冲突(同一索引的元素用链表存储)。
实现步骤:
1.初始化:创建哈希表(数组),初始容量为固定值(如16),负载因子阈值(如0.75,超过则扩容);
2.添加元素:
o计算元素的哈希值,通过哈希函数(如hash(e) % capacity)得到索引;
o遍历该索引处的链表,若元素已存在则返回;否则将元素插入链表头部;
o若添加后元素个数超过capacity * 负载因子,则扩容(容量翻倍)并重新哈希所有元素;
3.删除元素:
o计算索引,遍历链表找到元素,删除该节点(修改链表指针);
4.查找元素:
o计算索引,遍历链表判断元素是否存在。
代码实现(哈希集合)
python

	class HashSet: 
	def __init__(self, capacity=16, load_factor=0.75): 
	self.capacity = capacity # 哈希表容量 
	self.load_factor = load_factor # 负载因子阈值 
	self.size = 0 # 元素个数 
	self.table = [[] for _ in range(capacity)] # 哈希表(桶数组,每个桶为链表) 
	
	def _hash(self, e): 
	"""哈希函数:计算元素e的索引""" 
	return hash(e) % self.capacity # 取模保证索引在[0, capacity-1] 
	
	def add(self, e): 
	"""添加元素e(若已存在则不添加)""" 
	idx = self._hash(e) 
	# 检查元素是否已存在 
	for elem in self.table[idx]: 
	if elem == e: 
	return # 已存在,直接返回 
	# 添加元素到链表头部 
	self.table[idx].append(e) 
	self.size += 1 
	# 检查是否需要扩容 
	if self.size / self.capacity > self.load_factor: 
	self._resize() 
	
	def remove(self, e): 
	"""删除元素e,成功返回True,失败返回False""" 
	idx = self._hash(e) 
	bucket = self.table[idx] 
	for i in range(len(bucket)): 
	if bucket[i] == e: 
	bucket.pop(i) # 删除元素 
	self.size -= 1 
	return True 
	return False # 元素不存在 
	
	def contains(self, e): 
	"""判断集合是否包含元素e""" 
	idx = self._hash(e) 
	for elem in self.table[idx]: 
	if elem == e: 
	return True 
	return False 
	
	def _resize(self): 
	"""扩容哈希表(容量翻倍)""" 
	old_table = self.table 
	self.capacity *= 2 # 容量翻倍 
	self.table = [[] for _ in range(self.capacity)] # 新哈希表 
	self.size = 0 # 重置size,后续重新添加元素 
	# 将旧表元素重新哈希到新表 
	for bucket in old_table: 
	for elem in bucket: 
	self.add(elem) # 调用add方法自动处理新索引 
	
	def __len__(self): 
	return self.size 
	
	def is_empty(self): 
	return self.size == 0

性能分析
时间复杂度:
oadd(e)、remove(e)、contains(e):平均O(1)(哈希函数均匀分布时,链表长度接近1);最坏O(n)(所有元素哈希冲突,链表长度为n)。
空间复杂度:O(n)(哈希表容量随元素个数动态增长)。
13.2.3 应用场景
集合常用于去重(如从列表中提取唯一元素)、** membership 测试**(判断元素是否存在)、集合运算(如交集、并集、差集)等场景。例如:
python

	# 利用集合去重 
	nums = [1, 2, 2, 3, 3, 3] 
	unique_nums = list(HashSet(nums)) # [1, 2, 3]

13.3 映射
13.3.1 基本概念
映射(Map,又称字典)是键值对(Key-Value)的集合,其核心特性是键的唯一性(每个键对应唯一的值,值可重复)。
基本操作:
put(k, v):插入键值对(k, v)(若k已存在,则更新值为v);
remove(k):删除键为k的键值对,返回对应的值(若k不存在,抛出异常或返回None);
get(k):返回键k对应的值(若k不存在,返回None或指定默认值);
contains_key(k):判断映射是否包含键k,返回bool值;
size()、is_empty():同集合。
13.3.2 映射的实现
映射的实现与集合类似,核心是键的唯一性。常见实现方式包括基于哈希表(哈希映射)和平衡树(有序映射),其中哈希映射因高效性应用最广。
哈希映射(HashMap)的实现
哈希映射通过哈希表存储键值对,键通过哈希函数映射到索引,值与键关联存储。冲突处理仍采用链地址法(同一索引的键值对用链表存储)。
实现步骤:
1.键值对存储:每个链表节点存储键值对(key, value);
2.哈希函数:基于键计算索引;
3.操作逻辑:put、remove、get等操作围绕键展开,与哈希集合类似,但需处理值的存储与更新。
代码实现(哈希映射)
python

	class HashMap: 
	def __init__(self, capacity=16, load_factor=0.75): 
	self.capacity = capacity 
	self.load_factor = load_factor 
	self.size = 0 
	# 哈希表:每个桶为链表,存储键值对元组(key, value) 
	self.table = [[] for _ in range(capacity)] 
	
	def _hash(self, key): 
	return hash(key) % self.capacity 
	
	def put(self, key, value): 
	"""插入键值对(key, value),若key存在则更新value""" 
	idx = self._hash(key) 
	bucket = self.table[idx] 
	# 检查key是否已存在,若存在则更新value 
	for i in range(len(bucket)): 
	k, v = bucket[i] 
	if k == key: 
	bucket[i] = (key, value) # 更新值 
	return 
	# key不存在,添加新键值对 
	bucket.append((key, value)) 
	self.size += 1 
	# 检查扩容 
	if self.size / self.capacity > self.load_factor: 
	self._resize() 
	
	def remove(self, key): 
	"""删除键为key的键值对,返回value;若key不存在,返回None""" 
	idx = self._hash(key) 
	bucket = self.table[idx] 
	for i in range(len(bucket)): 
	k, v = bucket[i] 
	if k == key: 
	bucket.pop(i) 
	self.size -= 1 
	return v 
	return None 
	
	def get(self, key, default=None): 
	"""返回key对应的值,若不存在返回default""" 
	idx = self._hash(key) 
	for k, v in self.table[idx]: 
	if k == key: 
	return v 
	return default 
	
	def contains_key(self, key): 
	"""判断是否包含键key""" 
	idx = self._hash(key) 
	for k, _ in self.table[idx]: 
	if k == key: 
	return True 
	return False 
	
	def _resize(self): 
	"""扩容哈希表(容量翻倍)""" 
	old_table = self.table 
	self.capacity *= 2 
	self.table = [[] for _ in range(self.capacity)] 
	self.size = 0 
	# 重新哈希旧表键值对 
	for bucket in old_table: 
	for key, value in bucket: 
	self.put(key, value) 
	
	def __len__(self): 
	return self.size 
	
	def is_empty(self): 
	return self.size == 0

性能分析
时间复杂度:同哈希集合,put、remove、get平均O(1),最坏O(n)。
空间复杂度:O(n)(存储键值对及哈希表结构)。
13.3.3 应用场景
映射常用于键值对存储与查询,如配置参数(键为参数名,值为参数值)、缓存系统(键为数据ID,值为数据内容)、索引(键为关键字,值为对应记录)等。
13.4 跳表
13.4.1 基本概念
跳表(Skip List)是一种有序数据结构,通过在原始链表(底层)之上添加多级索引,实现高效的查找、插入和删除操作。其核心思想是“空间换时间”——通过少量索引节点,减少查找时的比较次数,平均时间复杂度达到O(log n)。
结构特点:
多级索引:从底层链表(包含所有元素)开始,每向上一级,索引节点数量约为下一级的1/2(或其他比例),形成“金字塔”结构;
有序性:所有节点按关键字升序排列(底层链表和各级索引均有序)。
13.4.2 跳表的结构
以有序序列[3, 6, 7, 9, 12, 17]为例,跳表结构如下:

	Level 2: 3 ----------------------> 12 ----------------------> None 
	Level 1: 3 --------> 6 --------> 9 --------> 12 --------> 17 --> None 
	Level 0: 3 --> 6 --> 7 --> 9 --> 12 --> 17 --> None (底层链表)

Level 0:底层链表,包含所有元素,节点间通过next指针连接;
Level 1:一级索引,每隔1个节点取一个索引(如3、6、9、12、17);
Level 2:二级索引,每隔3个节点取一个索引(如3、12);
索引层级越高,节点越稀疏,最高层级称为“跳表的高度”。
13.4.3 基本操作

  1. 查找操作
    目标:在跳表中查找关键字为key的节点。
    步骤:
    1.从最高级索引的头节点开始,向右比较:
    o若当前节点的下一个节点关键字<= key,则向右移动;
    o否则,向下移动一级索引;
    2.重复步骤1,直至到达底层链表(Level 0);
    3.在底层链表中向右查找,若找到关键字key的节点,返回该节点;否则返回None。
    示例:查找key=9
    从Level 2头节点开始:下一个节点为3(<=9)→ 无法向右(下一个是12>9)→ 下移至Level 1;
    Level 1:当前节点3→下一个节点6(<=9)→ 下一个节点9(<=9)→ 下一个节点12(>9)→ 下移至Level 0;
    Level 0:当前节点9→找到目标,返回节点9。
  2. 插入操作
    目标:插入关键字为key的节点,维持跳表的有序性和索引结构。
    步骤:
    1.查找插入位置:类似查找操作,记录每一级索引中最后一个小于key的节点(“前驱节点”),用于后续插入索引;
    2.生成随机层数:为新节点随机分配一个层数(level),决定该节点在哪些索引层出现(层数越高,被选中概率越低);
    3.插入节点:
    o在底层链表(Level 0)中插入新节点;
    o对于每一级索引(从1到level),若该级索引的前驱节点存在,将新节点插入到前驱节点之后;
    4.更新跳表高度:若新节点的层数高于当前跳表高度,更新跳表高度。
    随机层数生成:通过随机函数生成,通常以概率p(如0.5)决定是否增加层数,直至生成0或达到最大层数。例如:
    python
	def random_level(max_level=16, p=0.5): 
	level = 1 
	while random.random() < p and level < max_level: 
	level += 1 
	return level
  1. 删除操作
    目标:删除关键字为key的节点,同时更新各级索引。
    步骤:
    1.查找节点及前驱:类似插入操作,查找目标节点,并记录每一级索引中该节点的前驱节点;
    2.删除节点:
    o从底层链表中删除目标节点;
    o对于每一级索引,若该级索引中存在目标节点,通过前驱节点删除该索引节点;
    3.更新跳表高度:若删除后最高级索引为空,降低跳表高度。
    13.4.4 代码实现
    python
	import random 
	
	class SkipListNode: 
	def __init__(self, key=None, value=None, level=1): 
	self.key = key # 关键字 
	self.value = value # 值 
	self.next = [None] * level # 每级索引的next指针 
	
	class SkipList: 
	def __init__(self, max_level=16, p=0.5): 
	self.max_level = max_level # 最大层数 
	self.p = p # 层数增长概率 
	self.level = 1 # 当前跳表高度(初始为1) 
	self.head = SkipListNode(level=max_level) # 头节点(不存储数据) 
	
	def _random_level(self): 
	"""生成随机层数""" 
	level = 1 
	while random.random() < self.p and level < self.max_level: 
	level += 1 
	return level 
	
	def insert(self, key, value): 
	"""插入键值对(key, value)""" 
	update = [None] * self.max_level # 存储每级索引的前驱节点 
	current = self.head 
	
	# 1. 查找并记录各级索引的前驱节点 
	for i in range(self.level-1, -1, -1): 
	# 当前级索引向右查找,直至下一个节点key > 当前key 
	while current.next[i] and current.next[i].key < key: 
	current = current.next[i] 
	update[i] = current # 记录第i级索引的前驱节点 
	
	# 2. 检查key是否已存在(底层链表) 
	current = current.next[0] 
	if current and current.key == key: 
	current.value = value # 更新值 
	return 
	
	# 3. 生成随机层数 
	new_level = self._random_level() 
	
	# 4. 若新节点层数高于当前跳表高度,更新update和跳表高度 
	if new_level > self.level: 
	for i in range(self.level, new_level): 
	update[i] = self.head # 新层级的前驱节点为头节点 
	self.level = new_level 
	
	# 5. 创建新节点并插入各级索引 
	new_node = SkipListNode(key, value, new_level) 
	for i in range(new_level): 
	new_node.next[i] = update[i].next[i] # 新节点next指向原前驱节点的next 
	update[i].next[i] = new_node # 前驱节点next指向新节点 
	
	def search(self, key): 
	"""查找key对应的value,不存在返回None""" 
	current = self.head 
	# 从最高级索引向下查找 
	for i in range(self.level-1, -1, -1): 
	while current.next[i] and current.next[i].key < key: 
	current = current.next[i] 
	# 到达底层链表,检查是否找到 
	current = current.next[0] 
	return current.value if current and current.key == key else None 
	
	def delete(self, key): 
	"""删除key对应的节点,返回True,不存在返回False""" 
	update = [None] * self.max_level # 存储各级索引的前驱节点 
	current = self.head 
	
	# 1. 查找目标节点及前驱 
	for i in range(self.level-1, -1, -1): 
	while current.next[i] and current.next[i].key < key: 
	current = current.next[i] 
	update[i] = current 
	
	# 2. 检查key是否存在 
	current = current.next[0] 
	if not current or current.key != key: 
	return False # 不存在 
	
	# 3. 从各级索引中删除节点 
	for i in range(self.level): 
	if update[i].next[i] != current: 
	break # 该级索引中无此节点,无需删除 
	update[i].next[i] = current.next[i] 
	
	# 4. 更新跳表高度(若最高级索引为空) 
	while self.level > 1 and self.head.next[self.level-1] is None: 
	self.level -= 1 
	
	return True

13.4.5 性能分析
时间复杂度:
o查找、插入、删除:平均O(log n)(索引层数为O(log n),每层查找O(1));最坏O(n)(极端情况下索引失效,退化为链表)。
空间复杂度:O(n log n)(存储各级索引节点,索引节点总数约为n/(1-p),p为概率参数)。
13.4.6 应用场景
跳表因实现简单(无需像平衡树那样维护复杂的旋转操作)、性能稳定,被广泛应用于有序数据的高效操作场景,如Redis的有序集合(ZSet)、LevelDB等数据库的索引结构。
13.5 总结
本章介绍了集合、映射和跳表三种重要数据结构:
集合:基于哈希表实现,支持元素的唯一存储和高效增删查,平均时间复杂度O(1),适用于去重和membership测试;
映射:基于哈希表实现的键值对存储,键唯一,支持高效的键值对操作,是缓存、索引等场景的核心数据结构;
跳表:通过多级索引实现有序数据的高效操作,平均时间复杂度O(log n),实现简单,适用于有序集合和数据库索引。
实际应用中,需根据数据特性(有序性、唯一性、操作频率)选择合适的数据结构:无序唯一元素选集合,键值对存储选映射,有序数据且需高效操作选跳表或平衡树。

本站原创,转载请注明出处:https://www.xin3721.com/python3/python49279.html


相关教程