实验四:栈、队列和树

2024年重庆大学大二通信工程课程《数据结构与算法》Data Structure and Algorithms 实验作业记录。(错误在所难免,请多斧正!🤝)

目录


前言

 1. 教材  

课程选用教材,讲解详实全面。

数据结构与算法:Python语言描述(第2版)——裘宗燕--机械工业出版社 (cmpedu.com)icon-default.png?t=N7T8http://www.cmpedu.com/books/book/5605157.htm

上手难度低,易读性强。 

Hello 算法 (roadup.cc)icon-default.png?t=N7T8https://algo.roadup.cc/

 两本教材可以相互结合着看。

2. 环境

PyCharm:用于数据科学和 Web 开发的 Python IDE (jetbrains.com.cn)icon-default.png?t=N7T8https://www.jetbrains.com.cn/en-us/pycharm/


一、实验目的

  1. 理解并掌握栈(Stack)的原理及Python实现;
  2. 理解并掌握队列(Queue)算法及Python实现;
  3. 理解并掌握堆(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

二叉堆是一种基于完全二叉树的数据结构,它满足以下性质:

  1. 它是一个完全二叉树,意味着除了最后一层,其他层的节点都是满的,并且最后一层的节点尽量靠左排列。
  2. 每个节点的值都大于或等于其子节点的值(对于最大堆 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。

实验课后及时整理报告。发成博客是很好的选择,很能激励自己尽快完成。

  • 26
    点赞
  • 32
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Qiming_Peng

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值