# 堆排序和优先队列

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;

}

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;

}

// 建立小顶堆

private void buildMinHeap(int[] nums) {

int size = nums.length;

for (int j = size / 2 - 1; j >= 0; j--)

siftDown(nums, j, size);

}

extractMin

peek

delete(i)

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;

}

case 1 :

case 2

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

*/

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

*/

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)

*/

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

*/

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)

*/

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);

}

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();

}

}

2.堆的应用：堆排序

Trick

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();

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;

}

3.堆的应用：优先队列

Dijkstra’s algorithm（单源最短路问题中需要在邻接表中找到某一点的最短邻接边，这可以将复杂度降低。）

Huffman coding（贪心算法的一个典型例子，采用优先队列构建最优的前缀编码树(prefixEncodeTree)）

Prim’s algorithm for minimum spanning tree

Best-first search algorithms

Huffman编码是一种变长的编码方案，对于每一个字符，所对应的二进制位串的长度是不一致的，但是遵守如下原则：

Huffman编码的实现就是要找到满足这两种原则的 字符-二进制位串 对照关系，即找到最优前缀码的编码方案（前缀码：没有任何字符编码后的二进制位串是其他字符编码后位串的前缀）。

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 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 pQ = new PriorityQueue<>(new Comparator() {

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]));

}

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));

}

return pQ.poll();

}

/**

* tranversal

* @param root

* @param collector

* @param sb

*/

public void tranversalPrefixEncodeTree(Node root, Map 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

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

010110001011001101110011011100110111001101010110011110111011011100101110110111001010101100

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

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

B:另一种思路就是打擂台的方式,每个元素与K个待选元素比较一次，时间复杂度很高：O(k*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 index = new TreeSet<>();

for (;;) {

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];

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;

}

}

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

5 总结

heapifyinsertpeekextractMindelete(i)

O(n)O(logn)O(1)O(logn)O(logn)

### 推荐阅读更多精彩内容

• 转载自：https://egoistk.github.io/2016/09/10/Java%E6%8E%92%E5...