最小/大堆的操作及堆排序

摘自:《啊哈算法》

我们要用1, 2, 5, 12, 7, 17, 25, 19, 36, 99, 22, 28, 46, 92来建立最小堆,并且删除最小的数,并增加一个数23

如何建立这个堆:

//建堆
n = 0;
for (int i = 1; i <= m; i++) {
    n++;
    h[n] = a[n];
    shiftup(n);
}

我们还有更快的方法可以建立一个堆

思路:直接把1, 2, 5, 12, 7, 17, 25, 19, 36, 99, 22, 28, 46, 92放入一个完全二叉树中,在这个二叉树中,我们从最后一个节点开始,依次判断以这个节点为根的子树是否符合最小堆的特性。如果所有的子树都符合最小堆的特性,那么整棵树就是最小堆了。(注意完全二叉树有一个性质,最后一个非叶子节点是第n/2个节点,从1数起而不是从0数起)

算法:把n个元素建立一个堆,首先我们可以将这n个节点以自顶向下,从左到右的方式从1到n编码,可以将这n个节点转换成一棵完全二叉树。紧接着从最后一个非叶节点(节点编号为n/2)开始到根节点(节点编号为1),逐个扫描所有的节点,根据需要将当前节点向下调整,直到以当前结点为根节点的子树符合堆的特性。时间复杂度是O(N)

代码如下:

void create_heap() {
    for (int i = n/2; i >= 1; i--) {
        shiftdown(i);
    }
}

建堆后是完全二叉树,并且所有父节点都比子节点小

接下来,我们将堆顶删除,把新增加的23放在堆顶。

显然加了数后已经不符合最小堆的特性了,我们需要将新增加的数调整到合适的位置。

向下调整,将这个数与它的两个儿子2和5比较,选择较小的一个与它交换

此时我们发现还是不满足最小堆,于是继续将23与它的两个儿子中较小的一个交换。

再交换

向下调整的代码:

void shiftdown(int i) { //传入一个需要向下调整的结点编号i
    int t, flag = 0; //flag用来标记是否需要继续向下调整
    while (i * 2 <= n && flag == 0) {
        //首先判断它和左儿子的关系,并用t记录值较小的节点编号
        if (h[i] > h[i*2]) {
            t = i*2;
        } else {
            t = i;
        }

        //如果它有右儿子,再对右儿子进行讨论
        if (i*2 + 1 <= n) {
            //如果它的右儿子的值更小,更新较小的结点编号
            if (h[t] < h[i*2 + 1])
                t = i * 2 + 1;
        }

        //如果发现最小的编号不是自己,说明子结点中有比父节点更小的
        if (t != i) {
            swap(t, i);
            i = t;
        } else {
            flag = 1;
        }
    }
}

如果只是想新增一个数,而不是删除最小值,只需要将新元素插入到末尾,再根据情况判断新元素是否需要上移,直到满足新的特性位置。

加入我们现在要加入一个3

先将3与它的父节点25比较,发现比父节点小,需要与父节点交换。以此类推

向上调整的代码:

//新增加一个元素
void shiftup(int i) { //传入一个需要向上调整的结点编号i
    int flag = 0; //用来标记是否需要继续向上调整
    if (i == 1) return ; //如果是堆顶,就返回,不需要再调整了
    //不在堆顶,并且当前结点i的值比父节点小的时候就继续向上调整
    while (i != 1 && flag == 0) {
        //判断是否比父节点的小
        if (h[i] < h[i/2]) {
            swap(i, i/2);
        } else{
            flag = 1;
        }
        i = i/2;
    }
}

--------------------------------------------------------------------------------------------------

堆排序:

时间复杂度是O(NlogN),假如要从小到大排序,那么只需要建立最小堆,然后每次删除顶部元素并将顶部元素输出或者放入到一个新的数组中,直到堆为空为止。

int deleteMax() {
    int t;
    t = h[1];
    h[1] = h[n];
    n--; //堆的元素减少1
    shiftdown(1); //向下调整
    return t;
}

一种更好的方法是,从小到大排序的时候不是建立最小堆而是建立最大堆,最大堆建立好后,最大的元素在h[1],因为我们的需求是从小到大排序,希望最大的放在最后,因此我们将h[1]和h[n]交换,此时h[n]就是数组中的最大的元素。交换后将h[1]向下调整以保持堆的特性。

void heapsort() {
    while (n > 1) {
        swap(1, n);
        n--;
        shiftdown(1);
    }
}

-------------------------------------------------------------------------------------------------

堆的其他应用:

1.求一个数列中的第K大的数

建立一个大小为K的最小堆,堆顶就是第K大的数

例如,假设有10个数,要求求第3大的数,第一步选取任意的3个数,比如说是前3个,将这3个数建成最小堆,然后从第4个数开始,与堆顶
的数比较,如果比堆顶的数要小,那么这个数就不要,如果比堆顶的数大,则舍弃当前的堆顶而将这个数作为新的堆顶,并再去维护堆

时间: 2024-10-13 12:04:52

最小/大堆的操作及堆排序的相关文章

堆的插入、删除和建立操作,堆排序

1.        堆 堆:n个元素序列{k1,k2,...,ki,...,kn},当且仅当满足下列关系时称之为堆: (ki <= k2i,ki <= k2i+1) 或者(ki >= k2i,ki >= k2i+1), (i = 1,2,3,4,...,n/2) 若将和此次序列对应的一维数组(即以一维数组作此序列的存储结构)看成是一个完全二叉树,则堆的含义表明,完全二叉树中所有非终端结点的值均不大于(或不小于)其左.右孩子结点的值.由此,若序列{k1,k2,…,kn}是堆,则堆顶元

一、实现一个特殊的栈,在实现栈的基本功能的基础上,再实现返回栈中最小元素的操作

请指教交流! 1 package com.it.hxs.c01; 2 3 import java.util.Stack; 4 5 /* 6 实现一个特殊的栈,在实现栈的基本功能的基础上,再实现返回栈中最小元素的操作 7 */ 8 public class GetMinStack { 9 10 public static void main(String args[]) { 11 GetMinStack demoStack = new GetMinStack(); 12 demoStack.pus

SQL Server 最小化日志操作解析,应用

Sql Server 中数据库在BULK_LOGGED/SIMPLE模式下的一些操作会采用最小化日志的记录方式,以减小tran log落盘日志量从而提高整体性能. 这里我简单介绍下哪些操作在什么样的情况下会最小化日志记录.以及现实生产环境中如何应用最小化日志. 概念:SQL Server在满足相应条件的基础上时进行一些特定的操作如Rebuild Index时会进行最小化Tran Log记录操作,从而改善系统性能. 注意:含最小化操作日志操作段日志无法按时间点恢复(point in time) 需

python数据结构与算法——完全树 与 最小/大堆

1 # 完全树 最小堆 2 class CompleteTree(list): 3 def siftdown(self,i): 4 """ 对一颗完全树进行向下调整,传入需要向下调整的节点编号i 5 当删除了最小的元素后,当新增加一个数被放置到堆顶时, 6 如果此时不符合最小堆的特性,则需要将这个数向下调整,直到找到合适的位置为止""" 7 n = len(self) 8 # 当 i 节点有儿子(至少是左儿子时),并且有需要调整时,循环执行 9

最小堆及其操作函数

前几天在做Kth Largest Element in an Array 时使用到了堆,通过那倒题目也了解到了堆的make_heap,push_heap,pop_heap操作,看了C++ reference中的讲解也明白了heap_sort是什么回事.于是想着自己实现以下这四个函数. 堆的定义: 任意节点小于它的所有后裔,最小元在堆的根上(堆序性). 堆总是一棵完全树. #include <iostream> #include <string> using namespace st

SQL Server 最小化日志操作解析,应用[手稿]

Sql Server 中数据库在BULK_LOGGED/SIMPLE模式下的一些操作会采用最小化日志的记录方式,以减小tran log落盘日志量从而提高整体性能. 这里我简单介绍下哪些操作在什么样的情况下会最小化日志记录.以及现实生产环境中如何应用最小化日志. 概念:SQL Server在满足相应条件的基础上时进行一些特定的操作如Rebuild Index时会进行最小化Tran Log记录操作,从而改善系统性能. 注意:含最小化操作日志操作段日志无法按时间点恢复(point in time) 需

最小的n个数(堆排序)

最小的n个和 Time Limit: 1000 MS Memory Limit: 32768 K Total Submit: 136(41 users) Total Accepted: 38(32 users) Rating: Special Judge: No Description 给定A.B两个数列,各包含n个数,分别从A和B中任意取一个数相加得到和,这样会有n^2种结果(包括重复的),求n^2个结果中前n个最小的和. Input 有多组测试数据. 对于每组测试数据,第一行为n,第二行为数

最小的k个数1 堆排序实现

// 使用堆排序实现 其时间复杂度为O(nlgn)    private static void buildMaxHeap(int[] input, int end)    {        // 从非叶子节点开始进行        for (int i = (end - 1) / 2; i >= 0; i--)        {            // 当前节点 cur的字节点位cur*2+1&cur*2+2            int cur = i;            //

算法导论——最大堆,以及堆排序算法

本段代码实现了建堆,维护最大堆的性质,堆排序函数,优先队列的相关函数(插入,找最大值,提取出最大值,增加关键值,增加元素),以及相关的测试 1 #include <iostream> 2 #include <memory> 3 #include <iomanip> 4 #define LEFT(i) (2 * i) 5 #define RIGHT(i) (2*i + 1) 6 #define PARENT(i) (i >> 1) 7 8 using name