数据结构基础-优先队列和堆

优先队列概念

优先队列可以看做队列的一种,区别在于,在优先队列中,元素进入队列的顺序可能与其被操作的顺序不同。他支持插入(Insert)和删除最小值(DeleteMin)操作(返回并删除最小元素)或删除最大值(DeleteMax)操作(返回并删除最大元素)。


20190131174534.png

优先队列应用

以操作系统的进程调度为例,用户使用手机过程中,来电的优先级比较高,我们不要求所有的元素有序,只处理当前键值最大的那个就可以了。在这种情况下,我们需要实现的只是删除键值最大的元素(获取优先级最高的进程)和插入新的元素(插入新的进程)。
其他例子:

  • 数据压缩:好夫曼编码算法
  • 最短路径算法:Dijkstra算法
  • 事件驱动仿真:顾客排队算法
  • 选择问题:查找第k个最小元素
  • 最小生成树算法:Prim算法

优先队列ADT

优先队列是元素的容器,每一个元素有一个相关键值。

  1. 优先队列主要操作
  • Insert(key,data):插入键为key的数据到优先队列中,元素以key进行排序
  • DeleteMin/DeleteMax:删除并返回最小/最大键值的元素
  • GetMiniMum/GetMaxinum:返回最小/最大键值的元素,但不删除他
  1. 辅助操作
  • 第k最小/第k最大:返回队列中键值为第k个最小/最大的元素
  • 大小(Size):返回队列中元素个数
  • 堆排序(Heap Sort):基于键值的优先级将优先队列中元素进行排序

在正式进入优先队列分析之前,我们有必要先了解一下对于堆的概念和相关操作。我们定义当一棵二叉树的每个结点都要大于等于它的两个子结点的时候,称这棵二叉树堆有序。

堆的基本概念

堆是一棵具有特殊性质的二叉树,堆的基本要求是结点的值必须大于等于(或小于等于)其孩子结点的值。除此还有另一个特性,就是当h>0,所有叶子结点都位于第h或h-1层(其中h为树的高度),所有堆是一棵完全二叉树。

堆的分类
  • 最小堆:结点的值必须等于或者小于其孩子结点的值
  • 最大堆:结点的值必须大于或等于其孩子结点的值
堆的声明
public class Heap{
    public int[] array;
    public int count;
    public int capacity;
    public int heapType;
    public Heap(int capacity, int heapType)
    public Parent(int capacity, int heapType)
    public int leftChild(int i)
    public int rightCHild(int i)
    public int getMaximum(int i)
}
创建堆
public Heap(int capacity, int heapType){
    this.heapType = heapType;
    this.count = 0;
    this.capacity = capacity;
    this.array = new int[capacity];
}
结点的双亲
public int parent(int i){
    if(i <= 0 ||i >= this.count)
        return -1;
    return i-1/2;
}
结点的孩子
public int leftChild(int i){
    int left = 2*i + 1;
    if(left >= this.count)
        return -1;
    return left;
}

public int rightChild(int i){
    int right = 2*i + 2;
    if(right >= this.count)
        return -1;
    return right;
}


获取最大元素
public int getMaximum(){
    if(this.count == 0) return -1;
    return this.array[0];
}
堆化元素

堆化其实是执行某些操作后或者建立堆的时候会有让堆不满足堆的特性的风险,所以有相应操作去堆化元素。

下沉操作(siftDown)多见于建立堆或者删除最大或最小值操作后。

递归版本:

public void siftDown(int k){
    int l,r,max,temp;
    l = leftChild(k);
    r = rightChild(k);
    if(l != -1 && array[l] > array[k]){
        max = l;
    }else{
        max = k;
    }
    if(r != -1 && array[r] > array[max]){
        max = r;
    }
    if(max != i){
        temp = array[i];
        array[i] = array[max];
        array[max] = temp;
    }
    sifDown(max);
}

迭代版本:

public void siftDown(int k) {
    while(2*k <= n){
        int j = 2*k;
        if(j < n && array[j] < array[j+1]) j++;
        if(array[k] > array[j]) break;
        int tmp = array[j];
        array[j] = array[k];
        array[k] = tmp;
        k = j;
    }
}

上浮操作(siftUp)多见于插入操作后

public void siftUp(int k) {
    while(k > 0 && array[k] > array[(k-1)/2]){
        int tmp = array[k];
        array[k] = array[(k-1)/2];
        array[(k-1)/2] = array[k];
        k = k-1 / 2;
    }
}
删除元素

下面文章只讨论最大堆的情况,因为最小堆其实是最大堆的对称情况。删除元素就是删除二叉堆中根结点,然后将最后一个结点复制到根结点,然后删除最后元素。当用最后元素替换根结点后可能会导致不满足堆的性质。为之能再次成为堆,需要堆化(下沉)。

int deleteMax(){
    if(count == 0){
        return -1;
    }
    int data = array[0];
    array[0] = array[count-1];
    count--;
    siftDown(0);
}
插入元素

类似于堆化和删除过程

  1. 堆大小加1
  2. 将新元素放在堆的尾部
  3. 从下至上堆化这个元素
int insert(int data){
    int i;
    if(count == capacity){
        resizeHeap();
    }
    count++;
    i = count - 1;
    while(i >= 0 && data > array[(i-1)/2]){
        array[i] = array[(i-1)/2];
        i = i-1/2;
    }
    this.array[i] = data;
}

void resizeHeap(){
    int[] oldArray = new int[capacity];
    System.arraycopy(array,o,oldArray,count-1);
    array = new int[capacity*2];
    for(int i=0;i<capacity;i++)
        array[i] = oldArray[i];
    capacity *=2;
    oldArray = null;
}
数组建堆

tips:叶子结点总是满足堆的性质,所以在数组放入堆后要满足堆的性质堆化总是要关注非叶子结点。所以先要关注如何找到第一个非叶子结点。堆的最后一个元素位置是count-1,通过他能找到最后一个非叶子结点。

void buildHeap(Heap h, int A[], int n){
    if(h == null) return;
    while(n > capacity){
        h.resizeHeap();
    }
    for(int i=0;i<n;i++){
        h.array[i]=a[i];
    }
    h.count=n;
    for(int i=(n-1)/2;i>=0;i--){
        h.siftDown(i);
    }
}
堆排序
void heapSort(int A[], int n){
    Heap h = new Heap(n,0);
    int oldSize,i,temp;
    buildHeap(h,A,n);
    oldSize = h.count;
    for(i=n-1;i>0;i--){
        temp = h.array[0];h.array[0]=h.array[h.count-1];h.array[h.count-1]=temp;
        h.count--;
        h.siftDown(i);
    }
    h.count=oldSize;
}

优先队列的相关问题

eg1:高度为h的堆,其最小元素个数和最大元素个数是多少?
解:因为堆是一棵完全二叉树(除了最底层,其他所有层都是满的),它最多有2h+1-1个元素,最少是2h - 1 + 1 = 2^h(当最底层只有一个元素,而其他各层都满时)。

eg2:请说出n个元素的堆的高度为什么是logn?
解:由上得,2^h <= n <= 2^h+1 -1,因为h为整数,所有h=logn。

eg3:从最小堆中删除任意元素的算法。
解:先找到元素然后删掉,接下来的操作类似于删除最小元素。

int delete(Heap p, int i){
    int key;
    key = h.array[i];
    h.array[i]=h.array[h.count-1];
    h.siftDown(i);
    return key;
}

eg4:给出最小堆中找出第k小元素的算法。

解:借助一个辅助最小堆来实现。假定原始最小堆为HOrig,辅助最小堆为HAux。初始化时,将HOrig顶部最小放入HAux中。

Heap HOrig,HAux;
int findKthLargestEle(int k){
    int heapElement;
    int count = 1;
    HAux.insert(HOrig.deleteMin());
    while(true){
        heapElement = HAux.deleteMin();
        if(++count == k){
            return heapElement;
        }else{
            HAux.insert(heapElement.leftChild());
            HAux.insert(heapElement.rightChild);
        }
    }
}

eg5:给定一个包含上百万个数字的大文件,如何在这个文件中找出最大的10个值?
解:当需要找到最大n个元素时,最好的数据结构是优先队列。
把数据分割为1000个元素的集合,然后创建为堆。然后依次从堆中取出10个元素。最后采用堆对10个元素的集合进行排序,取出前10个元素。但是这需要耗费很大的内存。
重复使用前面堆的10个元素就可以解决这个问题。就是第一个采用1000个元素,后面的每个用990。初始时,对前1000个进行堆排序,取出最大的10个放入第二个集合中990个元素混合,再进行堆排序,取出放入第三个集合中990个元素混合,重复此操作到最后990个元素放入一个集合得到问题的解。

优先队列实现原理分析

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

推荐阅读更多精彩内容