常见的数据结构组合与实现方法

作者:禅与计算机程序设计艺术

数据结构(Data Structure)是指用来存储、组织和处理数据的集合。它是一个抽象的计算机科学概念,是一种对特定类型信息进行有效地组织、管理、访问及操纵的方式。常见的数据结构有栈、队列、数组、链表、哈希表、树、堆、图等。本文主要介绍了常见的数据结构中最常用的一些组合方式,并结合相应的代码实现。

2.基本概念术语说明

1.堆(Heap):堆是一个可以被看作是一棵完全二叉树(所有层次从左到右)的树结构。这种数据结构的特点是根节点的值最小或最大(取决于堆是否为最大堆),并且每个节点的值都要大于等于(或者小于等于)它的子节点的值。

2.双端队列(Deque):双端队列就是一个具有两端操作的队列,常见的双端队列包括队列和栈。在队列头部和尾部都可以插入或者删除元素,而其他位置则只能从头部或者尾部弹出。例如,栈和队列都是只允许在一端进行操作的线性数据结构,但双端队列却可以同时在头部和尾部进行操作。

3.优先级队列(Priority Queue):优先级队列是指按照某种顺序排列的队列。在优先级队列中,每个元素都有一个优先级,当有新的元素进入队列时,它会自动排序到适当的位置上去。优先级队列通常用二叉堆或者二叉搜索树来实现。

4.散列表(Hash Table):散列表(又称哈希表)是一个存储键-值对的无序结构。该结构通过把键映射到索引位置来快速查找值。在Python中,字典属于散列表的一种。

5.树(Tree):树是一种抽象数据类型,它是由一组节点组成的集合。其中,每一个节点代表一个元素,并且存在一条通向其他元素的路径。不同类型的树可以用于不同的任务,如二叉树、N叉树、满二叉树、伸展树、哈夫曼树、AVL树、红黑树等。本文将重点介绍二叉树、双端队列、优先级队列、散列表和树。

3.核心算法原理和具体操作步骤以及数学公式讲解

3.1 堆 (Heap)

堆(Heap)是一种特殊的树形数据结构。堆是一个经典的数据结构,主要应用于高速内存数据结构的实现以及搜索和排序算法的优化。堆分为两种:最大堆和最小堆。

最大堆:对于每个节点,其值都不大于(或不小于)其父节点的值。即,树的每个节点的值都大于(或者小于)它的所有后裔。这种结构通常可以用来解决最大和最小值的搜索问题,如二叉搜索树。

最小堆:对于每个节点,其值都不小于(或不大于)其父节点的值。即,树的每个节点的值都小于(或者大于)它的所有后裔。对于比较少出现较大的负值情况的情况,可以使用最小堆来优化性能。

根据堆的定义,我们可以创建堆的方法有很多,这里我们以数组表示法来实现一个最大堆,创建一个大小为n的最大堆的步骤如下:

  1. 从第一个非叶子节点(最后一个节点除外)开始调整堆(因为下标从0开始,所以最后一个非叶子节点下标为 n/2 - 1)。

  2. 假设当前节点的下标为 i,其左孩子下标为 j = 2i+1;右孩子下标为 k = 2i+2。

  3. 如果 j < n 且 arr[j] > arr[i],那么将arr[i]和arr[j]互换。

  4. 如果 k < n 且 arr[k] > arr[i],那么将arr[i]和arr[k]互换。

  5. 将当前节点下标设为 j 或 k 中的较大值,直至 j 和 k 越界或 arr[j] <= arr[i] 和 arr[k] <= arr[i]。

  6. 重复步骤2~5,直至全部节点调整完毕。

在Python中,使用list来实现一个最大堆如下所示:

class MaxHeap:
    def __init__(self, nums=[]):
        self.heap = [float('-inf')] + nums   # 初始化堆,首位放一个负无穷,使得首位空出。
        for i in range(len(nums)//2, -1, -1):
            self._sift_down(i)

    def push(self, val):
        self.heap.append(val)
        self._sift_up(len(self.heap)-1)

    def pop(self):
        if len(self.heap) == 1:
            raise Exception('The heap is empty!')

        last = self.heap.pop()   # 取出末尾节点
        if not self.heap:      # 当堆为空时,直接返回None
            return None
        
        root = self.heap[0]     # 堆顶元素赋值给root
        self.heap[0] = last     # 末尾节点赋值给堆顶元素
        del last               # 删除末尾节点
        self._sift_down(0)     # 下沉新根节点
        return root            # 返回堆顶元素
    
    def _sift_up(self, idx):
        while idx > 0 and self.heap[idx//2] < self.heap[idx]:    # 当前节点比父节点小
            self.heap[idx], self.heap[idx//2] = self.heap[idx//2], self.heap[idx]  # 交换两个节点
            idx //= 2                                            # 向上寻找
        
    def _sift_down(self, idx):
        while 2*idx + 1 < len(self.heap):                            # 有左孩子
            child = 2*idx + 1                                         # 选择左孩子作为子节点
            if child + 1 < len(self.heap) and self.heap[child+1] > self.heap[child]:
                child += 1                                             # 选择右孩子作为子节点
            if self.heap[child] >= self.heap[idx]:                      # 当前节点大于子节点,退出循环
                break
            self.heap[child], self.heap[idx] = self.heap[idx], self.heap[child]  # 交换两个节点
            idx = child                                               # 更新当前节点下标

3.2 双端队列 (Dequeue)

双端队列(deque,double-ended queue)是一种基于双端链表的先入先出队列。双端队列也可以看作是一系列的栈组合起来形成的一个队列,即先进的先出,后进的先出。双端队列的操作分为两端两侧分别操作,实现简单、高效。

双端队列支持以下操作:

  1. push_front(x),在队首加入元素 x;

  2. push_back(x),在队尾加入元素 x;

  3. front(), 获取队首元素;

  4. back(), 获取队尾元素;

  5. pop_front(), 删除队首元素;

  6. pop_back(), 删除队尾元素。

双端队列的 Python 实现如下:

class Deque:
    def __init__(self):
        self.items = []
 
    def isEmpty(self):
        return self.items == []
 
    def size(self):
        return len(self.items)
 
    def push_front(self, item):
        self.items.insert(0,item)
 
    def push_back(self, item):
        self.items.append(item)
 
    def pop_front(self):
        return self.items.pop(0)
 
    def pop_back(self):
        return self.items.pop()
 
    def peek_front(self):
        return self.items[0]
 
    def peek_back(self):
        return self.items[-1]
 
if __name__=='__main__':
    dq = Deque()
    print("IsEmpty?",dq.isEmpty())
    dq.push_back(1)
    dq.push_back(2)
    dq.push_front(3)
    dq.push_front(4)
    print(dq.size())
    print(dq.peek_front(),dq.peek_back())
    print(dq.pop_front(),dq.pop_back())
    print(dq.peek_front(),dq.peek_back())
    dq.push_back(7)
    dq.push_front(8)
    print(dq.size())
    print(dq.pop_front(),dq.pop_back())
    print(dq.peek_front(),dq.peek_back())

输出结果为:

IsEmpty? True
4
4 2
1 2
3 2
3
2
7 3
8 3

3.3 优先级队列 (PriorityQueue)

优先级队列是一种类似队列的数据结构,但是它的元素根据它们的优先级而不是FIFO顺序进行排序。优先级队列中的每个元素都有一个相关联的优先级。这些元素按照优先级来排序,优先级高的元素排在前面,优先级低的元素排在后面。

优先级队列主要有两种实现:

  1. 堆实现:利用堆数据结构来实现优先级队列。堆中的每个结点都是一个元素,并且堆保持每个结点的优先级。当从堆中取出元素时,其优先级最高的元素总是被返回。

  2. 斐波那契堆实现:斐波那契堆是一种复杂的数据结构,它支持动态增删改操作,而且复杂度为 O(logn)。斐波那契堆在很多场景下都比堆更快,尤其是在大量增删改操作时。

本文介绍的是堆实现优先级队列。首先,介绍一下二叉堆,然后介绍优先级队列的几种操作。

3.3.1 二叉堆

二叉堆是一类特殊的树结构。二叉堆是一个完全二叉树,所以除了最底层之外,其它层的节点都恰好有两个子节点。二叉堆的堆序是一个严格递减的顺序,这意味着树中任一节点的值都不大于(或者不小于)它的子女节点的值。

最大堆:满足堆序关系的二叉树叫做最大堆。每个节点的值都不大于其子女节点的值。

最小堆:满足堆序关系的二叉树叫做最小堆。每个节点的值都不小于其子女节点的值。

二叉堆的应用领域:

  1. 堆排序:堆排序是排序算法中最重要的一种,也是基于二叉堆的一种排序算法。它的最坏时间复杂度为O(nlgn),平均时间复杂度为O(lgn)。

  2. 求解最小/最大值:求解最小/最大值问题的二叉堆通常采用线性时间复杂度。因此,它可以在O(1)时间内找出最小值/最大值。

  3. 搜索:堆结构允许对任意节点进行快速检索,得到它的值,和它所有的子女节点值。

  4. 随机存取:由于二叉堆的高度比普通的二叉查找树高得多,所以访问任何节点的时间复杂度都很小。这样就可以在O(1)时间内存取任何节点。

3.3.2 优先级队列

优先级队列是一个队列,其中每个元素都有一个优先级,并按优先级来排序。优先级队列中的元素被赋予不同的优先级,其中元素的优先级越高则排在越前面。文章来源地址https://uudwc.com/A/AAXpr

3.3.2.1 创建一个空的优先级队列
from typing import List
import heapq 

class PriorityQueue:
    def __init__(self):
        self.__pq = []          # list to store elements in the priority queue
        self.__count = 0        # counter of number of elements in the priority queue
 
    def is_empty(self) -> bool:
        """Returns whether or not the priority queue is empty"""
        return self.__count == 0
 
    def put(self, item: int, priority: float) -> None:
        """Inserts an element into the priority queue with a given priority"""
        entry = (-priority, self.__count, item)
        heapq.heappush(self.__pq, entry)
        self.__count += 1
 
    def get(self) -> int:
        """Removes and returns the highest priority element from the priority queue"""
        if self.is_empty():
            raise Exception("Cannot remove from an empty priority queue")
 
        _, _, item = heapq.heappop(self.__pq)
        self.__count -= 1
        return item
3.3.2.2 查看优先级队列的第一个元素
def peek(self) -> int:
    """Gets the highest priority element without removing it"""
    if self.is_empty():
        raise Exception("Cannot view top of an empty priority queue")
 
    _, _, item = self.__pq[0]
    return item
3.3.2.3 改变优先级
def change_priority(self, item: int, new_priority: float) -> None:
    """Changes the priority of an element within the priority queue"""
    for index, (_, count, data) in enumerate(self.__pq):
        if data == item:
            entry = (-new_priority, count, item)
            self.__pq[index] = entry
            heapq._siftup(self.__pq, index)
            heapq._siftdown(self.__pq, 0, index)
            break

3.3.3 使用优先级队列的例子

p = PriorityQueue()
p.put(1, 5)       # Inserting an element with priority 5
p.put(2, 3)       # Inserting another element with priority 3
print(p.get())    # Removing and returning the first element with highest priority
                  # Output: 2

p.change_priority(1, 4)       # Changing the priority of an element
print(p.peek())              # Viewing the highest priority element
                            # Output: 1

原文地址:https://blog.csdn.net/universsky2015/article/details/131821206

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请联系站长进行投诉反馈,一经查实,立即删除!

h
上一篇 2023年09月29日 11:05
基于FPGA的运动目标检测跟踪系统项目,FPGA项目,FPGA图像处理(已实现)
下一篇 2023年09月29日 12:05