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

news2024/11/15 11:19:16

归并排序

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

我看《算法》这本书里对归并算法有两种实现,一种是“自顶向下”,另一种是“自底向上”。这两种方法,个人认为只是前者用了递归的方法,后者使用两个 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 位置上。

      left
      right
       |
[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] < pivot
            swap(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.coloradmin.cn/o/1237345.html

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

相关文章

酒店预订订房小程序源码系统+多酒店入驻 功能齐全 附带完整的搭建教程

随着互联网的快速发展&#xff0c;越来越多的人选择通过在线预订平台预订酒店。为了满足这一需求&#xff0c;我们开发了这个酒店预订订房小程序源码系统。该系统基于先进的云计算技术和大数据分析&#xff0c;旨在为用户提供更加便捷、智能的酒店预订服务。 以下是部分代码示…

喜爱拍拍宝宝照片的,一定要制作照片书方便保存

​制作照片书&#xff0c;让美好记忆长久保存。随着数码技术的普及&#xff0c;我们拥有了越来越多的照片&#xff0c;但如何妥善保存这些珍贵的回忆呢&#xff1f;一张张照片随意夹在相册里&#xff0c;时间一长&#xff0c;容易丢失或混乱。而照片书则不同&#xff0c;它把多…

前端如何判空

这样判空就会报错 loadNode(node, resolve)console.log("node")console.log(node)if (node.data ! null) {this.get(ctx /publicity/publicityType/typeTreeData?id node.data.id).then((res) > {resolve(res)})}}, 需要这样写&#xff0c;用typeof来做类型判…

解析大型语言模型的训练、微调和推理的运行时性能

背景 这篇论文是截至目前为数不多的介绍大模型训练配套环境比对的论文&#xff0c;对于想要入门大模型训练同学是个不错的入门资料。比较了不同尺寸模型&#xff08;比较常用的7、13、70b&#xff09;&#xff0c;在不同型号gpu、训练框架、推理框架数据。结合自己实际工作需要…

交流充电桩测试参考标准是哪些

功能性能测试&#xff1a;这是评价交流充电桩性能的重要标准&#xff0c;包括充电效率、充电稳定性、充电模式等。充电效率主要检查充电桩的充电功率、充电时间等&#xff1b;充电稳定性主要检查充电桩的电压、电流波动等&#xff1b;充电模式主要检查充电桩的恒流充电、恒压充…

Java基于B/S架构,包括PC后台管理端、APP移动端、可视化数据大屏的智慧工地源码

智慧工地管理平台充分运用数字化技术&#xff0c;聚焦施工现场岗位一线&#xff0c;依托物联网、互联网、AI等技术&#xff0c;围绕施工现场管理的人、机、料、法、环五大维度&#xff0c;以及施工过程管理的进度、质量、安全三大体系为基础应用&#xff0c;实现全面高效的工程…

Spark---集群搭建

Standalone集群搭建与Spark on Yarn配置 1、Standalone Standalone集群是Spark自带的资源调度框架&#xff0c;支持分布式搭建&#xff0c;这里建议搭建Standalone节点数为3台&#xff0c;1台master节点&#xff0c;2台worker节点&#xff0c;这虚拟机中每台节点的内存至少给…

使用CSS渲染不同形状

本文只是用来记录自己遇到的图形 1.图形一 2.图形二 3.图形三 4.图形四 5.图形五

uniapp 打包后各静态资源加载失败的问题(背景图,字体等)

原因: 1.部署地址不在域名根目录下 解决办法(推荐办法2): 办法1.如果部署在域名的文件夹下(例如h5), 则运行的基础路径修改为/h5/ 且注意路由模式 办法2.不修改运行的基础路径(还是./), 将代码中涉及背景图(background-image)和字体资源的路径前统一加,如图:

解决ElementUI时间选择器回显出现Wed..2013..中国标准时间.

使用饿了么组件 时间日期选择框回显到页面为啥是这样的&#xff1f; 为什么再时间框中选择日期&#xff0c;回显页面出现了这种英文格式呢&#xff1f;&#xff1f;&#xff1f;&#xff1f; 其实这个问题直接使用elementui的内置属性就能解决 DateTimePicker 日期时间选择…

时序预测 | MATLAB实现基于ELM-AdaBoost极限学习机结合AdaBoost时间序列预测

时序预测 | MATLAB实现基于ELM-AdaBoost极限学习机结合AdaBoost时间序列预测 目录 时序预测 | MATLAB实现基于ELM-AdaBoost极限学习机结合AdaBoost时间序列预测预测效果基本介绍模型描述程序设计参考资料 预测效果 基本介绍 1.Matlab实现ELM-Adaboost时间序列预测&#xff0c;极…

健康饮酒进家庭,国台酒业与碧桂园服务集团达成战略合作

11月19日&#xff0c;碧桂园服务集团与国台酒业集团战略合作发布会暨“健康饮酒进家庭”项目启动仪式在广州举行。 广东省酒类行业协会创会会长朱思旭&#xff0c;广东省酒类行业协会会长彭洪&#xff0c;碧桂园服务集团总裁徐彬淮&#xff0c;碧桂园服务集团酒类业务总经理、广…

报表控件Stimulsoft 操作演示:空数据和 Dock 样式

在今天的文章中&#xff0c;我们将讨论如何避免报告中出现空行。我们不仅会介绍在没有数据时禁用组件&#xff1b;还会介绍在没有数据时禁用组件。我们还将探索消除禁用组件时可能出现的空行。但在我们深入探讨之前&#xff0c;让我们检查一下数据带的零数据样本。 Stimulsoft…

【Rxjava详解】(一)观察者模式的拓展

文章目录 RxJava引入扩展的观察者模式RxJava的观察者模式基本实现 RxJava入门示例Action RxJava引入 在介绍RxJava之前先说一下Rx。全称是Reactive Extensions&#xff0c;直译过来就是响应式扩展 Rx基于观察者模式&#xff0c;它是一种编程模型&#xff0c;目标是提供一致的…

4.15每日一题(连续函数在有界闭区域上求最大/小值:拉格朗日乘数法、化条件为无条件法)

方法一&#xff1a;拉格朗日乘数法&#xff08;拉格朗日乘数设的方程比较好解的时候使用&#xff09; 方法二&#xff1a;化条件为无条件 &#xff08;拉格朗日乘数设的方程不好解的时候使用&#xff09; &#xff08;1&#xff09;直角坐标方程化条件为无条件法 &#xff08;2…

【量化】一个简版单档tick数据回测框架

这是一个简易的模拟实际交易流程的回测框架&#xff0c;所使用的行情数据是单档的tick成交数据。为了实现调用者可以实现自己的交易逻辑&#xff0c;本框架预留了几个函数予以调用者能够继承类后在子类中重写以实现买入卖出信号的生成&#xff08;check_sell()和check_buy()&am…

ASO优化之关于应用类别的选择

用户可以通过应用商店内的类别&#xff0c;来发现他们想要的新应用。接下来我们来一起了解如何为应用来选择最符合最准确的类别。 1、应用的类别所显示的区域&#xff1f; 我们可以为APP选择一个主要类别和一个次要类别&#xff0c;所选择的主要类别对于该应用在应用商店曝光度…

3.9-Dockerfile实战

这一节介绍怎么将python程序打包成一个image&#xff0c;然后运行为一个container。 首先&#xff0c;创建/home/python/目录 mkdir /home/python/ 然后创建app.py文件。 vim app.py app.py文件的内容如下&#xff1a; from flask import Flaskapp Flask(__name__)app.route(…

「L2C」型行业从线索到成交,听懂客户之「声」是关键

存量经营时代下&#xff0c;营销变得越来越难。无论是稳流量&#xff0c;或是促活跃&#xff0c;转化率就是难以提升。 相比传统快消行业&#xff0c;线索型&#xff08;L2C&#xff0c;Leads to Cash&#xff09;行业因为客单价高、决策周期长、用户触点分散等特性&#xff0…

COMSOL 多场耦合仿真技术与应用”光电常见案例应用

(一)案列应用实操教学&#xff1a; 案例一 光子晶体能带分析、能谱计算、光纤模态计算、微腔腔膜求解 案例二 类比凝聚态领域魔角石墨烯的moir 光子晶体建模以及物理分析 案例三 传播表面等离激元和表面等离激元光栅等 案例四 超材料和超表面仿真设计&#xff0c;周期性超表面…