数据结构(四十五)选择排序(1.直接选择排序(O(n²))2.堆排序(O(nlogn)))
一、选择排序的定义
选择排序的基本思想是:每次从待排序的数据元素集合中选取最小(或最大)的数据元素放到数据元素集合的最前(或最后),数据元素集合不断缩小,当数据元素集合为空时排序过程结束。常用的选择排序有直接选择排序和堆排序两种。堆排序是一种基于完全二叉树的排序。
二、直接选择排序
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)))的更多相关文章
- 孤荷凌寒自学python第四十五天Python初学基础基本结束的下阶段预安装准备
孤荷凌寒自学python第四十五天Python初学基础基本结束的下阶段预安装准备 (完整学习过程屏幕记录视频地址在文末,手写笔记在文末) 今天本来应当继续学习Python的数据库操作,但根据过去我自 ...
- (十四--十五)数据库查询优化Part I
(十四--十五)数据库查询优化Part I 如果理解的有问题.欢迎大家指出.这也是我在看课记得笔记..可能会有很多问题 查询优化的重要性 请记住用户只会告诉DMBS他们想要什么样的结果,而不会告诉他们 ...
- 第四十五个知识点:描述一些对抗RSA侧信道攻击的基础防御方法
第四十五个知识点:描述一些对抗RSA侧信道攻击的基础防御方法 原文地址:http://bristolcrypto.blogspot.com/2015/08/52-things-number-45-de ...
- NeHe OpenGL教程 第四十五课:顶点缓存
转自[翻译]NeHe OpenGL 教程 前言 声明,此 NeHe OpenGL教程系列文章由51博客yarin翻译(2010-08-19),本博客为转载并稍加整理与修改.对NeHe的OpenGL管线 ...
- Gradle 1.12用户指南翻译——第四十五章. 应用程序插件
本文由CSDN博客貌似掉线翻译,其他章节的翻译请参见: http://blog.csdn.net/column/details/gradle-translation.html 翻译项目请关注Githu ...
- SQL注入之Sqli-labs系列第四十一关(基于堆叠注入的盲注)和四十二关四十三关四十四关四十五关
0x1普通测试方式 (1)输入and1=1和and1=2测试,返回错误,证明存在注入 (2)union select联合查询 (3)查询表名 (4)其他 payload: ,( ,( 0x2 堆叠注入 ...
- “全栈2019”Java第四十五章:super关键字
难度 初级 学习时间 10分钟 适合人群 零基础 开发语言 Java 开发环境 JDK v11 IntelliJ IDEA v2018.3 文章原文链接 "全栈2019"Java第 ...
- 《手把手教你》系列技巧篇(四十五)-java+ selenium自动化测试-web页面定位toast-上篇(详解教程)
1.简介 在使用appium写app自动化的时候介绍toast的相关元素的定位,在Web UI测试过程中,也经常遇到一些toast,那么这个toast我们这边如何进行测试呢?今天宏哥就分两篇介绍一下. ...
- abp(net core)+easyui+efcore实现仓储管理系统——入库管理之九(四十五)
abp(net core)+easyui+efcore实现仓储管理系统目录 abp(net core)+easyui+efcore实现仓储管理系统——ABP总体介绍(一) abp(net core)+ ...
随机推荐
- JAVA Atm测试实验心得
通过一个假期的自学,完成了老师布置的样卷任务.使用Escipse编写一个学生成绩的管理系统. 一开始两眼摸黑,通过观看Java课程的视频,地址:https://www.bilibili.com/vid ...
- 【THE LAST TIME】彻底吃透 JavaScript 执行机制
前言 The last time, I have learned [THE LAST TIME]一直是我想写的一个系列,旨在厚积薄发,重温前端. 也是给自己的查缺补漏和技术分享. 欢迎大家多多评论指点 ...
- 基于python的scrapy环境搭建
0.1安装python软件 32位机的电脑安装python-3.6.3.exe 64位机的电脑安装python-3.6.3-amd64.exe 0.1.1 python环境搭建 执行安装程序 选择Ad ...
- centos7 远程连接其他服务器redis
在本地远程连接 在终端输入: redis-cli -h 服务器ip地址 -p 端口 -a 密码
- java 对数组进行截取组合操作
1.使用skip跳过和limit限制组合,示例: Integer skip = (page.getPageNum() - 1) * page.getPageNum(); Integer limit = ...
- 【SQL server基础】object_id()函数
在SQLServer数据库中,如果查询数据库中是否存在指定名称的索引或者外键约束等,经常会用到object_id('name','type')方法,做笔记如下: ? 语法:object_id('obj ...
- 应用角度看kafka的术语和功能
kafka的术语(Terminology) Topic 和Consumer Group Topic 每条发布到 Kafka 集群的消息都有一个类别,这个类别被称为 Topic.(物理上不同 Topic ...
- .Net Core 商城微服务项目系列(十五): 构建定时任务调度和消息队列管理系统
一.系统描述 嗨,好久不见各位老哥,最近有点懒,技术博客写的太少了,因为最近在写小说,写的顺利的话说不定就转行了,哈哈哈哈哈哈哈哈哈. 今天要介绍的是基于.Net Core的定时任务调度和消息队列管理 ...
- C++——指针
目录 一.地址和指针 1.1内存 1.2针和指针变量 二.指针变量 2.1始化 2.2赋值 2.3指针类型算术运算 2.4指针类型关系运算 2.5指向指针的指针 三.指针与数组 3.1指针运算 3.2 ...
- Kafka 学习笔记之 High Level Consumer相关参数
High Level Consumer相关参数 自动管理offset auto.commit.enable = true auto.commit.interval.ms = 60*1000 手动管理o ...