二叉堆的三种实现(附带实例)

二叉堆的介绍和实现

二叉堆是一种特殊的堆,二叉堆是完全二元树(二叉树)或者是近似完全二元树(二叉树)。

二叉堆有两种:最大堆最小堆

最大堆:父结点的键值总是大于或等于任何一个子节点的键值;

最小堆:父结点的键值总是小于或等于任何一个子节点的键值。

首先介绍第一种实现,也是最原始的实现,不借助其他东西,完全手敲(。。。。STL大法好)

大根堆:

#include <stdio.h>
#include <stdlib.h>

#define LENGTH(a) ( (sizeof(a)) / (sizeof(a[0])) )

static int m_heap[30];        // 数据
static int m_capacity=30;    // 总的容量
static int m_size=0;        // 实际容量(初始化为0)
 
/* 
 * 返回data在二叉堆中的索引
 *
 * 返回值:
 *     存在 -- 返回data在数组中的索引
 *     不存在 -- -1
 */
int get_index(int data)
{
    int i=0;

    for(i=0; i<m_size; i++)
        if (data==m_heap[i])
            return i;

    return -1;
}

/* 
 * 最大堆的向下调整算法
 *
 * 注:数组实现的堆中,第N个节点的左孩子的索引值是(2N+1),右孩子的索引是(2N+2)。
 *
 * 参数说明:
 *     start -- 被下调节点的起始位置(一般为0,表示从第1个开始)
 *     end   -- 截至范围(一般为数组中最后一个元素的索引)
 */
static void maxheap_filterdown(int start, int end)
{
    int c = start;          // 当前(current)节点的位置
    int l = 2*c + 1;     // 左(left)孩子的位置
    int tmp = m_heap[c];    // 当前(current)节点的大小

    while(l <= end)
    {
        // "l"是左孩子,"l+1"是右孩子
        if(l < end && m_heap[l] < m_heap[l+1])
            l++;        // 左右两孩子中选择较大者,即m_heap[l+1]
        if(tmp >= m_heap[l])
            break;        //调整结束
        else
        {
            m_heap[c] = m_heap[l];
            c = l;
            l = 2*l + 1;   
        }       
    }   
    m_heap[c] = tmp;
}

/*
 * 删除最大堆中的data
 *
 * 返回值:
 *      0,成功
 *     -1,失败
 */
int maxheap_remove(int data)
{
    int index;
    // 如果"堆"已空,则返回-1
    if(m_size == 0)
        return -1;

    // 获取data在数组中的索引
    index = get_index(data); 
    if (index==-1)
        return -1;

    m_heap[index] = m_heap[--m_size];        // 用最后元素填补
    maxheap_filterdown(index, m_size-1);    // 从index位置开始自上向下调整为最大堆

    return 0;
}

/*
 * 最大堆的向上调整算法(从start开始向上直到0,调整堆)
 *
 * 注:数组实现的堆中,第N个节点的左孩子的索引值是(2N+1),右孩子的索引是(2N+2)。
 *
 * 参数说明:
 *     start -- 被上调节点的起始位置(一般为数组中最后一个元素的索引)
 */
static void maxheap_filterup(int start)
{
    int c = start;            // 当前节点(current)的位置
    int p = (c-1)/2;        // 父(parent)结点的位置 
    int tmp = m_heap[c];        // 当前节点(current)的大小

    while(c > 0)
    {
        if(m_heap[p] >= tmp)
            break;
        else
        {
            m_heap[c] = m_heap[p];
            c = p;
            p = (p-1)/2;   
        }       
    }
    m_heap[c] = tmp;
}
  
/* 
 * 将data插入到二叉堆中
 *
 * 返回值:
 *     0,表示成功
 *    -1,表示失败
 */
int maxheap_insert(int data)
{
    // 如果"堆"已满,则返回
    if(m_size == m_capacity)
        return -1;
 
    m_heap[m_size] = data;        // 将"数组"插在表尾
    maxheap_filterup(m_size);    // 向上调整堆
    m_size++;                    // 堆的实际容量+1

    return 0;
}
  
/* 
 * 打印二叉堆
 *
 * 返回值:
 *     0,表示成功
 *    -1,表示失败
 */
void maxheap_print()
{
    int i;
    for (i=0; i<m_size; i++)
        printf("%d ", m_heap[i]);
}
    
void main()
{
    int a[] = {10, 40, 30, 60, 90, 70, 20, 50, 80};
    int i, len=LENGTH(a);

    printf("== 依次添加: ");
    for(i=0; i<len; i++)
    {
        printf("%d ", a[i]);
        maxheap_insert(a[i]);
    }

    printf("\n== 最 大 堆: ");
    maxheap_print();

    i=85;
    maxheap_insert(i);
    printf("\n== 添加元素: %d", i);
    printf("\n== 最 大 堆: ");
    maxheap_print();

    i=90;
    maxheap_remove(i);
    printf("\n== 删除元素: %d", i);
    printf("\n== 最 大 堆: ");
    maxheap_print();
    printf("\n");
}

小根堆:

#include <stdio.h>
#include <stdlib.h>

#define LENGTH(a) ( (sizeof(a)) / (sizeof(a[0])) )

static int m_heap[30];
static int m_capacity=30;    // 总的容量
static int m_size=0;        // 实际容量(初始化为0)
 
/* 
 * 返回data在二叉堆中的索引
 *
 * 返回值:
 *     存在 -- 返回data在数组中的索引
 *     不存在 -- -1
 */
int get_index(int data)
{
    int i=0;

    for(i=0; i<m_size; i++)
        if (data==m_heap[i])
            return i;

    return -1;
}

/* 
 * 最小堆的向下调整算法
 *
 * 注:数组实现的堆中,第N个节点的左孩子的索引值是(2N+1),右孩子的索引是(2N+2)。
 *
 * 参数说明:
 *     start -- 被下调节点的起始位置(一般为0,表示从第1个开始)
 *     end   -- 截至范围(一般为数组中最后一个元素的索引)
 */
static void minheap_filterdown(int start, int end)
{
    int c = start;          // 当前(current)节点的位置
    int l = 2*c + 1;     // 左(left)孩子的位置
    int tmp = m_heap[c];    // 当前(current)节点的大小

    while(l <= end)
    {
        // "l"是左孩子,"l+1"是右孩子
        if(l < end && m_heap[l] > m_heap[l+1])
            l++;        // 左右两孩子中选择较小者,即m_heap[l+1]
        if(tmp <= m_heap[l])
            break;        //调整结束
        else
        {
            m_heap[c] = m_heap[l];
            c = l;
            l = 2*l + 1;   
        }       
    }   
    m_heap[c] = tmp;
}
 
/*
 * 删除最小堆中的data
 *
 * 返回值:
 *      0,成功
 *     -1,失败
 */
int minheap_remove(int data)
{
    int index;
    // 如果"堆"已空,则返回-1
    if(m_size == 0)
        return -1;

    // 获取data在数组中的索引
    index = get_index(data); 
    if (index==-1)
        return -1;

    m_heap[index] = m_heap[--m_size];        // 用最后元素填补
    minheap_filterdown(index, m_size-1);    // 从index号位置开始自上向下调整为最小堆

    return 0;
}

/*
 * 最小堆的向上调整算法(从start开始向上直到0,调整堆)
 *
 * 注:数组实现的堆中,第N个节点的左孩子的索引值是(2N+1),右孩子的索引是(2N+2)。
 *
 * 参数说明:
 *     start -- 被上调节点的起始位置(一般为数组中最后一个元素的索引)
 */
static void filter_up(int start)
{
    int c = start;            // 当前节点(current)的位置
    int p = (c-1)/2;        // 父(parent)结点的位置 
    int tmp = m_heap[c];        // 当前节点(current)的大小

    while(c > 0)
    {
        if(m_heap[p] <= tmp)
            break;
    }
    m_heap[c] = tmp;
}
  
/* 
 * 将data插入到二叉堆中
 *
 * 返回值:
 *     0,表示成功
 *    -1,表示失败
 */
int minheap_insert(int data)
{
    // 如果"堆"已满,则返回
    if(m_size == m_capacity)
        return -1;
 
    m_heap[m_size] = data;        // 将"数组"插在表尾
    filter_up(m_size);            // 向上调整堆
    m_size++;                    // 堆的实际容量+1

    return 0;
}
  
/* 
 * 打印二叉堆
 *
 * 返回值:
 *     0,表示成功
 *    -1,表示失败
 */
void minheap_print()
{
    int i;
    for (i=0; i<m_size; i++)
        printf("%d ", m_heap[i]);
}

void main()
{
    int a[] = {80, 40, 30, 60, 90, 70, 10, 50, 20};
    int i, len=LENGTH(a);

    printf("== 依次添加: ");
    for(i=0; i<len; i++)
    {
        printf("%d ", a[i]);
        minheap_insert(a[i]);
    }

    printf("\n== 最 小 堆: ");
    minheap_print();

    i=15;
    minheap_insert(i);
    printf("\n== 添加元素: %d", i);
    printf("\n== 最 小 堆: ");
    minheap_print();

    i=10;
    minheap_remove(i);
    printf("\n== 删除元素: %d", i);
    printf("\n== 最 小 堆: ");
    minheap_print();
    printf("\n");

第二种实现

借助C++STL种的vector 还有一些函数等

首先建立一个动态数组vector

然后可以根据自己的需求写一个比较,来实现大根堆或者小根堆


//实现大根堆
bool cmp(int a,int b)
{
    return a < b;
}

//声明数组
vector<int> v;
//插入元素
cin >> n;
v.push_back(n);
//建堆
make_heap(v.begin(),v.end(),cmp);

//取得堆顶元素,此时为最大元素或者最小元素
int t = v[0];

//此时并没有删除,需要我们手动来实现
//首先将堆顶元素置于最后,重新排堆
pop_heap(v.begin(),v.end(),cmp);
//然后弹出最后一个元素
v.pop_back();

//到此实现了一步完整的操作

第三种实现

利用优先队列  priority_queue<int, vector<int>, less<int> (greater<int>) > heap;

具体用法可以参考优先队列的用法

内部重载 < 来实现 大根堆或者小根堆

下面开始实战

例题http://codevs.cn/problem/1063/

第二种堆写法AC代码:

#include <bits/stdc++.h>
using namespace std;
const int N = 1e6 + 5;

bool cmp(int a,int b)
{
    return a > b;
}

int main()
{
    ios::sync_with_stdio(false);

    vector<int> v;

    int n, t;
    cin >> n;

    for(int i = 1;i <= n;i ++)
    {
        cin >> t;
        v.push_back(t);
    }

    make_heap(v.begin(),v.end(),cmp);

    long long sum = 0;

    while(v.size() > 1)
    {
        //先取得一个最小元素
        t = v[0];
        //弹出,维护  再取第二个
        pop_heap(v.begin(),v.end(),cmp);
        v.pop_back();

        //维护堆,再取第二次
        push_heap(v.begin(),v.end(),cmp);

        t += v[0];
        pop_heap(v.begin(),v.end(),cmp);
        v.pop_back();

        sum += t;

        //重新加入元素  维护堆
        v.push_back(t);
        push_heap(v.begin(),v.end(),cmp);

    }
    cout << sum <<endl;
    return 0;
}

第三种实现AC代码

#include <bits/stdc++.h>
using namespace std;
const int N = 1e6 + 5;

int main()
{
    ios::sync_with_stdio(false);

    priority_queue<int, vector<int>, greater<int> > heap;

    int n, t;
    cin >> n;

    for(int i = 1;i <= n;i ++)
    {
        cin >> t;
        heap.push(t);
    }

    long long sum = 0;

    while(heap.size() > 1)
    {
        int t1 = heap.top();
        heap.pop();

        int t2 = heap.top();
        heap.pop();

        sum += t1 + t2;

        heap.push(t1 + t2);
    }

    cout << sum << endl;
    return 0;
}

猜你喜欢

转载自blog.csdn.net/Soul_97/article/details/81503998