排序算法-堆排序(C语言版)

堆排序是一种基于完全二叉树结构的一种排序算法,其整体思想很简单,就是构建完全二叉树,但是这里需要引入堆的概念。如下

大顶堆:每个结点的值都大于等于其左右孩子结点的值

小顶堆:每个结点的值都小于等于其左右孩子结点的值

大概就是下图的样子

 

细心的同学可能已经发现了,堆只保证根节点与子节点的关系,并不保证子节点与子节点的关系。是不是很疑惑这种数据构型怎么能用于排序,其实很简单,我们只需要利用节点特性然后不停的重构就可以达到排序的目的。只需要不停的将根节点就是最上面的那个节点的数据不停的取出,然后重构堆,就可以实现排序的目的 。我们可以用公式来描述一下。

以 i 为根节点的序号那么2*i+1就是左子节点,2*i+2就是右子节点。

大顶堆:a[i] >= a[2i+1]  &&  a[i] >= a[2i+2] 

小顶堆:a[i] <= a[2i+1]  &&  a[i] <= a[2i+2] 

排序思想升序()
1.首先将待排序的数组构造成一个大顶堆,此时,整个数组的最大值就是堆结构的顶端

2.将顶端的数与末尾的数交换,此时,末尾的数为最大值,剩余待排序数组个数为n-1

3.将剩余的n-1个数再构造成大顶堆,再将顶端数与n-1位置的数交换,如此反复执行,便能得到有序数组

注意:升序用大顶堆(因为大顶堆的顶点最大),降序就用小顶堆(小顶堆顶点最小)

说简单点就是利用大顶堆和小顶堆的特性:每次都取堆顶的元素,将其放在序列最后面,然后将剩余的元素重新调整为最大堆,依次类推,最终得到排序的序列。

不理解不要紧下面举个栗子:(短一点的举栗子不然图太多了)

利用python随机生成的六个数:3 6 8 2 4 5

形成这样一个数组:

 下面是序号:以序号为数的节点序号就会生成一个下图这样的树

 这里我们插入一下关于完全二叉树的知识便于我们理解:

如上面这个图其实就是完全二叉树,完全二叉树是由满二叉树而引出来的,若设二叉树的深度为h,除第 h 层外,其它各层 (1——(h-1)) 的结点数都达到最大个数(即(1——(h-1))层为一个满二叉树),第 h 层所有的结点都连续集中在最左边,这就是完全二叉树。

额,简单的说就是将所有数据从上到下,从左到右依次排,最后一层可以不排满。(排满为满二叉树)

那么因为要构建大顶堆,所以我们需要找到所有的非叶子节点。叶子节点就是没有子分支的节点。叶子节点就是有分支的节点。

那么怎么寻找呢?最后一个非叶子节点序号就是数组长度len/2-1。来我们来证明一下,非叶子节点只有两种情况:

1.只存在左子节点.

2.左右都存在

完全二叉树的性质之一是:如果节点序号为i,在它的左孩子序号为2*i+1,右孩子序号为2*i+2。

对于第一种情况左孩子的序号为n-1,则n-1=2*i-1,推出i=n/2-1。

对于第二种情况左孩子的序号为n-2,在n-2=2*i-1,推出i=(n-1)/2-1;右孩子的序号为n-1,则n-1=2*i+2,推出i=(n-1)/2-1。

很显然,当完全二叉树最后一个节点是其父节点的左孩子时,树的节点数为偶数;当完全二叉树最后一个节点是其父节点的右孩子时,树的节点数为奇数。

根据计算机的计算特点,整数除不尽时都是向下取整(好像语言不同取法不同,但是我学过的都是向下取整数),啥意思呢就是7/2=3,9/2=4计算机不会四舍五入,只会向下取整。

所以当第二种情况时,有右子节点长度就必定是个奇数,所以计算结果和第一种一样,

所以最后一个非叶子节点的序号是len/2-1。

 然后来复刻一下排序成大顶堆的过程:

先从最后一个非叶子节点开始,6/2-1=2,即最后一个非叶子节点为序号为2值为3这个点。(图中绿色的数字)然后查看该节点是否符合大顶堆特点(就是父节点(8这个数字节点)是否为最大值)。很明显该节点符合大顶堆特点。然后找下一个非叶子节点即序号为1的节点。

 序号为1值为6的节点很明显也符合大顶推特点。接下来检查根节点序号为0的节点。

 很明显序号为0的根节点是不符合大顶堆特点。从下图可知根节点的左分支与右分支都比根节点大。所以需要调整,选择其中最大值8和根节点交换数据。

 交换数据后为。

 因为交换数据后树形结构发生改变,最后一个非叶子节点不满足大顶堆条件,所以需要重新构建大顶堆结构,顾需要重现开始调整,还是回到最开始的最后一个非叶子节点2,继续进行判断。

很明显5比3大不符合大顶堆条件需要调整为下图。

 

 现在大顶堆构建完成。

然后我们就需要完成排序部分。上文提到我们排序就是不停的将根节点元素和最后一个节点的数据进项交换,然后重新构建其余所有节点为大顶堆结构。然后一直重复这个过程就完成了排序。

下面进行图例演示(重构过程就不演示了,和上面的重构过程一样):

先将根节点8和3交换然后对其余的进行大顶堆重构。

交换后如下图,然后进行重构。(注意现在节点2值为5的点现在变成了叶子节点,重构的数组长度为5,所以最后的非叶子节点现在变成了5/2-1=1了)

 重构后为下图:

 然后根节点与倒数第二个节点交换数据。交换后为下图:

 剩余节点进行重构:

 然后再交换数据。交换后为:

然后再重构:此时最后非叶子节点数发生变化变化为0: 

 

交换:

 然后重构,当然现在根节点符合大顶堆。然后交换。最后得到:

 就完成了排序。

然后上代码,我看了下菜鸟里的代码https://www.runoob.com/w3cnote/heap-sort.html我感觉这个有点不便于理解,就自己写了一个,当然肯定比较啰嗦没有菜鸟里这个这么简介。但是更容易理解一点。

#include<stdio.h>

void pile(int *a,int len)
{
    int i=0,j=0,temp=0;
    for (i=len;i>0;i--)//堆排序结构(通过i做减法让每次交换后的最后节点不进入重构)
    {
        maxtree(a,i);//构建大顶堆结构
        Swap(a,i);//交换数据
    }
}
void Swap(int *a,int len)//交换根节点和每次重构后树的最后一个节点的值
{
    int temp=0;

    temp=a[0];
    a[0]=a[len-1];
    a[len-1]=temp;
}
void maxtree(int *a,int len)//构建大顶堆
{
    int i=0,temp=0;
    for (i=len/2-1;i>=0;i--)//从最后一个非叶子节点开始判断是否符合大顶堆特点
    {
        if((2*i+1)<len && a[i]<a[2*i+1])//左子树大于根节点
        {
            temp=a[i];
            a[i]=a[2*i+1];
            a[2*i+1]=temp;//根节点与左子树交换值
            //这个if语句进行判断交换后的左子树的子节点是否符合大顶堆标准
            //不符合则进行重构
            if((2*(2*i+1)+1<len && a[2*i+1]<a[2*(2*i+1)+1]) || (2*(2*i+1)+2<len && a[2*i+1]<a[2*(2*i+1)+2]))
            {
                maxtree(a,len);
            }
        }
        if((2*i+2)<len && a[i]<a[2*i+2])//右子树大于根节点
        {
            temp=a[i];
            a[i]=a[2*i+2];
            a[2*i+2]=temp;//根节点与右子树交换值
            //这个if语句进行判断交换后的右子树的子节点是否符合大顶堆标准
            //不符合则进行重构
             if((2*(2*i+2)+1<len && a[2*i+2]<a[2*(2*i+2)+1]) || (2*(2*i+2)+2<len && a[2*i+2]<a[2*(2*i+2)+2]))
            {
                maxtree(a,len);
            }
        }
    }
}
int main()
{
    int n,i;
    scanf("%d",&n);
    int a[n+1];
    for(i=0; i<n; i++)
        scanf("%d",&a[i]);
    pile(a,n);//调用堆排序函数
    for (i=0;i<n;i++)
    {
        printf("%d ",a[i]);
    }
    return 0;
}

菜鸟代码

#include <stdio.h>
#include <stdlib.h>

void swap(int *a, int *b) {
    int temp = *b;
    *b = *a;
    *a = temp;
}

void max_heapify(int arr[], int start, int end) {

    int dad = start;//指向对应的节点
    int son = dad * 2 + 1;//指向对应节点的左子节点
    while (son <= end) { // 子节点存在才进行判断
        if (son + 1 <= end && arr[son] < arr[son + 1])//判断左子节点大还是右子节点大,son指向的是左子节点,所以右子节点就是son+1
            son++;
        if (arr[dad] > arr[son]) //判断父节点是否比子节点大,父节点大则跳出
            return;
        else {//若是子节点大,则交换数据并且判断其对应的子分支是否符合大顶堆标准
            swap(&arr[dad], &arr[son]);
            dad = son;//父节点变换为交换元素的子节点位置
            son = dad * 2 + 1;//对应更新子节点位置
        }
    }
}

void heap_sort(int arr[], int len) {
    int i;
    // 初始化,i从最后一个非叶子节点开始调整
    for (i = len / 2 - 1; i >= 0; i--)
        max_heapify(arr, i, len - 1);
    // 先将第一个元素和已排好元素前一位做交换,再重新调整,直到排序完成
    for (i = len - 1; i > 0; i--) {
        swap(&arr[0], &arr[i]);
        max_heapify(arr, 0, i - 1);
    }
}

int main() {
    int arr[] = { 3, 5, 3, 0, 8, 6, 1, 5, 8, 6, 2, 4, 9, 4, 7, 0, 1, 8, 9, 7, 3, 1, 2, 5, 9, 7, 4, 0, 2, 6 };
    int len = (int) sizeof(arr) / sizeof(*arr);
    heap_sort(arr, len);
    int i;
    for (i = 0; i < len; i++)
        printf("%d ", arr[i]);
    printf("\n");
    return 0;
}

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值