9种常用排序算法总结

一、插入排序

基本思想:每一趟将一个待排序的记录,按其关键字的大小插入到已经排序好的一组记录的适当位置上,直到全部待排序记录全部插入为止。

1.1 直接插入排序

排序过程:

  • 将待排序数组arr[1...n]看作两个集合,arr[1]为有序集合中元素,arr[2...n]为无序集合中元素,a[0]用来临时存放当前待排序记录
  • 外层循环每次从无序集合中选择一个待插入元素(n-1次),每次使用顺序查找法,内层循环查找arr[i]在有序集合中的位置(将有序集合中大于待插入元素的记录后移一位)
public class InsertionSort{ //直接插入排序方法 public static void insertionSort(int[] arr){ if (arr == null || arr.length<=1){ return; } //从第二个元素开始(第一个元素默认已排序) for (int i=1;i<arr.length;i++){ int key=arr[i]; int j=i-1; // 将比key大的元素向后移动 while(j>=0 && arr[j] > key){ arr[j+1]=arr[j]; j--; } // 插入key到正确位置 arr[j+1] = key; } } }

时间复杂度:

  • 最好情况下(待排序记录递增有序),总的比较次数为n-1次,记录不需要移动。
  • 最坏情况下(待排序记录递减有序),总的比较次数和移动次数均为n^2/2。
  • 平均情况下,比较次数和移动次数都是n^2/4。

空间复杂度:

只需要一个辅助空间arr[0],因此空间复杂度为O(1)

1.2 折半插入排序

基本思想同直接查找插入排序,不同点在于,在有序集合中搜索插入位置时折半插入采用二分搜索,可以有效的减少比较次数。

public static void binaryInsertionSortDetailed(int[] arr){ if(arr == null || arr.length <=1){ return; } for(int i=1; i<arr.length;i++){ int key = arr[i]; //待插入的元素 //使用二分查找在已排序部分[0,i-1]中找到插入位置 int insertPos = binarySearch(arr,0,i-1,key); //将元素后移,为插入腾出空间 for(int j=i-1;j>insertPos;j--){ arr[j=1]=arr[j]; } //插入元素 arr[insertPos]=key; } }

时间复杂度:

  • 该算法比较次数要小于直接插入排序,平均性能要优于直接插入排序,时间复杂度为O(n^2)
  • 该算法比较次数与待排序列初始排序列无关,依赖于有序序列的元素个数,插入第i个元素时比较次数为logi。折半插入排序的对象移动次数与直接排序相同,依赖对象的初始排列。

空间复杂度:

只需要一个辅助空间arr[0],因此空间复杂度为O(1)。

1.3 希尔排序

通过分析直接插入排序可以得出,待排序记录个数越少、待排序记录中逆序对越少,直接插入排序算法的效率越高。希尔排序正是通过将待排序记录分组来减少记录数量,通过对分组后的每个小组进行直接插入排序来减少逆序对的数量。

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=i; // 对当前元素进行插入排序(以gap为步长) while (j >= gap && arr[j - gap] > temp) { arr[j] = arr[j - gap]; j -= gap; } arr[j] = temp; } } }

时间复杂度:

  • 最坏情况,步长序列由n/2^k 计算得出,为O(n^2)
  • 最好情况,步长序列由下列公式计算得出,为O(n^(4/3))

空间复杂度:

仅用arr[0]作为辅助空间,因此时间复杂度为O(1)

二、交换排序

基本思想:两两比较待排序记录关键字,当两个关键字不满足次序要求时进行交换,直到整个序列满足要求为止。

2.1 冒泡排序

两两比较关键字,如逆序则交换顺序,较大关键字逐渐一端移动,直到序列有序。

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-1-i;j++){ //如果前一个元素大于后一个元素,则交换 if(arr[j] > arr[j + 1]){ // 交换两个元素 int temp = arr[j]; arr[j] = arr[j + 1]; arr[j + 1] = temp; } } } } }

时间复杂度:

  • 最好情况(初始序列正序),只需进行一次排序,在排序过程中进行n-1次关键字的比较,不移动记录
  • 最坏情况(初始序列逆序),进行n-1次排序,总的关键字比较次数为 n^2 / 2 ,记录移动次数为 (3n^2 )/ 2
  • 平均情况,比较次数和记录移动次数分别为 n^2 / 2 ,3n^2 / 4, 时间复杂度为O(n^2)

空间复杂度:

仅需arr[0]作为交换辅助空间,故空间复杂度为O(1)

2.2 快速排序

由冒泡排序改进得到,冒泡排序只对相邻两个记录进行比较,因此每次只能消除一个逆序,而快速排序一次交换可消除多个逆序,从而提高排序性能。

public class QuickSort { public static void quickSort(int[] arr, int low, int high) { if (low < high) { // 找到分区点 int pivotIndex = partition(arr, low, high); // 递归排序左半部分 quickSort(arr, low, pivotIndex - 1); // 递归排序右半部分 quickSort(arr, pivotIndex + 1, high); } } private static int partition(int[] arr, int low, int high) { // 选择最后一个元素作为基准 int pivot = arr[high]; // i 指向小于基准的区域的最后一个元素 int i = low - 1; // 遍历数组,将小于基准的元素移到左边 for (int j = low; j < high; j++) { if (arr[j] < pivot) { i++; // 交换 arr[i] 和 arr[j] swap(arr, i, j); } } // 将基准元素放到正确位置 swap(arr, i + 1, high); return i + 1; // 返回基准元素的最终位置 }

时间复杂度:​快速排序的趟数取决于递归树的深度

  • ​最好情况(每次排序后序列被分成两个大小大致相等的子表),定位枢轴所需的时间为O(n),总的排序时间为O(nlog2 n)
  • ​ 最坏情况(待排序列有序),递归树成为单支树,关键字的比较次数为n^2 / 2 ,这种情况下快速排序的速度已经退化为简单排序的水平。枢轴记录的合理选择可以避免最坏情况的出现,例如,可在待排序列中随机选择枢轴,并将枢轴交换到第一个位置。

平均情况,时间复杂度为O(nlog2 n)

空间复杂度:

​快速排序时递归的,最大递归调用次数与递归树的深度一致,因此最好情况为O(log2 n),最坏情况为O(n)

三、选择排序

基本思想:每一趟排序从待排序的记录中选出关键字最小的记录,按顺序放在已排序的记录中,直到全部排完为止。

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; } } // 如果最小值不在当前位置,则交换 if (minIndex != i) { // 交换 arr[i] 和 arr[minIndex] int temp = arr[i]; arr[i] = arr[minIndex]; arr[minIndex] = temp; } } }

时间复杂度:

  • ​ 最好情况(正序),记录不需要移动。
  • ​ 最坏情况(逆序),移动3(n-1)次

无论记录的初始状态如何,所进行的关键字之间的比较次数相同,均为n^2 / 2 ,因此时间复杂度为O(n^2)

空间复杂度:

​ 仅用arr[0]作为交换辅助空间,因此空间复杂度为O(1)

四、堆排序

每次将堆顶元素取出,与末尾元素交换,调整前n-1个元素,使其仍然成堆,重复上述过程,直到剩余元素为1时为止,即可得到非递减序列

public class HeapSort { public static void heapSort(int[] arr) { int n = arr.length; // 步骤1:构建最大堆(从最后一个非叶子节点开始) // 最后一个非叶子节点的索引 = (n/2) - 1 for (int i = n / 2 - 1; i >= 0; i--) { heapify(arr, n, i); } // 步骤2:一个个从堆顶取出元素(最大值),放到数组末尾 for (int i = n - 1; i > 0; i--) { // 将当前堆顶元素(最大值)与数组末尾元素交换 int temp = arr[0]; arr[0] = arr[i]; arr[i] = temp; // 重新调整堆,但这次只调整前i个元素(排除已排序的部分) heapify(arr, i, 0); } } // 调整以节点i为根的子树,使其成为最大堆 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 temp = arr[i]; arr[i] = arr[largest]; arr[largest] = temp; // 递归调整受影响的子树 heapify(arr, n, largest); } }

时间复杂度:

​ 堆排序平均性能接近于最坏性能,时间复杂度为O(nlog2 n)

空间复杂度:

​ 仅用arr[0]作为交换辅助空间,空间复杂度为O(1)

五、归并排序

基本思想:假设初始序列含有n个记录,则可看成时n个有序的子序列,每个子序列的长度为1,然后两两并归,得到n/2个长度为2或1的有序子序列;如此重复,直至得到一个长度为n的有序序列为止。

public class MergeSort { public static void mergeSort(int[] arr) { if (arr == null || arr.length <= 1) { return; } int[] temp = new int[arr.length]; mergeSort(arr, temp, 0, arr.length - 1); } private static void mergeSort(int[] arr, int[] temp, int left, int right) { if (left < right) { int mid = left + (right - left) / 2; // 递归排序左半部分 mergeSort(arr, temp, left, mid); // 递归排序右半部分 mergeSort(arr, temp, mid + 1, right); // 合并两个有序数组 merge(arr, temp, left, mid, right); } } private static void merge(int[] arr, int[] temp, int left, int mid, int right) { // 复制数据到临时数组 for (int i = left; i <= right; i++) { temp[i] = arr[i]; } int i = left; // 左子数组的起始索引 int j = mid + 1; // 右子数组的起始索引 int k = left; // 合并后数组的起始索引 // 合并两个有序数组 while (i <= mid && j <= right) { if (temp[i] <= temp[j]) { arr[k] = temp[i]; i++; } else { arr[k] = temp[j]; j++; } k++; } // 复制左子数组剩余的元素 while (i <= mid) { arr[k] = temp[i]; i++; k++; } // 注意:右子数组的剩余元素不需要复制,因为它们已经在正确位置 } public static void main(String[] args) { int[] arr = {12, 11, 13, 5, 6, 7}; System.out.println("排序前的数组:"); for (int num : arr) { System.out.print(num + " "); } mergeSort(arr); System.out.println("\n排序后的数组:"); for (int num : arr) { System.out.print(num + " "); } } }

时间复杂度:

​ n个记录需要进行log2 n趟归并排序,每一趟归并,其关键字比较次数不超过n,元素移动次数都是n,因此时间复杂度为O(nlog2 n)

空间复杂度:

​ 用顺序表实现递归时,需要和待排序记录相等的辅助存储空间,所以空间复杂度为O(n)

六、基数排序

基数排序是一种非比较型整数排序算法,其原理是将整数按位数切割成不同的数字,然后按每个位数分别比较。排序过程是将所有待比较数值统一为同样的数位长度,数位较短的数前面补零,然后从最低位开始,依次进行一次排序。这样从最低位排序一直到最高位排序完成以后, 数列就变成一个有序序列。

import java.util.Arrays; public class RadixSort { // 获取数组中的最大值 private static int getMax(int[] arr) { int max = arr[0]; for (int i = 1; i < arr.length; i++) { if (arr[i] > max) { max = arr[i]; } } return max; } // 使用计数排序对指定位进行排序 private static void countingSort(int[] arr, int exp) { int n = arr.length; int[] output = new int[n]; // 输出数组 int[] count = new int[10]; // 计数数组,0-9 // 初始化计数数组 Arrays.fill(count, 0); // 统计每个数字出现的次数 for (int i = 0; i < n; i++) { int digit = (arr[i] / exp) % 10; count[digit]++; } // 计算累计次数 for (int i = 1; i < 10; i++) { count[i] += count[i - 1]; } // 构建输出数组 for (int i = n - 1; i >= 0; i--) { int digit = (arr[i] / exp) % 10; output[count[digit] - 1] = arr[i]; count[digit]--; } // 将排序后的数组复制回原数组 System.arraycopy(output, 0, arr, 0, n); } // LSD 基数排序主函数 public static void radixSort(int[] arr) { if (arr == null || arr.length <= 1) { return; } // 找到最大值以确定位数 int max = getMax(arr); // 从个位开始,对每一位进行计数排序 for (int exp = 1; max / exp > 0; exp *= 10) { countingSort(arr, exp); } }

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

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

相关文章

AI读脸术自动化部署:CI/CD流水线集成实战教程

AI读脸术自动化部署&#xff1a;CI/CD流水线集成实战教程 1. 引言 1.1 业务场景描述 在智能安防、用户画像分析、无人零售等实际应用中&#xff0c;人脸属性识别是一项高频且关键的技术需求。通过自动判断图像中人物的性别与年龄段&#xff0c;系统可以实现更精准的服务推荐…

TurboDiffusion安装报错?SageAttention依赖环境配置避坑指南

TurboDiffusion安装报错&#xff1f;SageAttention依赖环境配置避坑指南 1. 引言&#xff1a;TurboDiffusion与SageAttention的工程挑战 1.1 技术背景 TurboDiffusion是由清华大学、生数科技与加州大学伯克利分校联合推出的视频生成加速框架&#xff0c;基于Wan2.1/Wan2.2模…

QR Code Master使用指南:生成与识别一站式解决方案

QR Code Master使用指南&#xff1a;生成与识别一站式解决方案 1. 引言 1.1 学习目标 本文将详细介绍 QR Code Master 的核心功能与使用方法&#xff0c;帮助开发者和普通用户快速掌握如何利用该工具实现高效、稳定的二维码生成与识别。通过本教程&#xff0c;您将能够&…

异或门温度特性研究:环境对阈值电压的影响

异或门的温度“脾气”&#xff1a;为什么它怕冷又怕热&#xff1f;你有没有想过&#xff0c;一个看似简单的异或门&#xff08;XOR Gate&#xff09;&#xff0c;在极端环境下也可能“罢工”&#xff1f;不是因为设计错了逻辑&#xff0c;也不是代码写崩了&#xff0c;而是——…

你的模型为何不推理?DeepSeek-R1-Distill-Qwen-1.5B强制换行技巧揭秘

你的模型为何不推理&#xff1f;DeepSeek-R1-Distill-Qwen-1.5B强制换行技巧揭秘 1. DeepSeek-R1-Distill-Qwen-1.5B 模型介绍 DeepSeek-R1-Distill-Qwen-1.5B 是 DeepSeek 团队基于 Qwen2.5-Math-1.5B 基础模型&#xff0c;通过知识蒸馏技术融合 R1 架构优势打造的轻量化版本…

Glyph视觉推理功能测评:长上下文建模新思路

Glyph视觉推理功能测评&#xff1a;长上下文建模新思路 1. 引言&#xff1a;长上下文建模的瓶颈与新路径 在大语言模型&#xff08;LLM&#xff09;快速发展的今天&#xff0c;长上下文理解能力已成为衡量模型智能水平的关键指标之一。无论是处理整本小说、法律合同&#xff…

如何验证微调成功?Qwen2.5-7B前后对比测试方法

如何验证微调成功&#xff1f;Qwen2.5-7B前后对比测试方法 在大语言模型的微调过程中&#xff0c;完成训练只是第一步。真正决定项目成败的关键在于&#xff1a;如何科学、系统地验证微调是否达到了预期目标。本文将围绕 Qwen2.5-7B-Instruct 模型&#xff0c;结合 ms-swift 微…

FST ITN-ZH中文逆文本标准化WebUI二次开发实战

FST ITN-ZH中文逆文本标准化WebUI二次开发实战 1. 引言 1.1 业务场景描述 在自然语言处理&#xff08;NLP&#xff09;的实际工程落地中&#xff0c;语音识别&#xff08;ASR&#xff09;输出的原始文本通常包含大量非标准化表达。例如&#xff0c;“二零零八年八月八日”或…

Python3.8自动化测试:云端并行执行,效率提升5倍

Python3.8自动化测试&#xff1a;云端并行执行&#xff0c;效率提升5倍 你是不是也遇到过这样的情况&#xff1f;团队用 Python 3.8 写的自动化测试用例越来越多&#xff0c;本地一台机器串行跑&#xff0c;一跑就是几个小时&#xff0c;CI/CD 流水线卡着等结果&#xff0c;开…

语音增强技术落地|结合FRCRN-16k镜像与ClearerVoice工具包

语音增强技术落地&#xff5c;结合FRCRN-16k镜像与ClearerVoice工具包 1. 引言&#xff1a;语音增强的工程化挑战与解决方案 在真实场景中&#xff0c;语音信号常受到背景噪声、混响、设备干扰等因素影响&#xff0c;导致语音识别准确率下降、通话质量变差。传统降噪方法&…

MinerU 2.5部署案例:企业年报PDF智能分析系统

MinerU 2.5部署案例&#xff1a;企业年报PDF智能分析系统 1. 引言 1.1 业务背景与挑战 在金融、审计和企业服务领域&#xff0c;每年都会产生海量的企业年报文档。这些报告通常以PDF格式发布&#xff0c;包含复杂的多栏排版、表格数据、图表图像以及数学公式等元素。传统的人…

Python不写类型注解?难怪你的代码总是报错且没人看懂!

目录&#x1f4da; 一、引言&#xff1a;告别“猜类型”时代&#xff0c;迎接工程化Python&#x1f570;️ 二、历史渊源&#xff1a;从动态灵活到静态严谨的演进2.1 动态类型的“自由”与“混乱”2.2 PEP 484&#xff1a;类型注解的诞生&#x1f9e9; 三、核心语法&#xff1a…

用Qwen3-1.7B做文本摘要,效果堪比商用模型

用Qwen3-1.7B做文本摘要&#xff0c;效果堪比商用模型 1. 引言&#xff1a;轻量级大模型的摘要能力突破 随着大语言模型在自然语言处理任务中的广泛应用&#xff0c;文本摘要作为信息压缩与内容提炼的核心功能&#xff0c;正从传统抽取式方法向生成式范式全面演进。然而&…

新手必学:Open-AutoGLM五步快速上手法

新手必学&#xff1a;Open-AutoGLM五步快速上手法 1. 引言&#xff1a;让手机拥有“贾维斯”般的智能助手 随着多模态大模型的发展&#xff0c;AI 正从“对话工具”向“自主执行者”演进。Open-AutoGLM 是由智谱AI开源的手机端 AI Agent 框架&#xff0c;基于 AutoGLM-Phone …

Python 返回值注解全解析:从语法到实战,让代码更具可读性

目录&#x1f4cc; 引言&#xff1a;为什么我们需要返回值注解&#xff1f;&#x1f9f1; 一、返回值注解的基础语法1.1 核心语法格式1.2 基础示例&#xff1a;内置类型注解1.3 关键特性&#xff1a;注解不影响运行时&#x1f9e9; 二、进阶用法&#xff1a;复杂类型的返回值注…

BAAI/bge-m3快速验证:30分钟搭建RAG召回评估系统

BAAI/bge-m3快速验证&#xff1a;30分钟搭建RAG召回评估系统 1. 引言 1.1 业务场景描述 在构建检索增强生成&#xff08;RAG&#xff09;系统时&#xff0c;一个核心挑战是如何准确评估检索模块的召回质量。传统基于关键词匹配的方法难以捕捉语义层面的相关性&#xff0c;导…

为什么SenseVoiceSmall部署总失败?GPU适配问题解决指南

为什么SenseVoiceSmall部署总失败&#xff1f;GPU适配问题解决指南 1. 引言&#xff1a;多语言语音理解的工程挑战 随着语音AI技术的发展&#xff0c;传统“语音转文字”已无法满足复杂场景下的语义理解需求。阿里巴巴达摩院推出的 SenseVoiceSmall 模型&#xff0c;作为一款…

SGLang推理延迟优化:批处理配置实战案例

SGLang推理延迟优化&#xff1a;批处理配置实战案例 1. 引言 1.1 业务场景描述 在大模型应用落地过程中&#xff0c;推理服务的延迟与吞吐量是决定用户体验和系统成本的核心指标。尤其是在多轮对话、结构化输出、任务编排等复杂场景下&#xff0c;传统LLM推理框架往往面临高…

Glyph+VLM=超强长文本理解能力

GlyphVLM超强长文本理解能力 1. 技术背景与核心价值 随着大语言模型&#xff08;LLM&#xff09;在各类自然语言任务中展现出强大能力&#xff0c;长上下文理解已成为衡量模型智能水平的关键指标。然而&#xff0c;传统基于token的上下文窗口扩展方式面临计算复杂度高、显存占…

工业机器人通信中断:USB转串口驱动排查指南

工业机器人通信中断&#xff1f;一文搞懂USB转串口驱动失效的根源与实战修复 一个让产线停摆的“小问题”&#xff1a;插上设备却找不到COM口 深夜&#xff0c;自动化车间报警灯闪烁——SCARA机器人突然停止点胶动作&#xff0c;HMI界面显示“通信超时”。现场工程师迅速赶到…