选择类排序-堆排序 简单选择排序

选择类排序,每一趟在n - i + 1 ( i = 1,2, … , n - 1)个记录中选取关键字最小的记录作为有序序列中的第i个记录。
区别于插入类排序和交换类排序,选择类排序在排序过程完成前,就能通过一趟排序,获得最终有序序列中处于特定位置的元素。这使得选择类排序在TopK问题中,得到广泛的应用。

TopK问题:从无序的n个元素中,选出最大或最小的K(在笔试\面试的过程中,topK问题往往要利用选择类排序的思想来解决,但结合分布式环境和高级数据结构,使得选择类排序的题目非常有难度。

选择类排序主要有两种:

  • 简单选择排序
  • 堆排序

1. 简单选择排序

基本思想:
第一趟时,从第一个记录开始,通过n–1次关键字比较,从n个记录中选出关键字最小的记录,并和第一个记录交换。第二趟从第二个记录开始,选择最小的和第二个记录交换。以此类推,总共需要n-1趟。
时间复杂度:O(n2)
空间复杂度:O(1)
是否稳定:否

//简单选择排序
void InSortMethod::SelectSort(int Record, int length)
{
    int temp;
    for(int i=0; i<length; i++)
    {
        for(int j= i+1; j<length; j++)
        {
            if(Record[j]<Record[i])
            {
                temp = Record[i];
                Record[i] = Record[j];
                Record[j] = temp;
            }
        }
    }
}

注意内外两层循环变量的初始值和循环结束条件的不同,这是容易出错的地方


2. 堆排序
基本思想:
把待排序记录的关键字存放在数组r1…n中,将r看成是一棵完全二叉树的顺序表示,每个节点表示一个记录,第一个记录r[1]作为二叉树的根,其余记录r[2…n]依次逐层从左到右顺序排列,任意节点r[i]的左孩子是r[2i],右孩子是r[2i+1],双亲是r[i/2向下取整]。然后对这棵完全二叉树进行调整建堆。
由无序序列建堆:待排序元素从下标1开始存储,调整堆从第n/2个元素(最后一个非叶子节点)开始向上调整,建堆完成后,形成大顶堆。
交换堆顶和最后一个元素后,重新调整堆:待排序列范围-1,然后重新调整[1,length],只需调整堆顶即可(其余部分仍然是大顶堆)。
时间复杂度:O(nlogn),最坏情况下也是O(nlogn)
空间复杂度:O(1)
是否稳定:否{5,5,3}
分析:堆排序是一种比较复杂的排序方法,很容易在笔试、面试中直接考察排序的代码。因此,同学们要认真掌握,达到可以默写的程度才可以哦。让我们剖析一下,堆排序的代码该怎么写。
实际上堆排序的过程可以分为两步:
1. 由n个元素的无序序列建大顶堆。实际上是从i/2(最后一个非叶子节点的下标是i/2)处向上调整堆,直到根节点。调整完后就得到一个大顶堆。
2. 将根节点(下标1)和最后一个节点(下标n)交换后,此时最大的元素一定位于数组末尾(下标n),除根节点外,其余的子树依然保持大顶堆的性质(节点1~n-1),因此只需要调整根节点就可以了。
可见,1,2两步都是在调整堆,只是调整的范围不一样:建堆时需要调整所有的非叶子节点,而选出一个最大元素后,只需调整剩下元素中的根节点就可以。我们设调整堆的函数为
void HeapAdjust(int Record[], int begin, int end)
根据以上分析,我们容易写出下面的代码:

//堆排序
void InsSortMethod::HeapSort(int Record[], int length)
{
    //记录下标从1开始,建堆.
    for (int i = length / 2; i > 0; i--)
        HeapAdjust(Record, i, length);
    for (int j = length; j > 1; j--)
    {
        int temp = Record[1];
        Record[1] = Record[j];
        Record[j] = temp;
        HeapAdjust(Record, 1, j - 1);
    }
}

注意,两次调用HeapAdjust()方法的参数不同,如果你理解了为什么不同,那么说明你已经理解了,请继续往下看。
现在,堆排序的主体框架已经有了,只剩下HeapAdjust方法没有实现了,下面要解决如何调整堆的问题,我们通过一个实例来看:
这里写图片描述

上图展示了建队过程中的一趟调整的详细过程,上述例子中共有8个节点,所以从4(8/2=4)号节点开始调整,每次调整完一个节点i后,都使得以i为根的子树成为大顶堆。
根据以上过程,写出HeapAdjust的实现:

void InsSortMethod::HeapAdjust(intRecord[], intbegin, intend)
{
    int temp = Record[begin];
    for (int i = begin * 2; i <= end; i *= 2)
    {
        if (i < end && Record[i] < Record[i + 1])
            i++;
        if (temp >= Record[i])
            break;
        Record[begin] = Record[i];
        begin = i;
    }
    Record[begin] = temp;
}

完整代码:

//堆排序
void InsSortMethod::HeapSort(int Record[], int length)
{
    //记录下标从1开始,建堆.
    for (int i = length / 2; i > 0; i--)
        HeapAdjust(Record, i, length);
    for (int j = length; j > 1; j--)
    {
        int temp = Record[1];
        Record[1] = Record[j];
        Record[j] = temp;
        HeapAdjust(Record, 1, j - 1);
    }
}

void InsSortMethod::HeapAdjust(intRecord[], intbegin, intend)
{
    int temp = Record[begin];
    for (int i = begin * 2; i <= end; i *= 2)
    {
        if (i < end && Record[i] < Record[i + 1])
            i++;
        if (temp >= Record[i])
            break;
        Record[begin] = Record[i];
        begin = i;
    }
    Record[begin] = temp;
}

原文链接:
http://www.tobebatman.com/2015/05/07/%E9%80%89%E6%8B%A9%E7%B1%BB%E6%8E%92%E5%BA%8F/

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值