当前位置:   article > 正文

【少儿编程Python:趣味编程,探索未来】第五章 算法与数据结构,编程的智慧结晶 / 第一节 数据结构的魔法仓库

【少儿编程Python:趣味编程,探索未来】第五章 算法与数据结构,编程的智慧结晶 / 第一节 数据结构的魔法仓库

欢迎进入Python编程的奇幻世界!在这个课程中,我们将一起探索编程的乐趣,通过生动有趣的方式,培养孩子们的逻辑思维和创造力,让他们成为未来的科技小达人。
以下是我们课程的大纲:
少儿编程Python:趣味编程,探索未来

在这里插入图片描述

1. 前言

Python,这门优雅而强大的编程语言,为我们提供了丰富的数据结构选择。从简单直观的列表、元组,到高效灵活的栈、队列,再到复杂而强大的树、图,每一种数据结构都有其独特的魔法和用途。它们如同仓库中琳琅满目的魔法道具,等待着我们去发掘、去运用。

让我们一起走进这个充满魔力的仓库,探索Python数据结构的奥秘吧!在这里,你将发现编程的无限可能,感受到数据结构的神奇魅力。无论你的目标是成为一名优秀的开发者,还是追求编程的乐趣和成就感,这个魔法仓库都将是你不可或缺的伙伴。

2. 线性数据结构的魔法世界

在Python的魔法世界里,有几种特别酷炫的线性数据结构,它们就像魔法师手中的魔杖,能够帮助我们更轻松、高效地管理数据。接下来,让我们一起探索这些魔法般的线性数据结构吧!

2.1 列表(List) - 魔法宝盒

想象一下,你有一个神奇的魔法宝盒,里面可以装下各种各样的宝贝。这个魔法宝盒就是Python中的列表!你可以随时向里面添加新的宝贝,也可以随时取出或修改里面的宝贝。

2.1.1 魔法清单增加魔法物品(列表创建及新增)

示例:想象一下你有一个魔法清单,并且你想要添加一些新的魔法物品。你可以这样做:

# 创建一个空的魔法清单
magic_list = []

# 向魔法清单中添加一个魔法棒
magic_list.append('魔法棒')

# 在列表开头插入一个魔法帽
magic_list.insert(0, '魔法帽')

# 使用extend方法添加多个魔法物品
new_items = ['魔法书', '魔法水晶']
magic_list.extend(new_items)

# 展示魔法清单
print("魔法清单:", magic_list)  # 输出: ['魔法帽', '魔法棒', '魔法书', '魔法水晶']
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15

2.1.2 从魔法清单中移除魔法物品(列表的元素删除)

示例:如果你不需要某个魔法物品了,你可以从魔法清单中删除它。

# 移除魔法清单中的第一个魔法棒(如果有多个,只移除第一个)
magic_list.remove('魔法棒')

# 使用索引删除特定的魔法物品
if '魔法水晶' in magic_list:
    del magic_list[magic_list.index('魔法水晶')]

# 展示修改后的魔法清单
print("修改后的魔法清单:", magic_list)  # 输出可能是: ['魔法帽', '魔法书'](假设魔法水晶被移除了)

# 清空整个魔法清单
magic_list.clear()
print("清空后的魔法清单:", magic_list)  # 输出: []
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13

2.1.3 更新魔法清单中的魔法物品(修改列表元素)

示例:如果你有一个新的魔法棒,想要替换旧的魔法棒,你可以这样做:

# 假设我们知道魔法棒在列表中的位置
magic_list[1] = '新魔法棒'

# 展示更新后的魔法清单
print("更新后的魔法清单:", magic_list)  # 假设列表是['魔法帽', '旧魔法棒', '魔法书'],输出则是['魔法帽', '新魔法棒', '魔法书']
  • 1
  • 2
  • 3
  • 4
  • 5

2.1.4 查找魔法清单中的魔法物品(查找列表中的元素)

示例:你可以很容易地检查某个魔法物品是否在魔法清单中。

# 检查魔法帽是否在清单中
if '魔法帽' in magic_list:
    print("魔法帽在清单中!")
else:
    print("魔法帽不在清单中.")

# 查找并获取魔法书的索引(如果不存在,将抛出错误)
book_index = magic_list.index('魔法书')
print("魔法书的索引是:", book_index)

# 查找元素但不引发错误(如果元素不存在,返回-1)
crystal_index = magic_list.index('魔法水晶') if '魔法水晶' in magic_list else -1
print("魔法水晶的索引是:", crystal_index)  # 输出: -1(假设魔法水晶不在列表中)
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13

2.1.5 按字母顺序整理魔法清单(对魔法物品进行排序)

示例:你可以将魔法清单中的物品按字母顺序排序。

# 假设魔法清单包含可以排序的字符串
magic_list = ['魔法书', '魔法水晶', '魔法帽', '魔法棒']

# 对魔法清单进行排序(默认是字母升序顺序)
magic_list.sort()

# 展示排序后的魔法清单
print("排序后的魔法清单:", magic_list)  # 输出: ['魔法棒', '魔法帽', '魔法书', '魔法水晶']

# 如果你想要反向排序(从Z到A)
magic_list.sort(reverse=True)
print("反向排序后的魔法清单:", magic_list)  # 输出: ['魔法水晶', '魔法书', '魔法帽', '魔法棒']
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12

希望这些示例能帮助你更好地理解Python列表的操作,并使学习变得更加有趣!

2.2 栈(Stack) - 魔法书堆

想象一下你有一堆魔法书,每次只能取最上面的一本或者往上面放一本新的。这就是栈的工作原理,也被称为“后进先出”(LIFO)。

我们通常可以使用列表(List)或者collections.deque来实现。但需要注意的是,栈的核心操作是push(入栈,相当于增)和pop(出栈,相当于删),并不直接支持“改”和“查”的栈内元素操作,以及“排序”操作(因为排序会改变栈的后进先出特性)。

栈的核心操作

  • 压入(Push): 将一个元素添加到栈的顶端。
  • 弹出(Pop): 移除并返回栈顶元素。
  • 查看栈顶元素(Peek/Top): 返回栈顶元素而不移除它。
  • 检查是否为空(IsEmpty): 检查栈是否为空。
  • 获取栈的大小(Size): 获取栈中元素的数量。

2.2.1 向魔法栈中添加魔法物品(栈的压入 push)

# 使用列表模拟栈
magic_stack = []

# 添加元素到栈顶(增)
def push(stack, item):
    stack.append(item)

push(magic_stack, '魔法棒')
push(magic_stack, '魔法帽')
print("添加后的栈:", magic_stack)  # 输出: ['魔法棒', '魔法帽']
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10

2.2.2 从魔法栈中移除并返回栈顶的元素(栈的弹出 pop)

# 移除栈顶元素(删)
def pop(stack):
    if not stack:
        return None
    return stack.pop()

top_item = pop(magic_stack)
print("移除的元素:", top_item)  # 输出: 魔法帽
print("移除后的栈:", magic_stack)  # 输出: ['魔法棒']
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9

2.2.2 检查魔法栈中物品是否为空

# 检查栈是否为空
def is_empty(stack):
    return len(stack) == 0
 
# 示例
print(is_empty(stack))  # 输出: True(因为栈是空的)
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6

2.2.3 获取魔法栈的大小

# 获取栈的大小
def get_size(stack):
    return len(stack)
 
# 示例
print(get_size(stack))  # 输出: 0(因为栈是空的)

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7

在栈的实际应用中,我们通常只关注push(增)和pop(删)操作,因为它们体现了栈的后进先出特性。而“改”和“排序”操作并不常见,因为它们会破坏栈的这一特性。如果你需要频繁地修改元素或排序,那么可能列表或其他数据结构更适合你的需求。

2.3 队列(Queue) - 魔法任务队列

想象一下你有一个魔法任务队列,第一个接受任务的人必须先完成任务,然后才能轮到下一个人。这就是队列的工作原理,也被称为“先进先出”(FIFO)。

from collections import deque

# 使用deque模拟一个魔法任务队列
magic_tasks = deque()

# 往任务队列中添加任务
magic_tasks.append('炼制魔法药水')
magic_tasks.append('制作魔法面包')

# 查看任务队列的第一个任务
print(magic_tasks[0])  # 输出:炼制魔法药水

# 完成并移除任务队列中的第一个任务
completed_task = magic_tasks.popleft()
print("完成的任务是:", completed_task)  # 输出:完成的任务是:炼制魔法药水
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15

2.4 链表

链表(Linked List)是一种常见的数据结构,它包含一系列按照顺序排列的元素,但每个元素并不存储在连续的内存空间中,而是存储在各自独立的空间中,并通过指针(在Python中通常是引用)连接在一起。链表中的每个元素被称为节点(Node),节点包含两个部分:数据部分和指向下一个节点的指针部分。

Python中的链表不像其他语言那样有内置的链表类型,但我们可以使用类(Class)来模拟链表。

链表常用操作

  • 插入节点:在链表的开头、末尾或指定位置插入一个新的节点。
  • 删除节点:删除链表中的指定节点或头节点、尾节点。
  • 遍历链表:从头节点开始,访问链表中的每个节点,直到到达尾节点的下一个节点(通常是None)。
  • 查找节点:根据节点的值在链表中查找节点。
  • 修改节点值:修改链表中指定节点的值。

2.4.1 单链表

在这里插入图片描述
示例:创建一个简单的单链表,并实现了插入、删除和遍历链表的基本操作。

# 定义节点
# item:数据
# next:下一个节点
class Node:
    def __init__(self, data=None):
        self.data = data
        self.next = None


#定义单链表
class LinkedList:
    def __init__(self):
        self.head = None

    # 插入节点到链表末尾
    def append(self, data):
        if not self.head:
            self.head = Node(data)
        else:
            cur = self.head
            while cur.next:
                cur = cur.next
            cur.next = Node(data)

    # 插入节点到链表开头
    def insert_at_beginning(self, data):
        new_node = Node(data)
        new_node.next = self.head
        self.head = new_node

    # 删除节点
    def delete(self, key):
        # 处理空链表和头节点
        if self.head is None:
            return
        if self.head.data == key:
            self.head = self.head.next
            return

        cur = self.head
        while cur.next:
            if cur.next.data == key:
                cur.next = cur.next.next
                return
            cur = cur.next

        # 如果没找到要删除的节点
        print(f"Key {key} not found in linked list")

    # 遍历链表并打印每个节点的值
    def print_list(self):
        cur = self.head
        while cur:
            print(cur.data, end=" ")
            cur = cur.next
        print()


# 使用示例
linked_list = LinkedList()
linked_list.append(1)
linked_list.append(2)
linked_list.append(3)
linked_list.insert_at_beginning(0)
linked_list.print_list()  # 输出: 0 1 2 3
linked_list.delete(2)
linked_list.print_list()  # 输出: 0 1 3
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67

2.4.2 双链表

双链表(Doubly Linked List)是一种特殊的链表,其中每个节点除了包含数据部分和指向下一个节点的指针(通常称为next)外,还包含一个指向前一个节点的指针(通常称为prev)。因此,双链表中的每个节点都可以直接访问它的前一个和后一个节点。这种结构使得双链表在某些操作(如插入和删除)上比单链表更加灵活。

双链表实现示例

# 定义节点
# item:数据
# next:下一个节点
# pre:上一个节点
class Node:
    def __init__(self, data=None):
        self.data = data
        self.next = None
        self.prev = None

#定义双链表
class DoublyLinkedList:
    def __init__(self):
        self.head = None
        self.tail = None

    # 插入节点到链表末尾
    def append(self, data):
        new_node = Node(data)
        if self.tail is None:  # 如果是空链表
            self.head = new_node
            self.tail = new_node
        else:
            new_node.prev = self.tail
            self.tail.next = new_node
            self.tail = new_node

    # 插入节点到链表开头
    def insert_at_beginning(self, data):
        new_node = Node(data)
        if self.head is None:  # 如果是空链表
            self.head = new_node
            self.tail = new_node
        else:
            new_node.next = self.head
            self.head.prev = new_node
            self.head = new_node

    # 删除节点
    def delete(self, key):
        cur = self.head
        while cur:
            if cur.data == key:
                if cur.prev is None:  # 如果是头节点
                    self.head = cur.next
                    if self.head:
                        self.head.prev = None
                elif cur.next is None:  # 如果是尾节点
                    self.tail = cur.prev
                    self.tail.next = None
                else:  # 如果是中间节点
                    cur.prev.next = cur.next
                    cur.next.prev = cur.prev
                return
            cur = cur.next
        print(f"Key {key} not found in doubly linked list")

    # 遍历链表并打印每个节点的值
    def print_list(self):
        cur = self.head
        while cur:
            print(cur.data, end=" ")
            cur = cur.next
        print()


# 使用示例
dll = DoublyLinkedList()
dll.append(1)
dll.append(2)
dll.append(3)
dll.insert_at_beginning(0)
dll.print_list()  # 输出: 0 1 2 3
dll.delete(2)
dll.print_list()  # 输出: 0 1 3
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75

在这个示例中,我们定义了一个Node类来表示双链表中的节点,每个节点都有一个数据部分和两个指针(nextprev)。我们还定义了一个DoublyLinkedList类来表示双链表本身,并实现了插入、删除和遍历等常用操作。

2.4.3 循环链表

循环链表(Circular Linked List)是一种特殊的链表结构,其中最后一个节点的next指针指向头节点,从而使链表形成一个环。这种结构使得循环链表在某些场景(如环形缓冲区、约瑟夫环问题)中非常有用。循环链表和单链表的主要区别在于循环链表的最后一个节点指向头节点,而单链表的最后一个节点指向None

不包含了删除节点的循环链表示例

# 定义节点
# item:数据
# next:下一个节点
class Node:
    def __init__(self, data=None):
        self.data = data
        self.next = None

# 定义循环链表
class CircularLinkedList:
    def __init__(self):
        self.head = None

    # 插入节点到链表末尾
    def append(self, data):
        new_node = Node(data)
        if self.head is None:
            new_node.next = new_node  # 形成闭环
            self.head = new_node
        else:
            cur = self.head
            while cur.next != self.head:  # 找到最后一个节点
                cur = cur.next
            cur.next = new_node  # 将新节点添加到末尾
            new_node.next = self.head  # 新节点的next指向头节点,形成闭环

    # 插入节点到链表开头
    def insert_at_beginning(self, data):
        new_node = Node(data)
        if self.head is None:
            new_node.next = new_node  # 形成闭环
            self.head = new_node
        else:
            cur = self.head
            while cur.next != self.head:  # 找到最后一个节点
                cur = cur.next
            new_node.next = self.head  # 新节点的next指向头节点
            cur.next = new_node  # 最后一个节点的next指向新节点
            self.head = new_node  # 新节点成为头节点

    # 删除节点
    def delete(self, key):
        if self.head is None:
            return

        if self.head.data == key:
            if self.head.next == self.head:  # 如果链表只有一个节点
                self.head = None
            else:
                cur = self.head
                while cur.next != self.head:  # 找到倒数第二个节点
                    cur = cur.next
                cur.next = self.head.next  # 跳过头节点
                self.head = self.head.next  # 更新头节点
                if self.head:
                    self.head.prev = None  # 假设我们也有prev指针(在这个简单示例中没有,但可以作为扩展)
            return

        cur = self.head
        while cur.next != self.head:  # 遍历链表寻找节点
            if cur.next.data == key:
                cur.next = cur.next.next  # 跳过要删除的节点
                return
            cur = cur.next

        # 如果没有找到要删除的节点
        print(f"Key {key} not found in circular linked list")

    # 遍历链表并打印每个节点的值
    def print_list(self):
        if self.head is None:
            return
        cur = self.head
        while True:
            print(cur.data, end=" ")
            cur = cur.next
            if cur == self.head:  # 回到头节点,说明遍历完成
                break
        print()


# 使用示例
cll = CircularLinkedList()
cll.append(1)
cll.append(2)
cll.append(3)
cll.insert_at_beginning(0)
cll.print_list()  # 输出: 0 1 2 3
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77
  • 78
  • 79
  • 80
  • 81
  • 82
  • 83
  • 84
  • 85
  • 86
  • 87
  • 88

包含了删除节点的循环链表示例

class Node:
    def __init__(self, data=None):
        self.data = data
        self.next = None


class CircularLinkedList:
    def __init__(self):
        self.head = None

    # 插入节点到链表末尾
    def append(self, data):
        new_node = Node(data)
        if self.head is None:
            new_node.next = new_node
            self.head = new_node
        else:
            cur = self.head
            while cur.next != self.head:
                cur = cur.next
            cur.next = new_node
            new_node.next = self.head

    # 删除节点
    def delete(self, key):
        if self.head is None:
            print("List is empty")
            return

        if self.head.data == key:
            if self.head.next == self.head:  # 只有一个节点
                self.head = None
            else:
                temp = self.head
                while temp.next != self.head:  # 找到最后一个节点
                    temp = temp.next
                temp.next = self.head.next  # 跳过头节点
                self.head = self.head.next
        else:
            cur = self.head
            while cur.next != self.head:
                if cur.next.data == key:
                    cur.next = cur.next.next
                    break
                cur = cur.next
            else:  # 如果循环结束没有找到节点,说明不存在该节点
                print(f"Key {key} not found in circular linked list")

    # 遍历链表并打印每个节点的值
    def print_list(self):
        if self.head is None:
            print("List is empty")
            return
        cur = self.head
        while True:
            print(cur.data, end=" ")
            cur = cur.next
            if cur == self.head:
                break
        print()


# 使用示例
cll = CircularLinkedList()
cll.append(1)
cll.append(2)
cll.append(3)
cll.print_list()  # 输出: 1 2 3 

cll.delete(2)
cll.print_list()  # 输出: 1 3 

cll.delete(1)
cll.print_list()  # 输出: 3 

cll.delete(3)
cll.print_list()  # 输出: List is empty
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77

这个示例中,我们实现了循环链表的append(在链表末尾添加节点)、delete(删除指定值的节点)和print_list(遍历并打印链表的所有节点)方法。delete方法现在可以正确地处理删除头节点和链表中任意位置节点的情况。在删除操作中,我们首先检查要删除的节点是否是头节点,然后相应地调整链表结构。如果删除的不是头节点,我们遍历链表找到要删除的节点,并更新前一个节点的next指针来跳过要删除的节点。

2.5 元组(Tuple) - 魔法封印

元组就像是一个被魔法封印的宝藏箱,一旦你把宝藏放进去,就不能再更改了。但是,你可以随时查看里面的宝藏,也可以调整宝藏放置的顺序。

2.5.1 创建并查询一个魔法封印的宝藏箱

# 创建一个魔法封印的宝藏箱(元组)
sealed_treasure = ('金币', '宝石', '魔法水晶')

# 查看宝藏箱里的第一个宝藏
print(sealed_treasure[0])  # 输出:金币

# 尝试修改宝藏箱里的宝藏(会报错哦,因为被魔法封印了)
# sealed_treasure[1] = '更大的宝石'  # 这会抛出TypeError异常
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8

2.5.2 对魔法物品进行排序

# 创建一个包含魔法物品的元组
magic_tuple = ('魔法棒', '魔法帽', '魔法书', '魔法水晶')

# 将元组转换为列表并排序
magic_list = list(magic_tuple)
magic_list.sort()
 
# 如果需要,可以将排序后的列表转换回元组
sorted_tuple = tuple(magic_list)
 
# 展示排序后的元组
print("排序后的元组:", sorted_tuple)  # 输出: ('魔法棒', '魔法帽', '魔法书', '魔法水晶')

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13

3. 非线性数据结构的魔法世界

Python 中支持多种非线性数据结构,这些结构在数据处理和算法实现中非常有用。以下是一些常见的非线性数据结构及其简要说明:

3.1 树(Tree)

3.1.1 树是什么?

想象一下,你有一个文件夹(我们叫它“根文件夹”),里面有一些子文件夹和文件。如果你打开其中一个子文件夹,你会发现它里面又有一些其他的子文件夹和文件,这样一直下去,就形成了一个层级结构。这就是树形结构的一个简单例子!

在计算机科学中,**树(Tree)**就是这样一种数据结构,它有一个根节点(就像我们的“根文件夹”),然后根节点下可以有多个子节点(子文件夹或文件),每个子节点又可以有自己的子节点,依此类推。这种层级关系就构成了树。

3.1.2 二叉树(Binary Tree)

二叉树是一种特殊的树形结构,其中每个节点最多有两个子节点,通常称为左子节点和右子节点。

常见的二叉树操作包括插入、删除、查找、遍历(前序、中序、后序和层序遍历)等。

示例:简单的二叉树(每个节点最多有两个子节点的树)

class TreeNode:
	# 二叉树节点
	# value:二叉树的值
	# left:左节点
	# right:右节点
    def __init__(self, value):
        self.value = value
        self.left = None
        self.right = None

# 创建一个简单的二叉树
#     1
#    / \
#   2   3
#  / \
# 4   5
root = TreeNode(1)
root.left = TreeNode(2)
root.right = TreeNode(3)
root.left.left = TreeNode(4)
root.left.right = TreeNode(5)

# 遍历二叉树(前序遍历)
def preorder_traversal(node):
    if node is not None:
        print(node.value)
        preorder_traversal(node.left)
        preorder_traversal(node.right)

# 执行前序遍历
preorder_traversal(root)  # 输出:1 2 4 5 3
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31

在上面的示例中,我们首先定义了一个TreeNode类来表示树的节点。然后,我们用这个类创建了一个简单的二叉树。最后,我们定义了一个前序遍历的函数来遍历这个二叉树,并输出每个节点的值。

3.2 图(Graph)

想象一下,假设有一个班级的学生,每个学生都喜欢一些编程语言。有些学生可能喜欢Python,有些学生可能喜欢Python和Java,还有些学生喜欢其他不同的语言。现在,我们用一个图形的方式来展示这种关系。

图形表示

  • 节点(Node):每个学生是一个节点,节点上写着学生的名字。
  • 边(Edge):如果学生喜欢某种语言,我们就从该学生的节点引出一条边到代表该语言的节点。

但是,在这个例子中,为了简化,我们不会将语言作为真正的节点,而是作为学生节点的属性或标签。这样,图就只包含学生节点和他们之间的(虚拟)连接,这些连接表示他们有共同喜欢的语言。

示例

首先,我们定义一个简单的类来表示学生和他们喜欢的语言。

class Student:
    def __init__(self, name, languages):
        self.name = name
        self.languages = languages

# 示例数据
students = [
    Student("Alice", ["Python", "Java"]),
    Student("Bob", ["Python", "JavaScript"]),
    Student("Charlie", ["C++", "Java"]),
    Student("David", ["JavaScript", "Ruby"])
]

# 打印学生的信息
for student in students:
    print(f"{student.name} likes {student.languages}")

# 注意:这里我们没有构建一个真正的图形结构,但你可以想象它
# 在图形中,每个Student对象是一个节点,但边是隐式的(通过他们喜欢的语言)
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19

如果你真的想构建一个图形结构,并考虑语言作为节点,那么你可能需要使用更复杂的图数据结构,比如邻接列表或邻接矩阵,或者使用像networkx这样的图库。但上面的简单示例足以向学生介绍图的基本概念。

使用networkx库构建图

如果你想要一个更完整的图形表示,并希望使用图算法,那么可以使用networkx库。

import networkx as nx

# 创建一个空的图
G = nx.Graph()

# 添加学生节点(这里假设学生的ID是唯一的)
student_ids = ["Alice", "Bob", "Charlie", "David"]
for student_id in student_ids:
    G.add_node(student_id, bipartite=0)  # 假设我们将学生和语言分为两个不同的集合,这里设置bipartite属性为0表示学生节点

# 假设我们也有一个语言节点的列表(但在这个例子中,我们不真正添加它们作为节点)
# language_nodes = ["Python", "Java", ...]

# 添加边(这里我们假设边是无向的,并且只是表示学生喜欢某种语言)
for student in students:
    for language in student.languages:
        # 注意:在这个简单的示例中,我们不添加语言节点,但你可以在需要时添加它们
        # 并使用多部分图(bipartite graph)来表示学生和语言之间的关系
        G.add_edge(student.name, language, weight=1)  # weight可以表示喜欢的程度,但这里我们只是简单设为1

# 可视化图(需要安装matplotlib库)
import matplotlib.pyplot as plt
nx.draw(G, with_labels=True)
plt.show()

# 注意:上面的可视化代码不会显示语言节点,因为它们在这个简单示例中没有被添加为图的一部分
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26

请记住,上面的networkx示例是为了教学目的而简化的。在真实的场景中,你可能会使用更复杂的图形来表示学生和语言之间的关系,并可能包括其他类型的节点和边来表示更多的信息。

3.3 堆(Heap)

想象一下你有一堆乱糟糟的书,你想按照某种顺序(比如从厚到薄或从A到Z)重新排列它们。但是,你每次只能从堆顶取一本书或者往堆顶放一本书。那么,有没有一种方法能够让你在取书或放书的时候,都能保持这个堆是有序的呢?

在计算机科学中,**堆(Heap)**就是这样一种数据结构,它通常被实现为一个完全二叉树,并且满足堆属性:父节点的值总是大于或等于(最大堆)或小于或等于(最小堆)其子节点的值。堆的一个重要特性是,我们可以在O(log n)的时间复杂度内完成插入和删除操作,同时保持堆的有序性。

示例:

在Python中,我们可以使用内置的heapq模块来操作堆。这个模块实现了一个最小堆,但是通过一些小技巧,我们也可以实现最大堆。

import heapq

# 使用heapq实现最小堆
min_heap = []
heapq.heappush(min_heap, 3)
heapq.heappush(min_heap, 1)
heapq.heappush(min_heap, 4)
print(heapq.heappop(min_heap))  # 输出:1,因为1是最小的

# 使用负值实现最大堆(一种常见的技巧)
max_heap = []
heapq.heappush(max_heap, -3)  # 实际上我们存储了负值
heapq.heappush(max_heap, -1)
heapq.heappush(max_heap, -4)
print(-heapq.heappop(max_heap))  # 输出:4,因为-4在负值中是最大的

# 一个更复杂的示例:使用最大堆实现优先队列
tasks = [('做作业', 1), ('打扫卫生', 2), ('吃饭', 3)]
priority_queue = [(-priority, desc) for desc, priority in tasks]  # 我们存储(-priority, desc)以模拟最大堆
heapq.heapify(priority_queue)  # 将列表转换为堆

while priority_queue:
    next_task = heapq.heappop(priority_queue)[1]  # 弹出并打印优先级最高的任务
    print(f"下一步任务:{next_task}")
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24

在上面的示例中,我们首先展示了如何使用heapq模块来操作最小堆。然后,我们展示了如何使用负值来模拟最大堆。最后,我们给出了一个使用最大堆来实现优先队列的示例,其中每个任务都有一个与之关联的优先级。

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/爱喝兽奶帝天荒/article/detail/833989
推荐阅读
相关标签
  

闽ICP备14008679号