深度剖析八大排序算法

         欢迎并且感谢大家指出我的问题,由于本人水平有限,有些内容写的不是很全面,只是把比较实用的东西给写下来,如果有写的不对的地方,还希望各路大牛多多指教!谢谢大家!🥰

        在计算机科学领域,排序算法是基础且重要的算法类别,它能够将一组无序的数据按照特定的顺序(如升序或降序)重新排列。接下来,我们将详细探讨冒泡排序、选择排序、插入排序、希尔排序、快速排序、归并排序、堆排序和计数排序这八大排序算法。

冒泡排序

基本概念

冒泡排序是一种简单的比较排序算法,它通过多次比较相邻元素并交换位置,将最大(或最小)的元素逐步 “冒泡” 到数组的末尾。

核心思想

重复地走访要排序的数列,一次比较两个元素,如果它们的顺序错误就把它们交换过来。走访数列的工作是重复地进行直到没有再需要交换,也就是说该数列已经排序完成。

算法步骤

  1. 比较相邻的元素。如果第一个比第二个大(升序),就交换它们两个。
  1. 对每一对相邻元素作同样的工作,从开始第一对到结尾的最后一对。这步做完后,最后的元素会是最大的数。
  1. 针对所有的元素重复以上的步骤,除了最后一个。
  1. 持续每次对越来越少的元素重复上面的步骤,直到没有任何一对数字需要比较。

代码实现(Java)

public class BubbleSort {public static void bubbleSort(int[] arr) {int n = arr.length;for (int i = 0; i < n - 1; i++) {for (int j = 0; j < n - i - 1; j++) {if (arr[j] > arr[j + 1]) {int temp = arr[j];arr[j] = arr[j + 1];arr[j + 1] = temp;}}}}
}

优缺点及适用场景

  • 优点:实现简单,代码量少,比较稳定(相同元素的相对顺序在排序前后不会改变)。
  • 缺点:时间复杂度较高,为 O (n²),在数据量较大时效率较低。
  • 适用场景:适用于数据量较小且对稳定性有要求的场景。

选择排序

基本概念

选择排序是一种简单直观的排序算法,它在每一趟选择未排序序列中最小(或最大)的元素,存放到已排序序列的末尾。

核心思想

首先在未排序序列中找到最小(大)元素,存放到排序序列的起始位置,然后,再从剩余未排序元素中继续寻找最小(大)元素,然后放到已排序序列的末尾。以此类推,直到所有元素均排序完毕。

算法步骤

  1. 初始状态:无序区为整个数组,有序区为空。
  1. 第 1 趟排序:在无序区中选出最小(大)的元素,将它与无序区的第 1 个元素交换,此时有序区增加 1 个元素,无序区减少 1 个元素。
  1. 重复第 2 步,直到无序区为空。

代码实现(Java)

public class SelectionSort {public static void selectionSort(int[] arr) {int n = arr.length;for (int i = 0; i < n - 1; i++) {int minIndex = i;for (int j = i + 1; j < n; j++) {if (arr[j] < arr[minIndex]) {minIndex = j;}}int temp = arr[minIndex];arr[minIndex] = arr[i];arr[i] = temp;}}
}

优缺点及适用场景

  • 优点:简单直观,无论什么数据进去都是 O (n²) 的时间复杂度,所以如果数据规模较小,可以考虑使用。
  • 缺点:时间复杂度高,同样为 O (n²),且不稳定。
  • 适用场景:适用于数据量较小,对时间复杂度要求不高,且对稳定性没有要求的场景。

插入排序

基本概念

插入排序是将一个数据插入到已经排好序的有序数据中,从而得到一个新的、个数加一的有序数据。

核心思想

把 n 个待排序的元素看成一个有序表和一个无序表。开始时有序表中只包含 1 个元素,无序表中包含 n - 1 个元素;排序过程中每次从无序表中取出第一个元素,将它插入到有序表中的适当位置,使之成为新的有序表,重复 n - 1 次可完成排序过程。

算法步骤

  1. 从第一个元素开始,该元素可以认为已经被排序。
  1. 取出下一个元素,在已经排序的元素序列中从后向前扫描。
  1. 如果已排序元素大于新元素,将已排序元素移到下一位置。
  1. 重复步骤 3,直到找到已排序的元素小于或者等于新元素的位置。
  1. 将新元素插入到该位置后。
  1. 重复步骤 2~5。

代码实现(Java)

public class InsertionSort {public static void insertionSort(int[] arr) {int n = arr.length;for (int i = 1; i < n; i++) {int key = arr[i];int j = i - 1;while (j >= 0 && arr[j] > key) {arr[j + 1] = arr[j];j = j - 1;}arr[j + 1] = key;}}
}

优缺点及适用场景

  • 优点:在数据量较小或者部分有序的情况下,效率较高,并且是稳定的排序算法。
  • 缺点:时间复杂度在最坏情况下为 O (n²),不适用于大规模数据排序。
  • 适用场景:适用于数据量较小、部分有序的数据排序,以及对稳定性有要求的场景。

希尔排序

基本概念

希尔排序,也称递减增量排序算法,是插入排序的一种更高效的改进版本。它通过将原始数据分成多个子序列,对每个子序列进行插入排序,逐步减少增量,最终使整个序列有序。

核心思想

先将整个待排序的记录序列分割成为若干子序列分别进行直接插入排序,待整个序列中的记录 “基本有序” 时,再对全体记录进行依次直接插入排序。

算法步骤

  1. 选择一个增量序列 t1,t2,…,tk,其中 ti > tj, tk = 1。
  1. 按增量序列个数 k,对序列进行 k 趟排序。
  1. 每趟排序,根据对应的增量 ti,将待排序列分割成若干长度为 m 的子序列,分别对各子表进行直接插入排序。仅增量因子为 1 时,整个序列作为一个表来处理,表长度即为整个序列的长度。

代码实现(Java)

public class ShellSort {public static void shellSort(int[] arr) {int n = arr.length;for (int gap = n / 2; gap > 0; gap /= 2) {for (int i = gap; i < n; i++) {int temp = arr[i];int j;for (j = i; j >= gap && arr[j - gap] > temp; j -= gap) {arr[j] = arr[j - gap];}arr[j] = temp;}}}
}

优缺点及适用场景

  • 优点:希尔排序的时间复杂度比 O (n²) 要好很多,在中等规模数据下表现良好,并且实现相对简单。
  • 缺点:希尔排序是不稳定的排序算法,并且其时间复杂度分析较为复杂。
  • 适用场景:适用于中等规模数据的排序,对稳定性要求不高的场景。

快速排序

基本概念

快速排序是一种分治的排序算法,它通过选择一个基准元素,将数组分为两部分,使得左边部分的元素都小于基准元素,右边部分的元素都大于基准元素,然后递归地对左右两部分进行排序。

核心思想

  1. 从数列中挑出一个基准值(通常选择第一个元素或最后一个元素)。
  1. 重新排序数列,所有元素比基准值小的摆放在基准前面,所有元素比基准值大的摆在基准的后面(相同的数可以到任一边)。在这个分区退出之后,该基准就处于数列的中间位置。这个称为分区操作。
  1. 递归地把小于基准值元素的子数列和大于基准值元素的子数列排序。

算法步骤

  1. 选择基准值 pivot。
  1. 初始化两个指针 left 和 right,left 指向数组的起始位置,right 指向数组的末尾位置。
  1. 从 right 开始,向左移动 right 指针,直到找到一个小于 pivot 的元素;从 left 开始,向右移动 left 指针,直到找到一个大于 pivot 的元素。
  1. 如果 left < right,交换这两个元素,然后继续步骤 3。
  1. 当 left >= right 时,将 pivot 与 left 指向的元素交换,此时 pivot 左边的元素都小于它,右边的元素都大于它。
  1. 递归地对 pivot 左边和右边的子数组进行快速排序。

代码实现(Java)

public class QuickSort {public static void quickSort(int[] arr, int low, int high) {if (low < high) {int pi = partition(arr, low, high);quickSort(arr, low, pi - 1);quickSort(arr, pi + 1, high);}}private static int partition(int[] arr, int low, int high) {int pivot = arr[high];int i = (low - 1);for (int j = low; j < high; j++) {if (arr[j] < pivot) {i++;int temp = arr[i];arr[i] = arr[j];arr[j] = temp;}}int temp = arr[i + 1];arr[i + 1] = arr[high];arr[high] = temp;return i + 1;}
}

优缺点及适用场景

  • 优点:平均时间复杂度为 O (nlogn),在大多数情况下表现非常高效,并且是一种原地排序算法(不需要额外的大量辅助空间)。
  • 缺点:在最坏情况下(如数组已经有序),时间复杂度会退化为 O (n²),并且快速排序是不稳定的排序算法。
  • 适用场景:适用于大规模数据的排序,对稳定性没有要求的场景。

归并排序

基本概念

归并排序是建立在归并操作上的一种有效的排序算法,该算法是采用分治法的一个非常典型的应用。它将一个数组分成两个子数组,对每个子数组进行排序,然后将排序好的子数组合并成一个最终的有序数组。

核心思想

  1. 把长度为 n 的输入序列分成两个长度为 n/2 的子序列。
  1. 对这两个子序列分别采用归并排序。
  1. 将两个排序好的子序列合并成一个最终的有序序列。

算法步骤

  1. 分解:将数组不断地分成两个子数组,直到子数组的长度为 1。
  1. 合并:将两个有序的子数组合并成一个更大的有序数组。合并时,比较两个子数组的元素,将较小的元素依次放入一个临时数组中,直到其中一个子数组的元素全部放入临时数组,然后将另一个子数组的剩余元素也放入临时数组。
  1. 将临时数组中的元素复制回原数组,完成一次合并。
  1. 重复步骤 2 和 3,直到所有子数组都合并成一个有序数组。

代码实现(Java)

public class MergeSort {public static void mergeSort(int[] arr) {int n = arr.length;if (n < 2) {return;}int mid = n / 2;int[] left = new int[mid];int[] right = new int[n - mid];System.arraycopy(arr, 0, left, 0, mid);System.arraycopy(arr, mid, right, 0, n - mid);mergeSort(left);mergeSort(right);merge(arr, left, right);}private static void merge(int[] arr, int[] left, int[] right) {int i = 0, j = 0, k = 0;while (i < left.length && j < right.length) {if (left[i] < right[j]) {arr[k] = left[i];i++;} else {arr[k] = right[j];j++;}k++;}while (i < left.length) {arr[k] = left[i];i++;k++;}while (j < right.length) {arr[k] = right[j];j++;k++;}}
}

优缺点及适用场景

  • 优点:时间复杂度始终为 O (nlogn),并且是稳定的排序算法,适用于大规模数据的排序。
  • 缺点:需要额外的空间来存储临时数组,空间复杂度为 O (n)。
  • 适用场景:适用于对稳定性有要求,数据量较大的场景,如外部排序。

堆排序

基本概念

堆排序是指利用堆这种数据结构所设计的一种排序算法。堆积是一个近似完全二叉树的结构,并同时满足堆积的性质:即子结点的键值或索引总是小于(或者大于)它的父节点。

核心思想

  1. 首先将待排序的数组构建成一个最大堆(升序排序时)或最小堆(降序排序时)。
  1. 然后将堆顶元素与堆的最后一个元素交换,此时堆的最后一个元素就是最大(或最小)的元素,将其从堆中移除(实际上是缩小堆的范围)。
  1. 对剩余的元素重新调整为最大堆(或最小堆),重复步骤 2,直到堆中只剩下一个元素,此时数组已经有序。

算法步骤

  1. 初始化堆:将数组构建成一个最大堆(或最小堆)。从最后一个非叶子节点开始,依次对每个非叶子节点进行向下调整操作,使其满足堆的性质。
  1. 交换与调整:将堆顶元素与堆的最后一个元素交换,然后对堆顶元素进行向下调整操作,使堆重新满足堆的性质。
  1. 重复步骤 2,直到堆中只剩下一个元素。

代码实现(Java)

public class HeapSort {public static void heapSort(int[] arr) {int n = arr.length;// 构建最大堆for (int i = n / 2 - 1; i >= 0; i--) {heapify(arr, n, i);}// 一个个取出元素for (int i = n - 1; i > 0; i--) {int temp = arr[0];arr[0] = arr[i];arr[i] = temp;// 调整堆heapify(arr, i, 0);}}private static void heapify(int[] arr, int n, int i) {int largest = i;int left = 2 * i + 1;int right = 2 * i + 2;if (left < n && arr[left] > arr[largest]) {largest = left;}if (right < n && arr[right] > arr[largest]) {largest = right;}if (largest!= i) {int swap = arr[i];arr[i] = arr[largest];arr[largest] = swap;// 递归调整受影响的子树heapify(arr, n, largest);}}
}

优缺点及适用场景

  • 优点:时间复杂度为 O (nlogn),并且是原地排序算法,不需要额外的大量辅助空间。
  • 缺点:堆排序是不稳定的排序算法,并且实现相对复杂。
  • 适用场景:适用于大规模数据的排序,对稳定性没有要求,并且空间有限的场景。

计数排序

基本概念

计数排序是一种非比较排序算法,它通过统计每个元素在数组中出现的次数,然后根据统计结果将元素按照顺序输出,从而实现排序。

核心思想

        1.找出待排序数组中的最大值和最小值,确定计数数组的范围。

        2.统计每个元素在数组中出现的次数,将统计结果存储在计数数组中。

        3.根据计数数组,将每个元素按照出现的次数依次输出到结果数组中。

算法步骤

  1. 确定范围:遍历待排序数组,找出其中的最大值max和最小值min,计算出计数数组的长度countLength = max - min + 1。
  2. 初始化计数数组:创建一个长度为countLength的计数数组count,并将所有元素初始化为 0。
  3. 统计元素出现次数:再次遍历待排序数组,对于数组中的每个元素num,将count[num - min]的值加 1,表示该元素出现的次数。
  4. 计算前缀和:对计数数组进行处理,计算每个元素的前缀和。即从计数数组的第二个元素开始,每个元素都加上前一个元素的值。这一步的目的是让count[i]表示小于等于i + min的元素个数。
  5. 填充结果数组:创建一个与待排序数组长度相同的结果数组result。从待排序数组的末尾开始遍历,对于每个元素num,根据count[num - min]的值确定其在结果数组中的位置,然后将count[num - min]减 1。重复此步骤,直到所有元素都放入结果数组中。

代码实现

  1. public class CountingSort {public static void countingSort(int[] arr) {if (arr == null || arr.length == 0) {return;}int max = arr[0];int min = arr[0];// 找出最大值和最小值for (int num : arr) {if (num > max) {max = num;}if (num < min) {min = num;}}int countLength = max - min + 1;int[] count = new int[countLength];// 统计每个元素出现的次数for (int num : arr) {count[num - min]++;}// 计算前缀和for (int i = 1; i < countLength; i++) {count[i] += count[i - 1];}int[] result = new int[arr.length];// 填充结果数组for (int i = arr.length - 1; i >= 0; i--) {result[count[arr[i] - min] - 1] = arr[i];count[arr[i] - min]--;}// 将结果复制回原数组System.arraycopy(result, 0, arr, 0, arr.length);}
    }

    优缺点及适用场景

  2. 优点:时间复杂度为 O (n + k),其中 n 是待排序数组的长度,k 是数据的范围。在数据范围不大且数据量较大时,效率非常高。并且计数排序是稳定的排序算法。
  3. 缺点:对数据的要求比较苛刻,只能用于数据范围较小且数据为整数的情况。同时,需要额外的空间来存储计数数组,空间复杂度为 O (k) 。
  4. 适用场景:适用于数据范围有限,且对稳定性有要求的场景,比如对学生成绩(分数范围固定)进行排序等。

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

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

相关文章

在深度学习中,样本不均衡问题是一个常见的挑战,尤其是在你的老虎机任务中,某些的中奖倍数较高

在深度学习中,样本不均衡问题是一个常见的挑战,尤其是在你的老虎机任务中,某些的中奖倍数较高 在深度学习中,样本不均衡问题是一个常见的挑战,尤其是在你的老虎机任务中,某些的中奖倍数较高而其他的中奖倍数较低。这种不均衡会导致模型偏向于高频样本(低中奖倍数的),…

04树 + 堆 + 优先队列 + 图(D1_树(D10_决策树))

目录 一、引言 二、算法原理 三、算法实现 四、知识小结 一、引言 决策树算法是一种常用的机器学习算法&#xff0c;可用于分类和回归问题。它基于特征之间的条件判断来构 建一棵树&#xff0c;树的每个节点代表一个特征&#xff0c;每个叶节点代表一个类别或回归值。决策…

简单介绍一下什么是OpenFeign

OpenFeign是什么&#xff1f; OpenFeign是一个声明式的Http客户端&#xff0c;它可以用来发起Http请求 它主要用于SpringCloud微服务之间的通讯&#xff0c;让调用另一个服务的Java方法和调用本地方法一样快速和便捷 之前我们是用RestTemplate写一大堆东西发起Http请求远程调…

Hugging Face GGUF 模型可视化

Hugging Face GGUF 模型可视化 1. Finding GGUF files (检索 GGUF 模型)2. Viewer for metadata & tensors info (可视化 GGUF 模型)References 无知小儿&#xff0c;仙家雄霸天下&#xff0c;依附强者才是唯一的出路。否则天地虽大&#xff0c;也让你们无路可走&#xff0…

Python 与 PostgreSQL 集成:深入 psycopg2 的应用与实践

title: Python 与 PostgreSQL 集成:深入 psycopg2 的应用与实践 date: 2025/2/4 updated: 2025/2/4 author: cmdragon excerpt: PostgreSQL 作为开源关系型数据库的佼佼者,因其强大的功能与性能被广泛应用于各种项目中。而 Python 则因其简洁易用的语法、丰富的库和强大的…

Vant框架:助力移动端开发的利器

Vant框架&#xff1a;助力移动端开发的利器 在移动互联网飞速发展的今天&#xff0c;开发一款用户体验出色、界面美观且功能强大的移动端应用并非易事。而Vant框架&#xff0c;作为一款专为移动端设计的Vue.js UI组件库&#xff0c;凭借其轻量级、高度可定制化以及丰富的组件库…

生成式AI安全最佳实践 - 抵御OWASP Top 10攻击 (上)

今天小李哥将开启全新的技术分享系列&#xff0c;为大家介绍生成式AI的安全解决方案设计方法和最佳实践。近年来&#xff0c;生成式 AI 安全市场正迅速发展。据 IDC 预测&#xff0c;到 2025 年全球 AI 安全解决方案市场规模将突破 200 亿美元&#xff0c;年复合增长率超过 30%…

《LLM大语言模型深度探索与实践:构建智能应用的新范式,融合代理与数据库的高级整合》

文章目录 Langchain的定义Langchain的组成三个核心组件实现整个核心组成部分 为什么要使用LangchainLangchain的底层原理Langchain实战操作LangSmithLangChain调用LLM安装openAI库-国内镜像源代码运行结果小结 使用Langchain的提示模板部署Langchain程序安装langserve代码请求格…

开发板上Qt运行的环境变量的三条设置语句的详解

在终端中运行下面三句命令用于配置开发板上Qt运行的环境变量&#xff1a; export QT_QPA_GENERIC_PLUGINStslib:/dev/input/event1 export QT_QPA_PLATFORMlinuxfb:fb/dev/fb0 export QT_QPA_FONTDIR/usr/lib/fonts/设置成功后可以用下面的语句检查设置成功没有 echo $QT_QPA…

e2studio开发RA4M2(6)----GPIO外部中断(IRQ)配置

e2studio开发RA4M2.6--GPIO外部中断&#xff08;IRQ&#xff09;配置 概述视频教学样品申请硬件准备参考程序源码下载新建工程工程模板保存工程路径芯片配置工程模板选择时钟设置SWD调试口设置GPIO口配置按键中断配置中断回调函数主程序 概述 GPIO&#xff08;通用输入/输出&a…

Kafka SSL(TLS)安全协议

文章目录 Kafka SSL&#xff08;TLS&#xff09;安全协议1. Kafka SSL 的作用1.1 数据加密1.2 身份认证1.3 数据完整性1.4 防止中间人攻击1.5 确保安全的分布式环境1.6 防止拒绝服务&#xff08;DoS&#xff09;攻击 2. Kafka SSL 配置步骤&#xff08;1&#xff09;创建 SSL 证…

解决带空格的字符串输入问题:C/C++中的几种常用函数

解决带空格的字符串输入问题&#xff1a;C/C中的几种常用函数 在C/C编程中&#xff0c;读取带空格的字符串一直是一个常见的问题。传统的 scanf 和 gets 函数在处理带空格字符串时往往会遇到一些限制和问题。为了更加安全地处理带空格的字符串输入&#xff0c;我们可以选择不同…

鸿蒙Harmony-双向数据绑定MVVM以及$$语法糖介绍

鸿蒙Harmony-双向数据绑定MVVM以及$$语法糖介绍 1.1 双向数据绑定概念 在鸿蒙&#xff08;HarmonyOS&#xff09;应用开发中&#xff0c;双向数据改变&#xff08;或双向数据绑定&#xff09;是一种让数据模型和UI组件之间保持同步的机制&#xff0c;当数据发生变化时&#x…

git基础使用--3---git安装和基本使用

文章目录 git基础使用--3--git-安装和基本使用1. git工具安装1.1 git1.2 TortoiseGit1.3 远程仓2. git本地仓库版本管理2.1 git常用命令2.2 git基本操作2.2.1 设置用户名和邮箱 2.2 git基本操作2.2.1 初始化本地仓 git init2.2.2 查看本地库状态 git status2.2.3 添加暂缓区2.2…

倍增-最近公共祖先LCA查询

问题描述 给定一棵有 NN 个节点的树&#xff0c;每个节点有一个唯一的编号&#xff0c;从 11 到 NN。树的根节点是 11 号节点。接下来&#xff0c;你会得到 QQ 个查询。对于每个查询&#xff0c;你将得到两个节点的编号&#xff0c;你的任务是找到这两个节点的最低公共祖先。 …

JVM执行流程与架构(对应不同版本JDK)

直接上图&#xff08;对应JDK8以及以后的HotSpot&#xff09; 这里主要区分说明一下 方法区于 字符串常量池 的位置更迭&#xff1a; 方法区 JDK7 以及之前的版本将方法区存放在堆区域中的 永久代空间&#xff0c;堆的大小由虚拟机参数来控制。 JDK8 以及之后的版本将方法…

信息安全专业2025最新毕业设计选题汇总:课题精选

目录 前言 毕设选题 开题指导建议 更多精选选题 选题帮助 最后 前言 大家好,这里是海浪学长毕设专题! 大四是整个大学期间最忙碌的时光&#xff0c;一边要忙着准备考研、考公、考教资或者实习为毕业后面临的升学就业做准备,一边要为毕业设计耗费大量精力。学长给大家整理…

1. junit5介绍

JUnit 5 是 Java 生态中最流行的单元测试框架&#xff0c;由 JUnit Platform、JUnit Jupiter 和 JUnit Vintage 三个子项目组成。以下是 JUnit 5 的全面使用指南及示例&#xff1a; 一、环境配置 1. Maven 依赖 <dependency><groupId>org.junit.jupiter</grou…

c++ 定点 new 及其汇编解释

&#xff08;1&#xff09; 代码距离&#xff1a; #include <new> // 需要包含这个头文件 #include <iostream>int main() {char buffer[sizeof(int)]; // 分配一个足够大的字符数组作为内存池int* p new(&buffer) int(42); // 使用 placement new…