数据结构复习指导之选择排序(简单选择排序和堆排序)

文章目录

选择排序

复习提示

1.简单选择排序

1.1基本思想

1.2简单选择排序算法代码

1.3性能分析

2.堆排序

2.1堆的定义

2.2堆的性质与特点

2.3基本思想

2.4初始建堆的操作

2.5输出堆顶元素后调整堆的比较次数的分析

2.6建立大根堆算法代码

2.7堆排序算法代码

 2.8堆的插入操作及比较次数的分析

2.9堆在海量数据中选出最小k个数的应用及效率分析

2.10性能分析

知识回顾


选择排序

复习提示

选择排序的基本思想是:每一趟(如第 i 趟)在后面 n-i+1(i=1,2,…,n-1)个待排序元素中选取关键字最小的元素,作为有序子序列的第i个元素,直到第n-1趟做完,待排序元素只剩下1个,就不用再选。选择排序中的堆排序是历年统考考查的重点

1.简单选择排序

1.1基本思想

根据上面选择排序的思想,可以很直观地得出简单选择排序算法的思想

假设排序表为 L[1..n],第 i 趟排序即从 L[i..n] 中选择关键字最小的元素与 L(i) 交换,

每一趟排序可以确定一个元素的最终位置,这样经过n-1趟排序就可使得整个排序表有序。

1.2简单选择排序算法代码

简单选择排序算法的代码如下:

void SelectSort(ElemType A[],int n){
    for(int i=0;i<n-l;i++){              //一共进行 n-1 趟
        int min=i;                       //记录最小元素位置
        for(int j=i+1;j<n;j++)           //在A[i..n-1]中选择最小的元素
            if(A[j]<A[min]) min=j;       //更新最小元素位置
        if(min!=i) swap(A[i],A[min]);    //封装的swap()函数共移动元素3次
    }
}

1.3性能分析

简单选择排序算法的性能分析如下:

空间效率:仅使用常数个辅助单元,所以空间效率为O(1)


时间效率:从上述伪码中不难看出,

  • 在简单选择排序过程中,元素移动的操作次数很少,不会超过 3(n-1)次,最好的情况是移动0次,此时对应的表已经有序;
  • 但元素间比较的次数与序列的初始状态无关,始终是 n(n-1)/2 次,因此时间复杂度始终是 O(n²)

稳定性

  • 在第 i 趟找到最小元素后,和第 i 个元素交换,可能会导致第 i 个元素与含有相同关键字的元素的相对位置发生改变。
  • 例如,表 L={2,2,1},经过一趟排序后 L={1,2,2),最终排序序列也是L={1,2,2},显然,2与2的相对次序已发生变化。
  • 因此,简单选择排序是一种不稳定的排序算法

适用性:简单选择排序适用于顺序存储和链式存储的线性表,以及关键字较少的情况

2.堆排序

2.1堆的定义

堆的定义如下,n个关键字序列 L[1..n]称为堆,当且仅当该序列满足:

① L(i)>=L(2i)且L(i)>=L(2i+1)或

② L(i)<=L(2i)且L(i)<=L(2i+1)  (1\leqslant i\leqslant\left \lfloor n/2 \right \rfloor )

  • 其中每个节点都有一个值,通常称为键值(key),并且满足堆属性:
  • 最大堆中,父节点的键值总是大于或等于任何一个子节点的键值;
  • 最小堆中,父节点的键值总是小于或等于任何一个子节点的键值。
  • 堆可以用数组来表示,具体的表示方式取决于堆是最大堆还是最小堆。
  • 在堆中,根节点的键值是堆中所有节点键值中最大或最小的值

2.2堆的性质与特点

可以将堆视为一棵完全二叉树,

  • 满足条件①的堆称为大根堆(大顶堆),大根堆的最大元素存放在根结点,且其任意一个非根结点的值小于或等于其双亲结点值。
  • 满足条件②的堆称为小根堆(小顶堆),小根堆的定义刚好相反,根结点是最小元素。图 8.5所示为一个大根堆。

2.3基本思想

堆排序的思路很简单:首先将存放在 L[1..n]中的n个元素建成初始堆,因为堆本身的特点(以大顶堆为例),所以堆顶元素就是最大值。

输出堆顶元素后,通常将堆底元素送入堆顶,此时根结点已不满足大顶堆的性质,堆被破坏,将堆顶元素向下调整使其继续保持大顶堆的性质,再输出堆顶元素。

如此重复,直到堆中仅剩一个元素为止。可见,堆排序需要解决两个问题:

① 如何将无序序列构造成初始堆?

② 输出堆顶元素后,如何将剩余元素调整成新的堆?

2.4初始建堆的操作

堆排序的关键是构造初始堆

  • n个结点的完全二叉树,最后一个结点是第\left \lfloor n/2 \right \rfloor个结点的孩子。
  • 对以第\left \lfloor n/2 \right \rfloor个结点为根的子树筛选(对于大根堆,若根结点的关键字小于左右孩子中关键字较大者,则交换),使该子树成为堆。
  • 之后向前依次对以各结点 (\left \lfloor n/2 \right \rfloor-1~1) 为根的子树进行筛选,看该结点值是否大于其左右子结点的值,若不大于,则将左右子结点中的较大值与之交换,
  • 交换后可能会破坏下一级的堆,于是继续采用上述方法构造下一级的堆,直到以该结点为根的子树构成堆为止。
  • 反复利用上述调整堆的方法建堆,直到根结点。

如图 8.6 所示,

  1. 初始时调整 L(4)子树,09<32,交换,交换后满足堆的定义;
  2. 向前继续调整 L(3)子树,78< 左右孩子的较大者 87,交换,交换后满足堆的定义;
  3. 向前调整 L(2)子树,17<左右孩子的较大者 45,交换后满足堆的定义;
  4. 向前调整至根结点 L(1),53<左右孩子的较大者 87,交换,交换后破坏了 L(3)子树的堆,
  5. 采用上述方法对 L(3)进行调整,53< 左右孩子的较大者 78,交换,至此该完全二叉树满足堆的定义。

2.5输出堆顶元素后调整堆的比较次数的分析

输出堆顶元素后,将堆的最后一个元素与堆顶元素交换,此时堆的性质被破坏,需要向下进行筛选。

将 09和左右孩子的较大者 78 交换,交换后破坏了L(3)子树的堆,

继续对 L(3)子树向下筛选,将 09 和左右孩子的较大者65 交换,

交换后得到了新堆,调整过程如图8.7所示。

2.6建立大根堆算法代码

下面是建立大根堆的算法:

void BuildMaxHeap(ElemType A[],int len){
    for(int i=len/2;i>0;i--)    //从 i=[n/2]~1,反复调整堆
        HeadAdjust(A,i,len);
}
void HeadAdjust(ElemType All,int k,int len){
//函数 Headadjust 对以元素k为根的子树进行调整
    A[0]=A[k];                  //A[0]暂存子树的根结点
    for(int i=2*k;i<=len;i*=2){ //沿 key 较大的子结点向下筛选
        if(i<len && A[i]<A[i+1])
            i++;                //取key较大的子结点的下标
        if(A[0]>=A[i]) break;   //筛选结束
        else{
            A[k]=A[i];          //将A[i]调整到双亲结点上
            k=i;                //修改k值,以便继续向下筛选
        }
    }
    A[k]=A[0];                  //被筛选结点的值放入最终位置
}

调整的时间与树高有关,为 O(h)。

在建含n个元素的堆时,关键字的比较总次数不超过 4n,时间复杂度为 O(n),这说明可以在线性时间内将一个无序数组建成一个堆。

2.7堆排序算法代码

下面是堆排序算法:

void HeapSort(ElemType A[],int len){
    BuildMaxHeap(A,len);        //初始建堆
    for(int i=len;i>1;i--){     //n-1趟的交换和建堆过程
        Swap(A[i],A[1]);        //输出堆顶元素(和堆底元素交换)
        HeadAdjust(A,1,i-1);    //调整,把剩余的i-1个元素整理成堆
}

 2.8堆的插入操作及比较次数的分析

同时,堆也支持插入操作。对堆进行插入操作时,先将新结点放在堆的末端,再对这个新结点向上执行调整操作。大根堆的插入操作示例如图8.8所示。

2.9堆在海量数据中选出最小k个数的应用及效率分析

堆排序适合关键字较多的情况。

例如,在1亿个数中选出前100个最大值。

首先使用一个大小为 100 的数组,读入前 100 个数,建立小顶堆,而后依次读入余下的数,若小于堆顶则舍弃,否则用该数取代堆顶并重新调整堆,待数据读取完毕,堆中100个数为所求。

2.10性能分析

堆排序算法的性能分析如下:

空间效率:仅使用了常数个辅助单元,所以空间复杂度为O(1)


时间效率:建堆时间为 O(n),之后有n-1次向下调整操作,每次调整的时间复杂度为 O(h),

所以在最好、最坏和平均情况下,堆排序的时间复杂度为O(nlog₂n)


稳定性:进行筛选时,有可能把后面相同关键字的元素调整到前面,所以堆排序算法是一种不稳定的排序算法

  • 例如,表L={1,2,2),构造初始堆时可能将2交换到堆顶,此时L={2,1,2},最终排序序列为 L={1,2,2},显然,2与2的相对次序已发生变化。

适用性:堆排序仅适用于顺序存储的线性表

知识回顾

 

 

  • 22
    点赞
  • 13
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

心碎烤肠

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值