Java数据结构之优先级队列(堆)图文详解

Java数据结构之优先级队列(堆)图文详解

什么是优先级队列(堆)

优先级队列(堆)是一种非常重要的数据结构,它能够更好地管理数据,分配任务等。优先级队列的本质就是一种特殊的队列,它是一种可以根据元素的优先级来出队的数据结构。

通常情况下,队列中存储了一系列具有优先级的数据。当我们从队列中取出元素时,优先级高的元素会先出队。因此,我们需要一种数据结构,来对这些元素进行优先级处理。

在优先级队列中,堆是最常用的一种数据结构。堆本身就是一种树形结构,可以很好地管理优先级队列。

堆的基本概念

堆是一种满足两种性质的树形数据结构:

  1. 最大堆性质或最小堆性质

最大堆性质、最小堆性质可以根据实际情况来确定。在本文中我们以最大堆性质,也就是父节点的权值比子节点的权值大为例。

  1. 完全二叉树

完全二叉树可以这样理解:除了最后一层,其他每层都必须是完全填满的,最后一层仅允许出现最右侧的若干位置为空。

堆可以分为两种类型:大根堆(Max Heap)和小根堆(Min Heap)

  • 大根堆(Max Heap):在一个大根堆中,父节点的权值比子节点的权值大。
  • 小根堆(Min Heap):在一个小根堆中,父节点的权值比子节点的权值小。

在Java中,优先级队列可以通过java.util.PriorityQueue来实现,该类底层通过二叉小根堆实现。我们在实际开发过程中,只需要简单调用Java API中的方法来进行使用就好。

堆的应用

堆在数据结构中有广泛的应用,主要有以下两种:

  1. 堆排序

堆本身就是一种排序方法,也称为堆排序。它的时间复杂度是O(nlogn)。堆排序将一个无序的序列构建成一个堆,然后将堆的根节点输出,递归地输出剩余的元素。由于建堆的时间为O(n),输出堆顶元素后,可以每次将剩余堆的最后一个元素放到堆顶,再重新进行堆化。这个过程下来,只需要O(nlogn)的时间,即可对序列进行排序。

  1. 模拟优先级队列

在程序中,优先级队列是一种常见的数据结构,通常用于任务调度、网络通信等需要对任务进行优先级处理的场景。Java提供了PriorityQueue类来实现优先级队列,其底层是使用堆来实现的。我们只需要根据实际的需求,来实现对应的堆。

堆的实现

在Java中,堆的实现可以使用数组来完成,同时为了使数组下标从1开始,下标为0的位置不保存数值。

基本实现

public class Heap {

    // 数组存储堆的节点
    private int[] a;

    // 堆的大小
    private int n;

    // 堆的容量
    private int capacity;

    // 构造函数,初始化堆
    public Heap(int capacity) {
        this.a = new int[capacity + 1];
        this.n = 0;
        this.capacity = capacity;
    }

    // 获取堆中的最大值
    public int getMax() {
        if (n == 0) {
            return -1;
        }
        return a[1];
    }

    // 加入一个新的元素
    public boolean insert(int x) {
        if (n == capacity) {
            return false;
        }
        ++n;
        a[n] = x;
        // 堆化操作
        int i = n;
        while (i > 1 && a[i] > a[i/2]) {
            swap(a, i, i/2);
            i = i/2;
        }
        return true;
    }

    // 删除堆顶元素
    public boolean removeMax() {
        if (n == 0) {
            return false;
        }
        a[1] = a[n];
        --n;
        // 堆化操作
        heapify(a, n, 1);
        return true;
    }

    // 堆化操作
    private static void heapify(int[] a, int n, int i) {
        while (true) {
            int maxPos = i;
            if (i*2 <= n && a[i*2] > a[i]) {
                maxPos = i*2;
            }
            if (i*2+1 <= n && a[i*2+1] > a[maxPos]) {
                maxPos = i*2+1;
            }
            if (maxPos == i) {
                break;
            }
            swap(a, i, maxPos);
            i = maxPos;
        }
    }

    // 交换
    private static void swap(int[] a, int i, int j) {
        int temp = a[i];
        a[i] = a[j];
        a[j] = temp;
    }
}

堆排序实现

public static void heapSort(int[] a) {
        if (a.length <= 1) {
            return;
        }
        // 建堆
        buildHeap(a, a.length-1);
        // 排序
        for (int i = a.length-1; i >= 1; i--) {
            swap(a, 1, i);
            heapify(a, i-1, 1);
        }
    }

    // 建堆
    private static void buildHeap(int[] a, int n) {
        for (int i = n/2; i >= 1; i--) {
            heapify(a, n, i);
        }
    }

示例说明

示例一:使用堆实现TopK

假设我们有一个数据集合,我们需要从中找出前K个最大的数。这个时候,我们就可以使用堆来实现。

比如,假设数据集合为{1,3,5,7,2,4,6,8,9,0},我们需要找出前3个最大的数。那么我们就可以使用一个大小为3的最小堆来存储数据,每当插入一个新数据时,我们比较其与当前堆的根节点的大小,如果小于等于根节点,则跳过,否则则将该数据插入到堆中,并将堆中最小的数据删除。最终得到的就是前3个最大的数:{7,8,9}。

示例二:使用优先级队列模拟数据中心的任务调度

在某个数据中心中,需要处理很多任务,但是不同的任务的优先级不同。优先级高的任务需要先执行,优先级低的任务则需要等待。这个时候,我们可以使用优先级队列来模拟任务调度。

我们可以定义一个Task类,存储任务的信息,包括任务的优先级、创建时间、执行时间等等。然后使用PriorityQueue来存储这些任务。每当有新的任务进入时,我们可以将其加入到PriorityQueue中,PriorityQueue会自动根据优先级来排序。然后我们创建一个单独的线程来不断地从PriorityQueue中获取任务,执行任务,并更新PriorityQueue。

例如,我们有以下三个任务:

Task 1:
Priority: 2
CreateTime: 2021-10-01 08:00:00
ExecuteTime: 2021-10-01 08:10:00

Task 2:
Priority: 1
CreateTime: 2021-10-01 08:00:00
ExecuteTime: 2021-10-01 08:30:00

Task 3:
Priority: 3
CreateTime: 2021-10-01 08:00:00
ExecuteTime: 2021-10-01 08:20:00

首先,我们将三个任务加入PriorityQueue中,PriorityQueue会自动根据优先级排序。然后,我们开启一个线程来处理任务。该线程首先从PriorityQueue中获取优先级最高的任务,也就是Task 3;然后执行Task 3,并更新PriorityQueue;接着获取Task 1,执行Task 1,并更新PriorityQueue;最后获取Task 2,执行Task 2,并更新PriorityQueue。这个过程下来,所有的任务都会被正确地执行。

本站文章如无特殊说明,均为本站原创,如若转载,请注明出处:Java数据结构之优先级队列(堆)图文详解 - Python技术站

(0)
上一篇 2023年5月17日
下一篇 2023年5月17日

相关文章

  • C语言一篇精通链表的各种操作

    C 语言精通链表操作攻略 简介 链表是一种常用的数据结构,它相对于数组等数据结构来说,具有更高的灵活性和增删操作的效率。在 C 语言中,我们可以用指针来实现链表,这也是指针与动态内存分配的重要应用之一。本文将提供在 C 语言中精通链表操作的攻略,包括链表的创建、添加、删除、搜索、遍历等常用操作。 基本结构体定义 链表一般由结构体和指针构成,下面我们定义一个链…

    数据结构 2023年5月17日
    00
  • 数据结构与算法大作业:走迷宫程序(C语言,DFS)(代码以及思路)

    好家伙,写大作业,本篇为代码的思路讲解   1.大作业要求 走迷宫程序 问题描述: 以一个 m * n 的长方阵表示迷宫, 0和1分别表示迷宫的通路和障碍。 设计一个程序, 对任意设定的迷宫, 求出一条从入口到出口的通路, 或得出没有通路的结论。 基本要求: (1) 实现一个以链表做存储的栈类型, 然后编写一个求解迷宫的非递归程序。 求的通路以三元组(i, …

    算法与数据结构 2023年5月9日
    00
  • C语言数据结构 栈的基础操作

    C语言数据结构 栈的基础操作 1. 栈的基本概念 栈(Stack)是一种基于LIFO(后进先出)原理的数据结构,类似于一组盘子,只能在盘子的顶部进行操作。每次从顶部添加或移除盘子。 栈具有两个基本操作:入栈(push)和出栈(pop)。当添加一个元素时,我们称其为“push”,当移除一个元素时,我们称其为“pop”。 2. 栈的实现 栈可以使用数组或链表来实…

    数据结构 2023年5月17日
    00
  • Golang Mutex互斥锁源码分析

    Golang Mutex互斥锁源码分析 介绍 Golang的Mutex互斥锁机制是一种非常重要的并发控制方式,它可以保证在同一时刻,同一共享资源只能被一个goroutine访问,其他的goroutine必须等待当前访问者释放锁之后才能访问该共享资源。 在使用Mutex机制时,需要进行锁定、解锁等操作,而这一过程是由Mutex的底层实现——sync包来完成的。…

    数据结构 2023年5月17日
    00
  • 【ACM博弈论】SG函数入门(2):博弈树SG函数的转移与子游戏的合并

    上一篇文章我们讲了两种经典的博弈模型:《【ACM博弈论】SG函数入门(1):从巴什博奕到尼姆游戏》,这一节我们开始讲解SG函数。 ? 作者:Eriktse? 简介:19岁,211计算机在读,现役ACM银牌选手?力争以通俗易懂的方式讲解算法!❤️欢迎关注我,一起交流C++/Python算法。(优质好文持续更新中……)?? 阅读原文获得更好阅读体验:https:…

    算法与数据结构 2023年4月17日
    00
  • 用C语言举例讲解数据结构中的算法复杂度结与顺序表

    让我来为你讲解“用C语言举例讲解数据结构中的算法复杂度结与顺序表”的完整攻略。具体如下: 一、算法复杂度 1.1 什么是算法复杂度 算法复杂度是衡量算法运行效率的重要指标。包括时间复杂度和空间复杂度。时间复杂度指算法解决问题所用的时间,通常用大O符号表示;空间复杂度指算法解决问题所需的内存空间大小。 1.2 如何分析算法复杂度 可以从以下三个方面来分析算法复…

    数据结构 2023年5月17日
    00
  • 基于C++详解数据结构(附带例题)

    基于C++详解数据结构(附带例题)攻略 简介 该攻略是基于C++编程语言详解数据结构的,主要涉及数据结构中的相关概念、操作以及例题演练。C++语言作为一种高性能的编程语言,对于开发数据结构问题具有很大的优势。 数据结构概念 数据结构基本概念 数据结构是计算机存储、组织数据的方式。具体来说,数据结构可以理解为计算机存储数据的一种方式,也可以看作是一些组织数据的…

    数据结构 2023年5月17日
    00
  • 数据结构之线性表

    Linear_list 类型定义 一个线性表是n个数据元素的有限序列,线性表中的元素个数n定义为线性表的长度,n=0时成为空表;抽象数据类型: InitList(&L) //构造空线性表L DestroyList(&L) //销毁线性表L ClearList(&L) //将L重置为空表 ListEmpty(L) //若L为空表返回TR…

    算法与数据结构 2023年4月25日
    00
合作推广
合作推广
分享本页
返回顶部