堆排序,模拟堆(手写)

堆排序

acwing838题https://www.acwing.com/problem/content/840/

以小顶堆为例,堆是一个完全二叉树,所有结点都满足根节点大于左右儿子结点.
树的根节点是堆顶,是最小值.

用数组实现。下标从1开始,下标为x的左儿子下标为2x,右儿子下标为2x+1

手写堆

数组初始化成一个堆 n/2开始down
插入一个数 h[++ hsize] = x; up(hsize);
求集合最小值 h[1]
删除最小值 h[1] = h[hsize]; hsize --; down(1);
删除任意一个数 h[k] = h[hsize]; hsize --; down(k); up(k);
修改任意一个元素 h[k] = x; down(k); up(k);

down操作是如果根节点大就往下移
up操作是如果根节点小就往上移
down,up两操作都是递归

void down(int u)
{
	int t = u;
	if(2 * u <= hsize && h[t] > h[2*u]) t = 2 * u;
	if(2 * u + 1 <= hsize && h[t] > h[2*u + 1]) t = 2 * u + 1;
	if( u != t )
	{
		swap(h[t],h[u]);
		down(t);
	}
}
void up(int u)
{
	int t = u;
	if(2 * u <= hsize && h[t] < h[2*u]) t = 2 * u;
	if(2 * u + 1 <= hsize && h[t] < h[2 * u + 1]) t = 2*u + 1;
	if(u != t)
	{
		swap(h[t],h[u]);
		up(t);
	}
}

数组初始化成堆O(n)

for(int i = n/2; i > 0 ; i --)
{
	down(i);
}

从n/2位置下标开始down,时间复杂度是O(n)
证明:https://www.acwing.com/video/263/视频26分处开始

堆排序代码(小根堆)

#include<iostream>
#include<algorithm>

using namespace std ;

const int N = 1e5 + 10;

int h[N],hsize;
int n,m;

void down(int u)
{
    int t = u;
    if(2 * u <= hsize && h[t] > h[2 * u]) t = 2 * u;
    if(2 * u + 1 <= hsize && h[t] > h[2 * u + 1]) t = 2 * u + 1;
    
    if(u != t)
    {
        swap(h[u],h[t]);
        down(t);
    }
}

int main()
{
    scanf("%d %d",&n,&m);
    
    hsize = n;
    
    for(int i = 1 ; i <= n ; i ++) scanf("%d",&h[i]);
    
    for(int i = n/2 ; i ; i --) down(i);
    
    while(m --)
    {
        printf("%d ",h[1]);
        h[1] = h[hsize --];
        down(1);
    }
    
    return 0;
}

模拟堆

链接:AcWing839题.

因为要插入删除修改第k个插入到堆里面的数,就要多开数组来存放第k个插入的数在堆数组中的下标是多少。

ph[k] = cnt; // 第 k个插入的数的在堆数组下标为 cnt 
hp[cnt] = k; // 堆数组下标为 cnt是第 k个插入的数
p[cnt] = x; // 堆数组下标为 cnt的数的值是 x

响应的需要补充一个heap_swap函数,代替swap

// 因为补充了一个映射关系,所以在交换的时候也要交换ph[]、hp[]
void heap_swap(int a,int b) // a、b为堆数组中的下标
{
	swap(ph[hp[a]],ph[hp[b]]);
	swap(hp[a],hp[b]);
	swap(h[a],h[b]);
}

其他代码就把swap改成heapswap即可

模拟堆代码(小根堆)

#include<iostream>
#include<algorithm>
#include<cstring>

using namespace std ;

const int N = 100010 ;

int h[N],ph[N],hp[N],cnt; // cnt 为堆数组下标

void heap_swap(int a,int b)
{
    swap(ph[hp[a]],ph[hp[b]]);
    swap(hp[a],hp[b]);
    swap(h[a],h[b]);
}


void down(int u) // 小根堆的down操作,如果比两边的左右孩子小就交换
{
    int t = u ;
    if(2 * u <= cnt && h[2 * u] < h[t]) t = 2*u ; // 注意2*u <= cnt是可以取等的!
    if(2 * u + 1 <= cnt && h[2 * u + 1] < h[t]) t = 2 * u + 1 ;
    if(t != u)
    {
        heap_swap(t,u) ;
        down(t) ;
    }
}

void up(int u)
{
    while(u / 2 && h[u] < h[u/2])
    {
        heap_swap(u,u/2);
        u = u/2;
    }
}

int main()
{
    int n;
    scanf("%d",&n);
    
    int m = 0; // m 代表第m个插入的数
    while(n --)
    {
        char op[10];
        scanf("%s",op);
        int k,x; 
        if(!strcmp(op,"I"))
        {
            scanf("%d",&x);
            cnt ++;
            m ++;
            ph[m] = cnt, hp[cnt] = m;
            h[cnt] = x;
            up(cnt);
        }
        else if(!strcmp(op,"PM")) printf("%d\n",h[1]);
        else if(!strcmp(op,"DM"))
        {
            heap_swap(1,cnt);
            cnt --;
            down(1);
        }
        else if(!strcmp(op,"D"))
        {
            scanf("%d",&k);
            int idxk = ph[k];
            heap_swap(idxk,cnt);
            cnt --;
            up(idxk);
            down(idxk);
        }
        else
        {
            scanf("%d%d",&k,&x);
            int idxk = ph[k];
            h[idxk] = x;
            up(idxk);
            down(idxk);
        }
    }

    return 0 ;
}

posted @ 2022-08-05 10:16  r涤生  阅读(37)  评论(0编辑  收藏  举报