Java中的经典排序算法:快速排序、归并排序和计数排序详解(如果想知道Java中有关快速排序、归并排序和计数排序的知识点,那么只看这一篇就足够了!)

本文主要是介绍Java中的经典排序算法:快速排序、归并排序和计数排序详解(如果想知道Java中有关快速排序、归并排序和计数排序的知识点,那么只看这一篇就足够了!),希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

        前言:排序算法在计算机科学中占有重要地位,不同的算法适用于不同的场景。本文将深入探讨快速排序、归并排序和计数排序。


✨✨✨这里是秋刀鱼不做梦的BLOG

✨✨✨想要了解更多内容可以访问我的主页秋刀鱼不做梦-CSDN博客

先让我们看一下本文大致的讲解内容:

        对于每个排序算法,我们都会从算法简介、其原理与步骤、代码实现、时间复杂度分析、空间复杂度分析与该排序算法的应用场景这几个方面来进行讲解。

目录

1.快速排序 (Quicksort)

        (1)算法简介

        (2)算法的原理与步骤

        (3)Java代码实现

        【1】Hoare法

        【2】挖坑法

        【3】前后指针法

        (4)时间复杂度和空间复杂度

        (5)算法的应用场景

2.归并排序 (Merge Sort)

        (1)算法简介

        (2)算法的原理与步骤

        (3)Java代码实现

        (4)时间复杂度和空间复杂度

        (5)算法的应用场景

3.计数排序 (Counting Sort)

        (1)算法简介

        (2)算法的原理与步骤

        (3)Java代码实现

        (4)时间复杂度和空间复杂度

        (5)算法的应用场景


1.快速排序 (Quicksort)

        (1)算法简介

        快速排序是一种高效的排序算法,由C.A.R. Hoare在1960年提出。它采用分治法(Divide and Conquer),通过递归地将未排序的部分分割为较小的子数组进行排序,再将其合并。快速排序的平均时间复杂度为 O(nlog⁡n),在大多数情况下比其他 O(nlog⁡n) 的算法,如归并排序,具有更好的性能。

        当然,我相信读者读完快速排序的简介之后,对于快速排序算法的认识还是没什么认识的,接下来让我们直接带着你边学如何实现排序算法边理解该算法的内核。

        (2)算法的原理与步骤

        快速排序的核心思想是选择一个“基准”(pivot),并通过一系列的比较和交换操作将数组分成两部分:一部分所有元素小于基准,另一部分所有元素大于基准。然后,对这两部分分别递归地应用快速排序。具体步骤如下:

  1. 选择基准: 从数组中选择一个元素作为基准。选择基准的方法有多种,如选择第一个元素、最后一个元素、随机选择或三数取中法。

  2. 分区 (Partitioning): 通过遍历数组,将所有小于基准的元素放在基准的左边,所有大于基准的元素放在右边。

  3. 递归排序: 对基准左边和右边的子数组递归地进行快速排序。

  4. 组合: 由于分区已经确保了基准左边的元素小于基准,右边的元素大于基准,因此当递归完成后,数组已经是有序的。

        ——看完了上述大体如何实现快速排序算法之后,让我们从代码的层面来实现一下快速排序算法。

        (3)Java代码实现

以下为Java中实现快速排序的大致代码:

public void quickSort(int[] array) {int end = array.length - 1;quickSort(array, 0, end);
}private void quickSortHo(int[] array, int left, int right) {// 如果子数组长度为1或更小,则返回(即递归终止条件)if (left >= right) {return;}// 查找基准元素的位置,并将数组分成两部分int target = findTarget(array, left, right);// 对基准元素左边的子数组进行递归排序quickSort(array, left, target - 1);// 对基准元素右边的子数组进行递归排序quickSort(array, target + 1, right);
}

        从上述代码中我们可以看到我们在寻找基准元素位置的时候,使用了findTarget()方法,那么这个方法该如何实现呢?

        实现该方法的方式大致有三种,分别是:Hoare法、挖坑法和前后指针法。接下来我们一一进行讲解。

        【1】Hoare法

public int findTargetH(int[] array, int left, int right) {// 获取中间索引并将中间元素交换到数组的最左端int mid = middleIndex(array, left, right);swap(array, left, mid);// 将最左端的元素作为基准元素int temp = array[left];int keyi = left; // 保存基准元素的初始位置// 开始从数组的两端向中间扫描while (left < right) {// 从右向左扫描,找到第一个小于基准的元素while (left < right && array[right] >= temp) {right--;}// 从左向右扫描,找到第一个大于基准的元素while (left < right && array[left] <= temp) {left++;}// 交换左侧找到的大于基准的元素和右侧找到的小于基准的元素swap(array, left, right);}// 将基准元素放置到最终的位置,即左、右指针相遇的位置swap(array, keyi, left);// 返回基准元素的位置索引return left;
}

解释说明:

  • middleIndex(array, left, right):假设这是一个用于计算数组中间索引的方法,以获取中间元素的位置。

  • swap(array, left, mid):将中间元素和最左端元素交换,使得基准元素(最左端元素)在处理前放到适当的位置。

  • temp:基准元素,用于比较其他元素。

  • keyi:保存基准元素的初始索引位置,以便最终将基准元素放到正确的位置。

  • while (left < right):循环扫描数组,直到左右指针相遇。

  • swap(array, left, right):当找到左侧大于基准的元素和右侧小于基准的元素时,交换它们的位置。

  • 最后一次swap:将基准元素放回到排序后应在的位置上,并返回该位置的索引。

        ——这就是Hoare实现查找基准元素位置。

        【2】挖坑法

public int findTargetW(int[] array, int left, int right) {// 将最左端的元素作为基准元素int temp = array[left];// 开始从数组的两端向中间扫描while (left < right) {// 从右向左扫描,找到第一个小于基准的元素while (left < right && array[right] >= temp) {right--;}// 将小于基准的元素移到左侧array[left] = array[right];// 从左向右扫描,找到第一个大于基准的元素while (left < right && array[left] <= temp) {left++;}// 将大于基准的元素移到右侧array[right] = array[left];}// 将基准元素放置到最终的位置,即左、右指针相遇的位置array[left] = temp;// 返回基准元素的位置索引return left;
}

解释说明:

  • temp:基准元素,取自数组的最左端,用于比较和调整其他元素的位置。

  • while (left < right):主循环,通过左右指针的移动将数组分为小于基准和大于基准的两部分,直到左右指针相遇。

  • array[left] = array[right]:将右侧扫描中找到的小于基准的元素移动到左侧。

  • array[right] = array[left]:将左侧扫描中找到的大于基准的元素移动到右侧。

  • 最后一步:将基准元素放置在最终的正确位置,并返回该位置的索引,以供进一步的递归使用。

        ——这就是挖坑法实现查找基准元素位置。

        【3】前后指针法

public int findTargetQ(int[] array, int left, int right) {// 当前基准元素的位置(初始为左边界)int cur = left;// 从基准元素的下一个位置开始遍历int prev = left + 1;// 保存基准元素的值int temp = array[left];// 遍历数组,直到右边界while (prev <= right) {// 如果当前元素小于基准元素且 cur 与 prev 不相等,则交换这两个元素if (array[prev] < temp && ++cur != prev) {swap(array, cur, prev);}// 移动到下一个元素prev++;}// 将基准元素放置到最终的位置,即 cur 的位置swap(array, left, cur);// 返回基准元素的位置索引return cur;
}

解释说明:

  • cur:表示当前已经确定位置的元素的索引,初始值为左边界,负责标记小于基准元素的部分的末尾。

  • prev:用于遍历数组的索引,从基准元素的下一个位置开始。

  • temp:基准元素的值,用于与其他元素进行比较。

  • while (prev <= right):遍历数组中的元素,寻找小于基准元素的值,并将其与当前元素进行交换。

  • if (array[prev] < temp && ++cur != prev):如果当前元素小于基准元素,并且 curprev 不相等(确保不进行自我交换),则交换这两个元素。

  • swap(array, cur, left):将基准元素放置到最终的位置,即所有小于基准元素的元素的末尾。

  • return cur:返回基准元素的位置索引,用于进一步的排序。

        ——这就是前后指针法实现查找基准元素位置。

通过上述的学习,我们就了解了如何使用Java代码来实现快速排序算法。

        (4)时间复杂度和空间复杂度

  • 时间复杂度

    • 平均时间复杂度: O(nlog⁡n)。快速排序在分区步骤中,每次都将数组分成两个大致相等的部分,因此递归的深度为 O(log⁡n),每层递归处理的元素数为 O(n)。

    • 最坏时间复杂度: O(n2)。当每次分区产生的子数组大小极度不均匀(如数组已经有序或逆序)时,递归深度变为 O(n)。

  • 空间复杂度: O(log⁡n)(主要为递归调用栈的空间)。由于每次递归都会消耗栈空间,因此快速排序的空间复杂度与递归的深度相关。

        (5)算法的应用场景

快速排序适用于大部分需要排序的场景,尤其是:

  • 大数据集: 快速排序的平均性能优于其他排序算法,适合处理大规模数据集。

  • 内存空间有限的情况: 相较于归并排序,快速排序的空间复杂度更低,更适合内存资源有限的场景。

  • 不需要稳定排序的场景: 快速排序是一个不稳定的排序算法,不适用于需要保持相同元素相对顺序的场景。

        这样我们就学习完了快速排序算法了!!!

2.归并排序 (Merge Sort)

        (1)算法简介

        归并排序是一种稳定的排序算法,采用分治策略,将待排序的数组分成若干子数组,分别对每个子数组进行排序,再将这些子数组合并成一个有序数组。归并排序的时间复杂度为 O(nlog⁡n),在数据量较大且对排序稳定性要求较高的场景中有较好的表现。

同样,我们接下来带着你边学如何实现排序算法边理解该算法的内核。

        (2)算法的原理与步骤

        归并排序的基本思想是将数组分成尽可能小的子数组(每个子数组只有一个元素),然后逐步合并这些子数组,使得合并后的数组有序。具体步骤如下:

  1. 分割: 将数组分成两部分,递归地对每一部分进行归并排序。

  2. 排序并合并: 当每个子数组的长度为1时,开始合并相邻的子数组。在合并时,使用双指针技术,比较两个子数组的元素,将较小的元素放入临时数组中。

  3. 递归处理: 对左右子数组分别递归应用归并排序,直至最终将所有元素合并为一个有序数组。

        

        ——看完了上述大体如何实现归并排序算法之后,让我们从代码的层面来实现一下快速排序算法。

        (3)Java代码实现

以下为Java中实现归并排序的大致代码:

public void mergeSort(int[] array) {// 调用归并排序的递归方法,排序整个数组mergeInsertSort(array, 0, array.length - 1);
}public void mergeInsertSort(int[] array, int left, int right) {// 如果子数组只有一个元素或为空,则返回(递归终止条件)if (left >= right) {return;}// 计算中间位置,将数组分成两部分int mid = (left + right) / 2;// 对左半部分进行递归排序mergeInsertSort(array, left, mid);// 对右半部分进行递归排序mergeInsertSort(array, mid + 1, right);// 合并两个已排序的子数组merge(array, left, mid, right);
}public void merge(int[] array, int left, int mid, int right) {// 右半部分的起始位置int rightBegin = mid + 1;// 左半部分的起始位置int leftBegin = left;// 用于存放合并结果的临时数组int i = 0;int[] ret = new int[right - left + 1];// 合并两个已排序的子数组while (left <= mid && rightBegin <= right) {// 比较左右两部分的元素,将较小的元素放入临时数组if (array[left] < array[rightBegin]) {ret[i++] = array[left++];} else {ret[i++] = array[rightBegin++];}}// 将左半部分剩余的元素添加到临时数组中while (left <= mid) {ret[i++] = array[left++];}// 将右半部分剩余的元素添加到临时数组中while (rightBegin <= right) {ret[i++] = array[rightBegin++];}// 将临时数组中的元素复制回原数组的相应位置for (int j = 0; j < ret.length; j++) {array[j + leftBegin] = ret[j];}
}

解释:

  • mergeSort(int[] array):这是归并排序的入口方法,它调用 mergeInsertSort 方法对整个数组进行排序。

  • mergeInsertSort(int[] array, int left, int right):这是归并排序的递归方法,递归地将数组分成更小的部分并排序,然后调用 merge 方法合并已排序的子数组。

  • merge(int[] array, int left, int mid, int right):这是合并两个已排序的子数组的方法。它创建一个临时数组 ret,将左右两部分按顺序合并到 ret 中,然后将合并后的结果复制回原数组。

        ——这样我们就了解了如何使用Java代码来实现归并排序算法。

        (4)时间复杂度和空间复杂度

  • 时间复杂度: O(nlog⁡n)。每次分割将数组对半分,深度为 O(log⁡n),合并过程需要遍历整个数组,因此时间复杂度为 O(nlog⁡n)。

  • 空间复杂度: O(n)。归并排序需要额外的空间来存储临时数组,用于合并过程中临时存放子数组的元素。

        

        (5)算法的应用场景

归并排序的稳定性和时间复杂度使其适用于以下场景:

  • 稳定性要求高的排序: 归并排序是一种稳定排序算法,适用于对相同值的元素相对顺序有要求的场景。

  • 外部排序: 归并排序适用于处理超大数据集的外部排序,由于其稳定性和性能,尤其适用于磁盘文件的排序操作。

  • 数据集较大且未全部加载到内存: 在处理大规模数据时,归并排序由于其分割和合并的特性,可以有效地处理不能一次性加载到内存的数据。

        ——这样我们学习完了如何在Java中实现归并算法。

3.计数排序 (Counting Sort)

        (1)算法简介

        计数排序是一种非比较排序算法,主要用于对整数进行排序。它通过计算每个元素在数组中出现的次数来确定其在排序后数组中的位置。这种排序算法适用于元素范围较小且数据量较大的场景。

同样,我们接下来带着你边学如何实现排序算法边理解该算法的内核。

        (2)算法的原理与步骤

        计数排序的基本思想是创建一个计数数组,通过统计原始数组中每个元素的出现次数来确定它们在排序后数组中的正确位置。具体步骤如下:

  1. 计算最大值和最小值: 确定数组中的最大值和最小值,进而决定计数数组的大小。

  2. 计数: 创建一个计数数组,将每个元素出现的次数记录在计数数组中。

  3. 累加计数: 对计数数组中的值进行累加,以便确定每个元素的最终位置。

  4. 构建排序数组: 遍历原始数组,通过计数数组确定每个元素在排序后数组中的位置,构建最终的有序数组。

        ——接下来让我们使用代码来实现一下计数排序。

        (3)Java代码实现

public void countSort(int[] array) {// 初始化最小值和最大值为数组的第一个元素int min = array[0];int max = array[0];// 遍历数组,找到最小值和最大值for (int i = 0; i < array.length; i++) {if (array[i] < min) {min = array[i];}if (array[i] > max) {max = array[i];}}// 创建计数数组,用于统计每个元素出现的次数int[] count = new int[max - min + 1];// 统计数组中每个元素出现的次数for (int i = 0; i < array.length; i++) {count[array[i] - min]++;}// 将排序后的元素放回原数组int arrayIndex = 0;for (int i = 0; i < count.length; i++) {// 对计数数组进行处理,将每个元素根据其计数放入原数组中while (count[i] != 0) {array[arrayIndex++] = i + min;count[i]--;}}
}

解释:

  • int min = array[0]; int max = array[0];:初始化最小值和最大值为数组的第一个元素。

  • for (int i = 0; i < array.length; i++):遍历数组,找到最小值和最大值。

  • int[] count = new int[max - min + 1];:创建计数数组 count,其长度为 max - min + 1,用来存储每个值的出现次数。

  • for (int i = 0; i < array.length; i++):遍历数组并填充计数数组。

  • count[array[i] - min]++:更新计数数组中的值,表示 array[i] 出现的次数。

  • for (int i = 0; i < count.length; i++):遍历计数数组,并将排序后的元素放回原数组中。

  • while (count[i] != 0):根据计数数组的值将元素放入原数组中,每个元素按照计数的次数放入对应的位置。

        ——这样我们就实现了计数排序算法了!!!

        (4)时间复杂度和空间复杂度

  • 时间复杂度: O(n+k)O(n + k)O(n+k),其中 nnn 是数组的长度,kkk 是数组中元素的范围(最大值与最小值之间的差值)。

  • 空间复杂度: O(k)O(k)O(k)。计数排序需要额外的计数数组,空间复杂度与元素的范围成正比。

        (5)算法的应用场景

计数排序的特点使其适用于以下场景:

  • 数据范围较小的整数排序: 计数排序适合整数范围较小的排序任务,如考试成绩、年龄等。

  • 需要稳定排序的场景: 计数排序是一种稳定的排序算法,适用于需要保持相同元素相对顺序的场景。

  • 数据分布相对均匀: 如果数据分布极度不均匀,计数排序的效率会大大降低,因此适用于数据分布较为均匀的情况。

        ——这样我们学习完了如何在Java中实现计数排序算法。


以上就是本篇文章的全部内容了~~~

这篇关于Java中的经典排序算法:快速排序、归并排序和计数排序详解(如果想知道Java中有关快速排序、归并排序和计数排序的知识点,那么只看这一篇就足够了!)的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/1127911

相关文章

在Ubuntu上部署SpringBoot应用的操作步骤

《在Ubuntu上部署SpringBoot应用的操作步骤》随着云计算和容器化技术的普及,Linux服务器已成为部署Web应用程序的主流平台之一,Java作为一种跨平台的编程语言,具有广泛的应用场景,本... 目录一、部署准备二、安装 Java 环境1. 安装 JDK2. 验证 Java 安装三、安装 mys

Springboot的ThreadPoolTaskScheduler线程池轻松搞定15分钟不操作自动取消订单

《Springboot的ThreadPoolTaskScheduler线程池轻松搞定15分钟不操作自动取消订单》:本文主要介绍Springboot的ThreadPoolTaskScheduler线... 目录ThreadPoolTaskScheduler线程池实现15分钟不操作自动取消订单概要1,创建订单后

JAVA中整型数组、字符串数组、整型数和字符串 的创建与转换的方法

《JAVA中整型数组、字符串数组、整型数和字符串的创建与转换的方法》本文介绍了Java中字符串、字符数组和整型数组的创建方法,以及它们之间的转换方法,还详细讲解了字符串中的一些常用方法,如index... 目录一、字符串、字符数组和整型数组的创建1、字符串的创建方法1.1 通过引用字符数组来创建字符串1.2

SpringCloud集成AlloyDB的示例代码

《SpringCloud集成AlloyDB的示例代码》AlloyDB是GoogleCloud提供的一种高度可扩展、强性能的关系型数据库服务,它兼容PostgreSQL,并提供了更快的查询性能... 目录1.AlloyDBjavascript是什么?AlloyDB 的工作原理2.搭建测试环境3.代码工程1.

Java调用Python代码的几种方法小结

《Java调用Python代码的几种方法小结》Python语言有丰富的系统管理、数据处理、统计类软件包,因此从java应用中调用Python代码的需求很常见、实用,本文介绍几种方法从java调用Pyt... 目录引言Java core使用ProcessBuilder使用Java脚本引擎总结引言python

SpringBoot操作spark处理hdfs文件的操作方法

《SpringBoot操作spark处理hdfs文件的操作方法》本文介绍了如何使用SpringBoot操作Spark处理HDFS文件,包括导入依赖、配置Spark信息、编写Controller和Ser... 目录SpringBoot操作spark处理hdfs文件1、导入依赖2、配置spark信息3、cont

springboot整合 xxl-job及使用步骤

《springboot整合xxl-job及使用步骤》XXL-JOB是一个分布式任务调度平台,用于解决分布式系统中的任务调度和管理问题,文章详细介绍了XXL-JOB的架构,包括调度中心、执行器和Web... 目录一、xxl-job是什么二、使用步骤1. 下载并运行管理端代码2. 访问管理页面,确认是否启动成功

Java中的密码加密方式

《Java中的密码加密方式》文章介绍了Java中使用MD5算法对密码进行加密的方法,以及如何通过加盐和多重加密来提高密码的安全性,MD5是一种不可逆的哈希算法,适合用于存储密码,因为其输出的摘要长度固... 目录Java的密码加密方式密码加密一般的应用方式是总结Java的密码加密方式密码加密【这里采用的

Mysql 中的多表连接和连接类型详解

《Mysql中的多表连接和连接类型详解》这篇文章详细介绍了MySQL中的多表连接及其各种类型,包括内连接、左连接、右连接、全外连接、自连接和交叉连接,通过这些连接方式,可以将分散在不同表中的相关数据... 目录什么是多表连接?1. 内连接(INNER JOIN)2. 左连接(LEFT JOIN 或 LEFT

Java中ArrayList的8种浅拷贝方式示例代码

《Java中ArrayList的8种浅拷贝方式示例代码》:本文主要介绍Java中ArrayList的8种浅拷贝方式的相关资料,讲解了Java中ArrayList的浅拷贝概念,并详细分享了八种实现浅... 目录引言什么是浅拷贝?ArrayList 浅拷贝的重要性方法一:使用构造函数方法二:使用 addAll(