优先队列是用来作什么事情的?应用程序须要处理优先级的事件,为每一个事件分配一个优先级,优先处理优先级高的事件,这样就须要优先队列。
什么是优先队列?支持删除最大元素和插入元素。javascript
一、无序数组实现,在插入的时候往数组末尾插入,获取的时候再获取最大的,这样插入的时间复杂度是O(1),获取的时间复杂度是O(n)。
二、有序数组实现,在插入的时候排序,获取的时候获取元素第一个,这样插入时间复杂度是O(n),获取的时间复杂度是O(1)。
三、二叉堆实现。html
数据结构二叉堆能很好的实现优先队列的基本操做,在二叉堆数组中,每一个元素都要保证大于等于另外两个特定位置的元素,相应的,这些位置的元素又至少要大于等于数组中另外两个元素,以此类推。
定义:当一颗二叉树的每一个节点都大于等于它的两个子节点的时候,它被称为堆有序。
二叉堆表示法:若是用指针来表示,每一个元素都须要三个指针来找到它的上下结点(父节点和两个子节点各须要一个)。可是若是咱们使用彻底二叉树来表示,表达将会变得十分方便,彻底二叉树只须要用数组而不须要用指针就能够表示。
定义:二叉堆是一组可以用彻底二叉树排序的元素,而且在数组中按照层级存储(不使用数组的第一个位置)。
在一个堆中:位置为k的父节点位置为(k/2 须要向下取值),而它的两个子节点的位置分别为2K和2K+1,这样在不使用指针的状况下,咱们也能够经过计算数组的索引在树中上下移动。用数组(堆)实现的彻底二叉树是很严格的,可是它的灵活性已经足以让咱们高效的实现优先队列。用它们咱们将能实现对数级别的插入元素和删除最大的元素的操做。利用数组中无需指针便可沿着树上下移动的便利以及如下性质,算法保证了对数复杂度的性能。java
一、由下至上的堆有序化(上浮)若是堆的有序状态由于某个结点变得比它的父结点更大而被打破。那么咱们经过交换它和它的父节点来修复堆。
二、由上至下的堆有序化(下沉) 若是堆的有序状态由于某个结点变得比它的两个子节点或者是其中之一更小而被打破了,那么咱们能够经过将它和它的两个子结点中的较大者交换来恢复堆。
三、插入元素:咱们将新添加的加到数组末尾,增长堆的大小而且让这个新元素上浮到合适的位置。
四、删除最大元素:咱们从数组顶端删去最大元素并将数组的最后一个元素放到顶端,减少堆的大小,而且让这个元素下沉到合适的位置。
算法
优先队列由一个基于堆的彻底二叉树表示,存储于数组pq[1··N]中,p[0]没有使用,在insert中,咱们将N加一并把新元素添加在数组最后,而后用swim()恢复堆的秩序。在delMax()中咱们从pq1]中返回须要返回的元素,而后将pq[N]移动到pq[1],将N减一并用sink恢复堆的秩序,同时咱们还将再也不使用的pq[N+1]设置为null,以便回收所占用的空间。
命题Q。对于一个含有N个元素的基于堆的优先队列,插入元素操做只须要不过(lgN+1)次比较(上浮操做),删除最大元素的操做须要不超过2lgN次比较。(下沉操做)。
证实。由命题P可知,两种操做都须要在根节点和堆底之间移动元素,而路径长度不超过lgN,对于路径上的每一个结点,删除最大元素须要两次比较(除了堆底元素),一次用来找出最大的子结点,一次用来确认子结点是否须要上浮
对于须要大量混杂插入和删除最大元素的操做的典型应用来讲,命题Q意味着一个重要的性能突破。
使用有序或者无序数组的优先队列的初级实现老是须要线性时间来完成其中一种操做,但基于堆的实现可以保证在对数时间内完成他们,这种差异使得咱们解决之前没法解决的问题。
数组
堆排序的基本思想是:将待排序序列构形成一个大顶堆,此时,整个序列的最大值就是堆顶的根节点。将其与末尾元素进行交换,此时末尾就为最大值。而后将剩余n-1个元素从新构形成一个堆,这样会获得n个元素的次小值。如此反复执行,便能获得一个有序序列了
堆排序实现原理?
堆排序分为两个阶段:第一阶段实现一个堆,第二阶段而后再下沉阶段从堆中按照递减顺序取出全部元素并获得排序结果。
N个元素构造一个堆的时间复杂度是多少?NlogN
具体实现:咱们将数组直接做为一个堆,而后对这个堆进行下沉操做。这样作的好处是不须要额外空间了
一、堆的构造:用swim上浮方法,须要从左到右扫描所有,用sink下沉方法,只须要扫描一半 如何利用下沉进行排序?
一、从最后一个非叶子结点开始,从左至右,从下至上进行调整。这样就能够构成一个大碓顶
二、将大碓顶元素和最后一个互换,而后再将剩下的进行下沉操做,等到只剩一个元素的时候下沉操做完成,排序也就完成了,由于每次下沉操做的复杂度是logn因此整体复杂度是Nlogn
数据结构
class MaxPQ{
constructor(){
//声明一个数组保存二叉堆
//0开头启用
this.tp=[null];
}
size(){
return this.tp.length;
}
isEmpty(){
return this.tp.length===0;
}
//删除二叉堆最大的元素(删除头部元素,而后最末尾的补上来,再下沉)
delMax(){
//获取头部元素,而且头部元素置为空
var heade=this.tp[1];
this.tp[1]=this.tp.pop();
this.sink(1)
console.log(this.tp);
return heade;
//出队尾部
//而后赋值给头部,而后下沉
}
//插入(将新元素添加到数组末尾,增长堆的大小让这个新元素上浮到合适的位置)
insert(item){
this.tp.push(item);
this.swim(this.size()-1);
console.log(this.tp);
}
//上浮
swim(k){
// k>1表明不是根节点而且根节点比子节点小
while(k>1&&this.less(Math.floor(k/2),k)==-1){
this.exch(Math.floor(k/2),k);
k=Math.floor(k/2);
console.log(k);
}
}
//下沉
sink(k){
//若是是最后一个结点就不须要下沉了
var j;
while(2*k<=this.size()){
//判断
j=2*k;
//先比较子节点哪一个大,而后若是父节点比大的小,就交换
//j小于N是为了给另外结点留位置
if(j<this.size()&&this.less(j,j+1)==-1) j++;
//比对,若是k比j小就交换而后中止
if(this.less(j,k)==1){
this.exch(k,j);
}else{
break;
}
k=j;
//没有的话就把j赋值为k
}
}
//私有方法
//i小于j 返回-1 i大于j返回1 i==j返回0
less(i,j){
if(this.tp[i]<this.tp[j]){
return -1;
}
if(this.tp[i]>this.tp[j]){
return 1;
}
if(this.tp[i]==this.tp[j]){
return 0;
}
}
//交换ij
exch(i,j){
var temp=this.tp[i];
this.tp[i]=this.tp[j];
this.tp[j]=temp;
}
}
复制代码
堆排序实现less
function sort(arrs) {
//第一步获取大顶堆(到这个的时候已经构建成为了一个堆)
//如何获取大顶堆
//此时咱们从最后一个非叶子结点开始,从左至右,从下至上进行调整。
//这样从最后一棵树进行下沉操做每往上一个结点,就排好了一个堆,这样就初步获得了一个最大堆顶
for(let k=Math.floor(arr.length/2);k>=1;k--){
sink(arrs,k,arrs.length)
}
// console.log(arrs);
var n=arr.length;
while(n>1){
exch(arrs,1,n--);
sink(arrs,1,n);
}
console.log(arrs);
}
function sink(arrs, k, n) {
//下沉步骤
var j;
//保证本身没有降低到最后
while (2 * k <= n-1) {
//判断
j = 2 * k;
//先比较子节点哪一个大,而后若是父节点比大的小,就交换
//j小于N是为了给另外结点留位置
if (j < n-1 && less(j, j + 1) == -1) j++;
//比对,若是k比j小就交换而后中止
if (less(j, k) == 1) {
exch(arrs,k, j);
} else {
break;
}
k = j;
//没有的话就把j赋值为k
}
function less(i, j){
if(arrs[i]<arrs[j]){
return -1;
}
if(arrs[i]>arrs[j]){
return 1;
}
if(arrs[i]==arrs[j]){
return 0;
}
}
}
function exch(arrs, i, j) {
var temp = arrs[i];
arrs[i] = arrs[j];
arrs[j] = temp;
}
let arr = [null,1, 6, 5, 8, 4, 2];
sort(arr)
复制代码