欢迎来到尧图网

客户服务 关于我们

您的位置:首页 > 汽车 > 时评 > 进阶学习-----环形队列 堆排最大堆最小堆 深度优先遍历广度优先遍历

进阶学习-----环形队列 堆排最大堆最小堆 深度优先遍历广度优先遍历

2024/11/30 14:39:38 来源:https://blog.csdn.net/yuanbenshidiaos/article/details/140990942  浏览:    关键词:进阶学习-----环形队列 堆排最大堆最小堆 深度优先遍历广度优先遍历

什么是环形队列?

环形队列,也称为循环队列,是一种线性数据结构,它使用固定大小的数组来存储元素,并通过循环的方式来使用这个数组。环形队列的关键特性在于它能够高效地利用数组空间,并且支持先进先出(FIFO)的操作原则。

环形队列的特点

  1. 固定大小的数组:环形队列使用一个固定大小的数组来存储数据元素。
  2. 循环使用:当数组末尾的元素被取出后,新的元素可以从数组的开始位置继续插入,形成一个环。
  3. 两个指针:环形队列通常使用两个指针(或索引)来管理队列,分别是队头指针(front)和队尾指针(rear)。
  4. 空间重用:由于环形队列的循环特性,它能够重复使用已经出队的空间,避免了固定大小队列中可能出现的空间浪费问题。
  5. 在这里插入图片描述

环形队列的操作

以下是环形队列的基本操作:

  • 初始化:创建一个固定大小的数组,并将队头和队尾指针都设置为0。
  • 入队(Enqueue):在队尾插入一个元素,并更新队尾指针。如果队尾指针指向数组的末尾,它会“环绕”到数组的开头。
  • 出队(Dequeue):从队头移除一个元素,并更新队头指针。如果队头指针指向数组的末尾,它会“环绕”到数组的开头。
  • 判断队列是否为空:通常当队头指针和队尾指针相等时,队列为空。
  • 判断队列是否已满:通常使用一个额外的条件来判断队列是否已满,如(rear + 1) % capacity == front,其中capacity是数组的容量。

环形队列的实现示例

以下是一个简单的环形队列的伪代码实现:

class CircularQueue:def __init__(self, capacity):self.queue = [None] * capacityself.capacity = capacityself.front = self.rear = 0def is_empty(self):return self.front == self.reardef is_full(self):return (self.rear + 1) % self.capacity == self.frontdef enqueue(self, item):if self.is_full():raise Exception("Queue is full")self.queue[self.rear] = itemself.rear = (self.rear + 1) % self.capacitydef dequeue(self):if self.is_empty():raise Exception("Queue is empty")item = self.queue[self.front]self.front = (self.front + 1) % self.capacityreturn item

在这个示例中,enqueue方法用于将元素插入队列,而dequeue方法用于从队列中移除元素。is_emptyis_full方法分别用于检查队列是否为空或已满。注意,队列的容量是固定的,并且队列操作会通过取模运算来实现循环效果。

环形队列的作用

环形队列是一种数据结构,它的主要作用是管理和存储数据元素,支持以下操作:

  1. 先进先出(FIFO)存储:环形队列遵循先进先出的原则,即最先进入队列的元素将会最先被移出队列。
  2. 空间重用:由于环形队列的尾部可以环绕到数组开头,因此它能够更加高效地使用数组空间。在普通队列中,当元素出队后,队头后面的空间将无法被再次使用,而环形队列可以避免这种情况。
  3. 固定大小的内存使用:环形队列通常使用固定大小的数组来实现,这意味着它在初始化时就会分配好所需的最大内存,这对于内存管理是有益的。
  4. 多线程或进程间的通信:环形队列常用于多线程或多进程环境中的数据同步和通信,因为它是线程安全的(如果实现得当)。

堆排序的作用

堆排序是一种算法,主要用于对一组数据进行排序,它的作用如下:

  1. 数据排序:堆排序可以将一组数据按照从小到大或从大到小的顺序排列。
  2. 效率较高:堆排序的时间复杂度为O(n log n),这使得它在处理大量数据时比一些简单排序算法(如冒泡排序、选择排序)要高效得多。
  3. 内部排序:堆排序是一种内部排序算法,它不需要额外的存储空间,除了用于存储输入数据的数组之外。
  4. 用于构建优先队列:堆结构不仅可以用于排序,还可以用于实现优先队列,其中最大堆或最小堆可以快速地访问最大或最小元素。

#在这里插入图片描述

什么是堆排?

堆排序(Heap Sort)是一种基于比较的排序算法,它利用堆这种数据结构来进行排序。堆是一种特殊的完全二叉树,其中每个节点的值都大于或等于其子节点的值(最大堆)或者小于或等于其子节点的值(最小堆)。在堆排序中,通常使用最大堆来进行升序排序,使用最小堆来进行降序排序。

堆排序的基本步骤

堆排序算法可以分为两个大的步骤:

  1. 建立堆(Build Heap)
    • 将无序的输入数据构造成一个最大堆或最小堆。
    • 这个步骤的时间复杂度是O(n),其中n是数组中元素的数量。
  2. 排序(Sort)
    • 将堆顶元素(最大或最小值)与堆的最后一个元素交换,然后调整剩余元素构成的堆,使其满足堆的性质。
    • 重复上述交换和调整过程,直到所有元素都被移除堆,此时输入数组已经被排序。
    • 这个步骤的时间复杂度是O(n log n),因为每次调整堆的时间复杂度是O(log n),并且需要进行n次调整。

堆排序的伪代码

以下是堆排序的伪代码:

function heapSort(array):n = length(array)buildMaxHeap(array, n)for i from n downto 2:swap(array[1], array[i])  // 将堆顶元素与堆的最后一个元素交换heapify(array, 1, i - 1)  // 调整剩余元素构成的堆return array
function buildMaxHeap(array, n):for i from floor(n / 2) downto 1:heapify(array, i, n)
function heapify(array, i, n):left = 2 * iright = 2 * i + 1largest = iif left ≤ n and array[left] > array[largest]:largest = leftif right ≤ n and array[right] > array[largest]:largest = rightif largest ≠ i:swap(array[i], array[largest])heapify(array, largest, n)

在上述伪代码中,heapSort是主函数,它调用buildMaxHeap来构建最大堆,然后通过循环调用heapify来逐步将堆顶元素(最大值)移到数组的末尾,从而实现排序。
在这里插入图片描述

堆排序的特点

  • 不稳定的排序算法:堆排序不保证相同值的元素的相对顺序不变。
  • 原地排序:堆排序不需要额外的存储空间,除了用于存储输入数据的数组之外。
  • 时间复杂度:堆排序的平均和最坏情况时间复杂度都是O(n log n)。
    堆排序在实际应用中是一种高效的排序方法,尤其是在处理大量数据时。然而,由于它的不稳定性,当需要稳定排序时,可能会选择其他排序算法,如归并排序。

环形队列与堆排序的区别

以下是环形队列和堆排序之间的主要区别:

  1. 目的不同
    • 环形队列是一种数据结构,主要用于存储和管理数据元素,支持数据的插入和删除操作。
    • 堆排序是一种算法,用于对数据进行排序,它的目的是重新排列数据以实现特定的顺序。
  2. 使用场景不同
    • 环形队列适用于需要临时存储元素,并且需要频繁进行插入和删除操作的场景,如任务调度、缓冲区管理等。
    • 堆排序适用于需要对一组数据进行排序的场景,特别是在数据量较大且对排序效率有较高要求的情况下。
  3. 实现方式不同
    • 环形队列通常通过固定大小的数组来实现,并使用两个指针(队头和队尾)来管理队列的元素。
    • 堆排序通过构建最大堆或最小堆来对数据进行排序,涉及到堆的构建和调整操作。
  4. 时间复杂度不同
    • 环形队列的插入和删除操作时间复杂度通常是O(1),这是因为这些操作不需要遍历整个数据结构。
    • 堆排序的时间复杂度是O(n log n),因为构建堆需要O(n)时间,而每次调整堆的时间复杂度是O(log n),需要进行n次调整。
      总结来说,环形队列是一种用于数据管理的工具,而堆排序是一种用于数据排序的算法。它们在目的、使用场景、实现方式和时间复杂度上都有明显的不同。
      在这里插入图片描述

最大堆和最小堆

最大堆(Max Heap)和最小堆(Min Heap)是两种特殊的堆结构,它们都是基于完全二叉树的数据结构。在最大堆和最小堆中,树的每个节点的值都满足特定的条件。

最大堆(Max Heap)

在最大堆中,对于除了根节点之外的每个节点i,其父节点的值都大于或等于i节点的值。换句话说,根节点是堆中所有节点中最大的值。以下是最大堆的特性:

  • 根节点是最大值:在最大堆中,根节点存储了整个堆中的最大元素。
  • 父节点大于等于子节点:对于堆中的任意节点i,其父节点的值总是大于或等于i节点的值。
  • 完全二叉树:最大堆是一个完全二叉树,除了最后一层外,每一层都被完全填满,最后一层的节点从左向右填入。

最小堆(Min Heap)

在最小堆中,对于除了根节点之外的每个节点i,其父节点的值都小于或等于i节点的值。换句话说,根节点是堆中所有节点中最小的值。以下是最小堆的特性:

  • 根节点是最小值:在最小堆中,根节点存储了整个堆中的最小元素。
  • 父节点小于等于子节点:对于堆中的任意节点i,其父节点的值总是小于或等于i节点的值。
  • 完全二叉树:最小堆也是一个完全二叉树,其结构特性与最大堆相同。

最大堆和最小堆的操作

在最大堆和最小堆中,以下是一些基本操作:

  • 插入(Insert):向堆中插入一个新的元素,并调整堆以保持堆的性质。
  • 删除(Delete):从堆中删除根节点,并将堆的最后一个元素移动到根节点的位置,然后调整堆以保持堆的性质。
  • 查找最大/最小值:在最大堆中查找最大值或在最小堆中查找最小值,这可以通过访问根节点来实现,时间复杂度为O(1)。

示例

以下是一个最大堆的示例:

        10/  \8    9/ \  / \3  5 4   6

在这个最大堆中,根节点是10,它是堆中的最大值。每个父节点的值都大于或等于其子节点的值。
以下是一个最小堆的示例:

        1/  \3    2/ \  / \6  8 7   9

在这个最小堆中,根节点是1,它是堆中的最小值。每个父节点的值都小于或等于其子节点的值。
最大堆和最小堆在算法中有很多应用,例如在优先队列的实现中,以及在堆排序算法中。
在这里插入图片描述

深度优先遍历和广度优先遍历

深度优先遍历(Depth-First Search, DFS)和广度优先遍历(Breadth-First Search, BFS)是图和树等数据结构的两种基本遍历算法。它们用于访问树或图中的所有节点,但访问节点的顺序不同。

深度优先遍历(DFS)

深度优先遍历首先沿着一个分支深入遍历,直到该分支的末端,然后回溯并探索其他分支。

算法步骤:
  1. 从一个未访问的节点开始,标记它为已访问。
  2. 递归地访问当前节点的第一个未访问的邻接点。
  3. 如果当前节点没有未访问的邻接点,回溯到上一个节点。
  4. 重复步骤2和3,直到所有的节点都被访问过。
  5. 在这里插入图片描述
实现方式:
  • 递归:直接递归地访问节点的邻接点。
  • :使用栈来存储下一个要访问的节点。
示例:

假设我们有以下树结构:

    A/ \B   C/ \   \
D   E   F

深度优先遍历的顺序可以是:A -> B -> D -> E -> C -> F

广度优先遍历(BFS)

广度优先遍历首先访问离起点最近的节点,然后逐渐向外扩展,访问所有与起点距离相同的节点,再访问距离加一的节点,以此类推。

算法步骤:
  1. 从一个未访问的节点开始,标记它为已访问,并将其加入队列。
  2. 当队列不为空时,从队列中取出一个节点。
  3. 访问该节点的所有未访问的邻接点,标记它们为已访问,并将它们加入队列。
  4. 重复步骤2和3,直到队列为空。
实现方式:
  • 队列:使用队列来存储下一个要访问的节点。
示例:

使用同样的树结构,广度优先遍历的顺序是:A -> B -> C -> D -> E -> F

对比:

  • 搜索顺序:DFS是深度优先,BFS是广度优先。
  • 数据结构:DFS通常使用栈(递归实现隐式使用调用栈),而BFS使用队列。
  • 用途:DFS适合寻找路径或拓扑排序,而BFS适合寻找最短路径。
  • 空间复杂度:DFS的空间复杂度通常较小,因为它在任何时刻只需存储从根节点到当前节点的路径。BFS的空间复杂度可能较高,因为它需要存储整个广度的节点。
    在实际应用中,选择DFS还是BFS取决于具体的问题和需求。
代码示例

在这里插入图片描述

下面是深度优先遍历(DFS)和广度优先遍历(BFS)的Python代码示例。我们将使用一个无向图来展示这两种遍历方法。
首先,我们定义一个图的数据结构:

class Graph:def __init__(self):self.graph = {}  # 使用字典来表示图,键是节点,值是与键节点相连的节点列表def add_edge(self, node, neighbour):if node not in self.graph:self.graph[node] = []if neighbour not in self.graph:self.graph[neighbour] = []self.graph[node].append(neighbour)self.graph[neighbour].append(node)  # 因为是无向图,所以需要添加双向边def dfs(self, start):visited = set()  # 用于记录已访问的节点self._dfs_recursive(start, visited)return visiteddef _dfs_recursive(self, node, visited):visited.add(node)print(node, end=' ')for neighbour in self.graph[node]:if neighbour not in visited:self._dfs_recursive(neighbour, visited)def bfs(self, start):visited = set()queue = [start]while queue:node = queue.pop(0)if node not in visited:visited.add(node)print(node, end=' ')queue.extend(self.graph[node])return visited

现在,我们使用这个Graph类来创建一个图,并执行DFS和BFS遍历:

# 创建图实例
graph = Graph()
# 添加边到图中
graph.add_edge('A', 'B')
graph.add_edge('A', 'C')
graph.add_edge('B', 'D')
graph.add_edge('B', 'E')
graph.add_edge('C', 'F')
# 执行深度优先遍历
print("DFS traversal:")
graph.dfs('A')
# 执行广度优先遍历
print("\nBFS traversal:")
graph.bfs('A')

运行上述代码,你将得到以下输出:

DFS traversal:
A B D E C F 
BFS traversal:
A B C D E F 

在这个例子中,我们从节点’A’开始遍历图。DFS和BFS的遍历顺序可能不同,这取决于图的结构和遍历的实现方式。在上面的代码中,DFS使用了递归方法,而BFS使用了队列。

在这里插入图片描述

版权声明:

本网仅为发布的内容提供存储空间,不对发表、转载的内容提供任何形式的保证。凡本网注明“来源:XXX网络”的作品,均转载自其它媒体,著作权归作者所有,商业转载请联系作者获得授权,非商业转载请注明出处。

我们尊重并感谢每一位作者,均已注明文章来源和作者。如因作品内容、版权或其它问题,请及时与我们联系,联系邮箱:809451989@qq.com,投稿邮箱:809451989@qq.com