数据结构-8.Java. 七大排序算法(上篇)

数据结构-8.Java. 七大排序算法(上篇)


本篇博客给大家带来的是排序的知识点, 由于时间有限, 分两天来写, 上篇主要实现 前四种排序算法: 直接插入, 希尔, 选择, 堆排。



文章专栏: Java-数据结构

若有问题 评论区见

欢迎大家点赞 评论 收藏 分享

如果你不知道分享给谁,那就分享给薯条.

你们的支持是我不断创作的动力 .

    

目录

    

  王子公主请阅

1.排序的概念及应用

1.1 排序的概念

1.3 常见的排序算法

2. 常见排序算法的实现(默认排升序)

2.1 插入排序

2.1.1基本思想:

2.1.2 直接插入排序

直接插入排序具体操作: 

2.1.3 希尔排序(缩小增量排序)

2.2 选择排序

2.2.1基本思想:

2.2.3 堆排序


  王子公主请阅

1.排序的概念及应用

1.1 排序的概念

排序:所谓排序,就是使一串记录按照其中的某个或某些关键字的大小,递增或递减的排列起来的操作。

稳定性:假定在待排序的记录序列中,存在多个具有相同的关键字的记录,若经过排序,这些记录的相对次序保持不变,即在原序列中,r[i]=r[j],且r[i]在r[j]之前,而在排序后的序列中,r[i]仍在r[j]之前,则称这种排序算法是稳定的;否则称为不稳定的。

内部排序:数据元素全部放在内存中的排序。外部排序:数据元素太多不能同时放在内存中,根据排序过程的要求不能在内外存之间移动数据的排序。

1.3 常见的排序算法

2. 常见排序算法的实现(默认排升序)

2.1 插入排序

2.1.1基本思想:

直接插入排序是一种简单的插入排序法,其基本思想是:

把待排序的记录按其关键码值的大小逐个插入到一个已经排好序的有序序列中,直到所有的记录插入完为止,得到 一个新的有序序列 。实际中我们玩扑克牌时,就用了插入排序的思想。

如下模拟动图: 

2.1.2 直接插入排序

当插入第i(i>=1)个元素时,前面的array[0],array[1],…,array[i-1]已经排好序,此时用array[i]的排序码与array[i-1],array[i-2],…的排序码顺序进行比较,找到插入位置将array[i]插入,原来位置上的元素顺序后移如下图: 

直接插入排序具体操作: 

第一步 理清思路:

 当插入第i(i>=1)个元素时,前面的array[0],array[1],…,array[i-1]已经排好序,

此时用array[i]的排序码与array[i-1],array[i-2],…的排序码顺序进行比较,找到插入位置将array[i]插入,原来位置上的元素顺序后移第二步具体步骤: 

1.   定义 tmp 保存 排序前 i 下标对应的值.  for(i) 为 外循环, for(j) 为  内循环 , j = i -1 .

2.   遍历数组,  在内循环中,  tmp 与  array[ j ] 进行比较,, 若是 tmp 小 则  [ j + 1] = [ j ];

若是 tmp 大 则 直接 break;  

3.    在外循环中  将 最后 j + 1 位置的值 赋为 tmp值 , [ j + 1 ] = tmp;  

第三步  画图理解 , 一目了然 :

第四步  代码实现: 

 /** * 时间复杂度: * 最好情况:数据完全有序的时候 1 2 3 4 5 :O(N) * 最坏情况:数据完全逆序的时候 5 4 3 2 1 :O(N^2) * 结论: 数据越有序,排序越快, * 场景: 现在有一组基本有序的数据,那么用哪个排序好点? - 直接插入排序. * 空间复杂度: O(1) * 稳定性: 稳定的排序 * 一个本身就是稳定的排序 是可以实现为不稳定的排序的 * 相反 一个本身就不稳定的排序 是不可以实现为稳定的排序的. * * @param //直接插入排序 */ public static void insertSort(int[] array) { for (int i = 1; i < array.length; i++) { int tmp = array[i]; int j = i-1; for (; j >= 0; j--) { if(array[j] > tmp) { // >= 会变成不稳定的 array[j+1] = array[j]; }else { //array[j+1] = tmp; 执行了一次 break; } } //当j=-1时,a[j+1]=tmp这条语句没被执行,所以再写一次 array[j+1] = tmp; //执行了两次, 故把第一次屏蔽. } }

最后总结: 

  1. 元素集合越接近有序,直接插入排序算法的时间效率越高2. 时间复杂度:O(N^2)3. 空间复杂度:O(1),它是一种稳定的排序算法4. 稳定性:稳定 

2.1.3 希尔排序(缩小增量排序)

希尔排序本质上 就是 在对直接插入排序做优化。

第一步 了解基本思想: 

先选定一个整数 gap ,把待排序文件中所有记录分成多个组,此处 gap 通常都是 初始化成 gap = array.length/2;所有距离为 gap 的记录分在同一组内,并对每一组内的记录进行直接插入排序。每进行一次直接插入排序 gap /= 2 重复上述分组和排序的工作。当 gap = 1 时,所有记录在同一组内进行直接插入排序第二步 画图辅助理解: 

第三步 具体步骤:具体步骤与 直接插入排序的大体相同, 将 外循环条件换成for (int i = gap; i < array.length; i++),外循环中 j = i - gap 而不是 - 1;  同时, 内循环变成for (; j >= 0; j -= gap)赋值条件变为array[j+gap] = tmp;最后在直接插入排序外加一层 gap /= 2 的循环 就大功告成了.第四步  代码实现: 

/** * 希尔排序 * * 时间复杂度: * n^1.3 - n^1.5 * 空间复杂度: O(1) * * 稳定性: 不稳定 * @param //shell */ public static void shellSort(int[] array) { int gap = array.length; while(gap > 1) { gap /= 2; shell(array,gap); } //gap /= 2;不用写 因为gap=2或3时, gap/2 = 1.已经进行了直接插入排序. } private static void shell(int[] array,int gap) { for (int i = gap; i < array.length; i++) { int tmp = array[i]; int j = i-gap; for (; j >= 0; j -= gap) { if(array[j] > tmp) { array[j+gap] = array[j]; }else { break; } } array[j+gap] = tmp; } }

最后 希尔排序的特性总结: 

1. 希尔排序是对直接插入排序的优化。2. 当gap > 1时都是预排序,目的是让数组更接近于有序。当gap == 1时,数组已经接近有序的了,这样就会很快。这样整体而言,可以达到优化的效果。3. 希尔排序的时间复杂度不好计算,因为gap的取值方法很多,导致很难去计算,因此在好些树中给出的希尔排序的时间复杂度都不固定:O(n^1.25) ~ O(1.6 * n^1.25)4. 稳定性:不稳定

2.2 选择排序

2.2.1基本思想:

每一次从待排序的数据元素中选出最小(或最大)的一个元素,存放在序列的起始位置,直到全部待排序的数据元素排完 。如下模拟动图: 

第一步 理清思路:在元素集合array[ i ] ~ array[ n-1 ]中选择关键码最小的数据元素若它不是这组元素中的第一个元素,则将它与这组元素中的最后一个(第一个)元素交换在剩余的array[ i ]  ~ array[ n-2 ](array[ i+1 ]  ~  array[ n-1 ])集合中,重复上述步骤,直到集合剩余1个元素第二步 具体步骤: 1.  写个双层循环, for( i ) 为 外层循环,  for( j ) 为外层循环, 定义minIndex =  i , j = i + 1;2.   [minIndex] 与 [ j ] 比较, 当[minIndex] > [ j ] 时  二者交换. 第三步  代码实现:

/** * 选择排序 * * 时间复杂度: * 最好: O(N^2) * 最坏: O(N^2) *空间复杂度:O(N) * * 稳定性: 不稳定 * * @param array */ public static void selectSort(int[] array) { for (int i = 0; i < array.length-1; i++) { int minIndex = i; for (int j = i+1; j < array.length; j++) { if(array[j] < array[minIndex]) { minIndex = j; } } swap(array,minIndex,i); } } public static void swap(int[] array,int i,int j) { int tmp = array[i]; array[i] = array[j]; array[j] = tmp; }

选择排序有 第二种写法,  反正都要遍历一遍数组, 不如把最大值和最小值都找出来, 最小的往最前放, 最大的往最后放. 

第二种写法步骤:

 1.  定义 left = 0, right = array.length-1, i = left + 1; minIndex = maxIndex = 0;

2.   while( left < right )循环,  i 在循环中, 遍历数组 找到最小元素下标 和 最大元素下标, 出循环 与 minIndex 和 maxIndex 交换.  

3. 出循环后 考虑一个特殊情况 , 当 left = 0 下标 对应的值就是最大值时, 第二步出循环后的交换操作 将最大值交换到minIndex下标处了. 需要 再将 maxIndex 标记到最大值.

第二种写法代码实现:

public static void swap(int[] array,int i,int j) { int tmp = array[i]; array[i] = array[j]; array[j] = tmp; } //选择排序的第二种写法. public static void selectSort2(int[] array) { int left = 0; int right = array.length-1; while(left < right) { int minIndex = left; int maxIndex = left; for (int i = left; i <= right; i++) { if(array[i] < array[minIndex]) { minIndex = i; } if(array[i] > array[maxIndex]) { maxIndex = i; } } swap(array,left,minIndex); //有一种情况是maxIndex原本就在left位置上,上面的交换将最大值交换到minIndex下标处了.导致后续的排序不正确. if(maxIndex == left) { maxIndex = minIndex; } swap(array,right,maxIndex); left++; right--; } }

第四步 直接选择排序的特性总结:

1. 直接选择排序思考非常好理解,但是效率不是很好。实际中很少使用2. 时间复杂度:O(N^2)3. 空间复杂度:O(1)4. 稳定性:不稳定

2.2.3 堆排序

堆排序(Heapsort)是指利用堆积树(堆)这种数据结构所设计的一种排序算法,它是选择排序的一种。它是通过堆来进行选择数据。需要注意的是排升序要建大堆,排降序建小堆。第一步  具体步骤:1. 排升序, 建大堆 , 向下调整算法 , parent 从 (array.length - 1)/2 开始 减到 1 2. 将最后一个元素与首元素交换, 除末尾元素外, 其余元素调整成大根堆3. 定义一个end = array.length - 1; while (end > 0) 循环中进行 2 步骤.第二步  代码实现: 

 /** *堆排序 * * 时间复杂度:O(N)+O(N*logN) 约等于 O(N*logN) * * 如果都以最坏的情况来看,堆排序是目前来说最快的,希尔排序其次. * 假设希尔排序为O(N^1.3) 当N越大时,logN趋于不变所以,堆排序O(N*logN)要快一些. * * 空间复杂度:O(1) * 稳定性:不稳定 */ public static void heapSort(int[] array) { //建大堆 createBigHeap(array);//O(N) int end = array.length-1; //O(N*logN) while(end > 0) { swap(array,0,end); shiftDown(array,0,end); end--; } } private static void createBigHeap(int[] array) { for (int parent = (array.length-1-1)/2; parent >= 0; parent--) { shiftDown(array,parent,array.length); } } private static void shiftDown(int[] array,int parent,int end) { int child = (parent*2)+1; while(child < end) { //保证右子树存在并且当右子树大的时候,child++来到右子树的下标. if(child+1 < end && array[child+1] > array[child]) { child++; } if(array[child] > array[parent]) { swap(array,child,parent); parent = child; child = parent*2+1; }else { //本身就是大根堆 break; } } }

第三步   堆排序的特性总结:

1. 堆排序使用堆来选数,效率就高了很多。2. 时间复杂度:O(N*logN)3. 空间复杂度:O(1)4. 稳定性:不稳定

Read more

吃透 C++ 栈和队列:stack/queue/priority_queue 用法 + 模拟 + STL 标准实现对比

吃透 C++ 栈和队列:stack/queue/priority_queue 用法 + 模拟 + STL 标准实现对比

✨ 孤廖:个人主页 🎯 个人专栏:《C++:从代码到机器》 🎯 个人专栏:《Linux系统探幽:从入门到内核》 🎯 个人专栏:《算法磨剑:用C++思考的艺术》 折而不挠,中不为下 文章目录 * 正文: * 容器适配器 * STL标准库中stack和queue的底层结构 * deque的简单介绍(了解) * deque的缺陷 * 为什么选择deque作为stack和queue的底层默认容器 * stack的介绍和使用 * Satck的介绍 * Stack的使用 * stack的模拟实现 * queue的介绍和使用 * queue的介绍 * queue的使用 * queue的模拟实现 * priority_queue的介绍和使用 * priority_queue的介绍 * priority_queue的使用 * 在OJ中的使用 * priority_queue的模拟实现 * STL标准库中对于sta

By Ne0inhk
【C++:搜索二叉树】二叉搜索树从理论到实战完全解读:原理、两种场景下的实现

【C++:搜索二叉树】二叉搜索树从理论到实战完全解读:原理、两种场景下的实现

🔥艾莉丝努力练剑:个人主页 ❄专栏传送门:《C语言》、《数据结构与算法》、C/C++干货分享&学习过程记录、Linux操作系统编程详解、笔试/面试常见算法:从基础到进阶、测试开发要点全知道 ⭐️为天地立心,为生民立命,为往圣继绝学,为万世开太平 🎬艾莉丝的简介: 🎬艾莉丝的C++专栏简介: 目录 C++的两个参考文档 前言 1  ~>  理解二叉搜索树 1.1  二叉搜索树的概念 1.2  博主手记:核心特性 1.2.1  多元化的结构: 灵活的数据结构 1.2.2  天然的搜索优势:擅长搜索的数据结构 2  ~>  二叉搜索树性能分析 2.

By Ne0inhk
【C++贪心 DFS】2673. 使二叉树所有路径值相等的最小代价|1917

【C++贪心 DFS】2673. 使二叉树所有路径值相等的最小代价|1917

本文涉及知识点 C++贪心 反证法 决策包容性 C++DFS LeetCode2673. 使二叉树所有路径值相等的最小代价 给你一个整数 n 表示一棵 满二叉树 里面节点的数目,节点编号从 1 到 n 。根节点编号为 1 ,树中每个非叶子节点 i 都有两个孩子,分别是左孩子 2 * i 和右孩子 2 * i + 1 。 树中每个节点都有一个值,用下标从 0 开始、长度为 n 的整数数组 cost 表示,其中 cost[i] 是第 i + 1 个节点的值。每次操作,你可以将树中 任意 节点的值

By Ne0inhk
C++socket网络编程——udp服务器

C++socket网络编程——udp服务器

目录 一.端口号 VS  PID 二.套接字编程的类型 三.socket编程接口 四.基于udp的服务端和客户端全部代码 客户端 服务端 五.解释与运行 一些细节: 六.总结 一.端口号 VS  PID pid已经能够标识一台主机上的一个唯一一个进程了,为什么还需要端口号? 1. 不是所有的进程都需要网络通信,但是所有的进程都需要都pid; 2. 系统和网络功能解耦。         另外,一个进程可以绑定多个端口,但一个端口只能被一个进程绑定。         系统内定的端口号【0,1023】一般都要有固定的应用层协议使用,如http:80,https:443。 二.套接字编程的类型 1. 域间套接字编程——同一个机器内 2. 原始套接字编程——网络工具 3. 网络套接字编程—

By Ne0inhk