排序算法
排序算法
冒泡排序
-
冒泡排序(Bubble Sort)是一种简单的排序算法,它通过重复地遍历数组,比较相邻元素并交换位置,将较大的元素逐步“冒泡”到数组的末尾。
-
基本原理
- 比较相邻元素:
- 从数组的第一个元素开始,依次比较相邻的两个元素。
- 如果前一个元素大于后一个元素,则交换它们的位置。
- 重复遍历:
- 每一轮遍历会将当前未排序部分的最大元素“冒泡”到正确的位置。
- 重复上述过程,直到整个数组有序。
- 比较相邻元素:
-
算法步骤
- 外层循环:
- 控制遍历的轮数,每轮将一个最大元素放到正确的位置。
- 需要遍历
n-1轮(n是数组长度)。
- 内层循环:
- 在每一轮中,比较相邻元素并交换位置。
- 每轮结束后,未排序部分的最大元素会被放到末尾。
- 优化:
- 如果在某一轮中没有发生交换,说明数组已经有序,可以提前结束排序。
- 外层循环:
-
时间复杂度
- 最优情况:
- 数组已经有序,时间复杂度为 $
O\left( n \right)
$(通过优化实现)。
- 数组已经有序,时间复杂度为 $
- 最坏情况:
- 数组完全逆序,时间复杂度为$
O\left( n^2 \right)
$
- 数组完全逆序,时间复杂度为$
- 平均情况:
- 时间复杂度为$
O\left( n^2 \right)
$
- 时间复杂度为$
- 最优情况:
-
空间复杂度
- 冒泡排序是原地排序算法,空间复杂度为 O(1)。
-
代码
-
public static void bubbleSort(int[] arr){ boolean haveSwap; for (int i = 0; i < arr.length-1; i++) { haveSwap=false; for (int j = 1; j < arr.length-i; j++) { int swap; if(arr[j]<arr[j-1]){ // 交换相邻元素 swap=arr[j]; arr[j]=arr[j-1]; arr[j-1]=swap; haveSwap=true;// 发生交换 } } // 如果本轮没有发生交换,说明数组已经有序 if(!haveSwap){ break; } } }
-
选择排序
-
选择排序(Selection Sort)是一种简单直观的排序算法。它的基本思想是每次从未排序部分中选择最小(或最大)的元素,将其放到已排序部分的末尾。
-
基本原理
- 将数组分为两部分:
- 已排序部分(初始为空)。
- 未排序部分(初始为整个数组)。
- 选择最小元素:
- 在未排序部分中找到最小元素。
- 将其与未排序部分的第一个元素交换。
- 重复过程:
- 每次选择一个最小元素,放到已排序部分的末尾。
- 直到整个数组有序。
- 将数组分为两部分:
-
算法步骤
-
外层循环:
- 控制已排序部分的边界,从
0到n-1。
- 控制已排序部分的边界,从
-
内层循环:
- 在未排序部分中找到最小元素的索引。
-
交换元素:
- 将最小元素与未排序部分的第一个元素交换。
-
-
时间复杂度
- 无论最优、最坏还是平均都是$
O\left( n^2 \right)
$ - 无论数组是否有序,选择排序都需要进行$
n\left( n-1 \right) /2
$次比较。
- 无论最优、最坏还是平均都是$
-
空间复杂度
- 选择排序是原地排序算法,空间复杂度为 O(1)。
-
代码
-
public static void selectSort(int[] arr){ for (int i = 0; i <arr.length-1; i++) { // 假设当前未排序部分的第一个元素是最小值 int minIndex=i; // 在未排序部分中找到最小元素的索引 for (int j = i+1; j <arr.length ; j++) { if(arr[j]<arr[minIndex]){ minIndex=j; } } // 将最小元素与未排序部分的第一个元素交换 if(i!=minIndex){ int swap=arr[i]; arr[i]=arr[minIndex]; arr[minIndex]=swap; } } }
-
插入排序
-
插入排序(Insertion Sort)是一种简单直观的排序算法。它的基本思想是将数组分为已排序部分和未排序部分,然后逐个将未排序部分的元素插入到已排序部分的正确位置。
-
基本原理
- 将数组分为两部分:
- 已排序部分(初始为第一个元素)。
- 未排序部分(初始为第二个元素到最后一个元素)。
- 逐个插入:
- 从未排序部分中取出一个元素,将其插入到已排序部分的正确位置。
- 重复过程:
- 直到未排序部分为空,数组完全有序。
- 将数组分为两部分:
-
算法步骤
- 外层循环:
- 遍历未排序部分,从第二个元素开始。
- 内层循环:
- 将当前元素与已排序部分的元素从后向前比较,找到合适的插入位置。
- 插入元素:
- 将当前元素插入到正确位置,已排序部分扩大。
- 外层循环:
-
时间复杂度
- 最优情况:数组已经有序,时间复杂度为O(n)
- 最坏和平均都是$
O\left( n^2 \right)
$
-
空间复杂度
- 插入排序是原地排序算法,空间复杂度为 O(1)。
-
代码
-
public static void insertSort(int[] arr) { for (int i = 1; i < arr.length; i++) { int key = arr[i];// 当前需要插入的元素 int j = i - 1; // 将比 key 大的元素向后移动 while (j >= 0 && key < arr[j]) { arr[j + 1] = arr[j]; j--; } // 插入 key 到正确位置 arr[j + 1] = key; } }
-
希尔排序
- 希尔排序(Shell Sort)是插入排序的一种高效改进版本,也称为缩小增量排序。
- 它通过将数组分成若干子序列,对每个子序列进行插入排序,逐步缩小子序列的间隔,最终完成整个数组的排序。希尔排序的时间复杂度优于直接插入排序,适合中等规模数据排序。
基本原理:
- 分组插入排序:
- 将数组按一定的间隔(增量)分成若干子序列。
- 对每个子序列进行插入排序。
- 逐步缩小间隔:
- 重复上述过程,逐步缩小间隔,直到间隔为 1。
- 最终排序:
- 当间隔为 1 时,整个数组作为一个子序列进行插入排序,完成排序。
算法步骤:
- 选择增量序列:
- 常用的增量序列有:希尔增量(
gap = n/2, n/4, ..., 1)、Hibbard 增量等。
- 常用的增量序列有:希尔增量(
- 分组插入排序:
- 对每个增量
gap,将数组分成gap个子序列,对每个子序列进行插入排序。
- 对每个增量
- 缩小增量:
- 重复上述过程,直到
gap为 1。
- 重复上述过程,直到
时间复杂度
- 最优情况:
- 数组已经有序,时间复杂度为$
O\left( n\log n \right)
$ - 最坏情况:
- 数组完全逆序,时间复杂度为$
O\left( n^2 \right)
$
- 数组完全逆序,时间复杂度为$
- 平均情况:
- 时间复杂度取决于增量序列的选择
- 数组已经有序,时间复杂度为$
空间复杂度
- 希尔排序是原地排序算法,空间复杂度为 O(1)
代码
-
public static void shellSort(int[] arr){ int n=arr.length; for (int gap = n/2; gap >=1 ; gap/=2) { //i从gap的位置开始 for (int i = gap; i <n ; i++) { int key=arr[i]; int j=i-gap; while (j>=0 && arr[j]>key){ arr[j+gap]=arr[j]; j-=gap; } arr[j+gap]=key; } } }
归并排序
-
归并排序(Merge Sort)是一种基于分治法(Divide and Conquer)的高效排序算法。它的核心思想是将数组分成两个子数组,分别排序后再合并。
-
基本原理
- 分治法:
- 将数组递归地分成两个子数组,直到每个子数组只有一个元素。
- 合并:
- 将两个有序的子数组合并成一个有序数组。
- 分治法:
-
算法步骤
- 分解:
- 将数组从中间分成两个子数组。
- 递归排序:
- 对左半部分和右半部分分别递归调用归并排序。
- 合并:
- 将两个有序的子数组合并成一个有序数组。
- 分解:
-
时间复杂度
- 分解:
- 每次将数组分成两半,时间复杂度为$
O\left( \log n \right)
$
- 每次将数组分成两半,时间复杂度为$
- 合并:
- 每次合并需要遍历所有元素,时间复杂度为O(n)
- 总时间复杂度:$
O\left( n\log n \right)
$
- 分解:
-
空间复杂度
- 归并排序需要额外的空间存储临时数组,空间复杂度为 O(n)。
-
代码
-
public static void mergeSort(int[] arr, int beginIndex, int endIndex) { if (beginIndex < endIndex) { int mid = (beginIndex + endIndex) / 2; mergeSort(arr, beginIndex, mid); mergeSort(arr, mid + 1, endIndex); merge(arr, beginIndex, mid, endIndex); } } public static void merge(int[] arr, int beginIndex, int mid, int endIndex) { int[] swap = new int[endIndex - beginIndex + 1]; int swapIndex = 0; int leftIndex = beginIndex; int rightIndex = mid + 1; // 比较左右两部分的元素,将较小的元素放入临时数组 while (leftIndex <= mid && rightIndex <= endIndex) { if (arr[leftIndex] <= arr[rightIndex]) { swap[swapIndex] = arr[leftIndex]; leftIndex++; } else { swap[swapIndex] = arr[rightIndex]; rightIndex++; } swapIndex++; } // 将左半部分剩余的元素拷贝到临时数组 while (leftIndex <= mid) { swap[swapIndex] = arr[leftIndex]; leftIndex++; swapIndex++; } // 将右半部分剩余的元素拷贝到临时数组 while (rightIndex <= endIndex) { swap[swapIndex] = arr[rightIndex]; rightIndex++; swapIndex++; } if (swap.length >= 0) System.arraycopy(swap, 0, arr, beginIndex, swap.length); }
-
快速排序
-
快速排序(Quick Sort)是一种基于分治法(Divide and Conquer)的高效排序算法。它的核心思想是通过选择一个基准元素(Pivot),将数组分为两部分:一部分小于基准元素,另一部分大于基准元素,然后递归地对这两部分进行排序。
-
基本原理
- 选择基准元素:
- 从数组中选择一个元素作为基准(Pivot)。
- 将数组分为两部分:
- 左部分:所有元素小于等于基准元素。
- 右部分:所有元素大于基准元素。
- 递归排序:
- 对左部分和右部分分别递归调用快速排序。
- 选择基准元素:
-
算法步骤
- 选择基准元素:
- 通常选择数组的第一个元素、最后一个元素或中间元素作为基准。
- 分区:
- 使用双指针法(如
i和j)将数组分为两部分。
- 使用双指针法(如
- 递归排序:
- 对左部分和右部分分别递归调用快速排序。
- 选择基准元素:
-
时间复杂度
- 最优情况:
- 每次分区都能将数组均匀分成两部分,时间复杂度为$
O\left( n\log n \right)
$
- 每次分区都能将数组均匀分成两部分,时间复杂度为$
- 最坏情况:
- 每次分区都极度不均匀(如数组已经有序),时间复杂度为$
O\left( n^2 \right)
$
- 每次分区都极度不均匀(如数组已经有序),时间复杂度为$
- 平均情况:
- 时间复杂度为$
O\left( n\log n \right)
$
- 时间复杂度为$
- 最优情况:
-
空间复杂度
- 快速排序是原地排序算法,空间复杂度为$
O\left( \log n \right)
$(递归栈的深度)
- 快速排序是原地排序算法,空间复杂度为$
-
代码
-
public static void quickSort(int[] arr,int beginIndex,int endIndex){ if(beginIndex<endIndex){ //以开头元素为基准 //需要 首先 从右遍向左找比基准元素小的元素 //找到之后需要从坐向右找 int key=arr[beginIndex]; int leftIndex=beginIndex; int rightIndex=endIndex; while (leftIndex<rightIndex){ while (leftIndex<rightIndex && arr[rightIndex]>=key){ rightIndex--; } arr[leftIndex]=arr[rightIndex]; while (leftIndex<rightIndex && arr[leftIndex]<=key){ leftIndex++; } arr[rightIndex]=arr[leftIndex]; } arr[leftIndex]=key; quickSort(arr,beginIndex,leftIndex-1); quickSort(arr,rightIndex+1,endIndex); } }
-
排序算法对比
| 排序算法 | 应用场景 |
|---|---|
| 冒泡排序 | 小规模数据,数据基本有序,教学和学习。 |
| 选择排序 | 小规模数据,交换次数有限,内存有限。 |
| 插入排序 | 小规模数据,数据基本有序,需要稳定排序,在线排序。 |
| 希尔排序 | 中等规模数据,对稳定性无要求,内存有限。 |
| 归并排序 | 大规模数据,需要稳定排序,外部排序,并行计算。 |
| 快速排序 | 大规模数据,对稳定性无要求,内存有限,实际应用广泛。 |

浙公网安备 33010602011771号