堆和优先队列
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处插入一个新的元素。新插入的元素可能会破坏堆的性质,如果新插入的元素比它的父节点小,就需要将元素和父结点交换,直到到达合适的位置并成为根的右结点为止。
下面考虑下滤siftdown,假设给x[1]一个新值,使用siftdown函数使堆的性质保持。这里除了要考虑边界条件还要考虑到存在两个子结点,处理办法是总是与较小的子结点交换。
有了数据结构,开始考虑对堆进行的一些操作了。
构建堆:一般的算法是将N个关键字以任意顺序放入树中,保持结构特性。考虑将结点上滤可以完成一颗具有堆序的树。
封装成c++类:
堆排序:通过建立优先队列然后反复调用extractmin函数可以实现排序,建优先队列和extractmin的时间开销均为O(nlogn);但是优先队列和数组的空间开销分别为n+1和n.利用堆排序可以直接在数组上排序。时间复杂度O(nlogn).
上面讨论的堆舍弃了数组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);
}
}
堆的两个性格:(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);
}
}
相关文章推荐
- Oracle登录用户及用户使用<二>
- oracle 执行语句
- 斯坦福ML公开课笔记15—隐含语义索引、神秘值分解、独立成分分析
- PYTHON之路(二)
- 2016年计划
- 深入学习Java中的字符串,代码点和代码单元
- hdu1536&&hdu3023 SG函数模板及其运用
- python ---- doctest用法
- Linux — 文件系统(对硬盘的操作)
- viewpager切换添加动画效果
- Cocos2d-x 资源加载进度条
- 主机规划与磁盘分区
- Jquery实现纵向横向菜单
- 自然场景文字检测和识别
- android实现全屏 去标题去任务栏
- 【POJ 2195】 Going Home(KM算法求最小权匹配)
- 恢复刷题后的感觉
- [Baby steps giant steps] BSGS EXT-BSGS
- matlab函数
- 各种经典的进度条效果