前言
优先级队列是一种对最大(最小)关键字的数据提供便利访问的数据结构。
可用于计算机中的人物调度,在计算机中某些程序和活动需要比其他的程序和活动优先执行,因此需要分配更高的优先级。
优先级队列是一种抽象数据类型(ADT),它提供了删除最大(或最小)关键字值的数据项的方法,插入数据项的方法,优先级队列可以用有序数组来实现,这种实现方式尽管删除最大数据项的时间复杂度为O(1),但是插入还是需要较长的时间 O(N),因为每次插入平均需要移动一半的数据项,来保证插入后,数组依旧有序。
堆:优先级队列的另一种结构。用它来实现优先级队列的插入和删除时间复杂度都是O(logN),虽然删除速度变慢了,但是插入时间快很多。
堆的介绍
1、它是完全二叉树,除了树的最后一层节点不需要是满的,其它的每一层从左到右都是满的。注意下面两种情况,第二种最后一层从左到右中间有断隔,那么也是不完全二叉树。
2、它通常用数组来实现。
堆是完全二叉树的事实说明了堆的数组中没有洞,从下标0到N-1,每一个数据单元都有数据
这种用数组实现的二叉树,假设节点的索引值为index,那么:
节点的左子节点是 2*index+1,
节点的右子节点是 2*index+2,
节点的父节点是 (index-1)/2。
3、堆中的每一个节点的关键字都大于(或等于)这个节点的子节点的关键字。
优先级队列,堆和抽象数据类型
堆主要用于实现优先级队列,优先级队列和实现它的堆之间有非常紧密的关系。
class Heap
{
private Node[] heapArray;
private int maxSize; // size of array
private int currentSize; // number of nodes in array
// -------------------------------------------------------------
public Heap(int mx) // constructor
{
maxSize = mx;
currentSize = 0;
heapArray = new Node[maxSize]; // create array
}
public void insert(Node nd)
{}
public Node remove()
{}
class priorityQueue{
private Heap theHeap;
public void insert(Node nd){
theHeap.insert(nd);
}
public Node remove(){
return theHeap.remove()
}
}
}
弱序
这里要注意堆和前面说的二叉搜索树的区别,二叉搜索树中所有节点的左子节点关键字都小于右子节点关键字,在二叉搜索树中通过一个简单的算法就可以按序遍历节点。但是在堆中,按序遍历节点是很困难的,如上图所示,堆只有沿着从根节点到叶子节点的每一条路径是降序排列的,指定节点的左边节点或者右边节点,以及上层节点或者下层节点由于不在同一条路径上,他们的关键字可能比指定节点大或者小。所以相对于二叉搜索树,堆是弱序的。
堆不支持遍历,也不能在堆中顺利的查找关键字
对于快速的移除最大(或最小)节点,也就是根节点,以及能快速插入新的节点,这两个操作就足够了。
移除
移除是指删除关键字最大的节点(或最小),也就是根节点,索引总是0。
移除根节点之后,那树就空了一个根节点,也就是数组有了一个空的数据单元,这个空单元我们必须填上。
第一种方法:将数组所有数据项都向前移动一个单元,这比较费时。
第二种方法:
①、移走根
②、把最后一个节点移动到根的位置
③、一直向下筛选这个节点,直到它在一个大于它的节点之下,小于它的节点之上为止。
图a表示把最后一个节点移到根节点,图b、c、d表示将节点向下筛选到合适的位置,它的合适位置在最底层(有时候可能在中间),图e表示节点在正确位置的情景。
注意:向下筛选的时候,将目标节点和其子节点比较,谁大就和谁交换位置。
插入
插入时,选择向上筛选,节点初始时插入到数组最后第一个空着的单元,数组容量大小增一。然后进行向上筛选的算法。
注意:向上筛选和向下不同,向上筛选只用和一个父节点进行比较,比父节点小就停止筛选了。
比较删除和插入,结果不一定是恢复为原来的堆。
一组给定的节点可能有很多种合法的堆,这取决于节点插入的顺序。
不是真的交换
上面的删除和插入操作显示了向下筛选和向上筛选的过程节点交换位置的情况。换位是在概念上理解插入和删除最简单的方法。并且实际上某些堆的实现也确实使用了换位。
一次交换需要三次复制,因此采用b中的复制替代交换,可以减少所需要的复制总数。
代码实现
// heap.java
// demonstrates heaps
// to run this program: C>java HeapApp
import java.io.*;
class Node
{
private int iData; // data item (key)
// -------------------------------------------------------------
public Node(int key) // constructor
{ iData = key; }
// -------------------------------------------------------------
public int getKey()
{ return iData; }
// -------------------------------------------------------------
public void setKey(int id)
{ iData = id; }
// -------------------------------------------------------------
} // end class Node
class Heap
{
private Node[] heapArray;
private int maxSize; // size of array
private int currentSize; // number of nodes in array
// -------------------------------------------------------------
public Heap(int mx) // constructor
{
maxSize = mx;
currentSize = 0;
heapArray = new Node[maxSize]; // create array
}
// -------------------------------------------------------------
public boolean isEmpty()
{ return currentSize==0; }
// -------------------------------------------------------------
public boolean insert(int key)
{
if(currentSize==maxSize)
return false;
Node newNode = new Node(key);
heapArray[currentSize] = newNode;
trickleUp(currentSize++);
return true;
} // end insert()
// -------------------------------------------------------------
public void trickleUp(int index)
{
int parent = (index-1) / 2;
Node bottom = heapArray[index];
while( index > 0 &&
heapArray[parent].getKey() < bottom.getKey() )
{
heapArray[index] = heapArray[parent]; // move it down
index = parent;
parent = (parent-1) / 2;
} // end while
heapArray[index] = bottom;
} // end trickleUp()
// -------------------------------------------------------------
public Node remove() // delete item with max key
{ // (assumes non-empty list)
Node root = heapArray[0];
heapArray[0] = heapArray[--currentSize];
trickleDown(0);
return root;
} // end remove()
// -------------------------------------------------------------
public void trickleDown(int index)
{
int largerChild;
Node top = heapArray[index]; // save root
while(index < currentSize/2) // while node has at
{ // least one child,
int leftChild = 2*index+1;
int rightChild = leftChild+1;
// find larger child
if(rightChild < currentSize && // (rightChild exists?)
heapArray[leftChild].getKey() <
heapArray[rightChild].getKey())
largerChild = rightChild;
else
largerChild = leftChild;
// top >= largerChild?
if( top.getKey() >= heapArray[largerChild].getKey() )
break;
// shift child up
heapArray[index] = heapArray[largerChild];
index = largerChild; // go down
} // end while
heapArray[index] = top; // root to index
} // end trickleDown()
// -------------------------------------------------------------
public boolean change(int index, int newValue)
{
if(index<0 || index>=currentSize)
return false;
int oldValue = heapArray[index].getKey(); // remember old
heapArray[index].setKey(newValue); // change to new
if(oldValue < newValue) // if raised,
trickleUp(index); // trickle it up
else // if lowered,
trickleDown(index); // trickle it down
return true;
} // end change()
// -------------------------------------------------------------
public void displayHeap()
{
System.out.print("heapArray: "); // array format
for(int m=0; m<currentSize; m++)
if(heapArray[m] != null)
System.out.print( heapArray[m].getKey() + " ");
else
System.out.print( "-- ");
System.out.println();
// heap format
int nBlanks = 32;
int itemsPerRow = 1;
int column = 0;
int j = 0; // current item
String dots = "...............................";
System.out.println(dots+dots); // dotted top line
while(currentSize > 0) // for each heap item
{
if(column == 0) // first item in row?
for(int k=0; k<nBlanks; k++) // preceding blanks
System.out.print(' ');
// display item
System.out.print(heapArray[j].getKey());
if(++j == currentSize) // done?
break;
if(++column==itemsPerRow) // end of row?
{
nBlanks /= 2; // half the blanks
itemsPerRow *= 2; // twice the items
column = 0; // start over on
System.out.println(); // new row
}
else // next item on row
for(int k=0; k<nBlanks*2-2; k++)
System.out.print(' '); // interim blanks
} // end for
System.out.println("\n"+dots+dots); // dotted bottom line
} // end displayHeap()
// -------------------------------------------------------------
} // end class Heap
class HeapApp
{
public static void main(String[] args) throws IOException
{
int value, value2;
Heap theHeap = new Heap(31); // make a Heap; max size 31
boolean success;
theHeap.insert(70); // insert 10 items
theHeap.insert(40);
theHeap.insert(50);
theHeap.insert(20);
theHeap.insert(60);
theHeap.insert(100);
theHeap.insert(80);
theHeap.insert(30);
theHeap.insert(10);
theHeap.insert(90);
while(true) // until [Ctrl]-[C]
{
System.out.print("Enter first letter of ");
System.out.print("show, insert, remove, change: ");
int choice = getChar();
switch(choice)
{
case 's': // show
theHeap.displayHeap();
break;
case 'i': // insert
System.out.print("Enter value to insert: ");
value = getInt();
success = theHeap.insert(value);
if( !success )
System.out.println("Can't insert; heap full");
break;
case 'r': // remove
if( !theHeap.isEmpty() )
theHeap.remove();
else
System.out.println("Can't remove; heap empty");
break;
case 'c': // change
System.out.print("Enter current index of item: ");
value = getInt();
System.out.print("Enter new key: ");
value2 = getInt();
success = theHeap.change(value, value2);
if( !success )
System.out.println("Invalid index");
break;
default:
System.out.println("Invalid entry\n");
} // end switch
} // end while
} // end main()
//-------------------------------------------------------------
public static String getString() throws IOException
{
InputStreamReader isr = new InputStreamReader(System.in);
BufferedReader br = new BufferedReader(isr);
String s = br.readLine();
return s;
}
//-------------------------------------------------------------
public static char getChar() throws IOException
{
String s = getString();
return s.charAt(0);
}
//-------------------------------------------------------------
public static int getInt() throws IOException
{
String s = getString();
return Integer.parseInt(s);
}
//-------------------------------------------------------------
} // end class HeapApp
基于树的堆
一颗完全树,没有空节点,这样的树称为树堆。
堆排序
基本思想是使用普通的insert()在插入全部无序的数据项,然后重用remove(),就可以按序移除所有的数据项:
for(j=0;j<size;j++){
theHeap.insert(anArray[j]); //from unsorted array
}
for(j=0;i<size;j++){
anArray[j] = theHeap.remove(); //to sorted Array
}
每次insert和remove的时间复杂度都是O(logN),并且每次执行N次,因此整个排序操作O(NLogN)
向下筛选到适当位置
如果有一个不遵守堆有序条件的项占据了根位置,而它的两个字堆都是正确的,用trickledown()方法也可以创建一个正确的堆。
利用trickledown实现将无序数组转换为堆
for(int j=size/2;i>=0;j--)
theHeap.trickleDown(j);
使用递归
heapify()方法应用于根。它对根的两个子节点又调用自身,然后对这两个子节点的两个子节点分辨调用自身。以此类推。
heapify(int index){
if(index>N/2-1)
return ;
heapify(index*2+2); //turn right subtree into heap
heapify(index*2+1); //turn left subtree into heap
trickleDown(index); //apply trickleDown to this node
}
使用同一个数组
堆和初始数组可以使用同一个数组 ,堆排序的储存空间减半。
堆排序代码
// heapSort.java
// demonstrates heap sort
// to run this program: C>java HeapSortApp
import java.io.*;
class Node
{
private int iData; // data item (key)
// -------------------------------------------------------------
public Node(int key) // constructor
{ iData = key; }
// -------------------------------------------------------------
public int getKey()
{ return iData; }
// -------------------------------------------------------------
} // end class Node
class Heap
{
private Node[] heapArray;
private int maxSize; // size of array
private int currentSize; // number of items in array
// -------------------------------------------------------------
public Heap(int mx) // constructor
{
maxSize = mx;
currentSize = 0;
heapArray = new Node[maxSize];
}
// -------------------------------------------------------------
public Node remove() // delete item with max key
{ // (assumes non-empty list)
Node root = heapArray[0];
heapArray[0] = heapArray[--currentSize];
trickleDown(0);
return root;
} // end remove()
// -------------------------------------------------------------
public void trickleDown(int index)
{
int largerChild;
Node top = heapArray[index]; // save root
while(index < currentSize/2) // not on bottom row
{
int leftChild = 2*index+1;
int rightChild = leftChild+1;
// find larger child
if(rightChild < currentSize && // right ch exists?
heapArray[leftChild].getKey() <
heapArray[rightChild].getKey())
largerChild = rightChild;
else
largerChild = leftChild;
// top >= largerChild?
if(top.getKey() >= heapArray[largerChild].getKey())
break;
// shift child up
heapArray[index] = heapArray[largerChild];
index = largerChild; // go down
} // end while
heapArray[index] = top; // root to index
} // end trickleDown()
// -------------------------------------------------------------
public void displayHeap()
{
int nBlanks = 32;
int itemsPerRow = 1;
int column = 0;
int j = 0; // current item
String dots = "...............................";
System.out.println(dots+dots); // dotted top line
while(currentSize > 0) // for each heap item
{
if(column == 0) // first item in row?
for(int k=0; k<nBlanks; k++) // preceding blanks
System.out.print(' ');
// display item
System.out.print(heapArray[j].getKey());
if(++j == currentSize) // done?
break;
if(++column==itemsPerRow) // end of row?
{
nBlanks /= 2; // half the blanks
itemsPerRow *= 2; // twice the items
column = 0; // start over on
System.out.println(); // new row
}
else // next item on row
for(int k=0; k<nBlanks*2-2; k++)
System.out.print(' '); // interim blanks
} // end for
System.out.println("\n"+dots+dots); // dotted bottom line
} // end displayHeap()
// -------------------------------------------------------------
public void displayArray()
{
for(int j=0; j<maxSize; j++)
System.out.print(heapArray[j].getKey() + " ");
System.out.println("");
}
// -------------------------------------------------------------
public void insertAt(int index, Node newNode)
{ heapArray[index] = newNode; }
// -------------------------------------------------------------
public void incrementSize()
{ currentSize++; }
// -------------------------------------------------------------
} // end class Heap
class HeapSortApp
{
public static void main(String[] args) throws IOException
{
int size, j;
System.out.print("Enter number of items: ");
size = getInt();
Heap theHeap = new Heap(size);
for(j=0; j<size; j++) // fill array with
{ // random nodes
int random = (int)(java.lang.Math.random()*100);
Node newNode = new Node(random);
theHeap.insertAt(j, newNode);
theHeap.incrementSize();
}
System.out.print("Random: ");
theHeap.displayArray(); // display random array
for(j=size/2-1; j>=0; j--) // make random array into heap
theHeap.trickleDown(j);
System.out.print("Heap: ");
theHeap.displayArray(); // dislay heap array
theHeap.displayHeap(); // display heap
for(j=size-1; j>=0; j--) // remove from heap and
{ // store at array end
Node biggestNode = theHeap.remove();
theHeap.insertAt(j, biggestNode);
}
System.out.print("Sorted: ");
theHeap.displayArray(); // display sorted array
} // end main()
// -------------------------------------------------------------
public static String getString() throws IOException
{
InputStreamReader isr = new InputStreamReader(System.in);
BufferedReader br = new BufferedReader(isr);
String s = br.readLine();
return s;
}
//-------------------------------------------------------------
public static int getInt() throws IOException
{
String s = getString();
return Integer.parseInt(s);
}
// -------------------------------------------------------------
} // end class HeapSortApp
小结:
- 在一个升序优先级队列中,最大关键字的数据项被称为最高的优先级(在降序优先级队列中优先级最高的是最小的数据项)
- 优先级队列是提供了数据插入和移除的最大或者最小数据项方法的抽象数据类型;
- 堆不能有效地遍历所有的数据,不能找到特定关键字数据项位置,也不能移除特定关键字的数据项;
- 堆通常用数组来实现,表现为一颗二叉树,根节点的下标为0,最后一个节点的下标为N-1
- 每个节点的关键字都小于它的父节点,大于它的子节点;
- 要插入的数据项总是被存到数组中第一个空单元中,然后再向上筛选它至适当的位置;
- 当移除一个数据项时,用数组中最后一个数据项替代它的位置,然后再向下筛选这个节点至适当的位置;
- 向上和向下筛选算法可以看成是一系列交换,但更有效的做法是进行一系列的复制;
- 可以更改任一个数据项的优先级,首先更改它的关键字 ,关键字增加了,数据项就向上筛选,关键字减小了,数据项就向下筛选;
- 堆排序是一种高效的排序过程,时间复杂度O(NlogN);
- 堆的实现可以基于二叉树,它映射堆的结构,称为树堆;
- 通过对无序数组中的N/2个数据项施用向下筛选算法,而不作N次插入,可以使堆排序的运算速度更快;
- 可以使用同一个数组来存放初始无序的数据,堆以及最后有序的数据,因此堆排序不需要额外的储存空间。