一、选择排序的定义

  选择排序的基本思想是:每次从待排序的数据元素集合中选取最小(或最大)的数据元素放到数据元素集合的最前(或最后),数据元素集合不断缩小,当数据元素集合为空时排序过程结束。常用的选择排序有直接选择排序和堆排序两种。堆排序是一种基于完全二叉树的排序。

  

  二、直接选择排序

  1.直接选择排序的定义

  直接选择排序的基本思想是:从待排序的数据元素集合中选取最小的数据元素并将它与原始数据元素集合中的第一个数据元素交换位置;然后从不包括第一个位置上数据元素的集合中选取最小的数据元素并将它与原始数据集合中的第二个数据元素交换位置;如此重复,直到数据元素集合中只有一个数据元素为止。

  2.直接选择排序的实现

    public static void simpleSelectionSort(int[] L) {
for (int i = 0; i < L.length; i++) {
int min = i; // 将当前下标定义为最小值下标
for (int j = i + 1; j < L.length; j++) { // 循环之后的数据
if (L[min] > L[j]) { // 如果有小于当前最小值的关键字
min = j; // 将此关键字的下标赋值给min
}
}
if (i != min) { // 如果min不等于i,说明找到了最小值
swap(L, i, min); // 此时才交换
}
}
}   int[] array1 = {9,1,5,8,3,7,4,6,2};
  i=0时,min=0,j从1开始到8结束,min=1,min和i不相等,交换9和1,第一次排序结果{1}
  i=1时,min=1,j从2开始到8结束,min=8,min和i不相等,交换9和2,第二次排序结果{1,2}
  i=2时,min=2,j从3开始到8结束,min=4,min和i不相等,交换5和3,第三次排序结果{1,2,3}
  ...

  3.直接选择排序的时间复杂度

  (1)时间复杂度为O(n²)

  简单选择排序的最大的特点就是交换移动数据次数相当少,这样也就节约了相应的时间。

  对于比较而言,无论是最好还是最差的情况,其比较次数都是一样多的,第i趟排序需要进行n-i次比较,此时总的比较次数为1+2+...+(n-1) = n(n-1)/2。

  对于交换而言,最好情况交换0次,最坏情况交换n-1次。

  最终的排序时间是比较与交换的次数总和,因此,总的时间复杂度依然为O(n²)。

  尽管时间复杂度与冒泡排序相同,但是简单排序的性能上还是要略优于冒泡排序。

  (2)空间复杂度为O(1)。

  (3)由于每次从无序记录区选出最小记录后,与无序区的第一个记录交换,可能引起相同的数据元素位置发生变化。所以直接选择排序算法不是稳定的排序算法。

  三、堆排序

  1.堆排序的背景

  在直接选择排序算法中,放在数组中的n个数据元素排成一个线性序列,要从有n个数据元素的数组中选择出一个最小的数据元素需要比较n-1次。这样的操作并没有把每一趟的比较结果保存下来,在后一趟的比较中,有许多比较在前一趟已经做过了,但由于前一趟排序时并未保存这些比较结果,所以后一趟排序时又重复执行了这些比较操作,因而记录的比较次数较多。如果可以做到每次在选择到最小记录的同时,并根据比较结果对其他记录做出相应的调整,那样排序的总体效率就会非常高了。

  如果能把待排序的数据元素集合构成一个完全二叉树结构,则每次选择出一个最大(或最小)的数据元素值需比较完全二叉树的高度次,即logn次,则排序算法的时间复杂度就是O(nlogn)。

  2.堆的定义和性质

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

  

  根据完全二叉树的性质5,

  如果对一颗有n个结点的完全二叉树(其深度为【log2 n】+1)的结点按层序编号(从第1层到第【log2 n】+1层,每层从左到右),对任一结点i(0≤i≤n-1)有:

  • 如果i=0,则结点i是二叉树的根,无双亲;如果i>1,则其双亲是结点【(i-1)/2】
  • 如果2i+1>n,则结点i无左孩子;否则其左孩子是结点2i+1
  • 如果2i+2>n,则结点i无右孩子;否则其右孩子是结点2i+2

  对应到堆的定义,小顶堆:a[i]≤a[2i + 1]且a[i]≤a[2i + 2];大顶堆同理。

  则完全二叉树的顺序存储结构即堆数组表示为:

  

  3.堆排序算法的定义

  堆排序(Heap Sort)就是利用堆(假设利用大顶堆)进行排序的方法。它的基本思想是:将待排序的序列构造成一个大顶堆。此时,整个序列的最大值就是堆顶的根结点。将它移走(其实就是将其与堆数组的末尾元素交换,此时末尾元素就是最大值),然后将剩余n-1个序列重新构造成一个堆,这样就会得到n个元素中的次大值。如此反复执行,就能得到一个有序序列了。

  由定义可知:堆排序算法分为两步:1.由一个无序序列构造成一个堆。2.在输出堆顶元素后,调整剩余元素称为一个新的堆。

  (1)由一个无序序列构造成一个堆

  思路就是:遍历所有有孩子结点的数组下标,如果数组从0开始,则有孩子结点的数组下标为0,1,2,3,...,【(a.length-1)/2】,然后比较这些双亲结点和左右孩子的值,如果左右孩子的最大值大于双亲结点的值,就将最大值和双亲结点交换;交换之后,如果左孩子或者是有孩子恰好是另外两个结点的双亲,那么就要再次调整它们三个的关系,最后便利完所有有孩子的结点即可。

    private static void createHeap(int[] a, int n, int h) {
int i ,j, flag;
int temp;
i = h; // i为所有可能的双亲结点下标
j = 2 * i + 1; // j为i结点的左孩子结点下标
temp = a[i]; // temp为调整前双亲结点的值
flag = 0; while (j < n && flag != 1) {
if (j < n - 1 && a[j] < a[j+1]) {// 确定左右孩子结点最大值的数组下标
j ++;
}
if (temp > a[j]) { // 如果双亲结点的数值大于其左右孩子结点的最大值
flag = 1; // 置flag为1,跳出while循环
} else { // 如果双亲结点的数值小于其左右孩子结点的最大值
a[i] = a[j]; // 就令双亲结点的值为其左孩子或者右孩子中值最大的
i = j; // 判断调整后的孩子结点与其左右孩子结点关系是否满足
j = 2 * i + 1; // j为孩子结点的左孩子结点,同理,再来一遍,确保关系正确
}
}
a[i] = temp; // 将原来的左右孩子结点的值赋值为双亲结点的值
} public static void initCreateHeap(int[] a) {
int n = a.length;
for (int i = (n - 1)/2; i >= 0; i--) { // 遍历有孩子结点的数组下标
createHeap(a, n, i);
}
}

  结合代码和输出分析代码实现:

int[] array1 = {50,10,90,30,70,40,80,60,20};
i=4,第4层,j=9,超出范围了,序列不变
i=3,第3层,j=7,即第3层的第1个左孩子,temp=30,flag=0,j为7,a[3]=a[7]=60,i=7,j=15跳出while,a[7]=30,序列为:{50 10 90 60 70 40 80 30 20 }
i=2,第2层,j=5,即第2层的第2个左孩子,temp=90,flag=0,j为6,90>80,flag=1,跳出while,a[2]不变,序列为:{50 10 90 60 70 40 80 30 20 }
i=1,第1层,j=3,即第2层的第1个左孩子,temp=10,flag=0,j为4,a[1]=a[4]=70,i=4,j=9跳出,a[4]=10,序列为:{50 70 90 60 10 40 80 30 20 }
i=0,第0层,j=1,即第1层的第1个左孩子,temp=50,flag=0,j为2,a[0]=a[2]=9,i=2,j=5,继续while,
i=2,第1层,j=5,即第3层的第2个左孩子,temp=50,flag=0,j为6,a[2]=a[6]=80,i=6,j=13超出,a[6]=50,序列为:{90 70 80 60 10 40 50 30 20 }

  测试和输出:

        int[] array1 = {50,10,90,30,70,40,80,60,20};
        System.out.print("大顶堆创建前: ");
        print(array1);
        initCreateHeap(array1);
        System.out.print("大顶堆创建后: ");
        print(array1); 大顶堆创建前: 50 10 90 30 70 40 80 60 20
i的值为3时: 50 10 90 30 70 40 80 60 20
i的值为2时: 50 10 90 60 70 40 80 30 20
i的值为1时: 50 10 90 60 70 40 80 30 20
i的值为0时: 50 70 90 60 10 40 80 30 20
i的值为2时: 90 70 90 60 10 40 80 30 20
大顶堆创建后: 90 70 80 60 10 40 50 30 20

  (2)堆排序算法实现

  过程分为三步:

  • 把堆顶元素a[0]元素和当前大顶堆的最后一个元素交换。
  • 大顶堆元素个数减1。
  • 调整根结点使之满足大顶堆的定义。
    public static void heapSort(int[] a) {
int temp;
initCreateHeap(a); // 初始化创建大顶堆
for (int i = a.length - 1; i > 0; i--) { // 当前大顶堆个数依次减1
temp = a[0]; // 交换堆顶元素和最后一个元素
a[0] = a[i];
a[i] = temp;
createHeap(a, i, 0); // 将剩余数据元素调整为大顶堆
}
}

  结合代码和输出分析代码实现:

int[] array2 = {50,10,90,30,70,40,80,60,20};
首先根据给出的序列创建一个大顶堆:90 70 80 60 10 40 50 30 20
将90与20交换,然后将20 70 80 60 10 40 50 30调整为一个大顶堆
80 70 50 60 10 40 20 30,交换80与20,调整后为
70 60 50 30 10 40 20-交换70与20,调整为
60 30 50 20 10 40-然后
50 30 40 20 10-
40 30 10 20-
30 20 10-
20 10-
10

  测试和输出:

        int[] array2 = {50,10,90,30,70,40,80,60,20};
System.out.print("堆排序前: ");
print(array2);
heapSort(array2);
System.out.print("堆排序后: ");
print(array2);
堆排序前: 50 10 90 30 70 40 80 60 20
堆排序后: 10 20 30 40 50 60 70 80 90

  4.堆排序算法的性能分析

  (1)时间复杂度

  堆排序的运行时间主要是消耗在初始构建堆和在重建堆时的反复筛选上。

  构建堆的过程中,对于每个非终端结点来说,最多进行两次比较和互换操作,因此整个构建堆的时间复杂度为O(n)

  正式排序时,第i次取堆顶记录重建堆需要用O(logi)的时间,这是因为完全二叉树的某个结点到根结点的距离为【logi】+1,并且需要取n-1次堆顶记录,因此,重建堆的时间复杂度为O(nlogn)。

  总体来说,堆排序的时间复杂度为O(nlogn)。

  由于堆排序对原始记录的排序状态并不敏感,因此它无论是最好、最坏与平均情况下的时间复杂度都是O(nlogn),这在性能上远远好于冒泡、直接选择、直接插入的O(n²)的时间复杂度。

  (2)空间复杂度:由于没有用到多于的存储空间,因此空间复杂度也为O(1)。

  (3)稳定性:由于记录的比较与交换是跳跃式进行的,因此堆排序也是一种不稳定的排序方法。

  

  

  

数据结构(四十五)选择排序(1.直接选择排序(O(n²))2.堆排序(O(nlogn)))的更多相关文章

  1. 孤荷凌寒自学python第四十五天Python初学基础基本结束的下阶段预安装准备

     孤荷凌寒自学python第四十五天Python初学基础基本结束的下阶段预安装准备 (完整学习过程屏幕记录视频地址在文末,手写笔记在文末) 今天本来应当继续学习Python的数据库操作,但根据过去我自 ...

  2. (十四--十五)数据库查询优化Part I

    (十四--十五)数据库查询优化Part I 如果理解的有问题.欢迎大家指出.这也是我在看课记得笔记..可能会有很多问题 查询优化的重要性 请记住用户只会告诉DMBS他们想要什么样的结果,而不会告诉他们 ...

  3. 第四十五个知识点:描述一些对抗RSA侧信道攻击的基础防御方法

    第四十五个知识点:描述一些对抗RSA侧信道攻击的基础防御方法 原文地址:http://bristolcrypto.blogspot.com/2015/08/52-things-number-45-de ...

  4. NeHe OpenGL教程 第四十五课:顶点缓存

    转自[翻译]NeHe OpenGL 教程 前言 声明,此 NeHe OpenGL教程系列文章由51博客yarin翻译(2010-08-19),本博客为转载并稍加整理与修改.对NeHe的OpenGL管线 ...

  5. Gradle 1.12用户指南翻译——第四十五章. 应用程序插件

    本文由CSDN博客貌似掉线翻译,其他章节的翻译请参见: http://blog.csdn.net/column/details/gradle-translation.html 翻译项目请关注Githu ...

  6. SQL注入之Sqli-labs系列第四十一关(基于堆叠注入的盲注)和四十二关四十三关四十四关四十五关

    0x1普通测试方式 (1)输入and1=1和and1=2测试,返回错误,证明存在注入 (2)union select联合查询 (3)查询表名 (4)其他 payload: ,( ,( 0x2 堆叠注入 ...

  7. “全栈2019”Java第四十五章:super关键字

    难度 初级 学习时间 10分钟 适合人群 零基础 开发语言 Java 开发环境 JDK v11 IntelliJ IDEA v2018.3 文章原文链接 "全栈2019"Java第 ...

  8. 《手把手教你》系列技巧篇(四十五)-java+ selenium自动化测试-web页面定位toast-上篇(详解教程)

    1.简介 在使用appium写app自动化的时候介绍toast的相关元素的定位,在Web UI测试过程中,也经常遇到一些toast,那么这个toast我们这边如何进行测试呢?今天宏哥就分两篇介绍一下. ...

  9. abp(net core)+easyui+efcore实现仓储管理系统——入库管理之九(四十五)

    abp(net core)+easyui+efcore实现仓储管理系统目录 abp(net core)+easyui+efcore实现仓储管理系统——ABP总体介绍(一) abp(net core)+ ...

随机推荐

  1. Python奇技淫巧 - 持续更新中....

    Python奇技淫巧 人生苦短,我用Python: 编程界这绝对不是一句空话,尤其是对于使用过多个语言进行工作的同学们来说,用Python的时间越长,越有一种我早干嘛去了的想法,没事,啥时候用Pyth ...

  2. IPv6系列-彻底弄明白有状态与无状态配置IPv6地址

    深入研究自动分配IPv6地址的Stateless(无状态)与Stateful(有状态)方式 小慢哥的原创文章,欢迎转载 目录 ▪ 一. Link-Local Address的生成方式 ▪ 二. Glo ...

  3. linux分区与挂载

    分区是将一个硬盘驱动器分成若干个逻辑驱动器,分区是把硬盘连续的区块当做一个独立的磁盘使用.分区表是一个硬盘分区的索引,分区的信息都会写进分区表.通常情况下,为磁盘分区通常使用fdisk,它是对基于MB ...

  4. Typesetting HDU - 6107

    Yellowstar is writing an article that contains N words and 1 picture, and the i-th word contains aia ...

  5. LeetCode初级算法--数组02:旋转数组

    LeetCode初级算法--数组02:旋转数组 搜索微信公众号:'AI-ming3526'或者'计算机视觉这件小事' 获取更多算法.机器学习干货 csdn:https://blog.csdn.net/ ...

  6. .netCore+Vue 搭建的简捷开发框架--目录

    .netCore+Vue 搭建的简捷开发框架 .netCore+Vue 搭建的简捷开发框架 (2)--仓储层实现和EFCore 的使用 .netCore+Vue 搭建的简捷开发框架 (3)-- Ser ...

  7. 支撑微博亿级社交平台,小白也能玩转Redis集群(原理篇)

    Redis作为一款性能优异的内存数据库,支撑着微博亿级社交平台,也成为很多互联网公司的标配.这里将以Redis Cluster集群为核心,基于最新的Redis5版本,从原理再到实战,玩转Redis集群 ...

  8. 前端深入之css篇丨2020年前,彻底掌握css动画【animation】

    写在前面 马上就2020年了,不知道小伙伴们今年学习了css3动画了吗? 说起来css动画是一个很尬的事,一方面因为公司用css动画比较少,另一方面大部分开发者习惯了用JavaScript来做动画,所 ...

  9. POWERSPLOIT-Recon(信息侦察)脚本渗透实战

    Recon(信息侦察)模块 a) 调用invoke-Portscan扫描内网主机的端口. 1)通过IEX下载并调用invoke-portscan. PS C:\Users\Administrator& ...

  10. Bzoj 4806 炮 (dp)

    题目描述 众所周知,双炮叠叠将是中国象棋中很厉害的一招必杀技.炮吃子时必须隔一个棋子跳吃,即俗称"炮打隔子".  炮跟炮显然不能在一起打起来,于是rly一天借来了许多许多的炮在棋盘 ...