【堆】数据结构|详细解析|完美诠释|堆排序|模拟堆

一,堆的性质与特点

1.堆的数据结构

堆我们认为他是一种完全二叉树的数据结构,我们在使用这种数据结构的时候会使用一些二叉树种的性质,这些性质需要我们着重的理解和应用

二叉树的父子关系
设一个结点为u : 2 * u 为他的左子节点 , 2 * u+ 1 为他的右子结点
设一个结点为u : u / 2 为他的父亲结点

在这里插入图片描述

2.堆的存储结构

虽然数据结构中堆是一种非线性的数据结构,但是他的存储结构是一种绝对的线性存储结构,我们它通过一个指针cnt来完成这个堆结构的数据输入和输出操作.

3.堆的特点

堆是的h[1] 永远都是最小值,因为堆的压入压出操作

二,堆的操作和代码实现

1.堆的基础操作

堆两个基础操作组成,down ,up 操作,这两个操作涉及堆数据结构的 基本的构建和绝大多数操作。我们首先从down操作开始,down操作就是每次和他的儿子结点进行比较,如果他们比父亲大的话就进行交换操作,当这两个数值比他父亲结点小的时候,选用他们中比较小的一个进行交换.


void down (int u)
{
	int t = u ;
	if(2 * u  <= cnt && h[2 * u] < h[t])	t = 2 * u;
	if(2 * u + 1 <= cnt && h[2 * u + 1] < h [t]) t = 2 *  u + 1 ;
	
}

当完成这一步交换判断之后,我们首先判断一下他是否要进行交换,如果要进行交换操作,我们就进行交换操作,并且在交换完成之后完成剩下的再次down操作。

if(u ! = t)
{
	swap(h[t] , h[u] );
	down(t) ;
}

总结一下这个操作

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

当完成这个简单的down操作之后我们再来看看简单的up操作,up操作实际上就是看看父亲结点比他的大小关系,当父亲大的时候,进行交换并且再次up,下面我们看代码实现

void up (int u )
{
	while(u / 2 > 0 && h[u / 2] > h[u])
	{
		swap(h[u / 2],h[u]);
		u = u / 2;
	}
}

2.堆的操作

2.1堆的初始化操作

我们使用updown来完成初始化操作,首先我们知道堆是一个完全二叉树,使用这个完全二叉树的性质可以知道,堆的最小值是在最上面的,当我们用down完成这个操作的时候,我们是从下向上的完成这个操作,并且最后一行不用进行这次的操作
在这里插入图片描述当我们使用up操作的时候是不好控制的,一般情况下我们不使用up操作进行初始化

2.2堆的排序(集成操作)

堆每次都遍历一次h[1] 这个数,并且把他弹出,弹出之后我们再进行把末尾的元素赋值给h[1] 再进行down(1) 操作.

#include <iostream>
using namespace std ;

const int N = 100010 ;

int h[N] , cnt ;

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

}

int main ()
{
	int n ,m;
	cin >> n >> m;
	cnt = n ;
	for(int i = 1 ; i <= n ; i ++)
		cin >> h[i] ;
	for(int i = n / 2 ; i ; i --)
		down(i) ;
	for(int i = 0 ; i < m ; i ++ )
	{
		int x = h[1];
		h[1] = h[ cnt -- ];
		down(1) ;
		cout << x << " ";
	}
	return 0 ;
}

上述操作已经是比较完美了,但是,还是有一些能改进的地方,这个堆虽然能快速的反应最小值的情况但是,对于第k个插入的值我们还是没有办法,当然我们可以统过后面的堆模拟来改造一下swap_1这个函数就可以了。

3.堆的进阶模拟

在堆的进阶操作中,由于我们要进行堆的第k次运算的相关的操作,所以我们开了两个指针——相互指针ph[N]hp[N]
在这里插入图片描述首先我们在updown中要为了,查找这个相关值得操作更改一下swap函数要完成一些相关的更改

void swap_1(int u ,int v)
{
	swap(h[u],h[v]);
	swap(hp[u],hp[v]);
	swap(ph[hp[u]],ph[hp[v]]);
}

剩下操作的更改就是在初始化赋值的时候,我们要把两个指针都完成他们的赋值操作,ph[N]第几个插入的指向的位置,m++,cnt++很好的就能完成这个赋值操作,同样的hp[N]就是一个完整的取反操作

#include <iostream>
using namespace std ;

const int N = 100010 ;

int h[N] , cnt ;
int hp[N], ph[N];

void swap_1(int u ,int v)
{
	swap(h[u],h[v]);
	swap(hp[u],hp[v]);
	swap(ph[hp[u]],ph[hp[v]]);
}
void down (int u )
{
	int t = u ;
	if(2 * u <= cnt && h[2 * u] < h[t]) t = 2 * u;
	if(2 * u  + 1 <= cnt && h[2 * u + 1 ] < h[t]) t = 2 * u + 1;
	if(t != u)
	{
		swap_1(u, t);
		down(t);
	}

}
void up (int  u)
{
	while (u / 2 and h[u / 2 ] > h[u])
	{
		swap_1(u,u / 2);
		u = u / 2;
	}
}



int main ()
{
	int n ;
	cin >> n ;
	int m = 1 ;
	while ( n -- )
	{
	    string str ;
	    cin >> str ;
	    if(str == "I")
	    //I 操作是一个标准的插入操作;
	    {
	        int x ;
	        cin >> x ;
	        h[++ cnt ] = x ;
	        //基础的的赋值操作
	        ph[m] = cnt ; hp[cnt] = m ++ ;
	        
	        //常规的:ph[N].hp[N[赋值操作;
	        up(cnt);
	    }
	    else if(str == "PM")
	    //返回最小值
	        cout << h[1] <<endl;
	    else if(str == "DM")
	    //删除最小值
            {
                swap_1(1,cnt);
                cnt -- ;
                //删除
                down(1) ;
                //规整操作;
            }
        else if(str == "D")
        //删除第k个插入的数
            {
                int k ;
                cin >> k;
                k = ph[k] ;
                swap_1(cnt -- , k);
                down (k) ;
                up(k) ;
            }
        else 
        {
        //更换第k个插入的数值为x 
            int k , x;
            cin >> k >> x;
            k = ph[k];
            h[k] = x;
            up(k);
            down(k);
        }
	}
	return 0 ;
}
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值