堆的介绍
堆是具有以下特定的二叉树:
- 它是完全二叉树。即除了树的最后一层节点不需要是满的,其他的每一层从左到右都完全是满的。
- 它常常用一个数组实现。
- 堆中的每一个节点都满足堆的条件,即每一个节点的关键字都大于(或等于)这个节点的子节点的关键字。
弱序
堆和二叉搜索树相比是弱序的,在二叉搜索树中所有节点的左子节点的关键字都小于右子节点的关键字。
而对于堆来说,只要求沿着根到叶子的每一条路径,节点都是按降序排列的。指定节点的左子节点或者右子节点,以及上层节点或者下层节点,由于不在同一条路径上,它们的关键字可能比指定节点的关键字大或小。除了有共享节点的路径,路径之间都是相互独立的。
移除
即删除关键字最大(或最小,取决于排序规则)的节点。这个节点总是根节点,即数组中索引为0的的元素。
移除根节点后,为了保证堆的特性(完全二叉树),必须填补上这个空的位置。移除根节点的步骤如下:
- 移走根;
- 把最后一个节点移动到根的位置;
- 一直向下筛选这个节点,直到它在一个大于它的节点之下,小于它的节点之上为止。
插入
初识时插入到数组的最后,为了不破坏堆的特性,即子节点不能大于父节点,所以需要向上筛选,直到它在一个大于它的节点之下,在一个小于它的节点之上为止。
Java实现
package com.jikefriend.heap;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
public class Heap {
static class Node {
private int key;
public Node(int k) {
key = k;
}
public int getKey() {
return key;
}
public void setKey(int d) {
key = d;
}
}
private Node[] heapArray;
private int maxSize;
private int currentSize;
public Heap(int ms) {
maxSize = ms;
heapArray = new Node[maxSize];
currentSize = 0;
}
public boolean isEmpty() {
return currentSize == 0;
}
/**
* 插入
*
* @param key 要插入的关键字
* @return
*/
public boolean insert(int key) {
if (currentSize == maxSize) //数组如果已满,无法插入
return false;
Node node = new Node(key);
heapArray[currentSize] = node;
trickleUp(currentSize++); //向上筛选
return true;
}
/**
* 移除,即删除根节点
*
* @return
*/
public Node remove() {
Node root = heapArray[0];
heapArray[0] = heapArray[--currentSize];
trickleDown(0);
return root;
}
/**
* 更改关键字
*
* @param index 要更改的下表
* @param newValue 新的关键字
* @return
*/
public boolean change(int index, int newValue) {
if (index < 0 || index >= currentSize)
return false;
int oldValue = heapArray[index].getKey();
heapArray[index].setKey(newValue);
if (oldValue < newValue) //如果新的关键字比原来的大,则向上筛选
trickleUp(index);
else //如果小,则向下筛选
trickleDown(index);
return true;
}
public void display() {
System.out.print("Heap array: ");
for (int i = 0; i < currentSize; i++) {
if (heapArray[i] != null)
System.out.print(heapArray[i].getKey() + " ");
else
System.out.print("-- ");
}
System.out.println();
int nBlanks = 32;
int itemsPerRow = 1;
int column = 0;
int j = 0;
String dots = "***********************************";
System.out.println(dots + dots);
while (currentSize > 0) {
if (column == 0)
for (int i = 0; i < nBlanks; i++) {
System.out.print(" ");
}
System.out.print(heapArray[j].getKey());
if (++j == currentSize)
break;
if (++column == itemsPerRow) {
nBlanks /= 2;
itemsPerRow *= 2;
column = 0;
System.out.println();
} else {
for (int i = 0; i < nBlanks * 2 - 2; i++) {
System.out.print(" ");
}
}
}
System.out.println("\n" + dots + dots);
}
/**
* 将节点向上移动到适当位置
*
* @param index 新插入数据项的下标
*/
private void trickleUp(int index) {
int parent = (index - 1) / 2; //其父节点下标
Node bottom = heapArray[index]; //保存当前节点
/** 只要没有到达根,且index的父节点的关键字小于这个新节点,就一直执行 */
while (index > 0 && heapArray[parent].getKey() < bottom.getKey()) {
heapArray[index] = heapArray[parent]; //将其父节点下移
index = parent; //将其父节点下标作为新的节点下标
parent = (parent - 1) / 2; //计算下一个父节点下表
}
heapArray[index] = bottom; //将要插入的节点放入适当的位置
}
/**
* 将节点向下移动到适当位置
*
* @param index
*/
private void trickleDown(int index) {
int largerChild;
Node top = heapArray[index]; //保存根节点
/** 只要还有一个子节点就一直执行 */
while (index < currentSize / 2) {
int leftChild = 2 * index + 1;
int rightChild = leftChild + 1;
/** 找出较大的子节点 */
if (rightChild < currentSize && heapArray[leftChild].getKey() < heapArray[rightChild].getKey())
largerChild = rightChild;
else
largerChild = leftChild;
/** 如果原节点的关键字大于子节点,则向下筛选过程结束 */
if (top.getKey() >= heapArray[largerChild].getKey())
break;
heapArray[index] = heapArray[largerChild]; //较大子节点上移
index = largerChild;
}
heapArray[index] = top;
}
public static void main(String[] args) throws IOException{
int value, value2;
Heap heap = new Heap(31);
boolean success;
heap.insert(70);
heap.insert(40);
heap.insert(50);
heap.insert(20);
heap.insert(60);
heap.insert(100);
heap.insert(80);
heap.insert(30);
heap.insert(10);
heap.insert(90);
while (true) {
System.out.print("Enter first letter of ");
System.out.print("show, insert, delete, or change: ");
char choice = getChar();
switch (choice) {
case 's':
heap.display();
break;
case 'i':
System.out.print("Enter key value to insert: ");
value = getInt();
success = heap.insert(value);
if (!success)
System.out.println("Can't insert, heap full");
break;
case 'r':
if (!heap.isEmpty())
heap.remove();
else
System.out.print("Can't remove, heap empty");
break;
case 'c':
System.out.print("Enter current index of item: ");
value = getInt();
System.out.print("Enter new key: ");
value2 = getInt();
success = heap.change(value, value2);
if (!success)
System.out.println("Invalid index");
break;
default:
System.out.print("Invalid entry\n");
break;
}
}
}
public static String getString() throws IOException
{
InputStreamReader isr = new InputStreamReader(System.in);
BufferedReader br = new BufferedReader(isr);
return br.readLine();
}
public static char getChar() throws IOException
{
return getString().charAt(0);
}
public static int getInt() throws IOException
{
return Integer.parseInt(getString());
}
}
堆排序
堆排序的基本思想是使用不同的insert()在堆中插入全部无序的数据项,然后重复用remove()来有序的移除所有数据项。
堆排序的效率
堆排序的时间复杂度为O(N*logN)。尽管比快速排序略慢,但较快速排序的优点是对厨师数据的分布不敏感。在关键字存在某种排列顺序的情况下,快速排序的时间复杂度会降低到O(N^2),而堆排序怼任意排列的数据,其时间复杂度都是O(N*logN)。
提升效率的几个方法
向下筛选到适当的位置
所有的数据项字数组中都是任意排列的,然后通过重新排列成堆,这样就只需要应用N/2次的 向下筛选方法。
由两个正确的子堆形成一个正确的堆。如果有一个不遵守堆有序条件的项占据了根的位置,而它的两个子堆都是正确的堆,用向下筛选方法也可以得到一个正确的堆。即从数组末端的节点开始,然后上行到根的各个节点都应用向下筛选方法,就可以将一个无序的数组变为一个堆。对于那些没有子节点的节点,已经是正确的堆了,无序使用向下筛选的方法。
使用同一个数组
每从堆顶移除一个数据项,堆数组的末端单元就变为空的,堆减小一个节点。此时可以把最近一次移除的节点放到这个新空出的单元中。
Java实现
package com.jikefriend.heap;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
public class HeapSort {
static class Node {
private int key;
public Node(int k) {
key = k;
}
public int getKey() {
return key;
}
public void setKey(int d) {
key = d;
}
}
static class Heap {
private Node[] heapArray;
private int maxSize;
private int currentSize;
public Heap(int ms) {
maxSize = ms;
heapArray = new Node[maxSize];
currentSize = 0;
}
public boolean isEmpty() {
return currentSize == 0;
}
public void insertAt(int index, Node node) {
heapArray[index] = node;
}
public void incrementSize() {
currentSize++;
}
public Node remove() {
Node root = heapArray[0];
heapArray[0] = heapArray[--currentSize];
trickleDown(0);
return root;
}
private void trickleDown(int index) {
int largerChild;
Node top = heapArray[index];
while (index < currentSize / 2) {
int leftChild = 2 * index + 1;
int rightChild = leftChild + 1;
if (rightChild < currentSize && heapArray[leftChild].getKey() < heapArray[rightChild].getKey())
largerChild = rightChild;
else
largerChild = leftChild;
if (top.getKey() >= heapArray[largerChild].getKey())
break;
heapArray[index] = heapArray[largerChild];
index = largerChild;
}
heapArray[index] = top;
}
public void display() {
int nBlanks = 32;
int itemsPerRow = 1;
int column = 0;
int j = 0;
String dots = "***********************************";
System.out.println(dots + dots);
while (currentSize > 0) {
if (column == 0)
for (int i = 0; i < nBlanks; i++) {
System.out.print(" ");
}
System.out.print(heapArray[j].getKey());
if (++j == currentSize)
break;
if (++column == itemsPerRow) {
nBlanks /= 2;
itemsPerRow *= 2;
column = 0;
System.out.println();
} else {
for (int i = 0; i < nBlanks * 2 - 2; i++) {
System.out.print(" ");
}
}
}
System.out.println("\n" + dots + dots);
}
public void displayArray() {
for (int i = 0; i < maxSize; i++) {
System.out.print(heapArray[i].getKey() + " ");
}
System.out.println();
}
}
public static void main(String[] args) throws IOException{
int size, j;
System.out.print("Enter number of items: ");
size = getInt();
Heap heap = new Heap(size);
<span style="white-space:pre"> </span>/** 用随机的数据项填满数组 */
for (int i = 0; i < size; i++) {
int random = (int)(Math.random() * 100);
Node node = new Node(random);
heap.insertAt(i, node);
heap.incrementSize();
}
System.out.print("Random: ");
heap.displayArray();
<span style="white-space:pre"> </span>/** 执行N/2次向下筛选方法,把数组转变为堆 */
for (int i = size / 2 - 1; i >= 0 ; i--) {
heap.trickleDown(i);
}
System.out.print("Heap: ");
heap.displayArray();
heap.display();
<span style="white-space:pre"> </span>/** 从堆中移除数据项,并把它们写回数组的末端 */
for (int i = size - 1; i >= 0; i--) {
Node biggestNode = heap.remove();
heap.insertAt(i, biggestNode);
}
System.out.print("Sorted: ");
heap.displayArray();
}
public static String getString() throws IOException
{
InputStreamReader isr = new InputStreamReader(System.in);
BufferedReader br = new BufferedReader(isr);
return br.readLine();
}
public static int getInt() throws IOException
{
return Integer.parseInt(getString());
}
}
摘自《 Java 数据结构 与 算法 (第二版)》 [美] Robert Lafore 著