优先队列

堆这样的数据结构经常用到优先队列中,下面关于优先队列还是以最大优先队列作为例子,一个放最大优先队列应该支持一下的操作:

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 ]

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值