Bootstrap

数据结构

目录

1.栈(stack)

栈的定义

栈的常见操作

栈的实现

2.链表

链表的定义

链表的常见操作

插入

遍历

删除

单链表的实现

3.队列

普通队列

双端队列

优先队列

4.树

树的定义

树的术语

二叉树

二叉树的定义

二叉树特性

特殊的二叉树

满二叉树(Full Binary Tree)

完全二叉树(Complete Binary Tree)

二叉树的存储

二叉查找树

创建二叉查找树节点

创建二叉查找树类

插入节点

查找节点

删除节点

中序遍历

前序遍历

后序遍历


1.栈(stack)

栈的定义

一种运算受限的线性表,遵循后进先出(Last In First Out,LIFO)原则的数据结构。

  • LIFO(last in first out)表示就是后进入的元素, 第一个弹出栈空间. 类似于自动餐托盘, 最后放上的托盘, 往往先把拿出去使用.

  • 其限制是仅允许在表的一端进行插入和删除运算。这一端被称为栈顶,相对地,把另一端称为栈底。

  • 向一个栈插入新元素又称作进栈、入栈或压栈,它是把新元素放到栈顶元素的上面,使之成为新的栈顶元素;

  • 从一个栈删除元素又称作出栈或退栈,它是把栈顶元素删除掉,使其相邻的元素成为新的栈顶元素。

栈的常见操作

  • push(element): 添加一个新元素到栈顶位置.(入栈)

  • pop():移除栈顶的元素,同时返回被移除的元素。(出栈)

  • peek():返回栈顶的元素,不对栈做任何修改

  • isEmpty():如果栈里没有任何元素就返回true,否则返回false。

  • clear():移除栈里的所有元素。

  • size():返回栈里的元素个数。这个方法和数组的length属性很类似。

栈的实现

class Stack():
    def __init__(self, size):
        self.size = size
        self.items = []

    def isFull(self):
        return self.size == len(self.items)

    def isEmpty(self):
        return len(self.items) == 0

    # 压栈
    def push(self, element):
        if self.isFull():
            raise Exception('stack is full!')

        self.items.append(element)

    # 出栈
    def pop(self):
        if self.isEmpty():
            raise Exception('stack is empty!')

        return self.items.pop()

    # 查看栈顶的元素
    def peek(self):
        if self.isEmpty():
            raise Exception('stack is empty')
        return self.items[-1]

    # 清空栈
    def clear(self):
        self.items.clear()


if __name__ == '__main__':
    # 初始化stack1
    stack1 = Stack(5)

    print('测试:isEmpty()')
    print(stack1.isEmpty())
    print()

    print('压栈')
    for i in range(3):
        stack1.push(i)
        stack1.peek()
    print()

    print('查看栈顶元素')
    print(stack1.peek())
    print()

    print('出栈')
    stack1.pop()
    print(stack1.peek())
    print()

    print('测试:isFull()')
    stack1.push(98)
    stack1.push(99)
    stack1.push(100)
    print(stack1.isFull())
    print()

2.链表

链表的定义

一条相互链接的数据节点表。每个节点由两部分组成:数据和指向下一个节点的指针。

链表的常见操作

插入

尾部插入

从头结点开始逐个遍历链表,直到找到next=null,表示为最后一个节点,再将最后节点的next指向新增节点。

头部插入

如果头节点的next=null表示链表为空,直接将头节点的next指向新增节点

如果头节点的next!=null,表示头节点后已存在后续节点,需要将新增节点插入到头节点和后续节点中间。

遍历

从头结点开始,通过next遍历,直到next=null

删除

单链表的实现

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


class LinkList:
    def __init__(self):
        head = Node()
        self.head = head

    def isEmpty(self):
        return self.head.next == None

    def append(self, data):
        new_node = Node(data)
        if self.isEmpty():
            self.head.next = new_node
        else:
            node = self.head.next
            while node.next is not None:
                node = node.next
            node.next = new_node

    def prepend(self, data):
        new_node = Node(data)
        if self.isEmpty():
            self.head.next = new_node
        else:
            new_node.next = self.head.next
            self.head.next = new_node

    def remove(self, data):
        if self.isEmpty():
            raise Exception('Linked List is empty!')
        else:
            node = self.head
            while node.next.data != data:
                node = node.next
            node.next = node.next.next

    def display(self):
        if self.isEmpty():
            print('链表为空')
            return
        node = self.head.next
        while True:
            print(node.data)
            if node.next is None:
                break
            node = node.next


if __name__ == '__main__':
    linklist1 = LinkList()

    print('测试:isEmpty()')
    print(linklist1.isEmpty(), '\n')

    print('测试:append()和display()')
    linklist1.append(99)
    linklist1.display()
    print()

    print('测试:prepend()')
    linklist1.prepend(100)
    linklist1.prepend(199)
    linklist1.display()
    print()

    print('测试:remove()')
    linklist1.append(100)
    linklist1.remove(100)
    linklist1.display()

3.队列

队列(Queue),它是一种运算受限的线性表,先进先出(FIFO First In First Out)

  • 队列是一种受限的线性结构

  • 受限之处在于它只允许在表的前端(front)进行删除操作,而在表的后端(rear)进行插入操作

普通队列

queue.Queue 是 Python 标准库 queue 模块中的一个类,适用于多线程环境。它实现了线程安全的 FIFO(先进先出)队列。

import queue

que = queue.Queue()

# 入队
for i in range(5):
    que.put(i)

print("队列长度:")
print(que.qsize())

print("出队")
for i in range(5):
    print(que.get())

双端队列

双端队列(Deque,Double-Ended Queue)是一种具有队列和栈性质的数据结构,它允许我们在两端进行元素的添加(push)和移除(pop)操作。在Python中,双端队列可以通过collections模块中的deque类来实现。

deque是一个双端队列的实现,它提供了在两端快速添加和移除元素的能力。

from collections import deque

q = deque()

# 右端存
q.append(1)
q.append(2)
# 左端存
q.appendleft(3)
q.appendleft(4)

# 右端取
print(q.pop())
# 左端存
print(q.popleft())

当结合使用appendleft()和popleft()时,这两个操作正好模拟了栈的“压栈”和“弹栈”行为。append()和pop()结合使用同理。

优先队列

优先队列(Priority Queue)是一种特殊的队列,其中的元素按照优先级进行排序。优先级最高的元素总是最先出队。Python 标准库中提供了 queue.PriorityQueue 和 heapq 模块来实现优先队列。

queue.PriorityQueue是 Python 标准库 queue 模块中的一个类,适用于多线程环境。它实现了线程安全的优先队列。

import queue

q = queue.PriorityQueue()

q.put((3, 'item2'))
q.put((1, 'item1'))
q.put((2, 'item3'))
print(q.get())
print(q.get())
print(q.get())

4.树

树的定义

n(n≥0)个结点构成的有限集合。

注意:

  • 当n=0时,称为空树;

  • 对于任一棵非空树(n> 0),它具备以下性质:

  • 树中有一个称为“根(Root)”的特殊结点,用 root 表示;

  • 其余结点可分为m(m>0)个互不相交的有限集T1,T2,... ,Tm,其中每个集合本身又是一棵树,称为原来树的“子树(SubTree)”

  • 子树之间不可以相交

  • 除了根结点外,每个结点有且仅有一个父结点;

  • 一棵N个结点的树有N-1条边。

树的术语

  • 1.结点的度(Degree):结点的子树个数.

  • 2.树的度:树的所有结点中最大的度数. (树的度通常为结点的个数N-1)

  • 3.叶子结点(Leaf):度为0的结点. 

  • 4.父结点(Parent):有子树的结点是其子树结点的父结点

  • 5.子结点(Child):若A结点是B结点的父结点,则称B结点是A结点的子结点或孩子结点。

  • 6.兄弟结点(Sibling):具有同一父结点的各结点彼此是兄弟结点。

  • 7.路径和路径长度:从结点n1到nk的路径为一个结点序列n1 , n2,… , nk, ni是 ni+1的父结点。路径所包含边的个数为路径的长度。

  • 8.结点的层次(Level):规定根结点在1层,其它任一结点的层数是其父结点的层数加1。

  • 9.树的深度(Depth):树中所有结点中的最大层次是这棵树的深度。

二叉树

二叉树的定义

  • 二叉树可以为空, 也就是没有结点.

  • 若不为空,则它是由根结点和称为其左子树TL和右子树TR的两个不相交的二叉树组成。

二叉树的五种形态:

  • 注意c和d是不同的二叉树, 因为二叉树是有左右之分的

二叉树特性

  • 二叉树有几个比较重要的特性, 在笔试题中比较常见:

    • 一个二叉树第 i 层的最大结点数为:2^(i-1), i >= 1;

    • 深度为k的二叉树有最大结点总数为: 2^k - 1, k >= 1;

    • 对任何非空二叉树 T,若n0表示叶结点的个数、n2是度为2的非叶结点个数,那么两者满足关系n0 = n2 + 1。

特殊的二叉树

满二叉树(Full Binary Tree)
  • 在二叉树中, 除了最下一层的叶结点外, 每层节点都有2个子结点, 就构成了满二叉树.

完全二叉树(Complete Binary Tree)
  • 除二叉树最后一层外, 其他各层的节点数都达到最大个数.

  • 且最后一层从左向右的叶结点连续存在, 只缺右侧若干节点.

  • 满二叉树是特殊的完全二叉树.

  • 下面不是完全二叉树, 因为D节点还没有右结点, 但是E节点就有了左右节点.

 

二叉树的存储

二叉树最常见的方式还是使用链表存储.

每个结点封装成一个Node, Node中包含存储的数据, 左结点的引用, 右结点的引用.

二叉查找树

二叉查找树(Binary Search Tree, BST)是一种特殊的二叉树,性质如下:

  • 每个节点都有一个键值(key)。
  • 对于每个节点,其左子树中的所有节点的键值都小于该节点的键值。
  • 对于每个节点,其右子树中的所有节点的键值都大于该节点的键值。
  • 左子树和右子树也分别是二叉查找树。
  • 二叉查找树不允许出现键值相等的结点。

创建二叉查找树节点
class TreeNode():
    def __init__(self, key):
        self.key = key
        self.left = None
        self.right = None
  • key: 节点的键值。

  • left: 指向左子节点的指针。

  • right: 指向右子节点的指针。

创建二叉查找树类
class BinarySearchTree():
    def __init__(self):
        self.root = None
  • root: 指向二叉搜索树的根节点。初始时为 None。

插入节点

操作的步骤:

(1)如果树为空:直接将新节点作为根节点

(2)如果树不为空:

        ①从根节点开始,根据新节点的键值与当前节点的键值的比较结果,决定向左子树还是右子树移动。

        ②如果新节点的键值小于当前节点的键值,如果当前节点没有左子树,则将新节点插入到当前节点的左子树,否则向左子树移动。

        ③如果新节点的键值大于当前节点的键值,如果当前节点没有右子树,则将新节点插入到当前节点的右子树,否则向右子树移动。

        ④重复上述步骤,直到找到一个空位置,将新节点插入到该位置。

    def insert(self, key):
        if self.root is None:
            self.root = TreeNode(key)
        else:
            self.__insert(self.root, key)

    def __insert(self, node, key):
        if key < node.key:
            if node.left is None:
                node.left = TreeNode(key)
            else:
                self.__insert(node.left, key)
        elif key > node.key:
            if node.right is None:
                node.right = TreeNode(key)
            else:
                self.__insert(node.right, key)

说明:

  • insert(key): 公开的插入方法。如果树为空,则创建一个新节点作为根节点;否则,调用 _insert 方法进行递归插入。

  • _insert(node, key): 递归插入方法。根据键值的大小,递归地在左子树或右子树中插入新节点。

查找节点
    def search(self, key):
        return self.__search(self.root, key)

    def __search(self, node, key):
        if node is None or node.key==key:
            return node
        if key < node.key:
            return self.__search(node.left, key)
        return self.__search(node.right, key)

说明:

通过递归调用,查找key是否在二叉树中,若在返回其对象地址,若不在返回None

删除节点

操作步骤:

(1)递归查找待删除节点

  • 如果待删除节点的键值小于当前节点的键值,递归地在左子树中查找并删除。

  • 如果待删除节点的键值大于当前节点的键值,递归地在右子树中查找并删除。

(2)找到待删除节点

删除操作的步骤可以分为以下几种情况:

        ①待删除节点是叶子节点:直接删除该节点。

        ②待删除节点只有一个子节点:用其子节点替换该节点。

        ③待删除节点有两个子节点:

  •                 找到右子树中的最小节点(即后继节点)。
  •                 用后继节点的键值替换待删除节点的键值。
  •                 删除后继节点(后继节点要么是叶子节点,要么只有一个右子节点)。
    def delete(self, key):
        self.root = self.__delete(self.root, key)

    def __delete(self, node, key):
        # 终止条件
        if node is None:
            return node

        elif key < node.key:
            node.left = self.__delete(node.left, key)
        elif key > node.key:
            node.right = self.__delete(node.right, key)

        else:
            # ①节点为叶子节点
            if node.left is None and node.right is None:
                return None#删除操作

            # ②节点有一个子节点
            elif node.left is None:
                return node.right
            elif node.right is None:
                return node.left

            # ③节点有两个子节点
            else:
                temp = self.__min_value_node(node.right)
                node.key = temp.key
                node.right = self.__delete(node.right, temp.key)

        return node

    def __min_value_node(self, node):
        current = node
        while current.left is not None:
            current = current.left
        return current
中序遍历

先遍历左子树,然后访问当前节点,最后遍历右子树

  # 中序遍历
    def inorder_traversal(self):
        result = []
        self.__inorder_traversal(self.root, result)
        return result

    def __inorder_traversal(self, node, result):
        if node:
            self.__inorder_traversal(node.left, result)
            result.append(node.key)
            self.__inorder_traversal(node.right, result)
前序遍历

先访问根节点,然后遍历左子树,最后遍历右子树

# 前序遍历
    def preorder_traversal(self):
        result = []
        if self.root is None:
            return None
        self.__preorder_traversal(self.root, result)
        return result

    def __preorder_traversal(self, node, result):
        if node is None:
            return None
        result.append(node.key)
        self.__preorder_traversal(node.left, result)
        self.__preorder_traversal(node.right, result)
后序遍历

先遍历左子树、然后遍历右子树、最后访问根节点

    # 后序遍历
    def postorder_traversal(self):
        result = []
        if self.root is None:
            return None
        self.__postorder_traversal(self.root, result)
        return result

    def __postorder_traversal(self, node, result):
        if node is None:
            return None
        self.__postorder_traversal(node.left, result)
        self.__postorder_traversal(node.right, result)
        result.append(node.key)
;