一、选择排序的定义

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

  

  二、直接选择排序

  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. Centos7 快速安装Docker

    写在前面 Docker是一个开源的引擎,可以轻松的为任何应用创建一个轻量级的.可移植的.自给自足的容器.开发者在笔记本上编译测试通过的容器可以轻松批量地在生产环境中部署. 网上的安装教程也很多这里我推 ...

  2. Hadoop点滴-外围概念

    有句话说的好“大数据胜于好算法” 硬盘存储容量在不断提升的同时,访问速度(硬盘数据读取速度)却没有同步增长:1990年,访问全盘需要5分钟,20年后,需要2.5小时 不同的业务大数据,存储在一套HDF ...

  3. 使用apache的poi来实现数据导出到excel的功能——方式一

    利用poi导出复杂样式的excel表格的实现. 我们要实现的效果是: 我们利用提前设计好模板样式,再在模板中填充数据的方式. 首先,pom.xml引入poi. <dependency> & ...

  4. mysql执行过程以及顺序

    前言:mysql在我们的开发中基本每天都要面对的,作为开发中的数据中间件,mysql承担者存储数据和读写数据的职责.因为学习和了解mysql是至关重要的,那么当我们在客户端发起一个sql到出现详细的查 ...

  5. Linux Shell 基础知识(二)

    1.本文知识结构 2.文件的查询与检索 2.1. cd 目录切换 找到文件/目录位置:cd 切换到上一个工作目录: cd - 切换到home目录: cd or cd ~ 显示当前路径: pwd 更改当 ...

  6. Akka实现WordCount(Scala)

    Akka实现WordCount(Scala): 架构图: 项目结构: pom.xml <project xmlns="http://maven.apache.org/POM/4.0.0 ...

  7. springmvc中重定向该如何处理?

    如果登录成功,会重定向到系统首页 response.sendRedirect("jsp/frame.jsp"); 在springmvc中,应该如何处理?是否可以直接使用 retur ...

  8. python爬虫之基本类库

    简单梳理一下爬虫原理: 1.发送请求 通过HTTP库向目标站点发起请求,即发送一个Request,请求可以包含额外的headers等信息,等待服务器响应. 2.获取响应内容 如果服务器能正常响应(正常 ...

  9. HDU - 1512  Monkey King

    Problem Description Once in a forest, there lived N aggressive monkeys. At the beginning, they each ...

  10. 复杂模型可解释性方法——LIME

    一.模型可解释性     近年来,机器学习(深度学习)取得了一系列骄人战绩,但是其模型的深度和复杂度远远超出了人类理解的范畴,或者称之为黑盒(机器是否同样不能理解?),当一个机器学习模型泛化性能很好时 ...