【数据结构与算法】详解什么是优先级队列,并用代码手动实现一个优先级队列

上一篇文章讲解了队列的相关知识,同时用代码实现了一个队列结构。那么本文将介绍一下另外一种特殊的队列结构,叫作 优先级队列javascript

上一篇文章的跳转连接——【数据结构与算法】详解什么是队列,并用代码手动实现一个队列结构前端

公众号:Lpyexplore的编程小屋
关注我,天天更新,带你在python爬虫的过程当中学习前端,还有更多电子书和面试题等你来拿java

在这里插入图片描述

1、什么是优先级队列

在了解了什么是队列之后,咱们再来了解优先级队列,顾名思义,优先级队列就是在队列的基础上给每一个元素加上了前后顺序,咱们仍然拿排队买票的例子来说解。python

普通的排队买票队伍就是一个抽象的队列,如图
在这里插入图片描述
可是,此时这个买票窗口上贴上了如图上这样几个字
在这里插入图片描述
此时,某些排队的人就有了比别人优先买到票的权利了。假设 小人3 是孕妇,那么她能够排到第一个,比 小人4小人8 更早的买到票,而 小人4小人8 都没有特殊身份,可是由于 小人4小人8 来的早,因此 小人4 仍是排在 小人8 的前面,此时是这样的
在这里插入图片描述
通过这样一个讲解,相信你们都知道 优先级队列 和普通的队列的区别了吧。web

在向优先级队列插入元素时,每一个元素有一个本身的号码牌,表示该元素是排在队列的前端仍是后端。所以,在优先级队列里,也就没有先进先出这样一个结构特色了。面试

假如如今有这样一个空的优先级队列
在这里插入图片描述
咱们向这个空的优先级队列中插入一个元素 JavaScript,并给它一个号码牌 3,此时是这样的
在这里插入图片描述
这时咱们再向优先级队列中插入一个元素 python,也给它一个号码牌 1,假设号码牌上的数字越小,在队列中排得越靠前,那么此时是这样的
在这里插入图片描述
若是再插入一个元素 Java,给它一个号码牌 7,由于数字 713 都小,因此此时的队列是这样的
在这里插入图片描述
好了,对 优先级队列 的讲解就讲到这里,若是还有不明白,欢迎关注文章开头的公众号私聊我或者在本文底下留言评论,我看到会解答。算法

接下来咱们就来说解一下 优先级队列 经常使用的一些方法吧~编程

2、优先级队列的方法

其实优先级队列的方法跟普通队列的方法如出一辙,也无非是数据的插入 、删除 、查询等方法,只不过这二者的方法内部实现逻辑有略微的区别,前者比较复杂。c#

老样子,咱们仍是先列举一下,优先级队列的方法,以下表后端

方法 含义
enqueue() 向队列添加元素
dequeue() 删除队列最前端的一个元素,并返回该元素
front() 返回队列前端的元素,但不会移除该元素
isEmpty() 查看队列是否为空
size() 返回队列内元素的个数
toString() 以字符串的形式展现队列内的全部元素

3、用代码实现优先级队列

接下来,咱们也仍是用JavaScript实现一个基于数组的线性结构的类,由于是基于数组实现的队列,因此咱们能够把数组的头部看做是队列的前端,把数组的尾部看做是队列的后端。这里咱们规定数字越小的优先级越大

(1)建立一个构造函数

function PriorityQueue() {
	//属性
    this.list = []
}

(2)建立内部构造函数

这一步仍是挺有趣的,咱们准备在刚才建立的构造函数的内部再建立一个构造函数,为何要这么作呢?由于上面讲过,在优先级队列中存储的元素都具备两个值,分别是 存入的数据号码牌(优先级),因此咱们准备建立一个这样的构造函数,来存储这两个值,以后须要插入一个元素时,就能够直接 new 一个实例对象出来。

function PriorityQueue() {
	//属性
    this.list = []
	
	//建立内部构造函数,存储元素的数据和优先级
	function EachElement(e, num) {
        this.element = e
        this.priority = num
    }
}

(3)实现enqueue()方法

由于优先级队列的方法实现会比普通队列的方法复杂一点,我会讲解一下每一个方法的实现思路,方便你们理解。

enqueue()方法就是向优先级队列添加一个元素,并自动根据每一个元素的优先级插入到合适的位置。

方法实现思路:

  1. 先建立一个新元素的实例对象,将元素的值和优先级传给该实例对象
  2. 先判断队列是否为空。若为空,则直接想队列添加该元素
  3. 队列不为空,则从头遍历整个队列,判断咱们要添加的元素与队列中的元素哪一个优先级更大,而后在合适的位置插入元素
  4. 若咱们要添加的元素比当前队列中全部元素的优先级都要小,那么直接在队列后端添加该元素

思路讲完了,咱们直接来看代码

function PriorityQueue() {
	//属性
    this.list = []
	
	//建立内部构造函数,存储元素的数据和优先级
	function EachElement(e, num) {
        this.element = e
        this.priority = num
    }

	//向队列添加元素
    PriorityQueue.prototype.enqueue = function (e, priority) {
    	// 1.建立新元素的实例对象
        let element = new EachElement(e, priority)
        
		// 2.判断队列是否为空
        if(this.list.length === 0) {
            this.list.push(element)
            return;
        }
        
        // 3.队列不为空,遍历整个队列,比较优先级大小
        for(let i in this.list) {
            if(element.priority < this.list[i].priority) {
                this.list.splice(i, 0, element)
                return;
            }
        }
        
        // 4.新元素优先级最小,直接添加到队列的后端
        this.list.push(element)
    }
}

咱们来使用一下该方法

let pq = new PriorityQueue()

pq.enqueue('abc', 10)
pq.enqueue('abb', 1)
pq.enqueue('cdf', 9)

此时的优先级队列是这样的
在这里插入图片描述
在上面的基础上,咱们再向优先级队列添加一个元素 eee,并赋予优先级 9,即 pq.enqueue('eee', 9)。咱们看看此时的优先级是什么样的
在这里插入图片描述
能够看到,一样的优先级都为9,但咱们后添加的元素 eee 却排在了先添加的元素 cdf 的后面。咱们想一下,排队买票,那些有特殊身份的人有权利比咱们普通人先买到票,那很正常,可是那些没有特殊身份的普通人都是平等的(优先级相同),那必须得遵照个先来后到了,因此当优先级相同时,先添加的元素永远比后添加的元素靠前。

(4)实现dequeue()方法

dequeue()方法就跟普通队列同样啦,直接删除队列前端的第一个元素便可。

接下来咱们来单独实现一下该方法

function PriorityQueue() {
	//属性
    this.list = []
	
	//建立内部构造函数,存储元素的数据和优先级
	function EachElement(e, num) {
        this.element = e
        this.priority = num
    }

	//出列
	PriorityQueue.prototype.dequeue = function () {
        return this.list.shift()
    }
}

咱们来使用一下该方法

let pq = new PriorityQueue()

//先添加三个元素
pq.enqueue('abc', 10)
pq.enqueue('abb', 1)
pq.enqueue('cdf', 9)

pq.dequeue()              // 返回值为abb的元素实例对象

此时的优先级队列是这样的
在这里插入图片描述

(5)实现front()方法

front()方法就是获取当前优先级队列前端第一个元素,但不会删除该元素。这个方法也没什么好说的,跟普通队列的方法同样。

接下来咱们来单独实现一下该方法

function PriorityQueue() {
	//属性
    this.list = []
	
	//建立内部构造函数,存储元素的数据和优先级
	function EachElement(e, num) {
        this.element = e
        this.priority = num
    }

	//返回优先级队列第一个元素
    PriorityQueue.prototype.front = function () {
        return this.list[0]
    }
}

咱们来使用一下该方法

let pq = new PriorityQueue()

//先添加三个元素
pq.enqueue('abc', 10)
pq.enqueue('abb', 1)
pq.enqueue('cdf', 9)

pq.front()              // 返回值为abb的元素实例对象

dequeue()方法有区别,front()并无删除前端的第一个元素,因此此时的优先级队列仍然是这样的
在这里插入图片描述

(6)实现isEmpty()方法

isEmpty()方法是判断优先级队列里是否有元素,便是否为空。实现原理很简单,判断数组长度是否为0就能够了。

接下来咱们来单独实现一下该方法

function PriorityQueue() {
	//属性
    this.list = []
	
	//建立内部构造函数,存储元素的数据和优先级
	function EachElement(e, num) {
        this.element = e
        this.priority = num
    }

    //判断优先级队列是否为空
    PriorityQueue.prototype.isEmpty = function() {
        if(this.list.length === 0) {
            return true
        }
        else {
            return false
        }
    }
}

咱们来使用一下该方法

let pq = new PriorityQueue()

pq.isEmpty()            //返回 true,由于此时没有添加元素

//先添加三个元素
pq.enqueue('abc', 10)
pq.enqueue('abb', 1)
pq.enqueue('cdf', 9)

pq.isEmpty()            //返回 false,由于此时优先级队列内有三个元素

(7)实现size()方法

size()方法就是判断优先级队列中的元素个数。实现方式也很简单,直接返回数组长度便可。

接下来咱们来单独实现一下该方法

function PriorityQueue() {
	//属性
    this.list = []
	
	//建立内部构造函数,存储元素的数据和优先级
	function EachElement(e, num) {
        this.element = e
        this.priority = num
    }

    //返回优先级队列的元素个数
    PriorityQueue.prototype.size = function () {
        return this.list.length
    }
}

咱们来使用一下该方法

let pq= new PriorityQueue()

pq.size()     //返回 0,由于还未向优先级队列添加过元素

//添加三个元素
pq.enqueue('abc', 10)
pq.enqueue('abb', 1)
pq.enqueue('cdf', 9)

pq.size()     //返回 3,由于上面三行代码分别向优先级队列添加了一个元素

(8)实现toString()方法

toString()方法就是将优先级队列内的元素用字符串的方式展现出来(将数组转化成字符串)并返回,与普通队列的 toString()方法不一样的是,它不只会将元素的值展现出来,还会展现每一个元素的优先级。

接下来咱们来单独实现一下该方法

function PriorityQueue() {
	//属性
    this.list = []
	
	//建立内部构造函数,存储元素的数据和优先级
	function EachElement(e, num) {
        this.element = e
        this.priority = num
    }

    //返回当前优先级队列
    PriorityQueue.prototype.toString = function () {
        let string = ''
        for(let i in this.list) {
            string += `${this.list[i].element}:${this.list[i].priority} `
        }
        return string
    }
}

咱们来使用一下该方法

let pq= new PriorityQueue()

pq.enqueue('abc', 10)
pq.enqueue('abb', 1)
pq.enqueue('cdf', 9)

pq.toString()     //返回 abb:1 cdf:9 abc:10

(9)完整的代码

function PriorityQueue() {
	//属性
    this.list = []
    
	//建立内部构造函数,存储元素的数据和优先级
    function EachElement(e, num) {
        this.element = e
        this.priority = num
    }
    
    //入列
    PriorityQueue.prototype.enqueue = function (e, priority) {
        let element = new EachElement(e, priority)

        if(this.list.length === 0) {
            this.list.push(element)
            return;
        }

        for(let i in this.list) {
            if(element.priority < this.list[i].priority) {
                this.list.splice(i, 0, element)
                return;
            }
        }

        this.list.push(element)
    }
    
    //出列
    PriorityQueue.prototype.dequeue = function () {
        return this.list.shift()
    }
    
    //返回优先级队列的元素个数
    PriorityQueue.prototype.size = function () {
        return this.list.length
    }
    
    //返回优先级队列第一个元素
    PriorityQueue.prototype.front = function () {
        return this.list[0]
    }

    //判断优先级队列是否为空
    PriorityQueue.prototype.isEmpty = function() {
        if(this.list.length === 0) {
            return true
        }
        else {
            return false
        }
    }
    
    //展现优先级队列元素
    PriorityQueue.prototype.toString = function () {
        let string = ''
        for(let i in this.list) {
            string += `${this.list[i].element}:${this.list[i].priority} `
        }
        return string
    }
}

4、优先级队列的补充

本文咱们是用数组来实现优先级队列的,但大家有没有发现,当咱们每次添加元素时,都须要与优先级队列中的不少元素比较优先级大小,而后再找到一个合适的位置插入元素。由于是以数组形式实现的,因此在该优先级队列里,每个元素都有本身的下标值,而且咱们能够经过下标值直接获取到它。

以下图,如今有一个这样的优先级队列,而且它们的下标值也标在下面
在这里插入图片描述
而后此时咱们准备添加一个值为 c#,优先级为 2 的元素,那么咱们经过遍历队列元素比较优先级发现,应该在下标值为 1 的位置插入元素,因此,本来优先级队列中下标值为 1 以及以后的全部元素都要向后移动一个位置,即下标值 +1,结果以下图
在这里插入图片描述
由于这个例子中,添加一个元素,要改动 n-1 个元素的下标值,可想而知,这是一个很是消耗性能的操做,因此在这里用 数组 来实现优先级队列仍是有点不合适。

下一篇文章我会开始讲 链表 ,这种数据结构相对于数组的优点就在于往结构中插入元素性能比较高,不会牵一发而动全身。因此等到以后你们学习了链表,能够回过头来用链表实现一下优先级队列。

5、总结

优先级队列结构的讲解就到这里了,但愿你们对优先级队列有了更深一层的理解。

你们能够关注我,以后我还会一直更新别的数据结构与算法的文章来供你们学习,而且我会把这些文章放到【数据结构与算法】这个专栏里,供你们学习使用。

而后你们能够关注一下个人微信公众号:Lpyexplore的编程小屋,等这个专栏的文章完结之后,我会把每种数据结构和算法的笔记放到公众号上,你们能够去那获取。

我是Lpyexplore,创做不易,喜欢的加个关注,点个收藏,给个赞~ 带大家在Python爬虫的过程当中学习Web前端