您的位置:首页 > 其它

堆和优先队列

2016-01-24 16:10 218 查看
学习数据结构的时候就学习过堆,不过忘了很多了,《编程珠玑》里也有这一章,因此重新总结一下。

堆的两个性格:(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
= t;
siftup
;
}
函数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
4000
] < x[c]
++c;
if(x[i] <= x[c])
break;
swap(i, c);
}
}
void insert(t)
{
if(n >= maxsize)
std::cout << "元素已满“ << std::endl;
++n;
x
= t;
siftup
;
}
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);
}
}
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: