实现优先级队列的另一种结构::堆。堆是一种树,,由他实现的优先级队列的插入和删除的时间复杂度都是O(llooggN) 。
注意:这里的堆是一种特殊的二叉树,不要和java以及C++等编程语言里的“堆”混淆,后者指的是程序员用new能得到的计算机内存的可用部分。
堆是有如下特点的二叉树:
—-它是完全二叉树。也就是说,除了最后一层节点不是满的,其他的每一层从左到右都完全是满的。
—–它常常用一个数组实现。
——堆中的每一个节点都满足堆的条件,也就是说每一个节点的关键字都大于(或等于)这个节点的子节点的关键字。
堆和它的实现数组:
堆中数据元素的移除:
移除堆中关键字最大的节点,这个节点总是根节点,所以移除是比较容易的。根在堆数组中的索引总是0.
问题在于一旦移除了根节点,树就不再是完全了;数组里就有一个空的数据单元,为了将这个数据单元填满,可以把数组中的所有数据项都向前移动一个单元,但是还有快得多的方法,下面就是移除最大节点的步骤:
1.移走根
2.把最后一个节点移动到根的位置
3.一直向下筛选这个节点,直到它在一个大于它的节点之下,小于她的节点之上为止。
最后一个节点(last)是树最底层的最右端的数据项,它对应于数组中最后一个填入的数据单元,把这个节点直接复制到根节点处
heapArray[0] = heapArray[N-1];
插入:
插入节点也是很容易的。插入使用向上筛选,而不是向下筛选。节点初始时插入到数组最后一个空着的单元中,数组容量大小增1.
heapArray[N] = newNode
问题在于这样可能会破坏堆的条件。如果新插入的节点大于它的父节点时,就会发生这种情况。因为父节点在堆的底端,它可能很小,所以新节点就显得比较大。因此,需要向上筛选新节点,直到它在一个大于它的节点之下,在一个小于她的节点之上。
堆的java代码:
用数组表示一棵树时有一些要点:若数组中节点的索引为x,则:
—-它的父节点的下标为(X - 1) /2
—-它的左子节点的下标为 2 * X + 1
—-它的右子节点的下标为2 * X +2
插入:向上筛选的算法放在它自己的方法中。insert()方法直接调用这个trickleUp()方法。
public class test
{
public boolean insert(int key)
{
if(currentSize == maxSize)
return false;
Node newNode = new Node(key);
heapArray[currentSize] = newNode;
trickleUp(currentSize++);
return true;
}
先检查以确定数组不满,然后用参数传递的关键字创建一个新的节点。把这个节点插入到数组的末端。最后,调用trickleUp方法把这个节点移动到合适的位置
——————————————————————
public void trickleUp(int index)
{
int parent =(index - 1)/ 2;
Node buttom = heapArray[index];
while(index >0 && heapArray[parent].getKey() < buttom.getKey())
{
heapArray[index] = heapArray[parent];
index = parent;
parent = (parent -1)/2;
}
heapArray[index] = buttom;
}
trickleUp()方法的参数是新插入的数据项的下标。找到这个位置的父节点,把这个节点保存到名为buttom的变量里。在while循环内部,变量index沿着路径朝根的方向上行,依次指向每一个节点。只要没有达到根(index>0),且index的父节点的关键字(iData)小于这个新节点,循环就一直执行。
while循环体执行向上筛选过程的一个步骤。首先把父节点复制到index所指向的单元中,即向下移动了父节点,然后头盖骨把父节点的下标传给index,使得index上移了,并且还把父节点的下标给予它的父 节点。
最后,当退出循环时,临时存储在buttom里要插入的新结点,插入到index所指示的单元中。这就是第一个它大于父节点的位置。
————————
移除:如果在移除的过程中包含了向下筛选的算法,那么移除算法也不复杂,保存根节点,把最后一个节点(下标为currentSize -1) 放到根的位置上,然后调用trickleDown(),把这个节点放到适当的位置
public Node remove()
{
Node root = heapArray[0];
heapArray[0] = heapArray[–currentSize];
trickleDown(0);
return root;
}
trickleDown()稍微复杂,因为它必须判断哪个子节点更大。首先,把下标为index的节点保存到一个临时变量top中。
只要index没有到最底层–这就是说,只要它还有一个子节点,while循环就一直执行。在循环内部,检查是否有右子节点(可能只有一个左子节点),如果有,比较两个子节点的关键字,给largerChild设置合适的值。
然后检查原来节点(现在根位置的节点)的关键字是否大于largerChild,如果是,向下筛选的过程就完成了,并且退出循环。
在退出循环时,只需要把存储在top中的节点恢复到适当的位置上,即index所指的位置。
public 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;
}
关键字更改:
有了trickleUp()和trickleDown()方法之后,很容易实现改变节点的优先级(关键字)算法,先更改节点关键字的值,然后再把节点向上或者向下移动到适当的位置。
程序先检查给定的第一个参数index是否合法,如果是合法的,改变这个下标所指节点的iData字段,赋值为第二个参数 的值。
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;
}
——————————————————-
堆操作的完整java代码:
class Node
{
private int iData;
//..............
public Node(int key)
{
iData = key ;
}
//.............
public int getKey()
{
return iData;
}
//................
public void setKey(int id)
{
iData = id;
}
}
//......................................................
class Heap
{
private Node[] heapArray;
private int maxSize;
private int currentSize;
//................................
public Heap(int mx)
{
maxSize = mx;
currentSize = 0;
heapArray = new Node[maxSize];
}
//............................
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;
}
//........................................
public void trickleUp(int index)
{
int parent =(index - 1) / 2;
Node buttom = heapArray[index];
while(index >0 && heapArray[parent].getKey() < buttom.getKey())
{
heapArray[index] = heapArray[parent];
index = parent;
parent = (parent -1)/2;
}
heapArray[index] = buttom;
}
//........................................
public Node remove()
{
Node root = heapArray[0];
heapArray[0] = heapArray[--currentSize];
trickleDown(0);
return root;
}
//......................................
public 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 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 displayHeap()
{
System.out.print("heapArray:");
for (int m = 0; m < currentSize; m++)
if(heapArray[m] != null)
System.out.print(heapArray[m].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 k = 0; k < nBlanks; k++)
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 k = 0; k < nBlanks *2 -2; k++)
System.out.print(' ');
}
System.out.println("\n"+dots+dots);
}
}