堆排序

讲堆排序之前,先讲一下简单选择排序!

        1,简单选择排序

基本思想:就是通过n-i次关键字间的比较,从n-i+1个数据中选出关键字最小的数据,并与第i个数据进行交换。

        2,简单选择排序的代码实现
void Swap(int *a,int *b) //交换
{
    int tmp = *a;
    *a = *b;
    *b = tmp;
}

void SelectSort(int *arr, int n)
{
    assert(arr != NULL && n > 1);
    int i,j,min;
    for(i = 0; i < n; ++i)
    {
        min = i; //将当前位置设定为最小值
        for(j = i + 1; j < n; ++j)
        {
            if(arr[j] < arr[min]) //有小于当前最小值的数据
                min = j;//将其下标赋给min
        }
        if(min != i) //如果不相等,说明找到最小值
            Swap(&arr[min],&arr[i]);//将最小值交换到i位置
    }
}
      3,简单选择排序的时间复杂度

简单选择排序最大的特点就是交换移动数据的次数少,节省了时间,并且无论数据如何,其比较次数都是一样的多,n(n-1)/2次,交换次数,最好为0次,最差为n-1次,时间复杂度为O(n^2),空间复杂度为O(1),排序稳定。

        4,简单选择排序和堆排序的异同

简单选择排序是将待排序的n个数据中选择一个最小的数据需要比较n-1次,这样的操作并没有把每一趟的比较结果保留下来,在后一趟的比较中,有许多比较在前一趟都已经做过了,但由于前一趟未保存这些比较结果,所有以后的每一趟排序又得重复执行了这些比较操作,因此数据的比较次数就比较多。

如果可以做到每次在选择到最小数据的同时,并根据比较结果对其他数据做出相应的调整,那样排序的总体效率就高了,而堆排序就是对简单选择排序进行的优化。

        5,堆排序

何为堆排序?

堆排序 : 利用大根堆进行排序的方法。
基本思想:将待排序序列构造成一个大根堆,此时,整个序列的最大值就是堆顶的根节点,将它与堆数组末尾的元素进行交换,此时末尾就是最大值,然后将剩余的n-1个序列重新构造成一个堆,这样就可以得到次大值,如此执行下去,便可以得到一个有序序列。

何为堆?

堆是具有下列性质的完全二叉树:每个结点的值都大于或等于其左右孩子结点的值,称为大根堆;或者每个结点的值都小于或等于其左右孩子结点的值,称为小根堆。

如何建堆?

将堆排序的序列构建成一个大根堆,其实就是从下往上,从右往左,将每个非叶子结点当成根节点,将其子树调整成大根堆的过程。

    (1),让我用图为大家模拟一下建堆的过程

1,我们的原始数据 23 14 35 24 68 54 32 29.

这里写图片描述

2,根据从下往上,从右往左,将每一个非叶子结点当成根节点,将其子树构造为大根堆的原理,我们先讲以3下标为根节点的这棵树构建成大根堆,它只有左子树,只需比较它与左孩子的大小,大的为根。

这里写图片描述

3,接下来将2下标为根结点的二叉树,构建成大根堆,将2下标与左右子树中较大的数进行交换。

这里写图片描述

4,接下来构建1下标为根结点的这棵树,让它与4下标的值进行交换即可。

这里写图片描述

5,最后构建下标为0为根结点的这棵二叉树,此时构建完成要满足整棵树都是大根堆,即根大于左右孩子的值,所以当0下标与1下标交换之后,还需要和1下标的孩子进行比较,直到它大于左右孩子或左右孩子为NULL,才能满足整棵树是一个大根堆。

这里写图片描述

6,此时这个堆就构建完成,是一个大根堆,堆建好,我们就可以进行排序了。

            6,堆排序代码实现
//建堆
void HeapAdjust(int *arr,int i,int n)
{
    if(arr == NULL || i  < 0 || n < 1)
        return;
    int tmp = arr[i];  //把根结点的值保留下来
    int j = 2 * i + 1;         //j是i的左孩子   
    for(; j < n; j = 2 * j + 1)
    {
        if((j + 1) < n && arr[j] < arr[j+1] )  //右孩子的值大于左孩子
        ++j;  //j是左右孩子中较大值的下标

        if(tmp > arr[j]) //根结点大于左右孩子结点,,此时就是大根堆
               break;

        arr[i] = arr[j]; //左右结点较大的值给跟结点
        i = j;
    }
    arr[i] = tmp;//以前的根结点的值给较大孩子结点,相当于做了交换
}

//堆排序
void HeapSort(int *arr,int n)
{
    assert(arr != NULL && n > 1);

    int i = (n - 1) / 2;//i为要调整的第一个非叶子结点
    for(; i >= 0; --i)
    {
        HeapAdjust(arr,i,n); //把arr构建成一个大根堆
    }

   for(int j = n; j > 0; --j)
   {
      Swap(&arr[0],&arr[j-1]);//将堆顶元素与最后一个位置的元素进行交换
      HeapAdjust(arr,0,j-2);//将arr[0...j-2]重新调整成大根堆
   }
}

//测试一下  

int main()
{
   int arr[8] = { 23, 14, 35, 24, 68, 54, 32, 29 };
   int len = sizeof(arr) / sizeof(arr[0]);
   HeapSort(arr,len);
   for(int i = 0; i < len; ++i)
   {
       printf("%d ",arr[i]);
   }
   printf("\n");
   return 0;
}

这里写图片描述

        7,堆排序的时间复杂度

时间复杂度:O(nlogn), 由于堆排序对原始数据的顺序并不敏感,所以平均复杂度还为O(nlogn)。
堆排序的运行时间主要消耗在初始构建堆和重建堆时的反复筛选上,构建堆的时间复杂度为O(n),重构堆的时间复杂度为O(logn),空间复杂度为O(1),所以堆排序适合于数据量较大的排序,由于原始数据的比较和跳跃是交换式的,所以堆排序不是一个稳定的排序算法。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值