堆排序 讲解

要想完全懂堆排序,首先,你得知道堆这个结构,其实就是一个完全二叉树。

图片:

这就是一个完全二叉树。

其次就是大根堆和小跟堆的概念

大根堆:就是每个父节点的数都比他的左孩子和右孩子的数大

小根堆:就是每个父节点的数都比他的左孩子和右孩子的数小

注意:堆的所有操作都可以在数组中来进行操作

完全二叉树一个节点(i)的左孩子的下标是:i*2+1   右孩子的小标是:i*2+2  其根节点的下标是:(i-1)/2

还有,你还得知道堆的几种基本操作

1:当往堆中加入一个数后,把这个堆调成大根堆或小根堆,加堆操作

2:当堆中一个数变小后,调整为大根堆

:3:弹出堆顶的的数后,重新调整为大根堆,减堆操作

首先讲第一种操作(以大根堆为例):

当往堆中加入一个数后,把这个堆调成大根堆或小根堆,加堆操作

思路:

每次比较i位置上的数和它父节点上的数的大小,如果i位置上的数大于其父节点上的数,那么就交换这两个位置上的数,然后重复上述过程,直到其父节点为0时结束。

代码如下:

#include<bits/stdc++.h>
using namespace std;
int a[100000];
int n;
void swap(int i,int j)
{
    int temp=a[i];
    a[i]=a[j];
    a[j]=temp;
}
void heapinsert(int i)//将数组中第i个数放入大根堆中
{
    int index=i;//用index表示当前位置,每次都会更新index
    while (a[index]>a[(index-1)/2])//当前数大于他的父节点的数
    {
        swap(index,(index-1)/2);//交换这两个位置上的数
        index=(index-1)/2;//把index设置为父节点的位置,继续执行上述过程
    }
}
int main()
{
    cin>>n;
    for(int i=0;i<n;i++)
    {
        cin>>a[i];
        heapinsert(i);//每次加入一个数,都会进行一次heapisert操作,调整为大根堆
    }
    for(int i=0;i<n;i++)
        cout<<a[i]<<" ";


}

第二种操作(大根堆为例)

当堆中一个数变小后,调整为大根堆

基本思路:

先比较i位置(数变小位置)的左孩子和右孩子上的数的大小,然后左孩子,右孩子中大的数再和i位置上的数比较,如果i位置上的还是最大的,那么就不用换了,直接结束就行,如果i位置上的数小于左孩子右孩子位置中大的那个数,交换。然后继续执行上述操作。其实相当于把数变小位置上的数往下沉。

代码如下:

#include<bits/stdc++.h>
using namespace std;
int n;
int a[10000];
int b;//表示哪个位置上的值需要改变
int number;//表示b位置上的数改成number
void swap(int i,int j)
{
    int temp=a[i];
    a[i]=a[j];
    a[j]=temp;
}
void heapify(int i,int heapsize)//i表示哪个位置上的数发生了改变。R表示该大根堆的长度
{
    int index=i*2+1;//index表示改变的左孩子
    while (index<heapsize)//所有操作必须在左孩子小于堆的大小的条件下进行操作
    {
        int xmax=a[index]<a[index+1]&&((index+1)<heapsize)?index+1:index;//找出左孩子和右孩子中哪个大(注意,这个地方必须是这样,不能是int xmax=a[index]>a[index+1]&&((index+1)<R)?index:index+1,因为如果这样的话,当右孩子不存在时,就不对了)
        xmax=a[xmax]>a[i]?xmax:i;//将左孩子和右孩子中较大的那个和改变的那个比较,找出大的
        if(xmax==i)//当左孩子,右孩子和改变的那个中最大是改变的那个,直接结束(1:说明改变后它还是最大的,不需要换。2:已经把改变的那个往下换完了,不需要再换了)
            break;
        swap(xmax,i);
        i=xmax;//这时改变的那个已经换到下面了,更新坐标,继续执行上述过程
        index=i*2+1;
    }
}
int main()
{
    cin>>n;
    for(int i=0;i<n;i++)
        cin>>a[i];
    cin>>b;
    cin>>number;
    a[b]=number;
    heapify(b,n);
    for(int i=0;i<n;i++)
        cout<<a[i]<<" ";
}

第三种操作(大根堆)

弹出堆顶的的数后,重新调整为大根堆,减堆操作

思路:

先将大根堆顶的数和大根堆的最后一个数进行交换,然后进行2操作即可(因为交换后堆顶的数变为小数了,相当于堆顶的数变小了,此时进行2操作来调整成为大根堆)

代码如下:

#include<bits/stdc++.h>
using namespace std;
int n;
int a[1000];
void swap(int i,int j)
{
    int temp=a[i];
    a[i]=a[j];
    a[j]=temp;
}
void heapify(int i,int heapsize)//i表示哪个位置上的数需要往下沉。R表示该大根堆的长度
{
    int index=i*2+1;//index表示改变的左孩子
    while (index<heapsize)//所有操作必须在左孩子小于堆的大小的条件下进行操作
    {
        int xmax=a[index]<a[index+1]&&((index+1)<heapsize)?index+1:index;//找出左孩子和右孩子中哪个大
        xmax=a[xmax]>a[i]?xmax:i;//将左孩子和右孩子中较大的那个和改变的那个比较,找出大的
        if(xmax==i)//当左孩子,右孩子和改变的那个中最大是改变的那个,直接结束(1:说明改变后它还是最大的,不需要换。2:已经把改变的那个换完了,不需要再换了)
            break;
        swap(xmax,i);
        i=xmax;//这时改变的那个已经换到下面了,更新坐标,继续执行上述过程
        index=i*2+1;
    }
}
void heapiput(int size)
{
    swap(0,size-1);//将堆顶的数和最后一个数交换,执行heapify让堆顶的数往下沉(注意,这时堆得长度减一)
    heapify(0,size-1);
}
int main()
{
    cin>>n;
    for(int i=0;i<n;i++)
        cin>>a[i];
    heapiput(n);
    for(int i=0;i<n-1;i++)
        cout<<a[i]<<" ";


}

最后,开始讲堆排序(大根堆)(由小到大排序)

注意:其实选择排序用堆优化的版本就是堆排序

数组大小为n   0~~n-1

思路:

首先,把数组调整成为大根堆,然后把数组中第一个数(堆顶的数,也是数组中最大的数)和数组第n-1位置上的数(堆底的数)交换,堆的大小减一,然后进行2操作。然后再进行上述操作,只不过这次是把数组中第一个数(堆顶的数,也是数组中最大的数)和数组的第n-2位置上的数交换。直到堆的大小为0或1时,结束

代码如下:

#include<bits/stdc++.h>
using namespace std;
int n;//需要排序的数组的大小
int heapsize;//堆的大小
int a[1500000];
void swap(int i,int j)
{
    int temp=a[i];
    a[i]=a[j];
    a[j]=temp;
}
void heapify(int i,int heapsize)//大根堆数变小后如何调成大根堆   i表示哪个位置上的数发生了改变。heapsize表示该大根堆的长度
{
    int index=i*2+1;//index表示改变的左孩子
    while (index<heapsize)
    {
        int xmax=a[index]<a[index+1]&&((index+1)<heapsize)?index+1:index;//找出左孩子和右孩子中哪个大(注意,这个地方必须是这样,不能是int xmax=a[index]>a[index+1]&&((index+1)<R)?index:index+1,因为如果这样的话,当右孩子不存在时,就不对了)
        xmax=a[xmax]>a[i]?xmax:i;//将左孩子和右孩子中较大的那个和改变的那个比较,找出大的
        if(xmax==i)//当左孩子,右孩子和改变的那个中最大是改变的那个,直接结束(1:说明改变后它还是最大的,不需要换。2:已经把改变的那个换完了,不需要再换了)
            break;
        swap(xmax,i);
        i=xmax;//这时改变的那个已经换到下面了,更新坐标,继续执行上述过程
        index=i*2+1;
    }
}
void heapinsert(int i)//将数组中第i个数放入大根堆中
{
    int index=i;
    while (a[index]>a[(index-1)/2])//当前数大于他的父节点的数
    {
        swap(index,(index-1)/2);//交换这两个位置上的数
        index=(index-1)/2;//把index设置为父节点的位置,继续执行上述过程
    }
}
int main()
{
    cin>>n;
    for(int i=0;i<n;i++)
    {
        cin>>a[i];
        heapinsert(i);
    }
    heapsize=n-1;//此时大根堆长度为数组的长度
    while (heapsize)//当大根堆的长度不为0时,说明还有数字没有被排序 继续执行  其实每次执行完一次while循环后,就找到了数组中最大的一个
    {
        swap(0,heapsize);
        heapify(0,--heapsize);
    }
    for(int i=0;i<n;i++)
        cout<<a[i]<<" ";
    cout<<endl;
}

 

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值