2024年重庆大学大二通信工程课程《数据结构与算法》Data Structure and Algorithms 实验作业记录。(错误在所难免,请多斧正!🤝)
目录
- 前言
- 一、实验目的
- 二、实验内容
- 2.1 使用顺序表方法和单链表方法实现Stack
- 代码及结果
- 2.1.1 顺序表实现栈
- 2.1.2 单链表实现栈
- 2.2 列表(List)实现优先队列
- 2.3 堆实现优先队列
- 2.4 实现二叉堆
- 总结
前言
1. 教材
课程选用教材,讲解详实全面。
数据结构与算法:Python语言描述(第2版)——裘宗燕--机械工业出版社 (cmpedu.com)http://www.cmpedu.com/books/book/5605157.htm
上手难度低,易读性强。
Hello 算法 (roadup.cc)https://algo.roadup.cc/
两本教材可以相互结合着看。
2. 环境
PyCharm:用于数据科学和 Web 开发的 Python IDE (jetbrains.com.cn)https://www.jetbrains.com.cn/en-us/pycharm/
一、实验目的
- 理解并掌握栈(Stack)的原理及Python实现;
- 理解并掌握队列(Queue)算法及Python实现;
- 理解并掌握堆(Heap)的概念。
【栈】或称为堆栈,是一种遵循先入后出(LIFO,Last In First Out)逻辑的线性数据结构。(stack 原意是指一个干草堆;或者泛指一堆、一排或一列整齐堆放的物品。)
【队列】或称为队,是一种遵循先入先出(FIFO,First In First Out)规则的线性数据结构。顾名思义,队列模拟了排队现象。
【堆】用于实现优先队列的完全二叉树(Complete Binary Tree)。从使用角度来看, 可以将“优先队列”和“堆”看作等价的数据结构。分大顶堆和小顶堆,大顶堆相当于元素按从大到小的顺序出队的优先队列。
二、实验内容
2.1 使用顺序表方法和单链表方法实现Stack
【顺序表 Sequential List 】用于存储一系列具有顺序关系的元素,是一种基本的数据结构。Python中的列表(List)就是一种顺序表。
列表是可变的,可以包含任意类型的元素,并且可以根据需要动态添加、删除和修改元素。通过索引可以访问和操作列表中的元素,索引从0开始,依次递增。
顺序表的特点是元素的顺序固定,可以通过索引快速定位元素,并且支持在任意位置进行插入、删除和修改操作。
代码及结果
2.1.1 顺序表实现栈
class SStack:
def __init__(self):
self._stack = []
# 入栈
def push(self, item: int):
self._stack.append(item)
# 判断栈是否为空
def is_empty(self) -> bool:
return self._stack == []
# 出栈
def pop(self) -> int:
if self.is_empty():
raise IndexError('栈为空')
return self._stack.pop()
# 访问栈顶元素
def peek(self) -> int:
if self.is_empty():
raise IndexError('栈为空')
return self._stack[-1]
# 获取栈的长度
def size(self):
return len(self._stack)
# 返回列表
def to_list(self):
return self._stack
st = SStack()
for i in range(1,4):
st.push(i)
print(f'stack: {st.to_list()}')
print(f'peek: {st.peek()}')
print(f'pop: {st.pop()}')
print(f'stack: {st.to_list()}')
运行结果
stack: [1, 2, 3]
peek: 3
pop: 3
stack: [1, 2]Process finished with exit code 0
2.1.2 单链表实现栈
class ListNode:
def __init__(self, val: int):
self.val: int = val
self.next: None | ListNode = None
# 单链表实现栈
class LinkedListStack:
# 构造方法
def __init__(self):
self.__peek = None #栈顶(头节点)
self.__size = 0
# 获取栈的长度
def size(self):
return self.__size
# 判断栈是否为空
def is_empty(self):
return not self.__peek
# 入栈
def push(self, val):
node = ListNode(val)
node.next = self.__peek
self.__peek = node
self.__size += 1
# 出栈
def pop(self):
num = self.peek()
self.__peek = self.__peek.next
self.__size -= 1
return num
# 访问栈顶元素
def peek(self):
if not self.__peek:
return None
return self.__peek.val
# 返回列表用于打印
def to_list(self):
arr = []
node = self.__peek
while node:
arr.append(node.val)
node = node.next
arr = arr[::-1] # 等效于 arr.reverse() (该式子不返回任何值)
return arr
stack = LinkedListStack()
for i in range(4):
stack.push(i)
print(f'stack: {stack.to_list()}')
print(f'peek: {stack.peek()}')
print(f'pop: {stack.pop()}')
print(f'stack: {stack.to_list()}')
运行结果
stack: [0, 1, 2, 3]
peek: 3
pop: 3
stack: [0, 1, 2]Process finished with exit code 0
2.2 列表(List)实现优先队列
【优先队列 Priority Queue】一种特殊的队列数据结构,其中每个元素都被赋予优先级。
元素按照优先级的高低顺序进行排序,具有最高优先级的元素可以最先被访问或删除。优先队列常常用于解决具有优先级的问题,例如任务调度、数据压缩等。
代码实现
# 基于列表实现优先队列( 1 为优先级最高)
class PriorityQueue:
def __init__(self):
self.queue = []
# 判断是否为空
def is_empty(self):
return len(self.queue) == 0
# 插入
def insert(self, priority, item):
for i in range(len(self.queue)):
if self.queue[i][0] > priority:
self.queue.insert(i, (priority, item))
print(f'insert: ({priority}, {item})')
return # 不能用break
# 队列为空时
self.queue.append((priority, item))
print(f'insert: ({priority}, {item})')
# 弹出
def pop(self):
if self.is_empty():
raise Exception('pop from a empty priority queue')
return self.queue.pop(0)[1]
# 查看
def peek(self):
if self.is_empty():
raise Exception('peek from a empty priority queue')
return self.queue[0][1]
# 队列元素数量
def size(self):
return len(self.queue)
def to_list(self):
return self.queue
pq = PriorityQueue()
pq.insert(3, 'c')
pq.insert(1, 'a')
print(f'Queue: {pq.to_list()}')
print(f'size: {pq.size()}')
print(f'peek: {pq.peek()}')
print(f'pop: {pq.pop()}')
print(f'queue: {pq.to_list()}')
pq.insert(2, 'b')
print(f'queue: {pq.to_list()}')
结果显示
insert: (3, c)
insert: (1, a)
Queue: [(1, 'a'), (3, 'c')]
size: 2
peek: a
pop: a
queue: [(3, 'c')]
insert: (2, b)
queue: [(2, 'b'), (3, 'c')]Process finished with exit code 0
2.3 堆实现优先队列
在实际应用中,我们可以直接使用编程语言提供的堆类(或优先队列类)“ heapq ”。
代码实现
# 直接使用Python的heapq模块实现
import heapq
# heapq模块默认实现小顶堆
min_heap = []
heapq.heappush(min_heap, (1, 'a'))
heapq.heappush(min_heap, (2, 'b'))
heapq.heappush(min_heap, (3, 'c'))
print(f'min_heap: {min_heap}')
print(f'peek: {min_heap[0]}\n')
min_heap1 = [3, 9, 7, 4, 6]
heapq.heapify(min_heap1)
print(f'min_heap1: {min_heap1}')
print('pops: ',end='')
while min_heap1:
print(f'{heapq.heappop(min_heap1)}', end=' ')
用heapq时候为什么元组和整数都可以入堆?
在Python中,heapq 模块实现的是基于比较的堆,它要求堆中的元素是可比较的。故不论是整数、浮点数、字符串,还是自定义的对象或元组,都可以作为堆的元素。
对于元组,Python中元组的比较是基于元素逐个比较的,从左到右。如果两个元组在相应位置的元素相等,则比较下一个元素,直到找到不相等的元素为止,或者比较完所有元素。
结果显示
min_heap: [(1, 'a'), (2, 'b'), (3, 'c')]
peek: (1, 'a')min_heap1: [3, 4, 7, 9, 6]
pops: 3 4 6 7 9
Process finished with exit code 0
2.4 实现二叉堆
【二叉堆 Binary Heap】
二叉堆是一种基于完全二叉树的数据结构,它满足以下性质:
- 它是一个完全二叉树,意味着除了最后一层,其他层的节点都是满的,并且最后一层的节点尽量靠左排列。
- 每个节点的值都大于或等于其子节点的值(对于最大堆 max heap),或者每个节点的值都小于或等于其子节点的值(对于最小堆 min heap)。
二叉堆通常使用数组来表示,其中根节点存储在索引 0 处,而其他节点的位置可以通过一些算法来计算。这使得插入和删除操作的时间复杂度为 O(log n),其中 n 是堆中元素的数量。
除了常见的插入和删除操作,二叉堆还常用于实现优先级队列。在优先级队列中,每个元素都有一个关联的优先级,高优先级的元素先出队列。二叉堆的性质使得其能够高效地找到和删除具有最高(或最低)优先级的元素。
代码实现
# 用数组表示堆来实现优先队列
def left(i: int) -> int:
return i * 2 + 1
def right(i: int) -> int:
return i * 2 + 2
def parent(i: int) -> int:
return (i - 1) // 2 # //为向下整除
class my_heap:
def __init__(self):
self.min_heap = []
def size(self):
return len(self.min_heap)
# 访问堆顶
def peek(self):
if not self.min_heap:
raise IndexError('堆为空')
return self.min_heap[0]
def swap(self, i: int, p: int):
temp = self.min_heap[i]
self.min_heap[i] = self.min_heap[p]
self.min_heap[p] = temp
# 堆化 (上移)
def sift_up(self, i: int):
while i != 0:
if self.min_heap[i] < self.min_heap[parent(i)]:
self.swap(i, parent(i))
i = parent(i)
else:
break
# 入堆
def push(self, val: int):
self.min_heap.append(val)
self.sift_up(len(self.min_heap) - 1)
# 判断是否为空
def is_empty(self):
return not self.min_heap
# 出堆
def pop(self):
if self.is_empty():
raise IndexError('Empty Heap')
else:
self.swap(0, -1)
val = self.min_heap.pop()
self.sift_down(0)
return val
# 下移
def sift_down(self, i: int):
while True:
# 判断节点i,l,r中值最小的节点,记为mi(min在python是一个函数)
l, r, mi = left(i), right(i), i
if l < self.size() and self.min_heap[mi] > self.min_heap[l]:
mi = l
if r < self.size() and self.min_heap[mi] > self.min_heap[r]:
mi = r
# 跳出循环:i已经是最小节点,或者l,r越界
if i == mi:
break
# 交换节点
self.swap(mi, i)
# 继续堆化
i = mi
# 转成列表输出
def to_list(self):
return self.min_heap
heap = my_heap()
print('push: ',end='')
arr = [4,3,5,6,1,7,2]
for i in arr:
heap.push(i)
print(f'{i}',end=' ')
print(f'\npeek: {heap.peek()}')
print(f'heap: {heap.to_list()}')
print()
for _ in range(heap.size()):
print(f'pop: {heap.pop()}')
print(f'heap: {heap.to_list()};')
sift : sift 被借用来描述数据结构(尤其是堆)中的元素移动操作,如 "sift up" 和 "sift down",用来维护堆的有序性。原意为“筛分”、“过滤”,如筛面粉。
结果显示
push: 4 3 5 6 1 7 2
peek: 1
heap: [1, 3, 2, 6, 4, 7, 5]pop: 1
heap: [2, 3, 5, 6, 4, 7];
pop: 2
heap: [3, 4, 5, 6, 7];
pop: 3
heap: [4, 6, 5, 7];
pop: 4
heap: [5, 6, 7];
pop: 5
heap: [6, 7];
pop: 6
heap: [7];
pop: 7
heap: [];Process finished with exit code 0
总结
上实验课前要复习相关知识。
AI的强大:在写代码的过程中,遇到困难(知识点忘记,调试不过)是肯定的。以前是翻书,现在就是直接问AI。
实验课后及时整理报告。发成博客是很好的选择,很能激励自己尽快完成。