排序算法:归并排序、快速排序、堆排序

归并排序

要将一个数组排序,可以先将它分成两半分别排序,然后再将结果合并(归并)起来。这里的分成的两半,每部分可以使用其他排序算法,也可以仍然使用归并排序(递归)。

我看《算法》这本书里对归并算法有两种实现,一种是“自顶向下”,另一种是“自底向上”。这两种方法,个人认为只是前者用了递归的方法,后者使用两个 for 循环模拟了递归压栈出栈的算法,本质上还是相同的(如果理解错误,还望大佬指出)。

算法步骤

1、将要排序的序列分成两部分。
2、将两部分分别各自排序。然后再将两个已经排序好的序列“归并”到一起,归并后的整个序列就是有序的。
3、将两个有序的序列归并的步骤:
3.1、先申请一个空间,大小足够容纳两个已经排序的序列。
3.2、设定两个指针,最初位置分别为两个已经排序序列的起始位置。
3.3、比较两个指针所指向的元素,选择相对小的元素放入到合并空间,并移动指针到下一位置。
3.4、重复3.3 步骤。

归并排序,比较重要的是“分治”思想和“归并”的操作。

归并操作,是将两个“有序”的序列,合并成一个有序的序列的方法。而这两个有序的序列,又是根据“分治”思想将一个学列分割成的两部分(将一个序列不断的分隔,到最后就剩一个的时候他自然就是有序的)。

public class MergeSort {public static void main(String[] args) {int[] nums = {12,123,432,23,1,3,6,3,-1,6,2,6};;sort(nums,0,nums.length -1);System.out.printf("finish!");}public static void sort(int[] nums,int left,int right){if(left >= right){return;}// 递归的将左半边排序sort(nums,left,right - left / 2 - 1); // 递归的将右半边排序sort(nums,right - left / 2 ,right);// 此时左右半边都分别各自有序,再将其归并到一起。// 如:[1,9,10    ,  3,7,8]merge(nums,left,right - left / 2,right);}// 此方法叫做原地归并,将数组 nums 根据 mid 分隔开,左右看作是两个数组。// 类似于 merge(int[] nums1,int[] nums2),将 nums1 和 nums2 归并public static void merge(int[] nums ,int left,int mid,int right){int i = left,j = mid;int[] temp_nums = new int[nums.length];for(int key = left ; key <= right; key++)// 将原来数组复制到临时数组中。temp_nums[key] = nums[key];for(int key = i ; key <= right; key++){if(i > mid){nums[key] = temp_nums[j++];} else if (j > right) {nums[key] = temp_nums[i++];} else if (temp_nums[i] > temp_nums[j]) {nums[key] = temp_nums[j++];}else{nums[key] = temp_nums[i++];}}}}

快速排序

快速排序是一种分治的排序算法,它将一个数组分成两个子数组,将两部分独立的排序

快速排序可能是应用最广泛的排序算法了。快速排序流行的原因是它实现简单、适用于各种不同的输入数据且在一般应用中比其他排序算法都要快得多。——《算法(第四版)》

算法步骤

  1. 从数列中挑出一个元素,称为 "基准"(pivot);
  2. 所有元素比"基准"值小的摆放在前面,所有元素比"基准"值大的摆在后面,相同的数可以到任一边。这个称为分区(partition)操作。
  3. 递归地(recursive)使用同样的方法把小于基准值元素的子数列和大于基准值元素的子数列排序;

算法过程

1、给定一个乱序的数组

[5,1,4,6,2,66,34,8]

2、选择第一个为基准数,此时把第一个位置置空。两个指针,left从左到右,找比 piovt “大”的数;right 从右向左,找比 piovt “小”的数。

left            right|               |
[_,1,4,6,2,66,34,8]|
piovt = 5

3、right 从右向左(<-),找比 piovt “小”的数 2。

  left right|     |
[_,1,4,6,2,66,34,8]|
piovt = 5

4、left从左到右(->),找到了比 piovt 大的数 6。

    left  right| |
[_,1,4,6,2,66,34,8]|
piovt = 5

5、此时将 left 和 right 上的数对调。

    left right| |
[_,1,4,2,6,66,34,8]|
piovt = 5

6、right 继续向左查找,直到 left = right。(正常情况下要重复 4、5 步骤多次才会得到 left = right)
此时将 left 位置的数放到原来 piovt 位置上,将 piovt 放到 left 位置上。

      leftright|
[2,1,4,5,6,66,34,8]-     -|
piovt = 5

7、此时将整个数组根据 piovt 分割成两个部分,左边都比 piovt 小,右边都比 piovt 大。递归的处理左右两部分。

实现


public class QuickSort {public static void main(String[] args) {int[] nums = {5,1,4,6,2,66,34,8,34,534,5};int[] sorted = sort(nums,0 , nums.length - 1);System.out.println("finish!");}// 排序public static int[] sort(int[] nums , int left , int right){if(left <= right){ // 将 nums 以 mid 分成两部分// 左边的小于 nums[min]// 右边的大于 nums[min]int mid = partition(nums,left,right);// 递归sort(nums,left,mid - 1);sort(nums,mid + 1 ,right);}return nums;}public static int partition(int[] nums , int left , int right){//int pivot = left;int i = left , j = right + 1; // 左右两个指针int pivot = nums[left]; // 基准数,比他小的放到左边,比他大的放到右边。while ( true ){// 从右向左找比 pivot 小的。while (j > left && nums[--j] > pivot){if(j == left){// 到头了break;}}// 先从左向右找比 pivot 大的。while (i < right && nums[ ++ i] < pivot ){if( i == right){// 到头了break;}}if(i >= j ) break;// 交换 i 位置和 j 位置上的数// 因为此时 nums[i] > pivot 并且 nums[j] < pivotswap(nums,i , j);}// 由于 left 位置上的数是 pivot=// 此时 i = j 且 nums[i/j] 左边的数都小于 pivot , nums[i/j] 右边的数都大于 pivot。// 此时交换 left 和 j 位置上的数就是将 pivot 放到中间swap(nums,left,j);return j ;}// 交换数组中两个位置上的数public static void swap(int[] nums , int i1 , int i2){int n = nums[i1];nums[i1] = nums[i2];nums[i2] = n;}}

堆排序

堆排序主要是利用“堆”这种数据结构的特性来进行排序,它本质上类似于“选择排序”。都是每次将最大值(或最小值),找出来放到数列尾部。不过“选择排序”需要遍历整个数列后选出最大值(可以到上面再熟悉下选择排序算法),“堆排序”是依靠堆这种数据结构来选出最大值。但是每次重新构建最大堆用时要比遍历整个数列要快得多。

堆排序中用到的两种堆,大顶堆和小顶堆:

1、大顶堆:每个节点的值都大于或等于其子节点的值(在堆排序算法中一般用于升序排列);
2、小顶堆:每个节点的值都小于或等于其子节点的值(在堆排序算法中一般用于降序排列);

图片来自 dreamcatcher-cx 的文章

我们给树的每个节点编号,并将编号映射到数组的下标就是这样:

图片来自 dreamcatcher-cx 的文章

该数组从逻辑上是一个堆结构,我们用公式来描述一下堆的定义就是:

1、大顶堆:arr[i] >= arr[2i+1] && arr[i] >= arr[2i+2]
2、小顶堆:arr[i] <= arr[2i+1] && arr[i] <= arr[2i+2]

这里只要求父节点大于两个子节点,并没有要求左右两个子节点的大小关系。

算法过程

1、将一个 n 长的待排序序列arr = [0,……,n-1]构造成一个大顶堆。
2、此时数组的 0 位置(也就是堆顶),就是数组的最大值了,将其与数组的最后一个数交换。
3、将剩下 n-1 个数重复 1和2 操作,最终会得到一个有序的序列。

堆排序是“选择排序”的一种变体,算法中比较难的地方是用数组构建“大顶堆”或“小顶堆”的过程。

实现堆排序前,我们要知道怎么用数组构建一个逻辑上的最大堆,这里会用到几个公式(假设当前节点的序号是 i,可以结合上图理解下下面的公式):

1、左子节点的序号就是:2i + 1;
2、右子几点的序号就是:2i + 2;
3、父节点的序号就是:(i-1) / 2 (i不为0);

public class HeapSort {static int temp ;public static void main(String[] args) {int[] nums = {5,1,4,6,2,66,-1,34,-9,8};sort(nums);System.out.println("finish!");}public static void sort(int[] nums){// 第一步要先将 nums 构建成最大堆。for(int i = (nums.length - 1) / 2 ; i >= 0; i-- ){//从第一个非叶子结点从下至上,从右至左调整结构maxHeapify(nums,i,nums.length);}// 遍历数组// j 是需要排序的数组的最后一个索引位置。for(int j = nums.length - 1 ; j > 0 ; j --){// 每次都调整最大堆堆顶(nums[0]),与数组尾的数据位置(nums[j])。swap(nums,0,j);// 重新调整最大堆maxHeapify(nums,0,j);}}/*** 将 nums 从 i 开始的 len 长度调整成最大堆。* (注意:此方法只适合调整已经是最大堆但是被修改了的堆,或者只有三个节点的堆)* len :需要计算到数组 nums 的多长的地方。* i :父节点在的位置。*/public static void maxHeapify(int[] nums,int i , int len){// 是从左子节点开始int key = 2 * i + 1;if(key >= len){// 说明没有子节点。return;}// key + 1 是右子节点的位置。if(key + 1 < len && nums[key] < nums[key + 1]){// 此时说明右节点比左节点大。// 此时只要将父节点跟 右子节 点比就行了。key += 1;}if(nums[i] < nums[key]){// 子节点比父节点大,交换子父界节点的数据,将父节点设置为最大。swap(nums,i,key);// 此时子节点上的数变了,就要递归的再去,计算子节点是不是最大堆。maxHeapify(nums,key,len);}}/*** 交换 i 和 j 位置的数据*/public static void swap(int[] nums,int i,int j){temp = nums[i];nums[i] = nums[j];nums[j] = temp;}
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/171310.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

day64

跨域请求伪造 一、csrf跨站请求伪造详解 CSRF&#xff08;Cross-Site Request Forgery&#xff09;跨站请求伪造是一种常见的网络攻击方式。攻击者通过诱导受害者访问恶意网站或点击恶意链接 将恶意请求发送到目标网站上利用受害者在目标网站中已登录的身份来执行某些操作从而…

电源的纹波

电源纹波的产生 我们常见的电源有线性电源和开关电源&#xff0c;它们输出的直流电压是由交流电压经整流、滤波、稳压后得到的。由于滤波不干净&#xff0c;直流电平之上就会附着包含周期性与随机性成分的杂波信号&#xff0c;这就产生了纹波。 在额定输出电压、电流的情况下…

【开题报告】基于SpringBoot的高端美发产品商城的设计与实现

1.研究背景 随着人们对美容、护肤和个人形象的重视&#xff0c;高端美发产品在市场上的需求越来越大。传统的线下销售方式存在一些问题&#xff0c;如地域限制、库存管理困难等&#xff0c;不能满足现代消费者的需求。因此&#xff0c;建立一个在线商城平台&#xff0c;可以让…

leetCode 1080.根到叶路径上的不足节点 + 递归 + 图解

给你二叉树的根节点 root 和一个整数 limit &#xff0c;请你同时删除树中所有 不足节点 &#xff0c;并返回最终二叉树的根节点。假如通过节点 node 的每种可能的 “根-叶” 路径上值的总和全都小于给定的 limit&#xff0c;则该节点被称之为 不足节点 &#xff0c;需要被删除…

SQL Injection (Blind)`

SQL Injection (Blind) SQL Injection (Blind) SQL盲注&#xff0c;是一种特殊类型的SQL注入攻击&#xff0c;它的特点是无法直接从页面上看到注入语句的执行结果。在这种情况下&#xff0c;需要利用一些方法进行判断或者尝试&#xff0c;这个过程称之为盲注。 盲注的主要形式有…

Python之内置函数和模块

学习的最大理由是想摆脱平庸&#xff0c;早一天就多一份人生的精彩&#xff1b;迟一天就多一天平庸的困扰。各位小伙伴&#xff0c;如果您&#xff1a; 想系统/深入学习某技术知识点… 一个人摸索学习很难坚持&#xff0c;想组团高效学习… 想写博客但无从下手&#xff0c;急需…

基于单片机的可升降助眠婴儿床(论文+源码)

1.系统设计 本课题为基于单片机的可升降助眠婴儿床系统&#xff0c;在设计目标上确定如下&#xff1a; 1. 可以实现婴儿床的升降&#xff0c;摇床功能控制&#xff1b; 2. 具有音乐播放功能&#xff0c;并且有多首曲目&#xff1b; 3. 用户可以通过按键或者红外遥控&#x…

使用fetch封装get与post方法

网上看了一些对fetch的封装&#xff0c;有点看不伶清。如在request中配置timeout与responseType字段等&#xff0c;在文档上找根本找不到。于是自己封装个简单版的fetch&#xff0c;方便拿到项目中改造一下就可以使用。 注意点 使用fetch时会产生跨域问题&#xff0c;需服务端…

Runloop解析

RunLoop 前言 ​ 本文介绍RunLoop的概念&#xff0c;并使用swift和Objective-C来描述RunLoop机制。 简介 ​ RunLoop——运行循环&#xff08;死循环&#xff09;&#xff0c;它提供了一个事件循环机制在程序运行过程中处理各种事件&#xff0c;例如用户交互、网络请求、定…

用JAVA编程解决数位和相等问题

如果一个正整数转化成二进制与转换成八进制后所有数位的数字之和相等&#xff0c;则称为数位和相等的数。   前几个数位和相等的正整数为 1, 8, 9, 64, ……   请问第 23 个数位和相等的正整数是多少&#xff1f;用JAVA编程解决 可以通过编程计算第 23 个数位和相等的正整…

Xshell连接VMware虚拟机中的CentOS

Xshell连接VMware虚拟机中的CentOShttps://www.cnblogs.com/niuben/p/13157291.html 步骤&#xff1a; 1. 检查Linux虚拟机的网络连接模式&#xff0c;确保它是NAT模式。&#xff08;由于只在本机进行连接&#xff0c;所以没有选择桥接模式。当然&#xff0c;桥接模式的配置会…

利用ngrok实现内网穿透(全网最详细教程)

准备工具&#xff1a; 1、phpstudy 用于在本地搭建网站 2、ngrok 用于将自己的本地端口暴露到公网上&#xff0c;从而实现内网穿透 文章开始前给大家分享一个学习人工智能的网站&#xff0c;通俗易懂&#xff0c;风趣幽默 人工智能https://www.captainbed.cn/myon/ ~~~~~…

浅谈Linux bash脚本----获取脚本启动参数

${#} 用于获取传递给脚本的参数数目 params_count${#} echo $params_count > ./PATH/TO/my_script.sh param1p1 param2p2 > 2 ${} 用于获取传递给脚本的参数列表 params_list${} echo $params_list > ./PATH/TO/my_script.sh param1p1 param2p2 > param1p1 …

【教学类-06-12】20231126 (一)二位数 如何让加减乘除题目从小到大排序(以1-20之间加法为例,做正序排列用)

结果展示 优化后 优化前 背景需求&#xff1a; 生成列表 单独抽取显示题目排序方法 存在问题: 我希望 00 01 02……这样排序&#xff0c;但是实际上&#xff0c;除了第一个加数会从小到大排序&#xff0c;第二个被加数的第十位数和个位数都会从小到大排序&#xff0c;也就是…

Centos7离线安装chrome浏览器

很多时候在linux下直接安装chrome浏览器困难,或者速度极慢,这里总结下在离线的情况下,怎么下载安装chrome并且配置对应的driver驱动 1.首先如果有安装历史版本,可以先卸载,卸载命令: yum remove google-chrome-stable.x86_64 -y 2.最好下载历史版本chrome,比较稳定,…

Docker attach 命令

docker attach&#xff1a;连接到正在运行中的容器。 语法 docker attach [OPTIONS] CONTAINER要attach上去的容器必须正在运行&#xff0c;可以同时连接上同一个container来共享屏幕&#xff08;与screen命令的attach类似&#xff09;。 官方文档中说attach后可以通过CTRL-…

提示工程-Prompt Engineering

提示工程 提示工程 1、概述 Prompt Engineering&#xff1a; 提示工程 通过自然语言&#xff08;英语、汉语等&#xff09;来给AI下达指示&#xff0c;从而让AI完成你指定给他的工作的过程都可以称之为提示工程。&#xff08;面向自然语言编程&#xff09; 提示词要素 指令&…

Spring Web MVC

目录 一.简介 二.建立连接&#xff08;客户端和服务器&#xff09; 三.请求 1.传递单个参数 2.传递多个参数 3.对象 4.数组/集合 5.JSON 6.URL参数 7.上传文件 8.获取cookie和session &#xff08;1&#xff09;获取cookie &#xff08;2&#xff09;获取session …

electron 问题记录

23年11月24 electron项目npm install 卡在一个地方不动 原因&#xff1a;主要是 install electron 会卡住 解决方法&#xff1a; # 先解决install electron卡死 npm install -g cnpm --registryhttps://registry.npmmirror.com cnpm install electron# 然后下载其他依赖 np…

4D Gaussian Splatting:用于实时的动态场景渲染

Wu G, Yi T, Fang J, et al. 4d gaussian splatting for real-time dynamic scene rendering[J]. arXiv preprint arXiv:2310.08528, 2023. 更多参考资料如下&#xff1a; 文章总结&#xff1a;4D Gaussian Splatting for Real-Time Dynamic Scene Rendering&#xff1b;疑难问…