堆和优先队列

学习数据结构的时候就学习过堆,不过忘了很多了,《编程珠玑》里也有这一章,因此重新总结一下。

堆的两个性格:(1)顺序性质:任何结点的值都小于或等于子结点的值。(对于最小堆而言)

 (2)形状性质:二叉树结构,最多在两层上具有叶结点,其中最底层的结点尽可能的靠左分布。


堆的实现:考虑以数组实现,对于大小为n的堆,声明数组x[n+1],下标从1开始并浪费x[0]。树种常见的函数定义如下:

root = 1;

value(i) = x[i];

leftchild(i) = 2*i;

rightchild(i) = 2*i+1;

parent(i) = i /2;

null(i) = (i < 1) or (i > n).


堆的两个关键函数:即上滤siftup和下滤siftdown函数,后面操作都要反复调用这两个函数。

siftup函数:当存在heap(1,n-1),考虑在n处插入一个新的元素。新插入的元素可能会破坏堆的性质,如果新插入的元素比它的父节点小,就需要将元素和父结点交换,直到到达合适的位置并成为根的右结点为止。

void siftup(n)
{
    for(int i = n; x[i] < x[i/2] && i > 1; i /= 2)
        swap(x[i], x[i/2]
}

下面考虑下滤siftdown,假设给x[1]一个新值,使用siftdown函数使堆的性质保持。这里除了要考虑边界条件还要考虑到存在两个子结点,处理办法是总是与较小的子结点交换。

void siftdown(n)
{
    for(int i = 1; (c = i*2) <= n; i = c)
    {
        if(c + 1 <=n && x[c+1] < x[c]
            ++c;
        if(x[i] <= x[c])
            break;
        swap(x[i], x[c];
    }
}

有了数据结构,开始考虑对堆进行的一些操作了。

构建堆:一般的算法是将N个关键字以任意顺序放入树中,保持结构特性。考虑将结点上滤可以完成一颗具有堆序的树。

//上滤建堆
for(int i = 2; i <= n; ++i)
    siftup(i);
堆的插入操作:把新加入的元素放在堆得末尾,实现上滤就可以了,同时考虑边界。

void insert(t)
{
    if(n >= maxsize)
        /*report error/
    ++n;
    x[n] = t;
    siftup[n];
}
函数extractmin查找并删除集合中的最小元素,然后重新组织使其具有堆性质。思想是从堆顶取出元素,将堆的最后一个元素赋给堆顶然后下滤。

elementtype extractmin()
{
    if(n < 1)
        /*report error*/
    t = x[1];
    x[1] = x[n--];
    siftdown(1);
    return t;
}

封装成c++类:

//priqueue.h
template<class T>
class priqueue{
private:
    int n, maxsize;
    T *x;
    void swap(int i, int j)
    [T t = x[i]; x[i] = x[j]; x[j] = t;}
public:
    priqueue(int m)
    {
        maxsize = m;
        x = new T[maxsize+1];
        n = 0;
    }
    void siftup(n)
    {
        for(int i = n; x[i] < x[i/2] && i > 1; i /= 2)
            swap(i, i/2);
    }
    void siftdown(n)
    {
        for(int i = 1; (c = i*2) <= n; i = c)
        {
            if(c + 1 <=n && x[c+1] < x[c]
                ++c;
            if(x[i] <= x[c])
                break;
            swap(i, c);
        }
    }
    void insert(t)
    {
        if(n >= maxsize)
            std::cout << "元素已满“ << std::endl;
        ++n;
        x[n] = t;
        siftup[n];
    }
    T extractmin()
    {
        if(n < 1)
            std::cout << "优先队列不存在” << std::endl;
        T t = x[1];
        x[1] = x[n--];
        siftdown(1);
        return t;
    }
};

堆排序:通过建立优先队列然后反复调用extractmin函数可以实现排序,建优先队列和extractmin的时间开销均为O(nlogn);但是优先队列和数组的空间开销分别为n+1和n.利用堆排序可以直接在数组上排序。时间复杂度O(nlogn).

//优先队列排序
template<class T>
void pqsort(T v[], int n)
{
    priqueue<T> pq(n);
    int i;
    for(i = 0; i < n; ++i)
        pq.insert(v[i]);
    for(i = 0; i < n; ++i)
        v[i] = pq.extractmin();
}
//堆排序
void swap(int *v, int i, int j)
{
    int tmp = v[i];
    v[i] = v[j];
    v[j] = tmp;
}
void siftup(int *v, int n)
{
    for(int i = n; i>1 && v[i] > v[i/2]; i /= 2)
        swap(v, i, i/2);
}

void siftdown(int *v, int n)
{
    int c;
    for(int i = 1; (c = 2*i) <= n; i = c)
    {    
        if(c+1 <= n && v[c] < v[c+1])
            ++c;
        if(v[i] >= v[c])
            break;
        swap(v, i, c);
    }
}
        
//堆排序
void heapsort(int *v, int n)
{
    int i;
    for(i = 2; i <= n-1; ++i)
        siftup(v, i);
    for(i = 0; i < 14; i++)
        cout << v[i] << " ";
    cout << endl;
    for(i = n-1; i > 1; --i)
    {
        swap(v, 1, i);
        siftdown(v, i-1);
    }
}

上面讨论的堆舍弃了数组0位置不用,如果考虑以0位置作为根的话,则左子结点为2*i+1,父结点为(i-1)/2,重新利用堆排序如下:

void swap(int *v, int i, int j)
{
    int tmp = v[i];
    v[i] = v[j];
    v[j] = tmp;
}
void siftup(int *v, int n)
{
    int c;
    for(int i = n-1; i>0 && v[i] > v[c=(i-1)/2]; i = c)
        swap(v, i, c);
}

void siftdown(int *v, int n)
{
    int c;
    for(int i = 0; (c = 2*i+1) <= n-1; i = c)
    {    
        if(c+1 <= n-1 && v[c] < v[c+1])
            ++c;
        if(v[i] >= v[c])
            break;
        swap(v, i, c);
    }
}
        
//堆排序
void heapsort(int *v, int n)
{
    int i;
    for(i = 1; i <= n-1; ++i)
        siftup(v, i+1);
    for(i = 0; i < n-1; i++)
        cout << v[i] << " ";
    cout << endl;
    for(i = n-1; i > 0; --i)
    {
        swap(v, 0, i);
        siftdown(v, i);
    }
}









你可能感兴趣的:(堆和优先队列)