数据结构与算法分析 chapter06 -优先队列(堆)

 优先队列是允许至少两种操作的数据结构:insert(插入),以及deleteMin(删除最小者),它的工作是找出,返回并删除优先队列中最小的元素。insert操作等价于enqueue(入队),而deleteMin则是队列运算dequeue(出队)在优先队列中的等价操作。
 实现优先队列的一种方法是使用二叉查找树,操作的平均运行时间三O(logN)

二叉堆

 堆是一颗完全被填满的二叉树,有可能的例外是在底层,底层上的元素从左到右填入。这样的树称为完全二叉树。


完全二叉树.png

 因为完全二叉树这么有规律,它可以用一个数组表示而不使用链。


完全二叉树的数组实现.png

 对于数组中任一位置 i 上的元素,其左儿子在位置 2i 上,右儿子在左儿子后的单元 (2i+1) 中,它的父亲则在位置 i/2

 因此,一个堆结构将有一个数组和一个代表当前堆的大小的整数组成。

堆序性质

 让操作快速执行的性质是堆序性质。由于我们想要快速找出最小元,因此最小元应该在根上。如果我们考虑任意子树也应该是一个堆,那么任意节点就应该小于它的所有后裔。
堆序性质: 在一个堆中,对于每一个节点XX的父亲中的关键字小于(或等于)X中的关键字,根节点除外(它没有父亲)。

image.png

insert.png
deleteMin.png
package heap

//AnyType 实现compareTo的int
type AnyType int

func (a AnyType) compareTo(b AnyType) int {
   result := 0
   if a < b {
       result = -1
   } else if a == b {
       result = 0
   } else if a > b {
       result = 1
   }
   return result
}

// BinaryHeap 二叉堆
type BinaryHeap struct {
   CurrentSize int // 二叉堆的大小
   Aarray      []AnyType
}

// Insert 插入
func (h *BinaryHeap) Insert(a AnyType) {
   child := h.CurrentSize + 1
   if child > len(h.Aarray)-1 {
       array := make([]AnyType, child*2)
       for i := 0; i < len(h.Aarray); i++ {
           array[i] = h.Aarray[i]
       }
       h.Aarray = array
   }
   father := child / 2
   for {
       if father == 0 {
           break
       }
       if a.compareTo(h.Aarray[father]) < 0 {
           h.Aarray[child] = h.Aarray[father]
           child = child / 2
           father = father / 2
       } else {
           break
       }
   }
   h.Aarray[child] = a
   h.CurrentSize = h.CurrentSize + 1
}

// DeleteMin 删除最小元素
func (h *BinaryHeap) DeleteMin() AnyType {
   if len(h.Aarray) == 0 {
       return AnyType(-1)
   }
   any := h.Aarray[1]
   last := h.Aarray[h.CurrentSize]
   father := 1
   childleft := father * 2
   childRight := father*2 + 1

   for {
       if len(h.Aarray)-1 < childleft {
           break
       }
       if h.Aarray[childleft] == 0 && h.Aarray[childRight] == 0 {
           break
       }
       if len(h.Aarray)-1 >= childRight {
           if h.Aarray[childleft] >= h.Aarray[childRight] {
               if last <= h.Aarray[childRight] {
                   h.Aarray[father] = last
                   break
               } else {
                   h.Aarray[father] = h.Aarray[childRight]
                   father = childRight
               }
           } else {
               if last <= h.Aarray[childRight] {
                   h.Aarray[father] = last
                   break
               } else {
                   h.Aarray[father] = h.Aarray[childleft]
                   father = childleft
               }
           }
           childleft = father * 2
           childRight = father*2 + 1
       }
   }
   h.Aarray[father] = last
   h.Aarray[h.CurrentSize] = 0
   h.CurrentSize = h.CurrentSize - 1
   return any
}

package heap

import (
   "testing"
)

func Test_BinaryHeap(t *testing.T) {
   binaryHeap := &BinaryHeap{
       CurrentSize: 0,
       Aarray:      []AnyType{-1},
   }

   binaryHeap.Insert(AnyType(13))
   binaryHeap.Insert(AnyType(14))
   binaryHeap.Insert(AnyType(16))
   binaryHeap.Insert(AnyType(19))
   binaryHeap.Insert(AnyType(21))
   binaryHeap.Insert(AnyType(19))
   binaryHeap.Insert(AnyType(68))
   binaryHeap.Insert(AnyType(65))
   binaryHeap.Insert(AnyType(26))
   binaryHeap.Insert(AnyType(32))
   binaryHeap.Insert(AnyType(31))

   binaryHeap.DeleteMin()
}

func Test_BinaryHeap2(t *testing.T) {
   binaryHeap := &BinaryHeap{
       CurrentSize: 0,
       Aarray:      []AnyType{-1},
   }

   binaryHeap.Insert(AnyType(13))
   binaryHeap.Insert(AnyType(14))
   binaryHeap.Insert(AnyType(16))
   binaryHeap.Insert(AnyType(35))
   binaryHeap.Insert(AnyType(21))
   binaryHeap.Insert(AnyType(19))
   binaryHeap.Insert(AnyType(68))
   binaryHeap.Insert(AnyType(65))
   binaryHeap.Insert(AnyType(36))
   binaryHeap.Insert(AnyType(32))
   binaryHeap.Insert(AnyType(31))

   binaryHeap.DeleteMin()
}

其他函数

decreaseKey(降低关键字的值)
 decreaseKey(p,△)操作降低在位置p处的项的值,降值的幅度为正的能量△。由于这可能破坏堆序性质,因此必须通过上滤对堆进行调整。

increaseKey(增加关键字的值)
 increaseKey(p,△)操作增加在位置p处的项的值,增加的幅度为正的能量△。由于这可能破坏堆序性质,因此必须通过下滤对堆进行调整。

delete(删除)
 delete(p)操作删除堆中位置p上的节点。该操作通过首先执行decreaseKep(p, ∞)然后再执行deleteMin()来完成。

buildHeap(构建堆)
 有时二叉堆是由一些项的初始集合构造而得。这种构造方法以N项作为输入,并把它们放入一个堆中。显然,这可以使用N个相继的insert操作来完成。由于每个insert将花费O(1)平均时间以及O(logN)的最坏情形时间,因此该算法的总运行时间是O(N)平均时间而不是最O(NlogN)最坏情形时间。


func (h *BinaryHeap) buildHeap(array []AnyType) {
   h.Aarray = make([]AnyType, len(array)+1)
   for i := 0; i < len(array); i++ {
       size := i + 1
       h.Aarray[size] = array[i]
   }
   h.CurrentSize = len(array)
   for i := h.CurrentSize / 2; i > 0; i-- {
       h.percolateDown(i)
   }
}

func (h *BinaryHeap) percolateDown(i int) {
   if i >= h.CurrentSize {
       return
   }
   for {
       if i*2 <= h.CurrentSize {
           if h.Aarray[i*2] <= h.Aarray[i*2+1] && h.Aarray[i] > h.Aarray[i*2] {
               tmp := h.Aarray[i*2]
               h.Aarray[i*2] = h.Aarray[i]
               h.Aarray[i] = tmp
           }
           if h.Aarray[i*2] > h.Aarray[i*2+1] && h.Aarray[i] > h.Aarray[i*2+1] {
               tmp := h.Aarray[i*2+1]
               h.Aarray[i*2+1] = h.Aarray[i]
               h.Aarray[i] = tmp
           }
           i = i / 2
       }
       break
   }
}

二叉堆的应用场景

选择问题
  输入N个元素以及一个整数k,找出第k个最大的元素。
解决:将N个元素读入一个数组。然后对该数组应该buildHeap算法。最后,执行k次delteMin操作,从该堆最前提取的元素就是我们的元素。---->堆排序(heapsort)TODO

d-堆

 d-堆是二叉堆的简单推广,它就像一个二叉堆,只是所有的节点都有d个儿子(因此,二叉堆是2-堆)。


3-堆.png

d-堆要比二叉堆浅得多,它将insert操作的运行时间改为O(logᵈN)。然而,对于大的d,deleteMin操作费时得多,虽然树是浅了,但是d个儿子中的最小者是必须要找出的,如使用标准的算法,这会花费d-1次比较,于是将操作的用时提高到O(d logᵈN)。


image.png

二项队列

  二项队列支持所有这三种操作(merge + insert + deleteMin), 每次操作的最坏情形运行时间为O(logN), 而插入操作平均花费常数时间;

二项队列与优先队列的区别:一个二项队列不是一颗堆序的树,而是堆序的树的集合,称为森林。每一科堆序树都是用约束的形式,叫做二项树

二项树.png

 从图中看到,二项树Bₖ由一个带有儿子B₀,B₁ ....Bₖ-₁的根组成。高度为k的二项树恰好有2ᵏ 个节点。

image.png
image.png

二项队列与二叉堆的比较

基本操作: insert(平均情况下) deleteMin merge
二项队列: O(1) O(logN) O(logN)
二叉堆: O(1) O(logN) O(N)

可见,二项队列有效地支持了merge操作。

但是,需要注意的是:二项队列的实现用到了链表,树中的每个元素存储在一个结点中,结点之间采用“左孩子右兄弟”表示法进行链接,此外还需要一个额外的数组来保存各棵二项树的根结点,存储开销要比二叉堆大。

而对于二叉堆的存储,则简单得多。它只需要一个一维数组即可存储各个元素。

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 194,390评论 5 459
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 81,821评论 2 371
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 141,632评论 0 319
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 52,170评论 1 263
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 61,033评论 4 355
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 46,098评论 1 272
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 36,511评论 3 381
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 35,204评论 0 253
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 39,479评论 1 290
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 34,572评论 2 309
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 36,341评论 1 326
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,213评论 3 312
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 37,576评论 3 298
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 28,893评论 0 17
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,171评论 1 250
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 41,486评论 2 341
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 40,676评论 2 335

推荐阅读更多精彩内容