堆排序 讲解

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

图片:


这就是一个完全二叉树。

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

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

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

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

完全二叉树一个节点(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;
}

猜你喜欢

转载自blog.csdn.net/qq_40938077/article/details/80088978