堆和堆的应用:堆排序和优先队列

基于MYD-YT153与AD7616的16通道高速ADC采集系统实战指南 在嵌入式系统与工业数据采集领域,模拟信号采集是连接物理世界与数字系统的关键技术。其核心原理是通过模数转换器(ADC)将连续的模拟电压信号转换为离散的数字码值,供处理器处理。这项技术的价值在于实现了对温度、压力、振动等关键物理量的精确感知与数字化,是工业自动化、设备监测、科学实验等场景的基石。本文聚焦于**多通道**、**高精度**采集这一常见需求,深入剖析如何利用全志T153处理器的LocalBus并行总线接口,高效驱动ADI的16位、16通道同步采样ADC芯片AD7616,构建一个稳定可靠的高速数据采集系 阅读详情

堆和堆的应用:堆排序和优先队列

1.堆

堆(Heap)是一种重要的数据结构,是实现优先队列(Priority Queues)

首选的数据结构。由于堆有很多种变体,包括二项式堆、斐波那契堆等,但是这里只考虑最常见的就是二叉堆(以下简称堆)。

堆是一棵满足一定性质的二叉树,具体的讲堆具有如下性质:父节点的键值总是不大于它的孩子节点的键值(小顶堆), 堆可以分为小顶堆和大顶堆,这里以小顶堆为例,其主要包含的操作有: 
- insert() 
- extractMin 
- peek(findMin) 
- delete(i)

由于堆是一棵形态规则的二叉树,因此堆的父节点和孩子节点存在如下关系:

设父节点的编号为 i, 则其左孩子节点的编号为2*i+1, 右孩子节点的编号为2*i+2 
设孩子节点的编号为i, 则其父节点的编号为(i-1)/2

由于二叉树良好的形态已经包含了父节点和孩子节点的关系信息,因此就可以不使用链表而简单的使用数组来存储堆。

要实现堆的基本操作,涉及到的两个关键的函数 
- siftUp(i, x) : 将位置i的元素x向上调整,以满足堆得性质,常常是用于insert后,用于调整堆; 
- siftDown(i, x):同理,常常是用于delete(i)后,用于调整堆;

具体的操作如下:

private void siftUp(int i) {
    int key = nums[i];
    for (; i > 0;) {
        int p = (i - 1) >>> 1;
        if (nums[p] <= key)
            break;
        nums[i] = nums[p];
        i = p;
    }
    nums[i] = key;
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
private void siftDown(int i) {
        int key = nums[i];
        for (;i < nums.length / 2;) {
            int child = (i << 1) + 1;
            if (child + 1 < nums.length && nums[child] > nums[child+1])
                child++;
            if (key <= nums[child])
                break;
            nums[i] = nums[child];
            i = child;
        }
        nums[i] = key;
  }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13

可以看到siftUp和siftDown不停的在父节点和子节点之间比较、交换;在不超过logn的时间复杂度就可以完成一次操作。

有了这两个基本的函数,就可以实现上述提及的堆的基本操作。

首先是如何建堆,实现建堆操作有两个思路:

  • 一个是不断地insert(insert后调用的是siftUp)
  • 另一个将原始数组当成一个需要调整的堆,然后自底向上地 
    在每个位置i调用siftDown(i),完成后我们就可以得到一个满足堆性质的堆。这里考虑后一种思路:

通常堆的insert操作是将元素插入到堆尾,由于新元素的插入可能违反堆的性质,因此需要调用siftUp操作自底向上调整堆;堆移除堆顶元素操作是将堆顶元素删除,然后将堆最后一个元素放置在堆顶,接着执行siftDown操作,同理替换堆顶元素也是相同的操作。

建堆

// 建立小顶堆
private void buildMinHeap(int[] nums) {
    int size = nums.length;
    for (int j = size / 2 - 1; j >= 0; j--)
        siftDown(nums, j, size);
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7

那么建堆操作的时间复杂度是多少呢?答案是O(n)。虽然siftDown的操作时间是logn,但是由于高度在递减的同时,每一层的节点数量也在成倍减少,最后通过数列错位相减可以得到时间复杂度是O(n)。

extractMin 
由于堆的固有性质,堆的根便是最小的元素,因此peek操作就是返回根nums[0]元素即可; 
若要将nums[0]删除,可以将末尾的元素nums[n-1]覆盖nums[0],然后将堆得size = size-1,调用siftDown(0)调整堆。时间复杂度为logn。

peek 
同上

delete(i)

删除堆中位置为i的节点,涉及到两个函数siftUp和siftDown,时间复杂度为logn,具体步骤是, 
- 将元素last覆盖元素i,然后siftDown 
- 检查是否需要siftUp

注意到堆的删除操作,如果是删除堆的根节点,则不用考虑执行siftUp的操作;若删除的是堆的非根节点,则要视情况决定是siftDown还是siftUp操作,两个操作是互斥的。


public int delete(int i) {
    int key = nums[i];
    //将last元素移动过来,先siftDown; 再视情况考虑是否siftUp
    int last = nums[i] = nums[size-1];
    size--;
    siftDown(i);
    //check #i的node的键值是否确实发生改变(是否siftDown操作生效),若发生改变,则ok,否则为确保堆性质,则需要siftUp 
    if (i < size && nums[i] == last) {
        System.out.println("delete siftUp");
        siftUp(i);
    }   
     return key;
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15

case 1 :

删除中间节点i21,将最后一个节点复制过来;

这里写图片描述

由于没有进行siftDown操作,节点i的值仍然为6,因此为确保堆的性质,执行siftUp操作;

这里写图片描述

case 2

删除中间节点i,将值为11的节点复制过来,执行siftDown操作; 
这里写图片描述

由于执行siftDown操作后,节点i的值不再是11,因此就不用再执行siftUp操作了,因为堆的性质在siftDown操作生效后已经得到了保持。

这里写图片描述


可以看出,堆的基本操作都依赖于两个核心的函数siftUp和siftDown;较为完整的Heap代码如下:

class Heap {
    private final static int N = 100; //default size
    private int[] nums;
    private int size;

    public Heap(int[] nums) {
        this.nums = nums;
        this.size = nums.length;
        heapify(this.nums);
    }

    public Heap() {
        this.nums = new int[N];
    }

    /**
     * heapify an array, O(n)
     * @param nums An array to be heapified. 
     */
    private void heapify(int[] nums) {
        for (int j = (size - 1) >> 1; j >= 0; j--)
            siftDown(j);
    }

    /**
     * append x to heap
     * O(logn)
     * @param x
     * @return
     */
    public int insert(int x) {
        if (size >= this.nums.length)
            expandSpace();
        size += 1;
        nums[size-1] = x;
        siftUp(size-1);
        return x;
    }

    /**
     * delete an element located in i position.
     * O(logn)
     * @param i
     * @return
     */
    public int delete(int i) {
        rangeCheck(i);
        int key = nums[i];
        //将last元素覆盖过来,先siftDown; 再视情况考虑是否siftUp;
        int last = nums[i] = nums[size-1];
        size--;
        siftDown(i);
        //check #i的node的键值是否确实发生改变,若发生改变,则ok,否则为确保堆性质,则需要siftUp; 
        if (i < size && nums[i] == last) 
            siftUp(i);
        return key;
    }

    /**
     * remove the root of heap, return it's value, and adjust heap to maintain the heap's property. 
     * O(logn)
     * @return
     */
    public int extractMin() {
        rangeCheck(0);
        int key = nums[0], last = nums[size-1];
        nums[0] = last;
        size--;
        siftDown(0);
        return key;
    }
    /**
     * return an element's index, if not exists, return -1;
     * O(n)
     * @param x
     * @return
     */
    public int search(int x) {
        for (int i = 0; i < size; i++)
            if (nums[i] == x)
                return i;
        return -1;
    }
    /**
     * return but does not remove the root of heap.
     * O(1)
     * @return
     */
    public int peek() {
        rangeCheck(0);
        return nums[0];
    }

    private void siftUp(int i) {
        int key = nums[i];
        for (; i > 0;) {
            int p = (i - 1) >>> 1;
            if (nums[p] <= key)
                break;
            nums[i] = nums[p];
            i = p;
        }
        nums[i] = key;
    }

    private void siftDown(int i) {
        int key = nums[i];
        for (;i < size / 2;) {
            int child = (i << 1) + 1;
            if (child + 1 < size && nums[child] > nums[child+1])
                child++;
            if (key <= nums[child])
                break;
            nums[i] = nums[child];
            i = child;
        }
        nums[i] = key;
    }

    private void rangeCheck(int i) {
        if (!(0 <= i && i < size))
            throw new RuntimeException("Index is out of boundary"); 
    }

    private void expandSpace() {
        this.nums = Arrays.copyOf(this.nums, size * 2);
    }

    @Override
    public String toString() {
        // TODO Auto-generated method stub
        StringBuilder sb = new StringBuilder();
        sb.append("[");
        for (int i = 0; i < size; i++)
            sb.append(String.format((i != 0 ? ", " : "") + "%d", nums[i]));
        sb.append("]\n");
        return sb.toString();
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77
  • 78
  • 79
  • 80
  • 81
  • 82
  • 83
  • 84
  • 85
  • 86
  • 87
  • 88
  • 89
  • 90
  • 91
  • 92
  • 93
  • 94
  • 95
  • 96
  • 97
  • 98
  • 99
  • 100
  • 101
  • 102
  • 103
  • 104
  • 105
  • 106
  • 107
  • 108
  • 109
  • 110
  • 111
  • 112
  • 113
  • 114
  • 115
  • 116
  • 117
  • 118
  • 119
  • 120
  • 121
  • 122
  • 123
  • 124
  • 125
  • 126
  • 127
  • 128
  • 129
  • 130
  • 131
  • 132
  • 133
  • 134
  • 135
  • 136
  • 137
  • 138
  • 139

2.堆的应用:堆排序

运用堆的性质,我们可以得到一种常用的、稳定的、高效的排序算法————堆排序。堆排序的时间复杂度为O(n*log(n)),空间复杂度为O(1),堆排序的思想是: 
对于含有n个元素的无序数组nums, 构建一个堆(这里是小顶堆)heap,然后执行extractMin得到最小的元素,这样执行n次得到序列就是排序好的序列。 
如果是降序排列则是小顶堆;否则利用大顶堆。

Trick

由于extractMin执行完毕后,最后一个元素last已经被移动到了root,因此可以将extractMin返回的元素放置于最后,这样可以得到sort in place的堆排序算法。

具体操作如下:

int[] n = new int[] {1,9,5,6,8,3,1,2,5,9,86};
Heap h = new Heap(n);
for (int i = 0; i < n.length; i++)
    n[n.length-1-i] = h.extractMin();
  • 1
  • 2
  • 3
  • 4
  • 5

当然,如果不使用前面定义的heap,则可以手动写堆排序,由于堆排序设计到建堆和extractMin, 两个操作都公共依赖于siftDown函数,因此我们只需要实现siftDown即可。(trick:由于建堆操作可以采用siftUp或者siftDown,而extractMin是需要siftDown操作,因此取公共部分,则采用siftDown建堆)。

这里便于和前面统一,采用小顶堆数组进行降序排列。


public void heapSort(int[] nums) {
        int size = nums.length;
        buildMinHeap(nums);
        while (size != 0) {
            // 交换堆顶和最后一个元素
            int tmp = nums[0];
            nums[0] = nums[size - 1];
            nums[size - 1] = tmp;
            size--;
            siftDown(nums, 0, size);
        }
    }

    // 建立小顶堆
    private void buildMinHeap(int[] nums) {
        int size = nums.length;
        for (int j = size / 2 - 1; j >= 0; j--)
            siftDown(nums, j, size);
    }

    private void siftDown(int[] nums, int i, int newSize) {
        int key = nums[i];
        while (i < newSize >>> 1) {
            int leftChild = (i << 1) + 1;
            int rightChild = leftChild + 1;
            // 最小的孩子,比最小的孩子还小
            int min = (rightChild >= newSize || nums[leftChild] < nums[rightChild]) ? leftChild : rightChild;
            if (key <= nums[min])
                break;
            nums[i] = nums[min];
            i = min;
        }
        nums[i] = key;
    }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36

3.堆的应用:优先队列

优先队列是一种抽象的数据类型,它和堆的关系类似于,List和数组、链表的关系一样;我们常常使用堆来实现优先队列,因此很多时候堆和优先队列都很相似,它们只是概念上的区分。 
优先队列的应用场景十分的广泛: 
常见的应用有: 
- Dijkstra’s algorithm(单源最短路问题中需要在邻接表中找到某一点的最短邻接边,这可以将复杂度降低。) 
- Huffman coding(贪心算法的一个典型例子,采用优先队列构建最优的前缀编码树(prefixEncodeTree)) 
- Prim’s algorithm for minimum spanning tree 
- Best-first search algorithms

这里简单介绍上述应用之一:Huffman coding。

Huffman编码是一种变长的编码方案,对于每一个字符,所对应的二进制位串的长度是不一致的,但是遵守如下原则: 
- 出现频率高的字符的二进制位串的长度小 
- 不存在一个字符c的二进制位串s是除c外任意字符的二进制位串的前缀

遵守这样原则的Huffman编码属于变长编码,可以无损的压缩数据,压缩后通常可以节省20%-90%的空间,具体压缩率依赖于数据的固有结构。

Huffman编码的实现就是要找到满足这两种原则的 字符-二进制位串 对照关系,即找到最优前缀码的编码方案(前缀码:没有任何字符编码后的二进制位串是其他字符编码后位串的前缀)。 
这里我们需要用到二叉树来表达最优前缀码,该树称为最优前缀码树 
一棵最优前缀码树看起来像这样:

这里写图片描述

算法思想:用一个属性为freqeunce关键字的最小优先队列Q,将当前最小的两个元素x,y合并得到一个新元素z(z.frequence = x.freqeunce + y.frequence), 
然后插入到优先队列中Q中,这样执行n-1次合并后,得到一棵最优前缀码树(这里不讨论算法的证明)。

一个常见的构建流程如下:

这里写图片描述

树中指向某个节点左孩子的边上表示位0,指向右孩子的边上的表示位1,这样遍历一棵最优前缀码树就可以得到对照表。

import java.util.Comparator;
import java.util.HashMap;
import java.util.Map;
import java.util.PriorityQueue;

/**
 * 
 *                            root 
 *                            /   \ 
 *                    --------- ---------- 
 *                    |c:freq | | c:freq | 
 *                    --------- ----------
 * 
 *
 */
public class HuffmanEncodeDemo {

    public static void main(String[] args) {
        // TODO Auto-generated method stub
        Node[] n = new Node[6];
        float[] freq = new float[] { 9, 5, 45, 13, 16, 12 };
        char[] chs = new char[] { 'e', 'f', 'a', 'b', 'd', 'c' };
        HuffmanEncodeDemo demo = new HuffmanEncodeDemo();
        Node root = demo.buildPrefixEncodeTree(n, freq, chs);
        Map<Character, String> collector = new HashMap<>();
        StringBuilder sb = new StringBuilder();
        demo.tranversalPrefixEncodeTree(root, collector, sb);
        System.out.println(collector);
        String s = "abcabcefefefeabcdbebfbebfbabc";
        StringBuilder sb1 = new StringBuilder();
        for (char c : s.toCharArray()) {
            sb1.append(collector.get(c));
        }
        System.out.println(sb1.toString());
    }

    public Node buildPrefixEncodeTree(Node[] n, float[] freq, char[] chs) {
        PriorityQueue<Node> pQ = new PriorityQueue<>(new Comparator<Node>() {
            public int compare(Node o1, Node o2) {
                return o1.item.freq > o2.item.freq ? 1 : o1.item.freq == o2.item.freq ? 0 : -1;
            };
        });
        Node e = null;
        for (int i = 0; i < chs.length; i++) {
            n[i] = e = new Node(null, null, new Item(chs[i], freq[i]));
            pQ.add(e);
        }

        for (int i = 0; i < n.length - 1; i++) {
            Node x = pQ.poll(), y = pQ.poll();
            Node z = new Node(x, y, new Item('$', x.item.freq + y.item.freq));
            pQ.add(z);
        }
        return pQ.poll();
    }

    /**
     * tranversal  
     * @param root
     * @param collector
     * @param sb
     */
    public void tranversalPrefixEncodeTree(Node root, Map<Character, String> collector, StringBuilder sb) {
        // leaf node
        if (root.left == null && root.right == null) {
            collector.put(root.item.c, sb.toString());
            return;
        }
        Node left = root.left, right = root.right;
        tranversalPrefixEncodeTree(left, collector, sb.append(0));
        sb.delete(sb.length() - 1, sb.length());
        tranversalPrefixEncodeTree(right, collector, sb.append(1));
        sb.delete(sb.length() - 1, sb.length());
    }

}

class Node {
    public Node left, right;
    public Item item;

    public Node(Node left, Node right, Item item) {
        super();
        this.left = left;
        this.right = right;
        this.item = item;
    }

}

class Item {
    public char c;
    public float freq;

    public Item(char c, float freq) {
        super();
        this.c = c;
        this.freq = freq;
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77
  • 78
  • 79
  • 80
  • 81
  • 82
  • 83
  • 84
  • 85
  • 86
  • 87
  • 88
  • 89
  • 90
  • 91
  • 92
  • 93
  • 94
  • 95
  • 96
  • 97
  • 98
  • 99
  • 100
  • 101

输出如下:

{a=0, b=101, c=100, d=111, e=1101, f=1100}
010110001011001101110011011100110111001101010110011110111011011100101110110111001010101100
  • 1
  • 2

4 堆的应用:海量实数中(一亿级别以上)找到TopK(一万级别以下)的数集合。

  • A:通常遇到找一个集合中的TopK问题,想到的便是排序,因为常见的排序算法例如快排算是比较快了,然后再取出K个TopK数,时间复杂度为O(nlogn),当n很大的时候这个时间复杂度还是很大的;

  • B:另一种思路就是打擂台的方式,每个元素与K个待选元素比较一次,时间复杂度很高:O(k*n),此方案明显逊色于前者。

对于一亿数据来说,A方案大约是26.575424*n;

  • C:由于我们只需要TopK,因此不需要对所有数据进行排序,可以利用堆得思想,维护一个大小为K的小顶堆,然后依次遍历每个元素e, 若元素e大于堆顶元素root,则删除root,将e放在堆顶,然后调整,时间复杂度为logK;若小于或等于,则考察下一个元素。这样遍历一遍后,最小堆里面保留的数就是我们要找的topK,整体时间复杂度为O(k+n*logk)约等于O(n*logk),大约是13.287712*n(由于k与n数量级差太多),这样时间复杂度下降了约一半。

A、B、C三个方案中,C通常是优于B的,因为logK通常是小于k的,当K和n的数量级相差越大,这种方式越有效。

以下为具体操作:

import java.io.File;
import java.io.FileNotFoundException;
import java.io.PrintWriter;
import java.io.UnsupportedEncodingException;
import java.util.Arrays;
import java.util.Scanner;
import java.util.Set;
import java.util.TreeSet;
public class TopKNumbersInMassiveNumbersDemo {

    public static void main(String[] args) {
        // TODO Auto-generated method stub
        int[] topK = new int[]{50001,50002,50003,50004,50005};
        genData(1000 * 1000 * 1000, 500, topK);
        long t = System.currentTimeMillis();
        findTopK(topK.length);
        System.out.println(String.format("cost:%fs", (System.currentTimeMillis() - t) * 1.0 / 1000));
    }

    public static void genData(int N, int maxRandomNumer, int[] topK) {
        File f = new File("data.txt");
        int k = topK.length;
        Set<Integer> index = new TreeSet<>();
        for (;;) {
            index.add((int)(Math.random() * N));
            if (index.size() == k)
                break;
        }
        System.out.println(index);
        int j = 0;
        try {
            PrintWriter pW = new PrintWriter(f, "UTF-8");
            for (int i = 0; i < N; i++)
                if(!index.contains(i))
                    pW.println((int)(Math.random() * maxRandomNumer));
                else
                    pW.println(topK[j++]);
            pW.flush();
        } catch (FileNotFoundException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        } catch (UnsupportedEncodingException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
    }

    public static void findTopK(int k) {
        int[] nums = new int[k];
        //read
        File f = new File("data.txt");
        try {
            Scanner scanner = new Scanner(f);
            for (int j = 0;j < k; j++)
                nums[j] = scanner.nextInt();
            heapify(nums);
            //core
            while (scanner.hasNextInt()) {
                int a = scanner.nextInt();
                if (a <= nums[0])
                    continue;
                else {
                    nums[0] = a;
                    siftDown(0, k, nums);
                }
            }
            System.out.println(Arrays.toString(nums));
        } catch (FileNotFoundException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }

    }

    //O(n), minimal heap
    public static void heapify(int[] nums) {
        int size = nums.length;
        for (int j = (size - 1) >> 1; j >= 0; j--)
            siftDown(j, size, nums);
    }

    private static void siftDown(int i, int n, int[] nums) {
        int key = nums[i];
        for (;i < (n >>> 1);) {
            int child = (i << 1) + 1;
            if (child + 1 < n && nums[child] > nums[child+1])
                child++;
            if (key <= nums[child])
                break;
            nums[i] = nums[child];
            i = child;
        }
        nums[i] = key;
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77
  • 78
  • 79
  • 80
  • 81
  • 82
  • 83
  • 84
  • 85
  • 86
  • 87
  • 88
  • 89
  • 90
  • 91
  • 92
  • 93
  • 94
  • 95

ps:大致测试了一下,10亿个数中找到top5需要140秒左右,应该是很快了。

5 总结

  • 堆是基于树的满足一定约束的重要数据结构,存在许多变体例如二叉堆、二项式堆、斐波那契堆(很高效)等。
  • 堆的几个基本操作都依赖于两个重要的函数siftUp和siftDown,堆的insert通常是在堆尾插入新元素并siftUp调整堆,而extractMin是在 
    删除堆顶元素,然后将最后一个元素放置堆顶并调用siftDown调整堆。
  • 二叉堆是常用的一种堆,其是一棵二叉树;由于二叉树良好的性质,因此常常采用数组来存储堆。 
    堆得基本操作的时间复杂度如下表所示:
heapifyinsertpeekextractMindelete(i)
O(n)O(logn)O(1)O(logn)O(logn)

- 二叉堆通常被用来实现堆排序算法,堆排序可以sort in place,堆排序的时间复杂度的上界是O(nlogn),是一种很优秀的排序算法。由于存在相同键值的两个元素处于两棵子树中,而两个元素的顺序可能会在后续的堆调整中发生改变,因此堆排序不是稳定的。降序排序需要建立小顶堆,升序排序需要建立大顶堆。

  • 堆是实现抽象数据类型优先队列的一种方式,优先队列有很广泛的应用,例如Huffman编码中使用优先队列利用贪心算法构建最优前缀编码树。

  • 堆的另一个应用就是在海量数据中找到TopK个数,思想是维护一个大小为K的二叉堆,然后不断地比较堆顶元素,判断是否需要执行替换对顶元素的操作,采用 
    此方法的时间复杂度为n*logk,当k和n的数量级差距很大的时候,这种方式是很有效的方法。

infercnv的实操 摘要: 博主在infercnv分析中遇到执行困难,外包服务不可靠(未按要求使用HMM方法)。通过优化策略(逐样本处理、精简细胞至1000个、固定随机种子)解决了运行问题,但HMM功能仍无法实现。最终参考Cell期刊标准设置阈值完成分析,后续将继续更新进展。该案例凸显了生物信息分析中自主优化的重要性。(150字) 阅读详情

相关推荐

WD西部数据2TB,2.5寸移动硬盘,因为磁头坏了,长时间通电导致划片划伤,维修过程通过反复更换磁头

WD西部数据2TB,2.5寸移动硬盘,因为磁头坏了,长时间通电导致划片划伤,维修过程通过反复更换磁头,才能利用专业设备,里面找到,镜像,把内容数据恢复出来。 1.说一下经历,借用的别人移动硬盘,去帮别人维修,拷数据出来,别人的文件,数据比较多,且复杂,而且是在PE系统,接口什么的是2.0,没有很好的思考就全选复制拷贝了; 2,拷贝的过程,极其慢,文件多,且文件有损坏,移动硬盘一直处于通电的状态,大概从下午,两点多开始100多G的数据,一直卡住,到,晚上7 。8点拷完还是没拷完。打开的时候就已经,就一直

weixin_43766920的博客 4301

堆的应用

本文主要整理关于堆的应用场景:优先级队列、TopK问题、利用堆求中位数

833

Python实战:用SkillMetrics库5分钟搞定泰勒图绘制(附完整代码)

本文详细介绍了如何使用Python的SkillMetrics库快速绘制专业泰勒图,以直观对比多个模型的预测性能。通过整合相关系数、标准差和中心化均方根误差三个核心指标,该工具能帮助数据科学家和研究者高效完成模型评估与可视化,提升分析报告质量。文章提供了从环境配置、数据准备到高级定制与子图对比的完整代码示例。

7z8x2c4v6b9的博客 371

堆及堆排序应用

堆结构、堆排序及其应用

smz的博客 498

激光甲烷传感器DIY指南:从DFB激光器选型到Herriott池光路搭建

本文详细介绍了如何DIY激光甲烷传感器,从DFB激光器选型到Herriott池光路搭建的全过程。通过TDLAS技术实现ppm级甲烷检测,适用于家用燃气泄漏监测和农业沼气监测等场景。文章包含核心器件选型、光路搭建技巧、信号调理方案及系统集成优化,为创客提供实用指南。

f9g0h的博客 594

WPF MVVM实战:构建可绑定的RichTextBox富文本编辑器

本文详细介绍了如何在WPF MVVM模式下构建可绑定的RichTextBox富文本编辑器,解决标准RichTextBox.Document属性不支持数据绑定的问题。通过创建自定义BindableRichTextBox控件,实现FlowDocument的绑定支持,并分享插入图片、文本格式控制等实战技巧,帮助开发者高效开发富文本编辑功能。

weixin_33717117的博客 305

【ZZ】堆和堆的应用:堆排序和优先队列

堆和堆的应用:堆排序和优先队列 https://mp.weixin.qq.com/s/dM8IHEN95IvzQaUKH5zVXw 堆和堆的应用:堆排序和优先队列 2018-02-27算法与数据结构 来源:Spground spground.github.io/2017/07/07/堆和堆的应用:堆排序和优先队列/ 1.堆 堆(Heap))...

weixin_34088583的博客 113

从仿真到真机:如何将XTDrone的VINS-Fusion+RTABMap建图方案部署到真实无人机上?

本文详细介绍了如何将XTDrone的VINS-Fusion+RTABMap建图方案从仿真环境迁移到真实无人机上。内容涵盖硬件选型、传感器配置、VINS-Fusion调优、RTABMap性能优化以及系统集成等关键步骤,特别针对三维稠密重建在真实环境中的挑战提供了实用解决方案。

weixin_29056781的博客 241

Java基于优先队列的贪心算法_堆和堆的应用:堆排序和优先队列

1.堆堆(Heap))是一种重要的数据结构,是实现优先队列(PriorityQueues)首选的数据结构。由于堆有很多种变体,包括二项式堆、斐波那契堆等,但是这里只考虑最常见的就是二叉堆(以下简称堆)。堆是一棵满足一定性质的二叉树,具体的讲堆具有如下性质:父节点的键值总是不大于它的孩子节点的键值(小顶堆),堆可以分为小顶堆和大顶堆,这里以小顶堆为例,其主要包含的操作有:insert()extrac...

weixin_32541333的博客 362

AD22等长布线规则

本文介绍了PCB布线中实现信号线等长的详细操作步骤:1.创建网络类(NetClass),将需要等长的信号线归入同一组;2.设置高速布线规则(High-SpeedRule),定义等长容差等参数;3.进行交互式等长布线(InteractiveLengthTuning),使用蛇形线调整线长;4.实时调整与最终检查,确保所有网络满足等长要求。文中还提供了实用技巧,如使用快捷键调整蛇形线参数、为关键信号设置更严格规则等。操作需在PCB设计软件中完成,通过统一管理、规则设置和精确调整来实现信号同步传输。

whm128的博客 1556

在 ABAP 世界把 SAML 2.0 玩明白:跨域 SSO 的通信模式、配置路径与落地实践

摘要 SAML 2.0是ABAP平台实现标准化SSO的推荐方案,提供Front-Channel(浏览器传递SAML报文)和Back-Channel(使用工件绑定通过SOAP兑换断言)两种通信方式。前者包括HTTP Redirect和POST绑定,后者通过Artifact绑定提升安全性。配置重点包括:激活ICF服务、使用SAML2事务码建立信任、设置用户标识映射、选择绑定方式(POST易调试、Artifact更安全)及配置安全策略(签名/加密)。典型流程包括SP-initiated和IdP-initiated

2007 年 ~ 2025 年,深耕 SAP 技术 18 年 290

掌握堆排序与优先队列

这种结构特性使得堆的根节点始终是当前集合中的极值。值得注意的是,堆并非优先队列的唯一实现方式,有序数组或链表也可实现,但其插入或删除操作的时间复杂度为O(n),不及堆的实现高效。此外,除了标准的二叉堆,还有诸如二项堆、斐波那契堆等更复杂的堆结构,它们在某些操作(如合并多个优先队列)上具有更好的摊还时间复杂度,但实现也更为复杂。这要求我们不仅能够编码实现,更要洞悉其背后的完全二叉树与数组索引的映射关系(通常,对于下标为i的节点,其父节点下标为(i-1)/2,左子节点下标为2i+1,右子节点下标为2i+2)。

etssde_840的博客 176

STM32 W5500 Freemodbus Modbus TCP协议功能实现

STM32F103VCT6 W5500(SPI1),实现Modbus-TCP协议的功能

坚韧与专注 2万+

堆排序:优先队列的应用

堆排序是一种高效的排序算法,掌握它不仅有助于解决相关的算法问题,也能帮助我们更好地理解堆数据结构。希望这篇文章对大家有所帮助,祝大家刷题愉快!本文由 cannonjinx 原创,转载请注明出处。

cannonjinx的博客 4129

jdk-8u144-linux-x64.tar.gz.

该资源为搭建Linux系统Java环境用,资源的后缀为tar.gz格式的压缩包

深度学习工具包 DeepLearningToolbox

深度学习工具包Deprecation notice.-----This toolbox is outdated and no longer maintained.There are much better tools available for deep learning than this toolbox, e.g. [Theano](http://deeplearning.net/software/theano/), [torch](http://torch.ch/) or [tensorflow](http://www.tensorflow.org/)I would suggest you use one of the tools mentioned above rather than use this toolbox.Best, Rasmus.DeepLearnToolbox================A Matlab toolbox for Deep Learning.Deep Learning is a new subfield of machine learning that focuses on learning deep hierarchical models of data.It is inspired by the human brain's apparent deep (layered, hierarchical) architecture.A good overview of the theory of Deep Learning theory is[Learning Deep Architectures for AI](http://www.iro.umontreal.ca/~bengioy/papers/ftml_book.pdf)For a more informal introduction, see the following videos by Geoffrey Hinton and Andrew Ng.* [The Next Generation of Neural Networks](http://www.youtube.com/watch?v=AyzOUbkUf3M) (Hinton, 2007)* [Recent Developments in Deep Learning](http://www.youtube.com/watch?v=VdIURAu1-aU) (Hinton, 2010)* [Unsupervised Feature Learning and Deep Learning](http://www.youtube.com/watch?v=ZmNOAtZIgIk) (Ng, 2011)If you use this toolbox in your research please cite [Prediction as a candidate for learning deep hierarchical models of data](http://www2.imm.dtu.dk/pubdb/views/publication_details.php?id=6284)```@MASTERSTHESIS\{IMM2012-06284, author = "R. B. Palm", title = "Prediction as a candidate for learning deep hierarchical models of data", year = "2012",}```Contact: rasmusbergpalm at gmail dot comDirectories included in the toolbox-----------------------------------`NN/` - A library for Feedforward Backpropagation Neural Networks`CNN/` - A library for Convolutional Neural Networks`DBN/` - A library for Deep Belief Networks`SAE/` - A library for Stacked Auto-Encoders`CAE/` - A library for Convolutional Auto-Encoders`util/` - Utility functions used by the libraries`data/` - Data used by the examples`tests/` - unit tests to verify toolbox is workingFor references on each library check REFS.mdSetup-----1. Download.2. addpath(genpath('DeepLearnToolbox'));Example: Deep Belief Network---------------------```matlabfunction test_example_DBNload mnist_uint8;train_x = double(train_x) / 255;test_x = double(test_x) / 255;train_y = double(train_y);test_y = double(test_y);%% ex1 train a 100 hidden unit RBM and visualize its weightsrand('state',0)dbn.sizes = [100];opts.numepochs = 1;opts.batchsize = 100;opts.momentum = 0;opts.alpha = 1;dbn = dbnsetup(dbn, train_x, opts);dbn = dbntrain(dbn, train_x, opts);figure; visualize(dbn.rbm{1}.W'); % Visualize the RBM weights%% ex2 train a 100-100 hidden unit DBN and use its weights to initialize a NNrand('state',0)%train dbndbn.sizes = [100 100];opts.numepochs = 1;opts.batchsize = 100;opts.momentum = 0;opts.alpha = 1;dbn = dbnsetup(dbn, train_x, opts);dbn = dbntrain(dbn, train_x, opts);%unfold dbn to nnnn = dbnunfoldtonn(dbn, 10);nn.activation_function = 'sigm';%train nnopts.numepochs = 1;opts.batchsize = 100;nn = nntrain(nn, train_x, train_y, opts);[er, bad] = nntest(nn, test_x, test_y);assert(er < 0.10, 'Too big error');```Example: Stacked Auto-Encoders---------------------```matlabfunction test_example_SAEload mnist_uint8;train_x = double(train_x)/255;test_x = double(test_x)/255;train_y = double(train_y);test_y = double(test_y);%% ex1 train a 100 hidden unit SDAE and use it to initialize a FFNN% Setup and train a stacked denoising autoencoder (SDAE)rand('state',0)sae = saesetup([784 100]);sae.ae{1}.activation_function = 'sigm';sae.ae{1}.learningRate = 1;sae.ae{1}.inputZeroMaskedFraction = 0.5;opts.numepochs = 1;opts.batchsize = 100;sae = saetrain(sae, train_x, opts);visualize(sae.ae{1}.W{1}(:,2:end)')% Use the SDAE to initialize a FFNNnn = nnsetup([784 100 10]);nn.activation_function = 'sigm';nn.learningRate = 1;nn.W{1} = sae.ae{1}.W{1};% Train the FFNNopts.numepochs = 1;opts.batchsize = 100;nn = nntrain(nn, train_x, train_y, opts);[er, bad] = nntest(nn, test_x, test_y);assert(er < 0.16, 'Too big error');```Example: Convolutional Neural Nets---------------------```matlabfunction test_example_CNNload mnist_uint8;train_x = double(reshape(train_x',28,28,60000))/255;test_x = double(reshape(test_x',28,28,10000))/255;train_y = double(train_y');test_y = double(test_y');%% ex1 Train a 6c-2s-12c-2s Convolutional neural network %will run 1 epoch in about 200 second and get around 11% error. %With 100 epochs you'll get around 1.2% errorrand('state',0)cnn.layers = { struct('type', 'i') %input layer struct('type', 'c', 'outputmaps', 6, 'kernelsize', 5) %convolution layer struct('type', 's', 'scale', 2) %sub sampling layer struct('type', 'c', 'outputmaps', 12, 'kernelsize', 5) %convolution layer struct('type', 's', 'scale', 2) %subsampling layer};cnn = cnnsetup(cnn, train_x, train_y);opts.alpha = 1;opts.batchsize = 50;opts.numepochs = 1;cnn = cnntrain(cnn, train_x, train_y, opts);[er, bad] = cnntest(cnn, test_x, test_y);%plot mean squared errorfigure; plot(cnn.rL);assert(er<0.12, 'Too big error');```Example: Neural Networks---------------------```matlabfunction test_example_NNload mnist_uint8;train_x = double(train_x) / 255;test_x = double(test_x) / 255;train_y = double(train_y);test_y = double(test_y);% normalize[train_x, mu, sigma] = zscore(train_x);test_x = normalize(test_x, mu, sigma);%% ex1 vanilla neural netrand('state',0)nn = nnsetup([784 100 10]);opts.numepochs = 1; % Number of full sweeps through dataopts.batchsize = 100; % Take a mean gradient step over this many samples[nn, L] = nntrain(nn, train_x, train_y, opts);[er, bad] = nntest(nn, test_x, test_y);assert(er < 0.08, 'Too big error');%% ex2 neural net with L2 weight decayrand('state',0)nn = nnsetup([784 100 10]);nn.weightPenaltyL2 = 1e-4; % L2 weight decayopts.numepochs = 1; % Number of full sweeps through dataopts.batchsize = 100; % Take a mean gradient step over this many samplesnn = nntrain(nn, train_x, train_y, opts);[er, bad] = nntest(nn, test_x, test_y);assert(er < 0.1, 'Too big error');%% ex3 neural net with dropoutrand('state',0)nn = nnsetup([784 100 10]);nn.dropoutFraction = 0.5; % Dropout fraction opts.numepochs = 1; % Number of full sweeps through dataopts.batchsize = 100; % Take a mean gradient step over this many samplesnn = nntrain(nn, train_x, train_y, opts);[er, bad] = nntest(nn, test_x, test_y);assert(er < 0.1, 'Too big error');%% ex4 neural net with sigmoid activation functionrand('state',0)nn = nnsetup([784 100 10]);nn.activation_function = 'sigm'; % Sigmoid activation functionnn.learningRate = 1; % Sigm require a lower learning rateopts.numepochs = 1; % Number of full sweeps through dataopts.batchsize = 100; % Take a mean gradient step over this many samplesnn = nntrain(nn, train_x, train_y, opts);[er, bad] = nntest(nn, test_x, test_y);assert(er < 0.1, 'Too big error');%% ex5 plotting functionalityrand('state',0)nn = nnsetup([784 20 10]);opts.numepochs = 5; % Number of full sweeps through datann.output = 'softmax'; % use softmax outputopts.batchsize = 1000; % Take a mean gradient step over this many samplesopts.plot = 1; % enable plottingnn = nntrain(nn, train_x, train_y, opts);[er, bad] = nntest(nn, test_x, test_y);assert(er < 0.1, 'Too big error');%% ex6 neural net with sigmoid activation and plotting of validation and training error% split training data into training and validation datavx = train_x(1:10000,:);tx = train_x(10001:end,:);vy = train_y(1:10000,:);ty = train_y(10001:end,:);rand('state',0)nn = nnsetup([784 20 10]); nn.output = 'softmax'; % use softmax outputopts.numepochs = 5; % Number of full sweeps through dataopts.batchsize = 1000; % Take a mean gradient step over this many samplesopts.plot = 1; % enable plottingnn = nntrain(nn, tx, ty, opts, vx, vy); % nntrain takes validation set as last two arguments (optionally)[er, bad] = nntest(nn, test_x, test_y);assert(er < 0.1, 'Too big error');```[![Bitdeli Badge](https://d2weczhvl823v0.cloudfront.net/rasmusbergpalm/deeplearntoolbox/trend.png)](https://bitdeli.com/free "Bitdeli Badge")

堆排序

堆 堆结构就是用数组实现的完全二叉树 完全二叉树如果子树最大值在顶部就是大根堆 完全二叉树如果子树最小值都在顶部就是小根堆 优先队列操作,就是堆操作 堆排序 1.先让整个数组都变成大根堆,建立堆过程。 1) 从上到小下,时间复杂度为O(N*logN) 2)从下到上的方法,时间复杂度为0(N) 2. 把堆的最大值和堆末尾的值交换,然后减少堆的大小之后再去调整堆,一直周而复始,时间复杂度为O(N ...

weixin_38522652的博客 132

复分析 阿尔福斯(Ahlfors) 第三版答案 英文

答案分为十章,有部分习题没有答案,全英文复分析阿尔福斯(Ahlfors) 第三版答案

堆的优先队列2

接着上一Pa说。就是如何建立这个堆呢。可以从空的堆开始,然后依次往堆中插入每一个元素,直到所有数都被插入(转移到堆中为止)。因为插入第i个元素的所用的时间是O(log i),所以插入所有元素的整体时间复杂度是O(NlogN),代码如下。 1 2 3 4 5 6 7 n=0; for(i=1;i {     n++;     h[ n]

wxliu1989的专栏 372

堆排序原理与实现:大顶堆与小顶堆的应用

堆排序的核心原理是:通过构建堆(通常使用大顶堆)并反复提取最大元素(堆顶),将其放置在数组末尾,逐步实现排序。整个过程时间复杂度为$O(n \log n)$,空间复杂度为$O(1)$,是一种原地排序算法。堆排序是一种高效的排序算法,基于完全二叉树的数据结构——堆。以下代码实现了基于大顶堆的堆排序。用户可轻松修改为小顶堆(只需在比较操作中反转符号)。堆排序通过合理利用堆的性质,实现了高效且稳定的排序,是大数据处理和实时系统的常用算法。

2501_93877981的博客 421

3分钟搞懂堆排序优化:从优先队列到OI实战技巧

堆排序是一种基于二叉堆数据结构的高效排序算法,其核心优势在于**时间复杂度稳定为O(n log n)**,且能原地排序节省内存空间。在算法竞赛(OI/ICPC)中,堆排序不仅是基础排序手段,更与优先队列、贪心策略等高级算法深度结合,成为解决复杂问题的关键工具。本文将从基础原理到实战优化,带你快速掌握堆排序的核心技巧。 ## 一、堆排序核心原理:从二叉堆到排序流程 ### 1.1 二叉堆的本质

gitblog_00575的博客 1105

【alg4-排序】堆排序

堆排序可以分为两个阶段。在堆的构造阶段中,我们将原始数组重新组织安排进一个堆中;然后在下沉排序阶段,我们从堆中按递减顺序取出所有元素并得到排序结果。 堆的构造:从右至左用sink()函数构造子堆。数组的每个位置都已经是一个子堆的根结点了,sink()对于这些子堆也适用。如果一个结点的两个子结点都已经是堆了,那么在该结点上调用sink()可以将它们变成一个堆。这个过程会递归地建立起堆的秩序。开始时我们只需要扫描数组中的一半元素,因为我们可以跳过大小为1的子堆。最后我们在位置1上调用sink()方法,扫描结束。

... 270

从零开始学二叉树(中):堆与完全二叉树的奥秘

为什么完全二叉树能用数组存?堆排序为什么快?Top-K 问题为何反直觉地用小堆?这篇博客将会给你答案

dongaoran的博客 6206

一文读懂特殊的树结构——堆

定义 堆是计算机科学中一类特殊的数据结构的统称。堆通常是一个可以被看做一棵树的数组对象。根据结点特征可以将堆分为大顶堆和小顶堆两种,将根节点最大的堆叫做最大堆或大顶堆,根节点最小的堆叫做最小堆或小顶堆。 性质 1.堆中某个节点的值总是不大于或不小于其父节点的值。 2.堆总是一棵完全二叉树。 ...

WPQ的博客 405
上一篇: java实现二分查找-两种方式
下一篇: ThreadLocal工作原理
还不够
博客等级 码龄16年 113粉丝 24原创
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

当前余额3.43元 前往充值 >
需支付:10.00元
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付元
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值