排序 算法(第4版)
本博客参考算法(第4版):算法(第4版) - LeetBook - 力扣(LeetCode)全球极客挚爱的技术成长平台
本文用Java实现相关算法。
我们关注的主要对象是重新排列数组元素的算法,其中每个元素都有一个主键。排序算法的目标就是将所有元素的主键按照某种方式排列(通常是按照大小或是字母顺序)。排序后索引较大的主键大于等于索引较小的主键。元素和主键的具体性质在不同的应用中千差万别。在 Java 中,元素通常都是对象,对主键的抽象描述则是通过一种内置的机制,如Comparable
接口。
大多数情况下,我们的排序代码只会通过两个方法操作数据:less()
方法对元素进行比较,exch()
方法将元素交换位置。exch()
方法的实现很简单,通过 Comparable
接口实现 less()
方法也不困难。将数据操作限制在这两个方法中使得代码的可读性和可移植性更好,更容易验证代码的正确性、分析性能以及排序算法之间的比较。
在本文主键全是整数,因此不做接口实现,直接实现相关函数。
private static void exch(int[] arr, int i, int j) {int temp = arr[i]; arr[i] = arr[j]; arr[j] = temp;
}
private static boolean less(int pre, int suf) {return pre < suf;
}
排序默认从小到大升序
初级排序算法
选择排序
一种最简单的排序算法是这样的:首先,找到数组中最小的那个元素,其次,将它和数组的第一个元素交换位置(如果第一个元素就是最小元素那么它就和自己交换)。再次,在剩下的元素中找到最小的元素,将它与数组的第二个元素交换位置。如此往复,直到将整个数组排序。这种方法叫做选择排序,因为它在不断地选择剩余元素之中的最小者。
代码实现:
public class lab {private static void exch(int[] arr, int i, int j) {int temp = arr[i]; arr[i] = arr[j]; arr[j] = temp;}private static boolean less(int pre, int suf) {return pre < suf;}public static void sort(int[] arr) {int N = arr.length; // 数组长度for(int i = 0; i < N; ++i) {int pos = i; // 最小元素下标for(int j = i + 1; j < N; ++j) {// 如果有比当前最小元素a[j]小的,则更新最小元素下标if(less(arr[j], arr[pos])) pos = j;}// 交换当前位置i 和最小元素下标exch(arr, i, pos);}}public static int[] a = {5, 4, 1, 56, 3};// 测试public static void main(String[] args) {sort(a);for(int i : a) System.out.println(i);}
}
交换元素的代码写在内循环之外,每次交换都能排定一个元素,因此交换的总次数是 N。所以算法的时间效率取决于比较的次数。
对于长度为 N 的数组,选择排序需要大约 N^2/2 次比较和N次交换。
选择排序两个鲜明的特点:
- 运行时间和输入无关
- 数据移动是最少的
插入排序
通常人们整理桥牌的方法是一张一张的来,将每一张牌插入到其他已经有序的牌中的适当位置。在计算机的实现中,为了给要插入的元素腾出空间,我们需要将其余所有元素在插入之前都向右移动一位。这种算法叫做插入排序。
和选择排序不同的是,插入排序所需的时间取决于输入中元素的初始顺序。例如,对一个很大且其中的元素已经有序(或接近有序)的数组进行排序将会比对随机顺序的数组或是逆序数组进行排序要快得多。
代码实现:
public class lab {private static void exch(int[] arr, int i, int j) {int temp = arr[i]; arr[i] = arr[j]; arr[j] = temp;}private static boolean less(int pre, int suf) {return pre < suf;}public static void sort(int[] arr) {int N = arr.length; // 数组长度for(int i = 1; i < N; ++i) {// 将 a[i] 插入到 a[i-1]、a[i-2]、a[i-3]...之中for(int j = i; j > 0 && less(a[j], a[j - 1]); --j) {exch(arr, j, j - 1);}}}public static int[] a = {5, 4, 1, 56, 3};// 测试public static void main(String[] args) {sort(a);for(int i : a) System.out.println(i);}
}
对于随机排序的长度N且主键不重复的数组,平均情况下插入需要N * N / 4
次比较及N * N / 4
次交换。最坏是N * N / 2
次比较和交换。最好情况是N - 1
次比较和0次交换(已有序数组)。
插入排序对部分有序的数组很有效果,例如:
- 数组中每个元素距离它的最终位置都不远;
- 一个有序的大数组接一个小数组;
- 数组中只有几个元素的位置不正确。
插入排序需要的交换操作和数组中倒置的数量相同,需要的比较次数大于等于倒置的数量,小于等于倒置的数量加上数组的大小再减一。
希尔排序
优化插入排序。希尔排序为了加快速度简单地改进了插入排序,交换不相邻的元素以对数组的局部进行排序,并最终用插入排序将局部有序的数组排序。
希尔排序的思想是使数组中任意间隔为 h 的元素都是有序的。这样的数组被称为 h 有序数组。对于任意以 1 结尾的 h 序列,我们都能够将数组排序,这就是希尔排序。
实现希尔排序的一种方法是对于每个 h,用插入排序将 h 个子数组独立地排序。但因为子数组是相互独立的,一个更简单的方法是在 h- 子数组中将每个元素交换到比它大的元素之前去(将比它大的元素向右移动一格)。只需要在插入排序的代码中将移动元素的距离由 1 改为 h 即可。这样,希尔排序的实现就转化为了一个类似于插入排序但使用不同增量的过程。
public class lab {private static void exch(int[] arr, int i, int j) {int temp = arr[i]; arr[i] = arr[j]; arr[j] = temp;}private static boolean less(int pre, int suf) {return pre < suf;}public static void sort(int[] arr) {int N = arr.length; // 数组长度int h = 1;while(h < N / 3) h = 3 * h + 1; // // 1, 4, 13, 40, 121, 364, 1093, ... 这样效果更优while(h >= 1) {// 虽然是两个for循环,但是加在一起是O(N)for(int i = h; i < N; ++i) {// 插入思想for(int j = i; j >= h && less(a[j], a[j - h]); j -= h) {exch(arr, j, j - h);}}h /= 3;}}public static int[] a = {5, 4, 1, 56, 3};// 测试public static void main(String[] args) {sort(a);for(int i : a) System.out.println(i);}
}
归并排序
归并排序,可以先(递归地)将它分成两半分别排序,然后将结果归并起来。归并排序有原地归并、自顶向下、自顶向上这几种方法,本文只讲自顶向下的方法。
public class lab {private static void exch(int[] arr, int i, int j) {int temp = arr[i]; arr[i] = arr[j]; arr[j] = temp;}private static boolean less(int pre, int suf) {return pre < suf;}public static void sort(int[] arr, int lo, int hi) {if(lo >= hi) return ;int mid = (lo + hi) / 2;// 将数组分为两部分,并对这两部分进行排序sort(arr, lo, mid); sort(arr, mid + 1, hi);merge(arr, lo, mid, hi);}// 将两个有序数组进行合并public static void merge(int[] arr, int lo, int mid, int hi) {int i = lo, j = mid + 1, k = 0;while(i <= mid && j <= hi) {if(less(arr[i], arr[j])) tmp[k++] = arr[i++];else tmp[k++] = arr[j++];}while(i <= mid) tmp[k++] = arr[i++];while(j <= hi) tmp[k++] = arr[j++];for(i = lo, k = 0; i <= hi; ++i, ++k) arr[i] = tmp[k];}public static int[] a = {5, 4, 1, 56, 3};public static int[] tmp = new int[5]; // 辅助数组// 测试public static void main(String[] args) {sort(a, 0, 4);for(int i : a) System.out.println(i);}
}
该算法需要N * lg(N) / 2
到 N * lg(N)
次比较。最多访问数组6 * N * lg(N)
次。
快速排序
快速排序可能是应用最广泛的排序算法了。快速排序流行的原因是它实现简单、适用于各种不同的输入数据且在一般应用中比其他排序算法都要快得多。快速排序引人注目的特点包括它是原地排序(只需要一个很小的辅助栈)。
快速排序是一种分治的排序算法。它将一个数组分成两个子数组,将两部分独立地排序。**快速排序和归并排序是互补的:归并排序将数组分成两个子数组分别排序,并将有序的子数组归并以将整个数组排序;而快速排序将数组排序的方式则是当两个子数组都有序时整个数组也就自然有序了。**归并排序的递归调用发生在处理整个数组之前;快速排序是递归调用发生在处理整个数组之和。
public class lab {private static void exch(int[] arr, int i, int j) {int temp = arr[i]; arr[i] = arr[j]; arr[j] = temp;}private static boolean less(int pre, int suf) {return pre < suf;}public static void sort(int[] arr, int lo, int hi) {if(lo >= hi) return ;// 进行切分,下标为partidx的元素在整个数组中是已经排序好的int partidx = partition(arr, lo, hi);// 对左右两部分进行排序sort(arr, lo, partidx - 1); sort(arr, partidx + 1, hi);}// 得到第j位是有序的对于整个数组来说public static int partition(int[] arr, int lo, int hi) {int i = lo, j = hi + 1; // 左右指针int v = arr[lo]; // 切分元素的值while(true) {// 扫描左右,检查扫描是否结束并交换元素while(less(arr[++i], v)) if(i == hi) break;while(less(v, arr[--j])) if(j == lo) break;if(i >= j) break;exch(arr, i, j);}exch(arr, lo, j);// 将这个数组第j位最终态找到return j;}public static int[] a = {5, 4, 1, 56, 3};public static int[] tmp = new int[5]; // 辅助数组// 测试public static void main(String[] args) {sort(a, 0, 4);for(int i : a) System.out.println(i);}
}
优先队列
优先队列是一种数据结构:支持快速的删除最大元素和插入元素。
优先队列可以用二叉堆实现。二叉堆,本质上是一种完全二叉树。
分类:二叉堆分为最大堆和最小堆两种类型,最大堆和最小堆分别又可称为大顶堆和小顶堆。最大堆中,任何一个父节点的值都大于或等于它的左、右孩子节点的值;最小堆中,任何一个父节点的值都小于或等于它的左、右孩子节点的值。
用数组(堆)实现的完全二叉树的结构是很严格的,但它的灵活性已经足以让我们高效地实现优先队列。用它们我们将能实现对数级别的插入元素和删除最大元素的操作。利用在数组中无需指针即可沿树上下移动的便利和以下性质,算法保证了对数复杂度的性能。
在有序化的过程中我们会遇到两种情况。当某个结点的优先级上升(或是在堆底加入一个新的元素)时,我们需要由下至上恢复堆的顺序。当某个结点的优先级下降(例如,将根结点替换为一个较小的元素)时,我们需要由上至下恢复堆的顺序。首先,我们会学习如何实现这两种辅助操作,然后再用它们实现插入元素和删除最大元素的操作。
由下向上的堆有序(上浮)
如果堆的有序状态因为某个结点变得比它的父结点更大而被打破,那么我们就需要通过交换它和它的父结点来修复堆。交换后,这个结点比它的两个子结点都大(一个是曾经的父结点,另一个比它更小,因为它是曾经父结点的子结点),但这个结点仍然可能比它现在的父结点更大。我们可以一遍遍地用同样的办法恢复秩序,将这个结点不断向上移动直到我们遇到了一个更大的父结点。
private void swim(int k)
{while (k > 1 && less(k/2, k)){exch(k/2, k);k /= 2;}
}
由上到下的堆有序(下沉)
private void sink(int k) {while(2 * k <= N) {int j = 2 * k;if(j < N && less(j, j + 1)) ++j;if(!less(k, j)) break;exch(k, j);k = j;}
}
堆排序
public class lab {private static void exch(int i, int j) {int temp = a[i]; a[i] = a[j]; a[j] = temp;}private static boolean less(int i, int j) {return a[i] < a[j];}public static void sort() {int N = a.length - 1;for(int k = N / 2; k >= 1; --k) {sink(k, N);}while(N > 1) {exch(1, N--);sink(1, N);}}private static void swim(int k) {while(k > 1 && less(a[k / 2], a[k])) {exch(k / 2, k);k /= 2;}}private static void sink(int k, int N) {while(2 * k <= N) {int j = 2 * k;if(j < N && less(j, j + 1)) ++j;if(!less(k, j)) break;exch(k, j);k = j;}}public static int[] a = {0, 5, 4, 1, 56, 3};// 测试public static void main(String[] args) {sort();for(int i : a) System.out.println(i);}
}
排序算法的时间复杂度和稳定性
算法(第4版) - LeetBook - 力扣(LeetCode)全球极客挚爱的技术成长平台
二叉堆的节点插入、删除以及构建过程_二叉堆插入-CSDN博客
常用十大排序算法-CSDN博客
相关文章:

排序 算法(第4版)
本博客参考算法(第4版):算法(第4版) - LeetBook - 力扣(LeetCode)全球极客挚爱的技术成长平台 本文用Java实现相关算法。 我们关注的主要对象是重新排列数组元素的算法,其中每个元素…...

asp.net 在线音乐网站系统VS开发sqlserver数据库web结构c#编程Microsoft Visual Studio
一、源码特点 asp.net 在线音乐网站系统是一套完善的web设计管理系统,系统具有完整的源代码和数据库,系统主要采用B/S模式开发。开发环境为vs2010,数据库为sqlserver2008,使用c#语言 开发 asp.net 在线音乐网站系统1 应用…...
ElastaticSearch -- es之Filters aggregation 先过滤再聚合
使用场景 使用es时,有时我们需要先过滤后再聚合,但如果直接在query的filter中过滤,不止会影响到一个聚合,还会影响到其他的聚合结果。 比如,我们想要统计深圳市某个品牌的总销售额,以及该品牌的女款衣服的…...
如何把一个接口设计好?
如何把一个接口设计好? 如何设计一个接口?是在我们日常开发或者面试时经常问及的一个话题。很多人觉得这不就是CRUD,能实现不就行了。单纯实现来说,并非难事,但要做到易用、易扩展、易维护并不是一件简单的事。这里并…...

mini-vue 的设计
mini-vue 的设计 mini-vue 使用流程与结果预览: <!DOCTYPE html> <html lang"en"><head><meta charset"UTF-8" /><meta http-equiv"X-UA-Compatible" content"IEedge" /><meta name&qu…...
React整理杂记(一)
1.React三项依赖 1.react.js -> 核心代码 2.react-dom.js -> 渲染成dom 3.babel.js->非必须,将jsx转为js 类组件中直接定义的方法,都属于严格模式下 this的绑定可以放到constructor(){}中 2. JSX语法 1.可以直接插入的元素: num…...
[100天算法】-统计封闭岛屿的数目(day 74)
题目描述 有一个二维矩阵 grid ,每个位置要么是陆地(记号为 0 )要么是水域(记号为 1 )。我们从一块陆地出发,每次可以往上下左右 4 个方向相邻区域走,能走到的所有陆地区域,我们将其…...
esp32-rust-std-examples-blinky
以下为在 ESP-IDF (FreeRTOS) 上运行的 blinky 示例: https://github.com/esp-rs/esp-idf-hal/blob/master/examples/blinky.rs //! Blinks an LED //! //! This assumes that a LED is connected to GPIO4. //! Depending on your target and the board you are …...
【docker容器技术与K8s】
【docker容器技术与K8s】 一、Docker容器技术 1、Docker的学习路线 (1)学习Docker基本命令(容器管理和镜像管理) (2)学习使用Docker搭建常用软件 (3)学习Docker网络模式 启动容器的…...

RT-DTER 引入用于低分辨率图像和小物体的新 CNN 模块 SPD-Conv
论文地址:https://arxiv.org/pdf/2208.03641v1.pdf 代码地址:https://github.com/labsaint/spd-conv 卷积神经网络(CNN)在图像分类、目标检测等计算机视觉任务中取得了巨大的成功。然而,在图像分辨率较低或对象较小的更困难的任务中,它们的性能会迅速下降。 这源于现有CNN…...
Folw + Room 实现自动观察数据库的刷新
1、Room :定义数据结构、创建数据库 // 定义实体 Entity data class TestModel ()// 定义数据库 Dao interface TestDao { Query("SELECT * FROM TestTable") fun getAll(): List<TestModel> }// 获取数据库 abstract class TestDatabase: RoomDat…...

黑马程序员微服务Docker实用篇
Docker实用篇 0.学习目标 1.初识Docker 1.1.什么是Docker 微服务虽然具备各种各样的优势,但服务的拆分通用给部署带来了很大的麻烦。 分布式系统中,依赖的组件非常多,不同组件之间部署时往往会产生一些冲突。在数百上千台服务中重复部署…...

虚拟化服务器+华为防火墙+kiwi_syslog访问留痕
一、适用场景 1、大中型企业需要对接入用户的访问进行记录时,以前用3CDaemon时,只能用于小型网络当中,记录的数据量太大时,本例采用破解版的kiwi_syslog。 2、当网监、公安查到有非法访问时,可提供基于五元组的外网访…...

FlinkSQL聚合函数(Aggregate Function)详解
使用场景: 聚合函数即 UDAF,常⽤于进多条数据,出⼀条数据的场景。 上图展示了⼀个 聚合函数的例⼦ 以及 聚合函数包含的重要⽅法。 案例场景: 关于饮料的表,有三个字段,分别是 id、name、price࿰…...

TensorFlow学习笔记--(3)张量的常用运算函数
损失函数及求偏导 通过 tf.GradientTape 函数来指定损失函数的变量以及表达式 最后通过 gradient(%损失函数%,%偏导对象%) 来获取求偏导的结果 独热编码 给出一组特征值 来对图像进行分类 可以用独热编码 0的概率是第0种 1的概率是第1种 0的概率是第二种 tf.one_hot(%某标签…...

RT-Thread:嵌入式实时操作系统的设计与应用
RT-Thread(Real-Time Thread)是一个开源的嵌入式实时操作系统,其设计和应用在嵌入式领域具有重要意义。本文将从RT-Thread的设计理念、核心特性,以及在嵌入式系统中的应用等方面进行探讨,对其进行全面的介绍。 首先&a…...
SpringBoot学习笔记-创建菜单与游戏页面(下)
笔记内容转载自 AcWing 的 SpringBoot 框架课讲义,课程链接:AcWing SpringBoot 框架课。 CONTENTS 1. 地图优化改进2. 绘制玩家的起始位置3. 实现玩家移动4. 优化蛇的身体效果5. 碰撞检测实现 本节实现两名玩家即两条蛇的绘制与人工操作移动功能。 1. 地…...
STM32一
0.前言 在B站经常看见有人用stm32做出了有趣的电子小玩艺儿,感到很羡慕,于是想了解一下。 1.什么是stm32 STM32 是一系列由STMicroelectronics(意法半导体)公司设计和制造的32位ARM Cortex-M微控制器。这一系列的微控制器广泛用…...
GPT-4 Turbo Assistants API
Assistants API Assistants API 允许您在自己的应用程序中构建 AI 助手。助手有指令,可以利用模型、工具和知识来响应用户查询。Assistants API 目前支持三种类型的工具:代码解释器、检索和函数调用。未来,我们计划发布更多 OpenAI 构建的工…...
day08_回顾与课程概括
回顾与课程概括 一、上节课复习 一、上节课复习 1、osi七层与数据传输 2、socketsocket是对传输层以下的封装ipport标识唯一一个基于网络通讯的软件3、tcp与udptcp:因为在通信之前必须建立双向连接,通常都是客户端主动连接服务端的,所以必须…...

【OSG学习笔记】Day 18: 碰撞检测与物理交互
物理引擎(Physics Engine) 物理引擎 是一种通过计算机模拟物理规律(如力学、碰撞、重力、流体动力学等)的软件工具或库。 它的核心目标是在虚拟环境中逼真地模拟物体的运动和交互,广泛应用于 游戏开发、动画制作、虚…...

shell脚本--常见案例
1、自动备份文件或目录 2、批量重命名文件 3、查找并删除指定名称的文件: 4、批量删除文件 5、查找并替换文件内容 6、批量创建文件 7、创建文件夹并移动文件 8、在文件夹中查找文件...

Redis相关知识总结(缓存雪崩,缓存穿透,缓存击穿,Redis实现分布式锁,如何保持数据库和缓存一致)
文章目录 1.什么是Redis?2.为什么要使用redis作为mysql的缓存?3.什么是缓存雪崩、缓存穿透、缓存击穿?3.1缓存雪崩3.1.1 大量缓存同时过期3.1.2 Redis宕机 3.2 缓存击穿3.3 缓存穿透3.4 总结 4. 数据库和缓存如何保持一致性5. Redis实现分布式…...

centos 7 部署awstats 网站访问检测
一、基础环境准备(两种安装方式都要做) bash # 安装必要依赖 yum install -y httpd perl mod_perl perl-Time-HiRes perl-DateTime systemctl enable httpd # 设置 Apache 开机自启 systemctl start httpd # 启动 Apache二、安装 AWStats࿰…...
FastAPI 教程:从入门到实践
FastAPI 是一个现代、快速(高性能)的 Web 框架,用于构建 API,支持 Python 3.6。它基于标准 Python 类型提示,易于学习且功能强大。以下是一个完整的 FastAPI 入门教程,涵盖从环境搭建到创建并运行一个简单的…...

Aspose.PDF 限制绕过方案:Java 字节码技术实战分享(仅供学习)
Aspose.PDF 限制绕过方案:Java 字节码技术实战分享(仅供学习) 一、Aspose.PDF 简介二、说明(⚠️仅供学习与研究使用)三、技术流程总览四、准备工作1. 下载 Jar 包2. Maven 项目依赖配置 五、字节码修改实现代码&#…...

【JVM】Java虚拟机(二)——垃圾回收
目录 一、如何判断对象可以回收 (一)引用计数法 (二)可达性分析算法 二、垃圾回收算法 (一)标记清除 (二)标记整理 (三)复制 (四ÿ…...

代码规范和架构【立芯理论一】(2025.06.08)
1、代码规范的目标 代码简洁精炼、美观,可持续性好高效率高复用,可移植性好高内聚,低耦合没有冗余规范性,代码有规可循,可以看出自己当时的思考过程特殊排版,特殊语法,特殊指令,必须…...
6️⃣Go 语言中的哈希、加密与序列化:通往区块链世界的钥匙
Go 语言中的哈希、加密与序列化:通往区块链世界的钥匙 一、前言:离区块链还有多远? 区块链听起来可能遥不可及,似乎是只有密码学专家和资深工程师才能涉足的领域。但事实上,构建一个区块链的核心并不复杂,尤其当你已经掌握了一门系统编程语言,比如 Go。 要真正理解区…...

相关类相关的可视化图像总结
目录 一、散点图 二、气泡图 三、相关图 四、热力图 五、二维密度图 六、多模态二维密度图 七、雷达图 八、桑基图 九、总结 一、散点图 特点 通过点的位置展示两个连续变量之间的关系,可直观判断线性相关、非线性相关或无相关关系,点的分布密…...