python基础②-数据结构
1.Dictionary字典
1.1 概念及常用函数
字典,也可以用dict表示,它是是 Python 中的内置数据结构,以键 - 值对(key - value pairs)的形式存储数据 ,具备高效的查找特性。但这里需要明白它的规则要求,
key 必须是可哈希的数据类型,像字符串、数字、元组等;
而 value 则灵活得多,可容纳任意数据类型,如列表、字典、函数等。
那接下来我们从以下几个函数进行介绍。
1.1.1 get函数
用于获取指定 key 对应的 value 。当 key 存在时,返回对应的 value;若 key 不存在,返回默认值(默认值未指定时返回 None
)。
例如说:
student = {'name': '小明', 'age': 20}
# key 存在,返回对应 value
print(student.get('name')) # key 不存在,返回默认值 None
print(student.get('gender')) # 指定默认值,key 不存在时返回该默认值
print(student.get('gender', '未知'))
相较于直接通过 dict[key]
取值,get
函数在 key 不存在时不会抛出 KeyError
异常。
1.1.2 setdefault函数
若 key 存在于字典中,返回对应的 value;若 key 不存在,会将该 key 插入字典,并将 value 设为指定的默认值。
例如说:
student = {'name': '小明', 'age': 20}
# key 存在,返回对应 value
print(student.setdefault('name', '小红')) # key 不存在,插入 key - value 对
student.setdefault('gender', '男') print(student)
主要应用在处理字典动态添加键值对的场景中极为便捷,比如统计列表中元素出现次数时,可用于初始化计数 。
字典的查找操作:
dict1 = {"school":"GPNU", "age":"18"}
print(dict1["school"])
此时会输出:
GPNU
字典的增加操作:(同理这也是字典的修改操作,一样的)
dict1["name"] = "LKR"
print(dict1["name"])
此时会输出:
LKR
字典的删除操作:
dict1.pop("school")
1.2 字典循环(解构)
对于常规循环,就是直接遍历字典,默认拿到的是 key ,通过 key 可进一步获取对应的 value 。
student = {'name': '小明', 'age': 20}
for key in student:print(key, student[key])
这边介绍一种解构循环,更好用,它是同时遍历 key 和 value,一次就能读出来。
student = {'name': '小明', 'age': 20}
for key, value in student.items():print(key, value)
结果一样,解构循环让代码更简洁直观,在需要同时操作键和值时,无需额外通过 key 去字典中取值 。
还可以采用item函数,一次性全输出出来,如:
dict1 = {"school":"GPNU", "age":"18"}
dict1["name"] = "LKR"
for i,j in dict1.items():print(i, j)
结果输出如下:
school GPNU
age 18
name LKR
1.3 字典的嵌套
字典的value也可以是字典类型,从而形成嵌套结构,用于表示更复杂的数据关系 。
例如:
# 嵌套字典表示学生信息,包含基本信息和课程成绩
student = {'basic_info': {'name': '小明', 'age': 20},'courses': {'math': 90, 'english': 85}
}
# 访问嵌套字典中的值
print(student['basic_info']['name'])
print(student['courses']['math'])
这个跟列表的嵌套核心的分析方法是一样的。
1.4 字典的循环删除
在 Python 中,直接在循环中删除字典元素(如 for key in dict: del dict[key]
)会引发错误,因为删除元素会改变字典的大小,导致迭代出错 。
这里的解决方法也还是一样,就是创建临时字典来存储数据,然后再进行删除。
例如:
student = {'name': '小明', 'age': 20, 'gender': '男'}
keys_to_delete = ['age', 'gender']
for key in keys_to_delete:del student[key]
print(student)
这里就成功删除了。
1.5 字符集与编码知识
在Python数据分析中,编码本质规定字符转二进制数据的规则,处理文本数据时需知晓编码规则,否则字典存储含中文等文本数据读写传输易出错;
多语言兼容需求下,Unicode等编码可统一字符表示、动态调整字节,影响字典key比较、序列化等操作;
编码知识贯穿数据采集、清洗、分析全流程,与字典嵌套结构关联紧密,构建完整编码体系才能保障高效数据分析实践,
这个还是比较重要的,毕竟后面从采集读不同编码文件、清洗转编码格式到分析分词统计词频,编码错则分词乱、词频错,懂编码才能让字典统计、嵌套解析不会出现错误,让分析结果更加精确。
以下代码演示了 GBK 编码与 UTF-8 编码的转换过程,核心逻辑是解码 → 重新编码
。
结合字符集知识,理解其作用需要从编码转换的必要性和实际场景出发,
# GBK 编码的字节串(假设原始数据是 GBK 编码)
bs = b'\xb8\xd5\xbd\xa8\xbc\xbc\xb9\xa4\xd7\xf7' # 1. 解码:将 GBK 字节串转换为 Unicode 字符串
# GBK 是解码时的“钥匙”,必须与原始编码一致,否则乱码
s = bs.decode("gbk") # 2. 重新编码:将 Unicode 字符串转换为 UTF-8 字节串
# UTF-8 是常用的通用编码,适合跨平台传输/存储
bs2 = s.encode("utf-8") print(bs2)
这个程序的关键在于:
解码(decode):把计算机存储的二进制字节(bs
)还原为人类可读的 Unicode 字符串(s
),需要指定原始编码(如 gbk
)。
编码(encode):把 Unicode 字符串(s
)重新转换为二进制字节(bs2
),指定目标编码(如 utf-8
),让数据能在不同系统/平台通用。
再比如需要分析一个 GBK 编码的文本文件:
import jieba# 1. 读取 GBK 编码的文件(模拟老系统数据)
with open("old_gbk_file.txt", "rb") as f:gbk_bytes = f.read() # 读取 GBK 字节串# 2. 解码为 Unicode 字符串
unicode_str = gbk_bytes.decode("gbk") # 3. 用字典统计词频(依赖正确的字符串)
word_count = {}
for word in jieba.lcut(unicode_str):if len(word) >= 2:word_count[word] = word_count.get(word, 0) + 1# 4. 编码为 UTF-8 写入新文件(跨平台通用)
with open("new_utf8_file.txt", "w", encoding="utf-8") as f:for word, count in word_count.items():f.write(f"{word}:{count}\n")
这里若是跳过编码转换,直接用 utf-8
读 GBK 文件,会触发 UnicodeDecodeError
或导致分词/统计完全错误。
总的来说,在 Python 数据分析中,编码转换对处理文本数据是很重要的,
不仅可以修复 GBK 等历史编码的乱码问题,还原真实文本含义。
此外,统一编码,也能确保字典、文件、网络传输的兼容性。
2.String字符串
字符串(String)是 Python 中最基本的数据类型之一,用于表示文本数据,掌握字符串的各种操作和函数对于编写高效的 Python 程序至关重要。
要学习字符串,首先要理解什么是字符,比方说“H”、"A"这种就是一个字符,而字符串就是由若干个这样的字符串到一起的,那就叫字符串,如“happy”这样子就算是一个字符串。
同理,字符串也有它的查找、切片,最大小值、求长度(len)等操作,跟前面介绍到的都是一样的,这里不过多赘述。
下面着重讲下它的String里特有的函数,下面一一进行介绍。
count函数:计算字符串中特定字符的数目
s = "Hello World"
print(s.count("o")) #结果是2,因为字符串里有两个“o”
isupper函数:计算字符串中是否都是大写字母,只返回True
或False
print(s.isupper()) #结果是False,因为字符串混着大小写字母
islower函数:计算字符串中是否都是小写字母,只返回True
或False
print(s.islower()) #结果是False,因为字符串混着大小写字母
lower函数:把字符串里的所有字符改成小写的形式
upper函数:把字符串里的所有字符改成大写的形式
strip函数:删除字符串首尾两端
的指定字符(默认是空白字符,包括空格、换行 \n
、制表符 \t
等 ),不影响中间的字符。
lstrip函数:把字符串最左边的所有空格删除
rstrip函数:把字符串最右边的所有空格删除
swapcase函数:把字符串的大小写形式互换
s = " Hello World "
print(s.lower())
print(s.upper())
print(s.strip())
print(s.lstrip())
print(s.rstrip())
print(s.swapcase())
此时输出为:
hello world HELLO WORLD
Hello World
Hello World Hello WorldhELLO wORLD
replace函数:把字符串的若干字符换成用户要更改成的字符
s = " Hello World "
print(s.replace('l','q')) #输出为 Heqqo Worqd
split函数:字符分割操作
s = " Hello World "
print(s.split("Hel")) #输出为[' ', 'lo World ']
以下是为每个函数补充的说明和示例,可对应填入图中每个函数下方的空白处:
title函数:将字符串中每个单词的首字母转为大写,常用来规范文本格式。
s = "hello world"
result = s.title()
print(result) # 输出: Hello World
find函数:查找子字符串在原字符串中第一次出现的索引,找不到则返回 -1
,用于定位文本位置。
s = "apple banana apple orange"
index = s.find("apple")
print(index) # 输出: 0(第一个apple的起始索引)
not_found = s.find("grape")
print(not_found) # 输出: -1(未找到)
index函数:与 find
类似,查找子字符串第一次出现的索引,但找不到时会报错(ValueError
),适合确认子串必须存在的场景。
s = "apple banana apple orange"
index = s.index("banana")
print(index) # 输出: 6(banana的起始索引)
# 下方代码会报错,因为grape不存在
# s.index("grape")
startswith函数:判断字符串是否以指定子串开头,返回布尔值(True
/False
),用于筛选文本前缀。
s = "https://www.example.com"
is_web = s.startswith("https://")
print(is_web) # 输出: Truefile_name = "data.txt"
is_txt = file_name.startswith("text")
print(is_txt) # 输出: False
isdigit函数:判断字符串是否仅由数字组成(0 - 9),返回布尔值,常用于校验输入是否为纯数字。
num_str = "12345"
print(num_str.isdigit()) # 输出: Truemixed_str = "12a34"
print(mixed_str.isdigit()) # 输出: False
join函数:将可迭代对象(列表、元组等)中的元素,用指定字符串连接成一个新字符串,常用于拼接文本。
words = ["hello", "world", "python"]
joined = "-".join(words)
print(joined) # 输出: hello-world-pythonnums = ("1", "2", "3")
joined_nums = "|".join(nums)
print(joined_nums) # 输出: 1|2|3
f变量
函数:在字符串前加 f
,用 {}
嵌入变量/表达式,快速拼接动态内容,比传统格式化更简洁。
name = "Alice"
age = 25
info = f"姓名: {name}, 年龄: {age}"
print(info) # 输出: 姓名: Alice, 年龄: 25# 也支持表达式
price = 10
count = 3
total = f"总价: {price * count}"
print(total) # 输出: 总价: 30
3.Array数组
数组(Array) 是一种存储相同类型元素的有序集合。数组元素必须是相同类型(如整数、浮点数),比列表(list)更节省内存,适合处理大量同类型数据。
Array数组的常用使用:
import array# 创建整数数组('i'表示整数类型、‘f’表示单精度浮点数、‘d’表示双精度浮点数,‘U’表示Unicode字符)
arr = array.array('i', [1, 2, 3, 4])# 访问元素
print(arr[0]) # 输出: 1# 修改元素
arr[1] = 10
print(arr) # 输出: array('i', [1, 10, 3, 4])# 添加元素
arr.append(5)
print(arr) # 输出: array('i', [1, 10, 3, 4, 5])
4.Linkedlist链表
4.1 存储结构(数组与链表)
维度 | 数组(Array) | 链表(Linked List) |
---|---|---|
内存布局 | 连续内存空间 | 非连续,通过指针连接各个节点 |
存储方式 | 元素按顺序存储在连续地址中 | 每个节点包含数据和指向下一节点的指针(单链表) |
图示 | [A, B, C, D] (连续存储) | A → B → C → D (节点通过指针连接) |
4.2 优缺点对比(数组与链表)
数组的优势
随机访问高效:适合需要频繁通过索引访问元素的场景(如遍历、二分查找)。
内存利用率高:连续存储无需额外指针,空间开销小。
缓存友好:连续内存可充分利用 CPU 缓存,提高访问速度。
数组的劣势
插入或删除效率低:尤其在数组中间操作时,需移动大量元素。
固定容量:多数语言中数组长度需预先定义,动态扩容代价高(如 Python 列表扩容时需重新分配内存)。
链表的优势
动态扩容:无需预先分配空间,可按需添加节点。
高效插入或删除:适合频繁插入删除的场景(如队列、栈)。
链表的劣势
随机访问低效:不支持直接通过索引访问,需从头遍历。
内存开销大:每个节点需额外存储指针,空间利用率低。
缓存不友好:非连续内存导致缓存命中率低。
4.3 程序解读
#定义一个名为“Node”的类,代表链表中的一个节点
class Node:def __init__(self, data):self.data = data #存储节点的数据self.next = None #指向下一个节点的指针,初始为None
如创建单个节点:
node = Node(10) # 创建一个节点,数据为 10
print(node.data) # 输出: 10
print(node.next) # 输出: None
如果要连接多个节点成为链表:
#定义一个名为“Node”的类,代表链表中的一个节点
class Node:def __init__(self, data):self.data = data #存储节点的数据self.next = None #指向下一个节点的指针,初始为none# 创建三个节点
node1 = Node(1)
node2 = Node(2)
node3 = Node(3)
node5 = Node(5)# 连接节点形成链表:1 → 2 → 3 → 5
node1.next = node2 # node1 的下一个节点是 node2
node2.next = node3 # node2 的下一个节点是 node3
node3.next = node5 # node3 的下一个节点是 node5# 遍历链表(从 node1 开始)
current = node1
while current is not None:print(current.data) # 依次输出 1, 2, 3, 5current = current.next
通过以上程序,对于链表有个初步的理解认识,常用的场景主要是用于实现队列,栈等数据结构,或者是操作系统中的内存管理,理解链表的关键是掌握“节点通过指针连接”这一思想。
5.Queue队列
5.1 特点
FIFO(先进先出):先进入队列的元素先出队,类似排队。
应用场景:任务调度、消息传递、广度优先搜索(BFS)等。
5.2 Python 实现
标准库 queue.Queue
(线程安全,适合多线程):
from queue import Queueq = Queue()
q.put(1) # 入队
q.put(2)
print(q.get()) # 出队,输出: 1
collections.deque
(双端队列,高效且常用):
from collections import dequeq = deque()
q.append(1) # 入队
q.append(2)
print(q.popleft()) # 出队,输出: 1
6.Stack栈
6.1 特点
LIFO(后进先出):最后进入栈的元素先出栈,类似叠盘子。
应用场景:函数调用栈、表达式求值、深度优先搜索(DFS)等。
6.2 Python 实现
使用列表(List):
stack = []
stack.append(1) # 入栈
stack.append(2)
print(stack.pop()) # 出栈,输出: 2
collections.deque
(推荐):
from collections import dequestack = deque()
stack.append(1) # 入栈
stack.append(2)
print(stack.pop()) # 出栈,输出: 2
7.Heap堆
7.1 特点
完全二叉树:每个节点的值都大于等于(最大堆)或小于等于(最小堆)其子节点的值。
高效操作:插入和删除元素的时间复杂度为 O(log n),获取最大值/最小值为 O(1)。
应用场景:优先队列、堆排序、求Top-K问题等。
7.2 Python 实现(最小堆)
使用 heapq
模块(基于列表实现):
import heapqheap = []
heapq.heappush(heap, 3) # 插入元素
heapq.heappush(heap, 1)
heapq.heappush(heap, 2)
print(heapq.heappop(heap)) # 弹出最小元素,输出: 1
最大堆技巧:插入元素时取负,取出时再取负:
import heapqmax_heap = []
heapq.heappush(max_heap, -3) # 插入 3 的负数
heapq.heappush(max_heap, -1)
print(-heapq.heappop(max_heap)) # 弹出最大元素,输出: 3
Heap还有大堆,大堆就是你按顺序取出的是从大到小,跟小堆刚好反过来。
但是由于python好像还没有一个函数是可以直接体现大堆的,所以我们在使用的时候还是要以小堆的那个heapify函数来,把各个元素添个负号就可以了。
7.3 对比总结
数据结构 | 特点 | Python 实现 | 典型场景 |
---|---|---|---|
队列 | FIFO(先进先出) | queue.Queue 或 deque | 任务调度、BFS |
栈 | LIFO(后进先出) | list 或 deque | 函数调用、DFS |
堆 | 完全二叉树,高效取极值 | heapq (最小堆) | 优先队列、堆排序 |
8.小结
本文系统地介绍了Python中多种重要数据结构,是python数据结构第一篇文章的补充。从字典的各类操作与嵌套,到字符串的丰富函数,再到数组、链表的特点与应用,以及队列、栈、堆的数据特性,希望对Python初学者理解和掌握数据结构有一定帮助。