当前位置: 首页 > web >正文

【数据结构】七种常见排序算法

🥰🥰🥰来都来了,不妨点个关注叭!
👉博客主页:欢迎各位大佬!👈

在这里插入图片描述

欢迎来到排序算法的学习,恭喜你!本期内容主要介绍排序算法,一起来探索吧~
(ps:真的一直都想写有关排序的文章,奈何每天尊嘟好忙,终于开写啦!)

文章目录

  • 1. 排序的基本概念
    • 1.1 排序预备知识
    • 1.2 内部排序与外部排序
    • 1.3 排序的稳定性
    • 1.4 排序的分类
  • 2. 插入类排序
    • 2.1 直接插入排序
    • 2.2 希尔排序
  • 3. 选择类排序
    • 3.1 直接选择排序
    • 3.2 堆排序
  • 4. 交换类排序
    • 4.1 冒泡排序
    • 4.2 快速排序
      • 4.2.1 三种实现方式
        • 4.2.1.1 hoare 法
        • 4.2.1.2 挖坑法
        • 4.2.1.3 前后指针法
        • 4.2.1.4 快排优化
      • 4.2.2 快排非递归实现
  • 5. 归并排序
  • 6. 特性总结

1. 排序的基本概念

1.1 排序预备知识

排序:排序的目的是使一串记录,将一组“无序”的记录序列调整为"有序"的记录序列,按照其中的某个或某些关键字的大小,递增或递减的排列起来的操作

1.2 内部排序与外部排序

内部排序:数据元素全部放在内存中的排序,内部排序的过程是一个逐步扩大记录的有序序列长度的过程。
外部排序:数据元素数量非常大,不能同时放在内存中,整个过程不可能在内存中完成。

1.3 排序的稳定性

稳定性:假定在待排序的记录序列中,存在两个或者两个以上的具有相同的关键字的记录,若经过排序,这些记录的相对次序保持不变,则称为稳定,否则为不稳定

1.4 排序的分类

在这里插入图片描述

插入类排序:将待排序的值插入到前面已经有序的序列中
选择类排序:每次排序选出序列的最大值/最小值,放到序列的最后面
交换排序:两两比较待排序的序列,并交换不满足序列的那对数

以上为经典排序,本文主要介绍常见的 7 种排序算法,我们需要掌握~ 一起来正式进入下面的学习吧!

2. 插入类排序

2.1 直接插入排序

【基本思想】 将记录的值,插入到已经排序好的有序序列中,直到所有记录的值全部插入,则该过程完成。

【基本思路】 仅有一个数据时,则认为该数据为已经排好序的,则我们可以这样思考:

  1. 第1个元素已排好序,从第2个元素开始,下标为i,取出该元素放在变量 tmp 中,从已排好序序列中从后往前使用 j 遍历比较
  2. 如果下标 j 的值大于 tmp,则 j+1 的值替换为 j 下标的值,继续遍历
  3. 如果下标 j 的值小于 tmp,则 break,跳出遍历
  4. 最后 j+1 的值替换成临时变量 tmp 存储的值
  5. 此时前两个元素已经是有序的了,再用 i 继续遍历(详细解释可以看示意图~)

有没有点像打扑克牌时候呢?拿到一张牌就在以往排好序的牌中插入~

在这里插入图片描述
【示意图】
在这里插入图片描述

【具体代码】

/*** Created with IntelliJ IDEA.* Description:* User: wlx* Date: 2025-06-01* Time: 23:58*/
public class InsertSort {public static void main(String[] args) {int[] array = {1,4,2,9,6,7};insertSort(array);System.out.println(Arrays.toString(array));}public static void insertSort(int[] array) {for(int i = 1; i < array.length; i++) {int tmp = array[i];int j = i-1;for(; j >= 0; j--) {if(array[j] > tmp) {array[j+1] = array[j];}else {//array[j+1] = tmp;break;}}array[j+1] = tmp;}}
}

【特性总结】

  1. 时间复杂度:O(n²),当数据本身就有序的时候,时间复杂度为O(n)
  2. 空间复杂度:O(1)
  3. 稳定性:稳定
  4. 适用场景:当数据基本趋于有序时,建议使用直接插入排序

2.2 希尔排序

基本思想】希尔排序又称为缩小增量法,其基本思想是先选定一个整数 gap,将待排序的数据分为多个组,每组距离 gap,对每一组进行排序,每一组两个元素,接着 gap/2 ,缩小每组的距离,当 gap = 1 时,所有数据就排好序了
简单理解就是按照 gap 分组,组内进行插入排序,当 gap = 1 时,则为直接插入排序

基本思路】与直接插入排序思路一致,希尔排序是直接插入排序的优化,先用 gap 分组,让数组预有序,当 gap 为 1 时,数组也基本有序了,此时就是直接插入排序~

示意图
在这里插入图片描述

具体代码

/*** Created with IntelliJ IDEA.* Description:* User: wlx* Date: 2025-06-02* Time: 10:28*/
public class ShellSort {public static void main(String[] args) {int[] array = {1,4,2,9,6,7};int gap = array.length;while(gap > 1) {shellSort(array,gap);gap /= 2;}shellSort(array,1);System.out.println(Arrays.toString(array));}public static void shellSort(int[] array,int gap) {for(int i = gap; i < array.length; i++) {int tmp = array[i];int j = i-gap;for(; j >= 0; j-=gap) {if(array[j] > tmp) {array[j+gap] = array[j];}else {//array[j+1] = tmp;break;}}array[j+gap] = tmp;}}

【特性总结】

  1. 时间复杂度:O(n^1.3) - O(n^1.5) ,当数据本身就有序的时候,时间复杂度为O(n),且希尔排序的时间复杂度取决于增量值 gap 的选取,即时间复杂度并不是一个定值
  2. 空间复杂度:O(1)
  3. 稳定性:不稳定
  4. 希尔排序是直接插入排序的优化,gap >1 的排序是预排序,使数据趋于有序,gap=1 时则是直接插入排序

3. 选择类排序

3.1 直接选择排序

基本思想】每次从待排序的序列中选出最小/最大元素,放在序列的起始位置,直到全部待排序的数据排完

基本思路】定义一个 minIndex 下标用来存储最小值的下标,第1次 minIndex = 0 下标,遍历整个待排序序列,当有更小数据时,更新 minIndex 下标,再交换起始位置 i 与 minIndex 下标值,这样就找到第1小的元素,接着再遍历,minIndex = 1,遍历后面的待排序元素,找到第2小数据,并进行交换,以此类推…

示意图

在这里插入图片描述
具体代码

/*** Created with IntelliJ IDEA.* Description:* User: wlx* Date: 2025-06-02* Time: 10:53*/
public class SelectSort {public static void main(String[] args) {int[] array = {1,4,2,9,6,7};selectSort(array);System.out.println(Arrays.toString(array));}public static void selectSort(int[] array) {for(int i = 0; i < array.length; i++) {int minIndex = i;for(int j = i+1; j < array.length; j++) {if(array[j] < array[minIndex]) {minIndex = j;}}swap(array,i,minIndex);}}public static void swap(int[] array,int i, int j) {int tmp = array[i];array[i] = array[j];array[j] = tmp;}
}

【特性总结】

  1. 时间复杂度:O(n²)
  2. 空间复杂度:O(1)
  3. 稳定性:不稳定

3.2 堆排序

基本思想】堆排序是指利用堆这种数据结构所设计的一种算法,它是选择排序的一种,通过堆进行选择数据,排升序是建大堆,排降序建立小堆

大根堆:每个节点的值都 >= 其子节点的值,用于升序排列
小根堆:每个节点的值都 <= 其子节点的值,用于降序排列

在这里插入图片描述

基本思路】堆排序就是先将我们的数组进行一次建堆,循环交换堆顶元素(最大值)与最后一个元素,即交换数组头和尾的元素,然后重新建堆,再进行交换堆顶元素与最后一个元素

示意图

在这里插入图片描述
具体代码

import java.util.Arrays;/*** Created with IntelliJ IDEA.* Description:* User: wlx* Date: 2025-06-22* Time: 23:52*/
public class HeapSort {public static void heapSort(int[] array) {createBigHeap(array);int end = array.length - 1;while (end > 0) {swap(array, 0, end);shiftDown(array, 0, end);end--;}}public static void createBigHeap(int[] array) {for (int parent = (array.length - 1 - 1) / 2; parent >= 0; parent--) {shiftDown(array, parent, array.length);}}//建大堆private static void shiftDown(int[] array, int parent, int len) {int child = 2 * parent + 1;while (child < len) {if (child + 1 < len && array[child] < array[child + 1]) {child++;}if (array[child] > array[parent]) {swap(array, child, parent);parent = child;child = 2 * parent + 1;} else {break;}}}public static void swap(int[] arr,int i,int j) {int tmp = arr[i];arr[i] = arr[j];arr[j] = tmp;}public static void main(String[] args) {int[] array = {1,4,2,9,6,7};heapSort(array);System.out.println(Arrays.toString(array));}
}

【特性总结】

  1. 时间复杂度:O(N*logN)
  2. 空间复杂度:O(1)
  3. 稳定性:不稳定

4. 交换类排序

4.1 冒泡排序

基本思想】将待排序的数据两两进行比较,按比较结果来交换序列位置,将值大/值小的数据放到序列的尾部,将值小/值大的数据放到序列的首部位置,依次比较放置,最后得到有序序列

基本思路】冒泡排序的每一次循环比较就是找出最大值(最小值),将最大值(最小值)放在数组尾部(首部),经过n次比较后,数据变成有序的,这里 flag 变量是优化作用,如果一轮下来没交换,则数组已经有序~

示意图

在这里插入图片描述

具体代码

/*** Created with IntelliJ IDEA.* Description:* User: wlx* Date: 2025-06-03* Time: 23:00*/
public class BubbleSort {public static void main(String[] args) {int[] array = {1,4,2,9,6,7};bubbleSort(array);for(int i = 0; i < array.length; i++) {System.out.print(array[i]+" ");}}private static void bubbleSort(int[] array) {for(int i = 0; i < array.length-1; i++) {boolean flag = false;for(int j = 0; j < array.length-1-i;j++) {if(array[j+1] < array[j]) {swap(array,j,j+1);flag = true;}}if(flag == false) {break;}}}public static void swap(int[] array,int i,int j) {int tmp = array[i];array[i] = array[j];array[j] = tmp;}
}

【特性总结】

  1. 时间复杂度:O(N^2)
  2. 空间复杂度:O(1)
  3. 稳定性:稳定

4.2 快速排序

基本思想】先从数组中取一个数作为基准数,进行分区,将比这个大的数全放到它的右边,小于或等于它的数全部放到它的左边,直到所有元素在它的位置上,此时就有序了~

主体框架

这是快速排序的主框架,我们可以看到这和二叉树的前序遍历递归实现的方式很类似,因此,我们在写快速排序的时候,可以联想到二叉树的前序遍历是如何写的,接下来,介绍快速排序的几种方式:

public class quickSort {public void quickSort(int[] num) {int left = 0;int right = num.length-1;quick(num,left,right);}private void quick(int[] num, int left, int right) {if(left >= right) {return;}// 按照基准值对数组的[left,right]划分int pivot = partition(num,left,right);// 划分区间:[left,pivot-1] [pivot,right]// 递归排序[left,pivot-1]quick(num,left,pivot-1);// 递归排序[pivot,right]quick(num,pivot+1,right);}private int partition(int[] num, int left, int right) {// 根据一定的规则返回基准值return -1;}
}

4.2.1 三种实现方式

4.2.1.1 hoare 法

基本思路

hoare法就是定义两个标志位,right 从右往左找到比基准值小的值停下,left 从左往右找到比基准值大的值停下,交换 left 和 right 位置的值,循环继续,直到 left 和 right 相遇,最后再交换基准值和相遇处的值,再对左右子序列重复此过程,直到数据变成有序

1)先取最左侧即第一个元素为基准值
2)两个指针 left 和 right,left 从最左侧向右走,找到比基准大的元素停下,right 从最右侧向左走,找到比基准小的元素停下,交换 left 下标的值和 right 下标的值
3)当 left >= right 两个指针不再移动,此时 left = right 的下标即为基准值对应的下标,交换基准值初始下标和left(right)下标

示意图

在这里插入图片描述

具体代码

import java.util.Arrays;/*** Created with IntelliJ IDEA.* Description:* User: wlx* Date: 2025-06-22* Time: 0:05*/
public class quickSort {public static void quickSort(int[] num) {int left = 0;int right = num.length-1;quick(num,left,right);}public static void quick(int[] num, int left, int right) {if(left >= right) {return;}int pivot = partition(num,left,right);quick(num,left,pivot-1);quick(num,pivot+1,right);}// 1.hoare法public static int partition(int[] num, int left, int right) {// 根据一定的规则返回基准值int i = left+1;int j = right;int tmp = num[left];while(true) {while(i <= j && num[i] < tmp) {i++;}while(i <= j && num[j] > tmp) {j--;}if(i >= j) {break;}swap(num,i,j);}swap(num,left,j);return left;}public static void swap(int[] num,int i,int j) {int tmp = num[i];num[i]  = num[j];num[j] = tmp;}public static void main(String[] args) {int[] array = {1,4,2,9,6,7};quickSort(array);System.out.println(Arrays.toString(array));}
}
4.2.1.2 挖坑法

基本思路

1)先取最左侧即第一个元素为基准值
2)两个指针 left 和 right,left 从最左侧向右走,找到比基准大的元素停下,将 right 下标处的值赋值给 left 下标的值,right 从最右侧向左走,找到比基准小的元素停下,将 left 下标处的值赋值给 right 下标的值,如此循环
3)当 left >= right 两个指针不再移动,此时 left = right 的下标即为基准值对应的下标,交换基准值初始下标和left(right)下标

示意图
在这里插入图片描述

具体代码

import java.util.Arrays;/*** Created with IntelliJ IDEA.* Description:* User: wlx* Date: 2025-06-22* Time: 0:10*/
public class quickSort {public static void quickSort(int[] num) {int left = 0;int right = num.length-1;quick(num,left,right);}public static void quick(int[] num, int left, int right) {if(left >= right) {return;}int pivot = partition(num,left,right);quick(num,left,pivot-1);quick(num,pivot+1,right);}// 2.挖坑法public static int partition(int[] arr,int left,int right) {int tmp = arr[left];while(left < right) {// 找到比基准tmp小的while(left < right && arr[right] >= tmp) {right--;}arr[left] = arr[right];// 找到比基准tmp大的while(left < right && arr[left] <= tmp) {left++;}arr[right] = arr[left];}arr[left] = tmp;return left;}public static void main(String[] args) {int[] array = {1,4,2,9,6,7};quickSort(array);System.out.println(Arrays.toString(array));}
}
4.2.1.3 前后指针法

基本思路

1)先取最左侧即第一个元素为基准值
2)前后指针 prev 和 cur,prev初始为left,cur初始为right,如果 cur 下标值小于基准值并且前指针prev++后的值与cur下标值不相等,前后指针至少一个间隔,并且值不相等,则交换前后指针的值
3)最后cur走到right,循环结束,交换prev和left值,此时prev就是基准值的位置

示意图

在这里插入图片描述

具体代码

import java.util.Arrays;/*** Created with IntelliJ IDEA.* Description:* User: wlx* Date: 2025-06-22* Time: 0:30*/
public class quickSort {public static void quickSort(int[] num) {int left = 0;int right = num.length-1;quick(num,left,right);}public static void quick(int[] num, int left, int right) {if(left >= right) {return;}int pivot = partition(num,left,right);quick(num,left,pivot-1);quick(num,pivot+1,right);}//3.前后指针法private static int partition(int[] array,int left,int right) {int prev = left ;int cur = left+1;while (cur <= right) {if(array[cur] < array[left] && array[++prev] != array[cur]) {swap(array,cur,prev);}cur++;}swap(array,prev,left);return prev;}public static void swap(int[] arr,int i,int j) {int tmp = arr[i];arr[i] = arr[j];arr[j] = tmp;}public static void main(String[] args) {int[] array = {1,4,2,9,6,7};quickSort(array);System.out.println(Arrays.toString(array));}
}
4.2.1.4 快排优化

快排的基准是随机选取的,一般情况下,快排的时间复杂度为 O(nlog(n)),比如数据是有序的1,2,3,4,5… 会变成 O(n²)

时间复杂度我们可以用二叉树来理解,如下:

一般情况下,基准到达对应的位置后,序列被分为了左右子序列,基准元素为根结点,左边都比根节点的值小,右边都比根节点的值大,此时时间复杂度为 O(nlog(n))

在这里插入图片描述
存在特殊情况,数据是有序的1,2,3,4,5…

在这里插入图片描述
只有一个分支,此时树的高度就是结点的个数,时间复杂度则为O(n²),而当数据量足够大的时候,上述代码就无法跑通了~

优化方法

1)基准优化:三数取中法

即基准值取 left,mid,right 三个值中间的值,而不再是单纯取下标为 left 的值

import java.util.Arrays;/*** Created with IntelliJ IDEA.* Description:* User: wlx* Date: 2025-06-22* Time: 0:40*/
public class quickSort {public static void quickSort(int[] num) {int left = 0;int right = num.length-1;quick(num,left,right);}public static void quick(int[] num, int left, int right) {if(left >= right) {return;}// 基准值优化int index = midThree(num,left,right);swap(num,index,left);int pivot = partition(num,left,right);quick(num,left,pivot-1);quick(num,pivot+1,right);}private static int midThree(int[] array,int left,int right) {int mid = (left + right) / 2;if (array[left] < array[right]) {if (array[mid] < array[left]) {return left;} else if (array[mid] > array[right]) {return right;} else {return mid;}} else {if (array[mid] < array[right]) {return right;} else if (array[mid] > array[left]) {return left;} else {return mid;}}}public static int partition(int[] arr,int left,int right) {int tmp = arr[left];while(left < right) {// 找到比基准tmp小的while(left < right && arr[right] >= tmp) {right--;}arr[left] = arr[right];// 找到比基准tmp大的while(left < right && arr[left] <= tmp) {left++;}arr[right] = arr[left];}arr[left] = tmp;return left;}public static void swap(int[] arr,int i,int j) {int tmp = arr[i];arr[i] = arr[j];arr[j] = tmp;}public static void main(String[] args) {int[] array = {1,4,2,9,6,7};quickSort(array);System.out.println(Arrays.toString(array));}
}

2) 递归至较小的子区间时,使用插入排序:

递归至较小区间的时间,数据渐渐趋于有序,当数据有序的时候,建议直接使用插入排序,这样效率是比较高的

4.2.2 快排非递归实现

import java.util.Arrays;
import java.util.Deque;
import java.util.LinkedList;/*** Created with IntelliJ IDEA.* Description:* User: wlx* Date: 2025-06-22* Time: 22:41*/// 非递归实现快排
public class quickSort {public static void quickSort(int[] array) {Deque<Integer> stack = new LinkedList<>();int left = 0;int right = array.length - 1;int pivot = partition(array,left,right);if (pivot > left + 1) {stack.push(left);stack.push(pivot - 1);}if (pivot < right-1) {stack.push(pivot+1);stack.push(right);}while (!stack.isEmpty()) {right = stack.pop();left = stack.pop();pivot = partition(array,left,right);if (pivot > left + 1) {stack.push(left);stack.push(pivot - 1);}if (pivot < right-1) {stack.push(pivot+1);stack.push(right);}}}public static int partition(int[] arr,int left,int right) {int tmp = arr[left];while(left < right) {// 找到比基准tmp小的while(left < right && arr[right] >= tmp) {right--;}arr[left] = arr[right];// 找到比基准tmp大的while(left < right && arr[left] <= tmp) {left++;}arr[right] = arr[left];}arr[left] = tmp;return left;}public static void main(String[] args) {int[] array = {1,4,2,9,6,7};quickSort(array);System.out.println(Arrays.toString(array));}}

5. 归并排序

基本思想】归并排序是建立在归并操作上的一种有效算法,该算法是分治法的一种典型应用,将已有序的子序列合并,得到完全有序的序列,即先使每个子序列有序,将两个有序序列合并为一个有序序列,称为二路归并

基本思路】使用递归的方式,先分解,再进行合并

示意图

在这里插入图片描述

具体代码

import java.util.Arrays;/*** Created with IntelliJ IDEA.* Description:* User: wlx* Date: 2025-06-21* Time: 22:53*/
public class Mergesort {public static void mergeSort(int[] arr) {mergeFunc(arr,0,arr.length-1);}public static void mergeFunc(int[] arr,int left,int right) {if(left >= right) {return;}int mid = (left+right) / 2;mergeFunc(arr,left,mid);mergeFunc(arr,mid+1,right);merge(arr,left,right,mid);}public static void merge(int[] arr,int left,int right,int mid) {int start1 = left;int start2 = mid+1;int k = 0;int[] tmp = new int[right-left+1];while(start1 <= mid && start2 <= right) {if(arr[start1] < arr[start2]) {tmp[k++] = arr[start1++];} else {tmp[k++] = arr[start2++];}}while(start1 <= mid) {tmp[k++] = arr[start1++];}while(start2 <= right) {tmp[k++] = arr[start2++];}for(int i = 0; i < k; i++) {arr[left+i] = tmp[i];}}public static void main(String[] args) {int[] arr = {1,4,2,9,6,7};mergeSort(arr);System.out.println(Arrays.toString(arr));}
}

特性总结

  1. 时间复杂度:O(O(N*logN))
  2. 空间复杂度:O(1)
  3. 稳定性:稳定
  4. 应用场景:适合数据特别大的时候,当待排序数据特别大的时候,比如内存只要 10G,但是待排序的数据有 100G,此时可以将待处理的数据分为 20份,每一份 512M,利用归并排序分别对这 512M 的数据进行排序,同时进行二路归并,最后使数据变为有序,这就是文章一开头介绍的外部排序~

6. 特性总结

排序名称平均时间复杂度最好情况最坏情况空间复杂度稳定性
直接插入排序O(n²)O(n)O(n²)O(1)稳定
希尔排序O(n^1.3) - O(n^1.5)O(n log²n)O(n log²n)O(1)不稳定
直接选择排序O(n²)O(n²)O(n²)O(1)不稳定
堆排序O(N*logN)O(N*logN)O(N*logN)O(1)不稳定
冒泡排序O(n²)O(n)O(n²)O(1)稳定
快速排序O(N*logN)O(N*logN)O(N*logN)O(logN)不稳定
归并排序O(N*logN)O(N*logN)O(N*logN)O(1)稳定

注意】这里的时间复杂度为最坏时间复杂度

✨✨✨本期内容到此结束啦~

http://www.xdnf.cn/news/14695.html

相关文章:

  • 商品中心—10.商品B端搜索系统的说明文档
  • 详解Redis数据库和缓存不一致的情况及解决方案
  • WEB3合约开发以太坊中货币单位科普
  • react day.js使用及经典场景
  • 【代码解析】opencv 安卓 SDK sample - 1 - HDR image
  • SQL 分页方法全解析:从基础到高级应用
  • 深入解析ID3算法:信息熵驱动的决策树构建基石
  • 【Qt开发】网络运用
  • 项目中后端如何处理异常?
  • JAVA锁机制:对象锁与类锁
  • 一、什么是生成式人工智能
  • GPT-1 与 BERT 架构
  • MySQL之InnoDB存储引擎深度解析
  • 软件工程期末试卷填空题版带答案(共40道)
  • 【环境配置】在Ubuntu Server上安装5090 PyTorch环境
  • CVE-2024-6387漏洞、CVE-2025-26465漏洞、CVE-2025-26466漏洞 一口气全解决
  • 题解:P11501 [ROIR 2019] 探险队(Day 2)
  • 【软考高级系统架构论文】论无服务器架构及其应用
  • 在 `setup` 函数中使用 Vuex
  • 通过 Lambda + API Gateway + 外部 API 实现。
  • Django数据库迁移
  • LLM:重构数字世界的“智能操作系统”
  • Java面试题025:一文深入了解数据库Redis(1)
  • Docker高级管理--容器通信技术与数据持久化
  • 【ubuntu下小工具】Crontab定时任务进行数据备份和清理
  • 【AGI】突破感知-决策边界:VLA-具身智能2.0
  • 格兰泰勒棱镜透射光强曲线优化处理
  • 嵌入式开发之嵌入式系统架构如何搭建?
  • Java ArrayList集合和HashSet集合详解
  • day38 打卡