队列,栈,堆,三大数据巨佬
堆一个可以被看由数组构成的二叉数,一般分为大根堆或小根堆
性质
- 堆中某个节点的值总是不大于或不小于其父节点的值;
- 堆总是一棵完全二叉树。
当节点的值小于父节点的值时,为大根堆,反之为小根堆
二叉的特性使在堆上操作时间复杂度一般为o(log n)
使用
堆的根节点为1,当一个节点标为i时,其父节点下标为i/2,儿子节点为i2或i2+1
一般存在一个一维数组里。
基本操作
- 建立一个空堆;
- 向堆中插入一个新元素;
- 使节点上浮;
- 使节点下沉;
- 获取当前堆顶元素的值;
- 删除堆顶元素;
建立一个空堆
int heap[1010];
向堆中插入一个新元素
插入后可能会时堆的性质改变,要上浮维护。
heap[++tot]=n;
shift_up(tot);
//tot为堆中节点的个数
使节点上浮
上浮,让节点元素根其父节点元素比较,如果比它大(如果是小根堆就是比其父亲节点小就上浮),就交换两个节点。
void shift_up(int i/*要上(下)浮节点的下标*/){
while(i/2){
if(heap[i]>heap[i/2]){
swap(heap[i],heap[i/2]);
}
i/=2;
}
}
使节点下沉
当你修改堆的根节点后,你要让他上浮,但上帝很不要脸地告诉你,我要让他下沉
下沉,让节点元素根其儿子节点元素比较,如果比它小(如果是小根堆就是比其儿子节点大就下沉),就交换两个节点。
void shift_down(int i){
while(i*2<=tot){
int t=i*2;
if(heap[i*2]>heap[i*2+1]){
t++;
}
if(heap[i]<heap[t]){
swap(heap[i],heap[t]);
}
i=t;
}
}
有两个孩子,所以有点麻烦。当为小根堆时,要判断是否出界
因为出界的0是算有点小的。
获取当前堆顶元素的值
也就是找根
heap[1];
删除堆顶元素
不能称之为删除,只是把根和下标最后一个元素交换,把元素个数减一
这时下沉操作就有用了,让新的根下沉,继续维持堆的特性。
void kill_root(){
swap(heap[1],heap[tot+1]);//根节点被换到了后面,又没记录下它,所以不会把他考虑进去,后面插入元素也会覆盖掉它
shift_down(heap[1]);
}
作用
优化算法
其特性可以与其他算法相结合
如Dij堆优化,spfa堆化,或其他算法的堆优化
将节省大量的时间
在时常加入新的数中找最大(最小)等
保证根节点一定为最大(最小)的特性,可以用于堆优化
当你需要知道许多不断更新的数中,哪个元素最大(最小),相比于sort,堆来找是个不错的选择
因为你可以不停地插入元素,而又能已最少的时间代价知道哪个最大(最小)。
堆排序
可以删除(更新)根,插入元素,保证根一定最大(最小),耗费时间底。
用来排序是个不错的选择
int n;
cin>>n;
int heap[n+10];
for(int i=1;i<=n;i++){
int x;
cin>>x;
heap[++tot]=x;
shift_up(tot);
}
//以上是输入///
while(tot){
cout<<heap[1]<<" ";
kill_root();
}
没错,堆排序就看起来这么简单
效率
朴素操作
普通的树
考虑一个元素在堆中向下移动的距离。大约一半的结点深度为d-1。四分之一的结点深度为d-2,而它们至多能向下移动一层。树中每向上一层,结点的数目为前一层的一半,而子树高度加一。
这种算法时间代价为Ο(n)
堆
由于堆有log n层深,插入结点、删除普通元素和删除最小元素的平均时间代价和时间复杂度都是
Ο(log n)。
堆排序时间复杂度
可与快排相媲美,只是排完序后要用o(n)的空间来存下
但时间复杂度是在操做上多了o(n)插入节点的时间,平均时间复杂度是令Oier垂涎欲滴的o(nlog(n))
虽然是不稳定的排序,时间复杂度有时高,有时底(差别不会很大)
其他
堆也可以有优化操作,堆加平衡树,斜堆,三叉堆,n叉堆等
最后配上图来说明真相
堆在数组里的表达效果
插入元素后在数组里表达的效果
上浮效果
交换
### 下沉效果