数据结构:字典

概述

  • 在数据结构中,字典(Dictionary) 是一种键值对(Key-Value Pair) 存储的数据结构,核心特性是通过键(Key) 快速查找对应值(Value),不依赖元素的插入顺序。
  • 注意:字典与我们之前讲的散列表(哈希表) 关系密切------散列表是字典的底层实现方式 (绝大多数编程语言中),而字典是面向开发者的抽象数据类型(ADT),定义了键值对的操作接口(如增删改查)。
  • 资料:https://pan.quark.cn/s/43d906ddfa1b

一、字典的核心特点

  1. 键唯一:每个键在字典中只能出现一次,重复插入相同键会覆盖对应的值;
  2. 键不可变:键必须是不可变类型(如整数、字符串、元组),不能用列表、字典等可变类型作为键;
  3. 无序性(传统字典):早期字典不保证元素的存储顺序(如Python 3.6之前),现代实现(Python 3.7+、Java HashMap)会保留插入顺序,但这不是字典的核心定义;
  4. 高效查找:平均时间复杂度 O(1),底层依赖散列表的哈希映射机制。

二、字典的基本操作

字典的操作围绕"键值对"展开,核心操作如下:

操作 描述 时间复杂度(平均)
insert(key, value) 插入键值对,键已存在则更新值 O(1)
get(key) 根据键查找值,键不存在返回 None 或抛异常 O(1)
delete(key) 根据键删除键值对,键不存在返回 False O(1)
contains(key) 判断键是否存在于字典中 O(1)
keys() 返回所有键的集合 O(n)
values() 返回所有值的集合 O(n)
items() 返回所有键值对的集合 O(n)
size() 返回字典中键值对的数量 O(1)
clear() 清空字典所有元素 O(n)

三、字典的底层实现(与散列表的关系)

字典的高效性完全依赖散列表(哈希表) 的实现,两者的对应关系:

  1. 字典的"键(Key)" → 散列表的"键",通过哈希函数计算索引;
  2. 字典的"值(Value)" → 散列表中存储的"数据";
  3. 字典的"键唯一" → 散列表中相同哈希值的键会通过冲突解决策略处理(如链地址法),确保键的唯一性。

简单说:字典是散列表的"上层应用",散列表提供了底层存储和查找能力,字典则封装了键值对的操作逻辑,更贴近开发者的使用场景。

四、字典的实现(Python示例)

Python 中的 dict 是字典的经典实现(底层为散列表),直接使用即可满足绝大多数需求。以下是自定义简易字典(基于散列表的链地址法),帮助理解底层逻辑:

python 复制代码
class Dictionary:
    def __init__(self, capacity=8):
        self.capacity = capacity  # 底层桶数组大小
        self.buckets = [None] * self.capacity  # 桶数组(每个桶存储链表)
        self.size = 0  # 键值对数量
        self.load_factor = 0.75  # 负载因子阈值

    class Node:
        """字典节点(链表节点)"""
        def __init__(self, key, value):
            self.key = key
            self.value = value
            self.next = None

    def _hash(self, key):
        """哈希函数:计算键的桶索引"""
        return hash(key) % self.capacity

    def _resize(self):
        """扩容:桶数组翻倍,重新哈希所有键值对"""
        old_buckets = self.buckets
        self.capacity *= 2
        self.buckets = [None] * self.capacity
        self.size = 0

        # 重新插入旧桶中的所有键值对
        for bucket in old_buckets:
            current = bucket
            while current:
                self.insert(current.key, current.value)
                current = current.next

    def insert(self, key, value):
        """插入/更新键值对"""
        # 检查是否需要扩容
        if self.size / self.capacity >= self.load_factor:
            self._resize()

        index = self._hash(key)
        current = self.buckets[index]

        # 键已存在,更新值
        while current:
            if current.key == key:
                current.value = value
                return
            current = current.next

        # 键不存在,插入链表头部
        new_node = self.Node(key, value)
        new_node.next = self.buckets[index]
        self.buckets[index] = new_node
        self.size += 1

    def get(self, key):
        """根据键查找值"""
        index = self._hash(key)
        current = self.buckets[index]

        while current:
            if current.key == key:
                return current.value
            current = current.next

        return None  # 键不存在

    def delete(self, key):
        """根据键删除键值对"""
        index = self._hash(key)
        current = self.buckets[index]
        prev = None

        while current:
            if current.key == key:
                # 调整链表指针
                if prev:
                    prev.next = current.next
                else:
                    self.buckets[index] = current.next
                self.size -= 1
                return True
            prev = current
            current = current.next

        return False  # 键不存在

    def contains(self, key):
        """判断键是否存在"""
        return self.get(key) is not None

    def keys(self):
        """返回所有键"""
        key_list = []
        for bucket in self.buckets:
            current = bucket
            while current:
                key_list.append(current.key)
                current = current.next
        return key_list

    def __str__(self):
        """打印字典"""
        items = []
        for bucket in self.buckets:
            current = bucket
            while current:
                items.append(f"{current.key}: {current.value}")
                current = current.next
        return "{" + ", ".join(items) + "}"

# 使用示例
d = Dictionary()
d.insert("name", "Alice")
d.insert("age", 25)
d.insert("city", "Beijing")
d.insert("age", 26)  # 更新age的值

print(d)  # 输出: {name: Alice, age: 26, city: Beijing}
print(d.get("age"))  # 输出: 26
print(d.contains("city"))  # 输出: True
print(d.delete("city"))  # 输出: True
print(d.keys())  # 输出: ['name', 'age']

五、字典的优缺点

优点
  1. 查找高效:平均 O(1) 时间复杂度,远快于数组、链表的 O(n);
  2. 使用直观:键值对映射符合人类认知(如"名字→值""ID→用户信息");
  3. 操作便捷:支持插入、更新、删除等完整操作,接口友好。
缺点
  1. 无序性(传统实现):无法直接按插入顺序或大小顺序遍历(现代实现已优化);
  2. 内存开销大:底层散列表需要额外空间存储桶和链表指针,空间利用率低于数组;
  3. 键不可变:限制了键的类型,灵活性不如某些数据结构;
  4. 最坏性能差:哈希冲突严重时,查找时间复杂度退化为 O(n)(需依赖良好的哈希函数和扩容策略)。

六、字典的应用场景

字典是工程中最常用的数据结构之一,典型场景:

  1. 配置存储 :存储系统配置(如 {"host": "localhost", "port": 8080});
  2. 缓存数据:临时存储高频访问数据(如用户会话信息);
  3. 数据索引:为大量数据建立键索引(如学生ID→学生信息);
  4. 词频统计 :统计文本中单词出现次数(如 {"apple": 5, "banana": 3});
  5. JSON/XML解析:JSON数据本质就是键值对,解析后常以字典形式存储。

七、字典 vs 散列表 vs 数组 vs 链表

数据结构 核心特性 查找效率 插入/删除效率 适用场景
字典 键值对映射、键唯一 O(1)(平均) O(1)(平均) 快速查找、键值对存储
散列表 哈希映射、冲突解决 O(1)(平均) O(1)(平均) 字典的底层实现
数组 连续存储、索引访问 O(1)(索引) O(n)(中间) 有序数据、随机访问
链表 非连续存储、指针连接 O(n) O(1)(已知节点) 频繁插入删除、无需随机访问

八、总结

字典是一种高效的键值对存储结构,底层依赖散列表实现,核心优势是"通过键快速查找值"。它平衡了时间效率和使用便捷性,是解决"映射查找"问题的首选数据结构。

实际开发中,无需自定义字典,直接使用编程语言内置实现(如 Python 的 dict、Java 的 HashMap、JavaScript 的 Object)即可,这些实现已优化了哈希函数、冲突解决和扩容策略,性能稳定且易用。

相关推荐
程序员东岸1 小时前
《数据结构——排序(下)》分治与超越:快排、归并与计数排序的终极对决
数据结构·c++·经验分享·笔记·学习·算法·排序算法
想唱rap1 小时前
C++之红黑树
开发语言·数据结构·c++·算法
Ayanami_Reii1 小时前
进阶数据结构应用-区间最大公约数
开发语言·数据结构·算法·线段树·差分·树状数组·fenwick tree
大千AI助手2 小时前
多维空间的高效导航者:KD树算法深度解析
数据结构·人工智能·算法·机器学习·大千ai助手·kd tree·kd树
kk”2 小时前
C++ AVL树
开发语言·数据结构·c++
爪哇部落算法小助手2 小时前
每日两题day61
数据结构·c++·算法
裤裤兔2 小时前
利用matlab进行FDR校正的实现方式
数据结构·算法·matlab·多重比较矫正·校正·fdr
敲代码的嘎仔2 小时前
LeetCode面试HOT100—— 206. 反转链表
java·数据结构·学习·算法·leetcode·链表·面试
自然语2 小时前
深度学习时代结束了,2025年开始只剩下轮廓
数据结构·人工智能·深度学习·学习·算法