数据结构是计算机科学中组织和存储数据的方式,它决定了数据如何被访问、修改和处理。Python提供了丰富的数据结构,包括内置的数据结构和标准库中的高级数据结构。本文将详细介绍Python数据结构的概念、特性、用法和最佳实践。
一、数据结构概述
1. 什么是数据结构?
数据结构是一种组织和存储数据的方式,它定义了数据之间的关系、操作数据的方法以及数据的访问效率。数据结构的选择直接影响程序的性能和可维护性。
2. Python数据结构的分类
Python数据结构可以分为以下几类:
2.1 内置基本数据结构
- 数值类型(Numeric)
- 序列类型(Sequence)
- 映射类型(Mapping)
- 集合类型(Set)
2.2 标准库中的高级数据结构
- collections模块:OrderedDict、defaultdict、Counter、deque、namedtuple
- heapq模块
- bisect模块
- array模块
- struct模块
- queue模块
- graphlib模块
3. 数据结构的重要性
选择合适的数据结构对于程序的性能和可维护性至关重要:
- 性能:不同的数据结构在时间复杂度和空间复杂度上有很大差异
- 可维护性
- 功能:不同的数据结构支持不同的操作,选择合适的数据结构可以简化代码
二、内置基本数据结构
1. 数值类型
1.1 整数(int)
整数是Python中最基本的数值类型,支持任意精度的整数运算。
# 整数示例
x =10# 十进制整数
y =0b1010# 二进制整数
z =0o12# 八进制整数
w =0xa# 十六进制整数
print(x)# 输出:10
print(y)# 输出:10
print(z)# 输出:10
print(w)# 输出:10
# 整数运算
print(x + y)# 输出:20
print(x - y)# 输出:0
print(x * y)# 输出:100
print(x / y)# 输出:1.0
print(x // y)# 输出:1
print(x % y)# 输出:0
print(x ** y)# 输出:10000000000
1.2 浮点数(float)
浮点数用于表示实数,使用双精度浮点数(64位)存储。
# 浮点数示例
x =3.14
y =1.23e4# 科学计数法,相当于12300.0
z =1.23e-3# 科学计数法,相当于0.00123
print(x)# 输出:3.14
print(y)# 输出:12300.0
print(z)# 输出:0.00123
# 浮点数运算
print(x + y)# 输出:12303.14
print(x - y)# 输出:-12296.86
print(x * y)# 输出:38622.0
print(x / y)# 输出:0.00025528455284552846
1.3 复数(complex)
复数由实部和虚部组成,虚部使用j或J表示。
# 复数示例
x =3+4j
print(x)# 输出:(3+4j)
print(x.real)# 输出:3.0(实部)
print(x.imag)# 输出:4.0(虚部)
print(x.conjugate())# 输出:(3-4j)(共轭复数)
# 复数运算
y =5+6j
print(x + y)# 输出:(8+10j)
print(x - y)# 输出:(-2-2j)
print(x * y)# 输出:(-9+38j)
print(x / y)# 输出:(0.639344262295082+0.03278688524590164j)
1.4 布尔值(bool)
布尔值表示真或假,True对应整数1,False对应整数0。
# 布尔值示例
x =True
print(x)# 输出:True
print(int(x))# 输出:1
# 布尔运算
print(x andFalse)# 输出:False
print(x orFalse)# 输出:True
print(not x)# 输出:False
# 比较运算
print(5>3)# 输出:True
print(5==3)# 输出:False
print(5<3)# 输出:False
2. 序列类型
序列类型是Python中最常用的数据结构之一,它们支持索引、切片、迭代等操作。
2.1 列表(list)
列表是Python中最灵活的序列类型,它可以存储任意类型的元素,并且支持动态修改。
# 列表示例
# 创建列表
empty_list =[]
numbers =[1,2,3,4,5]
mixed =[1,"hello",3.14,True]
# 访问元素
print(numbers[0])# 输出:1(索引从0开始)
print(numbers[-1])# 输出:5(负数索引从末尾开始)
# 切片
print(numbers[1:3])# 输出:[2, 3](索引1到2,不包含3)
print(numbers[:3])# 输出:[1, 2, 3](从开头到索引2)
print(numbers[3:])# 输出:[4, 5](从索引3到末尾)
print(numbers[::2])# 输出:[1, 3, 5](步长为2)
# 修改元素
numbers[0]=10
print(numbers)# 输出:[10, 2, 3, 4, 5]
# 添加元素
numbers.append(6)# 在末尾添加元素
print(numbers)# 输出:[10, 2, 3, 4, 5, 6]
numbers.insert(0,0)# 在指定位置插入元素
print(numbers)# 输出:[0, 10, 2, 3, 4, 5, 6]
# 删除元素
numbers.remove(10)# 删除指定值的元素
print(numbers)# 输出:[0, 2, 3, 4, 5, 6]
popped = numbers.pop()# 移除并返回末尾元素
print(popped)# 输出:6
print(numbers)# 输出:[0, 2, 3, 4, 5]
# 其他操作
print(len(numbers))# 输出:5(长度)
print(3in numbers)# 输出:True(元素是否存在)
print(numbers.index(3))# 输出:2(元素的索引)
print(numbers.count(3))# 输出:1(元素出现的次数)
numbers.sort()# 排序
print(numbers)# 输出:[0, 2, 3, 4, 5]
numbers.reverse()# 反转
print(numbers)# 输出:[5, 4, 3, 2, 0]
# 列表推导式
squares =[i **2for i inrange(10)]
print(squares)# 输出:[0, 1, 4, 9, 16, 25, 36, 49, 64, 81]
# 嵌套列表
matrix =[[1,2,3],[4,5,6],[7,8,9]]
print(matrix[0][1])# 输出:2(第一行第二列)
列表的性能分析:
列表的应用场景:
2.2 元组(tuple)
元组是一种不可变的序列类型,一旦创建就不能修改。
# 元组示例
# 创建元组
empty_tuple =()
numbers =(1,2,3,4,5)
mixed =(1,"hello",3.14,True)
single_tuple =(5,)# 单个元素的元组需要加逗号
# 访问元素
print(numbers[0])# 输出:1
print(numbers[-1])# 输出:5
# 切片
print(numbers[1:3])# 输出:(2, 3)
# 元组解包
x, y, z =(1,2,3)
print(x, y, z)# 输出:1 2 3
# 其他操作
print(len(numbers))# 输出:5
print(3in numbers)# 输出:True
print(numbers.index(3))# 输出:2
print(numbers.count(3))# 输出:1
# 元组推导式(实际是生成器表达式)
tuple_comp =tuple(i **2for i inrange(10))
print(tuple_comp)# 输出:(0, 1, 4, 9, 16, 25, 36, 49, 64, 81)
元组与列表的区别:
元组的应用场景:
2.3 字符串(str)
字符串是一种不可变的序列类型,用于表示文本数据。
# 字符串示例
# 创建字符串
s1 ="Hello"
s2 ='World'
s3 ="""这是一个多行
字符串"""
# 访问字符
print(s1[0])# 输出:H
print(s1[-1])# 输出:o
# 切片
print(s1[1:3])# 输出:el
# 字符串拼接
print(s1 +" "+ s2)# 输出:Hello World
# 字符串复制
print(s1 *3)# 输出:HelloHelloHello
# 字符串方法
print(s1.lower())# 输出:hello(转换为小写)
print(s1.upper())# 输出:HELLO(转换为大写)
print(s1.strip())# 输出:Hello(移除首尾空白)
print(s1.replace("H","J"))# 输出:Jello(替换字符)
print(s1.split("e"))# 输出:['H', 'llo'](分割字符串)
print("e"in s1)# 输出:True(判断子串是否存在)
print(s1.find("e"))# 输出:1(查找子串的索引)
print(s1.startswith("H"))# 输出:True(判断是否以指定前缀开头)
print(s1.endswith("o"))# 输出:True(判断是否以指定后缀结尾)
# 字符串格式化
name ="张三"
age =30
print(f"{name}今年{age}岁")# 输出:张三今年30岁
print("{}今年{}岁".format(name, age))# 输出:张三今年30岁
print("%s今年%d岁"%(name, age))# 输出:张三今年30岁
# 字符串编码与解码
s ="你好"
encoded = s.encode("utf-8")
print(encoded)# 输出:b'\xe4\xbd\xa0\xe5\xa5\xbd'
print(encoded.decode("utf-8"))# 输出:你好
字符串的性能分析:
字符串的应用场景:
2.4 字节串(bytes)
字节串是一种不可变的序列类型,用于表示二进制数据。
# 字节串示例
# 创建字节串
b1 =b"Hello"
b2 =bytes([72,101,108,108,111])
# 访问字节
print(b1[0])# 输出:72
print(b1[-1])# 输出:111
# 字节串操作
print(b1 +b" "+b"World")# 输出:b'Hello World'
print(b1.find(b"e"))# 输出:1
print(b"e"in b1)# 输出:True
# 字节串与字符串的转换
s = b1.decode("utf-8")
print(s)# 输出:Hello
b = s.encode("utf-8")
print(b)# 输出:b'Hello'
字节串与字符串的区别:
字节串的应用场景:
3. 映射类型
3.1 字典(dict)
字典是Python中唯一的映射类型,它存储键值对,键必须是可哈希的。
# 字典示例
# 创建字典
empty_dict ={}
person ={"name":"张三","age":30,"city":"北京"}
# 访问值
print(person["name"])# 输出:张三
print(person.get("age"))# 输出:30
print(person.get("email","无"))# 输出:无(键不存在时返回默认值)
# 修改字典
person["age"]=31# 修改已有键的值
person["email"]="zhangsan@example.com"# 添加新键值对
print(person)# 输出:{'name': '张三', 'age': 31, 'city': '北京', 'email': 'zhangsan@example.com'}
# 删除键值对
del person["email"]# 删除指定键
print(person)# 输出:{'name': '张三', 'age': 31, 'city': '北京'}
popped = person.pop("city")# 删除并返回指定键的值
print(popped)# 输出:北京
print(person)# 输出:{'name': '张三', 'age': 31}
# 字典遍历
print(person.keys())# 输出:dict_keys(['name', 'age'])(所有键)
print(person.values())# 输出:dict_values(['张三', 31])(所有值)
print(person.items())# 输出:dict_items([('name', '张三'), ('age', 31)])(所有键值对)
# 遍历键
for key in person:
print(key, person[key])
# 遍历键值对
for key, value in person.items():
print(f"{key}: {value}")
# 字典推导式
squares ={i: i **2for i inrange(5)}
print(squares)# 输出:{0: 0, 1: 1, 2: 4, 3: 9, 4: 16}
# 合并字典
dict1 ={"a":1,"b":2}
dict2 ={"b":3,"c":4}
merged ={**dict1,**dict2}
print(merged)# 输出:{'a': 1, 'b': 3, 'c': 4}(dict2的键会覆盖dict1的)
字典的性能分析:
字典的应用场景:
4. 集合类型
集合类型用于存储唯一的元素,它们支持数学集合的操作。
4.1 集合(set)
集合是一种可变的集合类型,它存储唯一的、无序的元素。
# 集合示例
# 创建集合
empty_set =set()
numbers ={1,2,3,4,5}
unique_numbers =set([1,2,2,3,3,3,4,5])# 自动去重
print(unique_numbers)# 输出:{1, 2, 3, 4, 5}
# 添加元素
numbers.add(6)
print(numbers)# 输出:{1, 2, 3, 4, 5, 6}
# 删除元素
numbers.remove(6)# 元素不存在会抛出KeyError异常
print(numbers)# 输出:{1, 2, 3, 4, 5}
numbers.discard(6)# 元素不存在不会抛出异常
# 集合操作
set1 ={1,2,3,4,5}
set2 ={4,5,6,7,8}
# 交集
print(set1 & set2)# 输出:{4, 5}
print(set1.intersection(set2))# 输出:{4, 5}
# 并集
print(set1 | set2)# 输出:{1, 2, 3, 4, 5, 6, 7, 8}
print(set1.union(set2))# 输出:{1, 2, 3, 4, 5, 6, 7, 8}
# 差集
print(set1 - set2)# 输出:{1, 2, 3}
print(set1.difference(set2))# 输出:{1, 2, 3}
# 对称差集
print(set1 ^ set2)# 输出:{1, 2, 3, 6, 7, 8}
print(set1.symmetric_difference(set2))# 输出:{1, 2, 3, 6, 7, 8}
# 子集和超集
print({1,2,3}.issubset(set1))# 输出:True
print(set1.issuperset({1,2,3}))# 输出:True
# 集合推导式
squares ={i **2for i inrange(10)}
print(squares)# 输出:{0, 1, 4, 9, 16, 25, 36, 49, 64, 81}
4.2 冻结集合(frozenset)
冻结集合是一种不可变的集合类型,一旦创建就不能修改。
# 冻结集合示例
fs =frozenset([1,2,3,4,5])
print(fs)# 输出:frozenset({1, 2, 3, 4, 5})
# 冻结集合的操作
set1 =frozenset([1,2,3,4,5])
set2 =frozenset([4,5,6,7,8])
print(set1 & set2)# 输出:frozenset({4, 5})
print(set1 | set2)# 输出:frozenset({1, 2, 3, 4, 5, 6, 7, 8})
print(set1 - set2)# 输出:frozenset({1, 2, 3})
集合的应用场景:
三、标准库中的高级数据结构
Python的标准库提供了许多高级数据结构,这些数据结构扩展了Python的内置功能,适用于更复杂的应用场景。
1. collections模块
collections模块提供了多种扩展的数据结构,包括:
1.1 OrderedDict
OrderedDict是一种有序的字典,它保留了键的插入顺序。
# OrderedDict示例
from collections import OrderedDict
# 创建OrderedDict
od = OrderedDict()
od["a"]=1
od["b"]=2
od["c"]=3
# 遍历OrderedDict
for key, value in od.items():
print(key, value)# 输出:a 1, b 2, c 3(保持插入顺序)
# 重新插入已存在的键
od["a"]=10
for key, value in od.items():
print(key, value)# 输出:a 10, b 2, c 3(键的位置不变)
# 移动键到末尾
od.move_to_end("a")
for key, value in od.items():
print(key, value)# 输出:b 2, c 3, a 10
1.2 defaultdict
defaultdict是一种字典,它为不存在的键提供默认值。
# defaultdict示例
from collections import defaultdict
# 创建defaultdict
# 使用int作为默认工厂函数
dd = defaultdict(int)
# 访问不存在的键
print(dd["a"])# 输出:0(int()返回0)
# 统计单词频率
text ="hello world hello python"
word_count = defaultdict(int)
for word in text.split():
word_count[word]+=1
print(dict(word_count))# 输出:{'hello': 2, 'world': 1, 'python': 1}
# 使用list作为默认工厂函数
dd_list = defaultdict(list)
dd_list["a"].append(1)
dd_list["a"].append(2)
dd_list["b"].append(3)
print(dict(dd_list))# 输出:{'a': [1, 2], 'b': [3]}
1.3 Counter
Counter是一种用于计数的字典子类,它可以快速统计元素的出现次数。
# Counter示例
from collections import Counter
# 创建Counter
c = Counter([1,2,2,3,3,3,4,5])
print(c)# 输出:Counter({3: 3, 2: 2, 1: 1, 4: 1, 5: 1})
# 统计单词频率
text ="hello world hello python"
word_count = Counter(text.split())
print(word_count)# 输出:Counter({'hello': 2, 'world': 1, 'python': 1})
# 获取出现次数最多的元素
print(word_count.most_common(2))# 输出:[('hello', 2), ('world', 1)]
# Counter运算
c1 = Counter([1,2,3,4,5])
c2 = Counter([4,5,6,7,8])
print(c1 + c2)# 输出:Counter({4: 2, 5: 2, 1: 1, 2: 1, 3: 1, 6: 1, 7: 1, 8: 1})
print(c1 - c2)# 输出:Counter({1: 1, 2: 1, 3: 1})
print(c1 & c2)# 输出:Counter({4: 1, 5: 1})
print(c1 | c2)# 输出:Counter({1: 1, 2: 1, 3: 1, 4: 1, 5: 1, 6: 1, 7: 1, 8: 1})
1.4 deque
deque是一种双端队列,它支持在两端快速添加和删除元素。
# deque示例
from collections import deque
# 创建deque
d = deque([1,2,3,4,5])
# 两端操作
d.append(6)# 在右侧添加元素
print(d)# 输出:deque([1, 2, 3, 4, 5, 6])
d.appendleft(0)# 在左侧添加元素
print(d)# 输出:deque([0, 1, 2, 3, 4, 5, 6])
d.pop()# 从右侧移除元素
print(d)# 输出:deque([0, 1, 2, 3, 4, 5])
d.popleft()# 从左侧移除元素
print(d)# 输出:deque([1, 2, 3, 4, 5])
# 旋转
d.rotate(2)# 向右旋转2位
print(d)# 输出:deque([4, 5, 1, 2, 3])
d.rotate(-1)# 向左旋转1位
print(d)# 输出:deque([5, 1, 2, 3, 4])
# 限制deque的长度
d = deque([1,2,3], maxlen=3)
d.append(4)
print(d)# 输出:deque([2, 3, 4])(超出maxlen的元素被自动移除)
d.appendleft(5)
print(d)# 输出:deque([5, 2, 3])(超出maxlen的元素被自动移除)
deque的应用场景:
1.5 namedtuple
namedtuple是一种命名元组,它为元组的元素提供了名称,提高了代码的可读性。
# namedtuple示例
from collections import namedtuple
# 创建namedtuple
Point = namedtuple("Point",["x","y"])
# 创建Point对象
p = Point(1,2)
# 访问元素
print(p.x)# 输出:1
print(p.y)# 输出:2
print(p[0])# 输出:1(仍然支持索引)
# 解包
x, y = p
print(x, y)# 输出:1 2
# 转换为字典
print(p._asdict())# 输出:OrderedDict([('x', 1), ('y', 2)])
# 替换元素
p2 = p._replace(x=3)
print(p2)# 输出:Point(x=3, y=2)
# namedtuple的应用场景
# 表示二维点、三维向量等
# 替代简单的类
# 提高元组的可读性
2. heapq模块
heapq模块提供了堆(优先队列)的实现,堆是一种特殊的二叉树,每个节点的值都小于或等于其子节点的值。
# heapq示例
import heapq
# 创建最小堆
heap =[5,3,8,1,2,9]
heapq.heapify(heap)
print(heap)# 输出:[1, 2, 8, 3, 5, 9](堆化后的列表)
# 插入元素
heapq.heappush(heap,4)
print(heap)# 输出:[1, 2, 8, 3, 5, 9, 4]
# 弹出最小元素
print(heapq.heappop(heap))# 输出:1
print(heap)# 输出:[2, 3, 8, 4, 5, 9]
# 获取最小元素但不弹出
print(heap[0])# 输出:2
# 合并多个有序列表
list1 =[1,3,5,7]
list2 =[2,4,6,8]
merged =list(heapq.merge(list1, list2))
print(merged)# 输出:[1, 2, 3, 4, 5, 6, 7, 8]
# 获取最大或最小的n个元素
numbers =[5,3,8,1,2,9]
print(heapq.nlargest(3, numbers))# 输出:[9, 8, 5](最大的3个元素)
print(heapq.nsmallest(3, numbers))# 输出:[1, 2, 3](最小的3个元素)
堆的应用场景:
3. bisect模块
bisect模块提供了二分查找的实现,它可以在有序列表中快速插入元素或查找元素的位置。
# bisect示例
import bisect
# 有序列表
numbers =[1,3,5,7,9]
# 查找元素的插入位置(保持列表有序)
print(bisect.bisect(numbers,4))# 输出:2(插入到索引2的位置)
print(bisect.bisect_left(numbers,3))# 输出:1(元素存在时返回最左边的位置)
print(bisect.bisect_right(numbers,3))# 输出:2(元素存在时返回最右边的位置)
# 插入元素(保持列表有序)
bisect.insort(numbers,4)
print(numbers)# 输出:[1, 3, 4, 5, 7, 9]
bisect.insort_left(numbers,3)
print(numbers)# 输出:[1, 3, 3, 4, 5, 7, 9]
bisect.insort_right(numbers,3)
print(numbers)# 输出:[1, 3, 3, 3, 4, 5, 7, 9]
# 二分查找元素是否存在
defbinary_search(arr, x):
idx = bisect.bisect_left(arr, x)
return idx <len(arr)and arr[idx]== x
print(binary_search(numbers,5))# 输出:True
print(binary_search(numbers,6))# 输出:False
bisect模块的应用场景:
4. array模块
array模块提供了数组数据结构,它类似于列表,但只能存储同类型的元素,比列表更节省内存。
# array示例
import array
# 创建数组
# 'i'表示整数类型
arr = array.array('i',[1,2,3,4,5])
print(arr)# 输出:array('i', [1, 2, 3, 4, 5])
# 访问元素
print(arr[0])# 输出:1
# 修改元素
arr[0]=10
print(arr)# 输出:array('i', [10, 2, 3, 4, 5])
# 添加元素
arr.append(6)
print(arr)# 输出:array('i', [10, 2, 3, 4, 5, 6])
# 删除元素
arr.pop()
print(arr)# 输出:array('i', [10, 2, 3, 4, 5])
# 数组类型码
# 'b':signed char
# 'B':unsigned char
# 'h':signed short
# 'H':unsigned short
# 'i':signed int
# 'I':unsigned int
# 'l':signed long
# 'L':unsigned long
# 'f':float
# 'd':double
array的应用场景:
5. struct模块
struct模块提供了将Python数据类型转换为C语言数据类型的功能,用于处理二进制数据。
# struct示例
import struct
# 打包数据
# 'i'表示int类型,'f'表示float类型
packed_data = struct.pack('if',10,3.14)
print(packed_data)# 输出:b'\n\x00\x00\x00\xc3\xf5\x88@'(二进制数据)
# 解包数据
unpacked_data = struct.unpack('if', packed_data)
print(unpacked_data)# 输出:(10, 3.140000104904175)
# 打包多个数据
packed_data = struct.pack('2i3sf',1,2,b'abc',3.14)
print(packed_data)# 输出:b'\x01\x00\x00\x00\x02\x00\x00\x00abc\x00\xc3\xf5\x88@'
# 解包多个数据
unpacked_data = struct.unpack('2i3sf', packed_data)
print(unpacked_data)# 输出:(1, 2, b'abc', 3.140000104904175)
# 格式字符
# '=':使用本地字节序
# '<':使用小端字节序
# '>':使用大端字节序
# '!':使用网络字节序(大端)
struct模块的应用场景:
四、数据结构的选择与比较
选择合适的数据结构对于程序的性能和可维护性至关重要。以下是一些常见场景下的数据结构选择建议:
1. 数据结构比较表
2. 数据结构选择建议
- 需要有序集合且频繁修改
- 需要有序集合且不修改
- 需要键值对映射
- 需要唯一元素
- 需要保持插入顺序的字典:使用OrderedDict(Python 3.7+的dict也保持插入顺序)
- 需要为不存在的键提供默认值
- 需要计数
- 需要双端队列
- 需要优先队列
- 需要存储大量同类型数据
- 需要处理二进制数据
五、数据结构的最佳实践
1. 选择合适的数据结构
根据应用场景选择合适的数据结构,考虑以下因素:
2. 优化数据结构的使用
- 列表:使用列表推导式代替循环创建列表,使用extend()代替+运算符拼接列表
- 字典:使用字典推导式创建字典,使用get()方法访问可能不存在的键
- 集合
- deque:对于频繁在两端操作的场景,使用deque替代列表
- heapq:对于需要频繁获取最小或最大元素的场景,使用heapq
3. 避免常见错误
六、总结
Python提供了丰富的数据结构,包括内置的数据结构(列表、元组、字符串、字典、集合等)和标准库中的高级数据结构(OrderedDict、defaultdict、Counter、deque、堆等)。每种数据结构都有其特点和适用场景,选择合适的数据结构对于程序的性能和可维护性至关重要。
本文详细介绍了Python的各种数据结构,包括它们的定义、特点、基本操作、高级用法、应用场景和性能分析。通过学习这些数据结构,您可以更好地理解Python的工作原理,并在实际应用中选择合适的数据结构来解决问题。
发布网站:荣殿教程(zhangrongdian.com)
作者:张荣殿