堆这样的数据结构经常用到优先队列中,下面关于优先队列还是以最大优先队列作为例子,一个放最大优先队列应该支持一下的操作:
1.INSERT(S,x),把元素x插入集合S中。
2.MAXIMUM(S),返回S中具有最大键值的元素。
3.EXTRACT-MAX(S):去掉并返回S中的具有最大键值的元素、
4.INCREASE-KEY(S,x,k):将元素x的键值增加到k,这里k不小于x。
看一下下面的C++的实现,其中的建堆的过程可以参考堆排序里面的建堆过程。我们侧重算法的过程,有些细节并没有特别注意,例如下面的实现过程我们假设了堆中的键值都是正数,所以我们用返回负数表示一种异常情况。maximum直接在队中有元素的时候返回数组的第一个元素,因为这是最大堆的性质。
int maximum(Heap &hp){
if(hp.size > 0)
return hp.array[0];
return -1;
}
extract_max记录下返回的数组的首个值,然后将数组的最后一个元素代替首个元素,并且将堆的大小减一,因为此时首元素的两个子树都是满足堆的条件的(删除了最后一个元素,不影响这两颗子树的性质),然后根据shift_down的性质,做完这个函数之后,这颗树是满足堆的性质的。
int extract_max(Heap &hp){
int max;
if(hp.size > 0){
max = hp.array[0];
hp.array[0] = hp.array[hp.size - 1];
hp.size--;
shift_down(hp, 0);
} else{
max = -1;
}
return max;
}
increase_key首先会检查插入的新值是不是比原来的大。然后是将旧知=值修改成新的值,然后沿着这个节点慢慢向上爬,知道根节点或者父节点的值比它本身要大。下面分析为什么这种操作可以让这颗树是满足堆的要求的。从修改一个节点的值开始,我们确定此时除了从根节点到该节点是不满足堆的要求外,其他的都是满足的。那么我每次跟父节点作比较,如果该节点比父节点小或者到达根节点,那么这个之前不满足的条件也满足了。如果没有到达根节点,而且比父节点要大,那么我们交换跟父节点的值,那么相当于把这个节点向上推进了一步,接着说明这种操作保持了原来的操作,我们只注重局部,因为交换之后,原来的子节点大于父节点,那么也必然大于它的兄弟节点的,所以交换之后,其子树是,满足堆的条件的,那么忧郁其他的部分是没有懂得,所以也是满足的,剩下仍然是从根节点到这个节点可能是不满足的,所以我们找到上面的两个条件之一就行了。
void increase_key(Heap &hp, int ori_index, int new_key){
if(new_key > hp.array[ori_index]){
hp.array[ori_index] = new_key;
while(ori_index > 0 && hp.array[parent(ori_index)] < hp.array[ori_index]){
int temp = hp.array[parent(ori_index)];
hp.array[parent(ori_index)] = hp.array[ori_index];
hp.array[ori_index] = temp;
ori_index = parent(ori_index);
}
}
}
insert这个函数其实在increase_key这个函数的基础上是比较容易实现的。具体就是将堆的大小扩大,然后将最后一个值设置为无穷小,那么此时这个数组还是满足堆的(因为这个添加在这个最后的值是在足够小,那么剩下的就是将需要插入的值替代掉这个无穷小的值,然后根据上面的函数就可以达到目的。这里还要说明的是,由于存在堆的扩大,所以这里当分配的数组的空间不够的时候是要扩容的,这里的具体实现时,大小翻倍,然后将原来的所有值拷贝过来。
void enlarge(Heap &hp){
if(hp.size < hp.max_size){
return;
}
int *new_array = new int[2 * hp.max_size];
if(new_array == NULL){
return;
}
for(int i = 0; i < hp.max_size; i++){
new_array[i] = hp.array[i];
}
hp.max_size *= 2;
delete []hp.array;
hp.array = new_array;
}
void insert(Heap &hp, int key){
enlarge(hp);
if(hp.size >= hp.max_size){
return;
}
hp.size++;
hp.array[hp.size - 1] = std::numeric_limits<int>::min();
increase_key(hp, hp.size - 1, key);
}
基于上面的实现我们写了如下的main函数进行测试:
int main()
{
const int ARRAY_LENGTH = 10;
int *array = new int[ARRAY_LENGTH];
Heap hp = {
.size = 0,
.max_size = ARRAY_LENGTH,
.array = array
};
hp.array[0] = 4;
hp.array[1] = 1;
hp.array[2] = 3;
hp.array[3] = 2;
hp.array[4] = 16;
hp.array[5] = 9;
hp.array[6] = 10;
hp.array[7] = 14;
hp.array[8] = 8;
hp.array[9] = 7;
build_heap(hp);
std::cout << "[ ";
for(int i = 0; i < hp.size; i++){
std::cout << hp.array[i] << " ";
}
std::cout << "]" << std::endl;
std::cout << "maximum(hp): " << maximum(hp) << std::endl;
std::cout << "[ ";
for(int i = 0; i < hp.size; i++){
std::cout << hp.array[i] << " ";
}
std::cout << "]" << std::endl;
std::cout << "extract_max(hp): " << extract_max(hp) << std::endl;
std::cout << "[ ";
for(int i = 0; i < hp.size; i++){
std::cout << hp.array[i] << " ";
}
std::cout << "]" << std::endl;
increase_key(hp, 5, 20);
std::cout << "increase_key(hp, 5, 20)" << std::endl;
std::cout << "[ ";
for(int i = 0; i < hp.size; i++){
std::cout << hp.array[i] << " ";
}
std::cout << "]" << std::endl;
insert(hp, 25);
std::cout << "insert(hp, 25)" << std::endl;
std::cout << "[ ";
for(int i = 0; i < hp.size; i++){
std::cout << hp.array[i] << " ";
}
std::cout << "]" << std::endl;
return 1;
}
输出的结果是正确的:
[ 16 14 10 8 7 9 3 2 4 1 ]
maximum(hp): 16
[ 16 14 10 8 7 9 3 2 4 1 ]
extract_max(hp): 16
[ 14 8 10 4 7 9 3 2 1 ]
increase_key(hp, 5, 20)
[ 20 8 14 4 7 10 3 2 1 ]
insert(hp, 25)
[ 25 20 14 4 8 10 3 2 1 7 ]
951

被折叠的 条评论
为什么被折叠?



