Java常用实现八种排序算法与代码实现

article/2025/10/24 6:43:12

一、交换排序

所谓交换,就是序列中任意两个元素进行比较,根据比较结果来交换各自在序列中的位置,以此达到排序的目的。

1. 冒泡排序

冒泡排序是一种简单的交换排序算法,以升序排序为例,其核心思想是:

  1. 从第一个元素开始,比较相邻的两个元素。如果第一个比第二个大,则进行交换。

  2. 轮到下一组相邻元素,执行同样的比较操作,再找下一组,直到没有相邻元素可比较为止,此时最后的元素应是最大的数。

  3. 除了每次排序得到的最后一个元素,对剩余元素重复以上步骤,直到没有任何一对元素需要比较为止。

用 Java 实现的冒泡排序如下

public void bubbleSortOpt(int[] arr) {if(arr == null) {throw new NullPoniterException();}if(arr.length < 2) {return;}int temp = 0;for(int i = 0; i < arr.length - 1; i++) {for(int j = 0; j < arr.length - i - 1; j++) {if(arr[j] > arr[j + 1]) {temp = arr[j];arr[j] = arr[j + 1];arr[j + 1] = temp;}}}
}

2. 快速排序

快速排序的思想很简单,就是先把待排序的数组拆成左右两个区间,左边都比中间的基准数小,右边都比基准数大。接着左右两边各自再做同样的操作,完成后再拆分再继续,一直到各区间只有一个数为止。

举个例子,现在我要排序 4、9、5、1、2、6 这个数组。一般取首位的 4 为基准数,第一次排序的结果是:

2、1、4、5、9、6

可能有人觉得奇怪,2 和 1 交换下位置也能满足条件,为什么 2 在首位?这其实由实际的代码实现来决定,并不影响之后的操作。以 4 为分界点,对 2、1、4 和 5、9、6 各自排序,得到:

1、2、4、5、9、6

不用管左边的 1、2、4 了,将 5、9、6 拆成 5 和 9、6,再排序,至此结果为:

1、2、4、5、6、9

为什么把快排划到交换排序的范畴呢?因为元素的移动也是靠交换位置来实现的。算法的实现需要用到递归(拆分区间之后再对每个区间作同样的操作)

用 Java 实现的快速排序如下

public void quicksort(int[] arr, int start, int end) {if(start < end) {// 把数组中的首位数字作为基准数int stard = arr[start];// 记录需要排序的下标int low = start;int high = end;// 循环找到比基准数大的数和比基准数小的数while(low < high) {// 右边的数字比基准数大while(low < high && arr[high] >= stard) {high--;}// 使用右边的数替换左边的数arr[low] = arr[high];// 左边的数字比基准数小while(low < high && arr[low] <= stard) {low++;}// 使用左边的数替换右边的数arr[high] = arr[low];}// 把标准值赋给下标重合的位置arr[low] = stard;// 处理所有小的数字quickSort(arr, start, low);// 处理所有大的数字quickSort(arr, low + 1, end);}
}

二、插入排序

插入排序是一种简单的排序方法,其基本思想是将一个记录插入到已经排好序的有序表中,使得被插入数的序列同样是有序的。按照此法对所有元素进行插入,直到整个序列排为有序的过程。

1. 直接插入排序

直接插入排序就是插入排序的粗暴实现。对于一个序列,选定一个下标,认为在这个下标之前的元素都是有序的。将下标所在的元素插入到其之前的序列中。接着再选取这个下标的后一个元素,继续重复操作。直到最后一个元素完成插入为止。我们一般从序列的第二个元素开始操作。

用 Java 实现的算法如下:

public void insertSort(int[] arr) {// 遍历所有数字for(int i = 1; i < arr.length - 1; i++) {// 当前数字比前一个数字小if(arr[i] < arr[i - 1]) {int j;// 把当前遍历的数字保存起来int temp = arr[i];for(j = i - 1; j >= 0 && arr[j] > temp; j--) {// 前一个数字赋给后一个数字arr[j + 1] = arr[j];}// 把临时变量赋给不满足条件的后一个元素arr[j + 1] = temp;}}
}

2. 希尔排序

某些情况下直接插入排序的效率极低。比如一个已经有序的升序数组,这时再插入一个比最小值还要小的数,也就意味着被插入的数要和数组所有元素比较一次。我们需要对直接插入排序进行改进。

怎么改进呢?前面提过,插入排序对已经排好序的数组操作时,效率很高。因此我们可以试着先将数组变为一个相对有序的数组,然后再做插入排序。

希尔排序能实现这个目的。希尔排序把序列按下标的一定增量(步长)分组,对每组分别使用插入排序。随着增量(步长)减少,一直到一,算法结束,整个序列变为有序。因此希尔排序又称缩小增量排序。

一般来说,初次取序列的一半为增量,以后每次减半,直到增量为一。

用 Java 实现的算法如下:

public void shellSort(int[] arr) {// gap 为步长,每次减为原来的一半。for (int gap = arr.length / 2; gap > 0; gap /= 2) {// 对每一组都执行直接插入排序for (int i = 0 ;i < gap; i++) {// 对本组数据执行直接插入排序for (int j = i + gap; j < arr.length; j += gap) {// 如果 a[j] < a[j-gap],则寻找 a[j] 位置,并将后面数据的位置都后移if (arr[j] < arr[j - gap]) {int k;int temp = arr[j];for (k = j - gap; k >= 0 && arr[k] > temp; k -= gap) {arr[k + gap] = arr[k];}arr[k + gap] = temp;}}}}
}

三、选择排序

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

1. 简单选择排序

选择排序思想的暴力实现,每一趟从未排序的区间找到一个最小元素,并放到第一位,直到全部区间有序为止。

用 Java 实现的算法如下:

public void selectSort(int[] arr) {// 遍历所有的数for (int i = 0; i < arr.length; i++) {int minIndex = i;// 把当前遍历的数和后面所有的数进行比较,并记录下最小的数的下标for (int j = i + 1; j < arr.length; j++) {if (arr[j] < arr[minIndex]) {// 记录最小的数的下标minIndex = j;}}// 如果最小的数和当前遍历的下标不一致,则交换if (i != minIndex) {int temp = arr[i];arr[i] = arr[minIndex];arr[minIndex] = temp;}}
}

2. 堆排序

我们知道,对于任何一个数组都可以看成一颗完全二叉树。堆是具有以下性质的完全二叉树:每个结点的值都大于或等于其左右孩子结点的值,称为大顶堆;或者每个结点的值都小于或等于其左右孩子结点的值,称为小顶堆。如下图:

像上图的大顶堆,映射为数组,就是 [50, 45, 40, 20, 25, 35, 30, 10, 15]。可以发现第一个下标的元素就是最大值,将其与末尾元素交换,则末尾元素就是最大值。所以堆排序的思想可以归纳为以下两步:

  1. 根据初始数组构造堆

  2. 每次交换第一个和最后一个元素,然后将除最后一个元素以外的其他元素重新调整为大顶堆

重复以上两个步骤,直到没有元素可操作,就完成排序了。

我们需要把一个普通数组转换为大顶堆,调整的起始点是最后一个非叶子结点,然后从左至右,从下至上,继续调整其他非叶子结点,直到根结点为止。

/*** 转化为大顶堆* @param arr 待转化的数组* @param size 待调整的区间长度* @param index 结点下标*/
public void maxHeap(int[] arr, int size, int index) {// 左子结点int leftNode = 2 * index + 1;// 右子结点int rightNode = 2 * index + 2;int max = index;// 和两个子结点分别对比,找出最大的结点if (leftNode < size && arr[leftNode] > arr[max]) {max = leftNode;}if (rightNode < size && arr[rightNode] > arr[max]) {max = rightNode;}// 交换位置if (max != index) {int temp = arr[index];arr[index] = arr[max];arr[max] = temp;// 因为交换位置后有可能使子树不满足大顶堆条件,所以要对子树进行调整maxHeap(arr, size, max);}
}/*** 堆排序* @param arr 待排序的整型数组*/
public static void heapSort(int[] arr) {// 开始位置是最后一个非叶子结点,即最后一个结点的父结点int start = (arr.length - 1) / 2;// 调整为大顶堆for (int i = start; i >= 0; i--) {SortTools.maxHeap(arr, arr.length, i);}// 先把数组中第 0 个位置的数和堆中最后一个数交换位置,再把前面的处理为大顶堆for (int i = arr.length - 1; i > 0; i--) {int temp = arr[0];arr[0] = arr[i];arr[i] = temp;maxHeap(arr, i, 0);}
}

四、归并排序

归并排序是建立在归并操作上的一种有效,稳定的排序算法。该算法采用分治法的思想,是一个非常典型的应用。归并排序的思路如下:

  1. 将 n 个元素分成两个各含 n/2 个元素的子序列

  2. 借助递归,两个子序列分别继续进行第一步操作,直到不可再分为止

  3. 此时每一层递归都有两个子序列,再将其合并,作为一个有序的子序列返回上一层,再继续合并,全部完成之后得到的就是一个有序的序列

关键在于两个子序列应该如何合并。假设两个子序列各自都是有序的,那么合并步骤就是:

  1. 创建一个用于存放结果的临时数组,其长度是两个子序列合并后的长度

  2. 设定两个指针,最初位置分别为两个已经排序序列的起始位置

  3. 比较两个指针所指向的元素,选择相对小的元素放入临时数组,并移动指针到下一位置

  4. 重复步骤 3 直到某一指针达到序列尾

  5. 将另一序列剩下的所有元素直接复制到合并序列尾

用 Java 实现的归并排序如下:

/*** 合并数组*/
public static void merge(int[] arr, int low, int middle, int high) {// 用于存储归并后的临时数组int[] temp = new int[high - low + 1];// 记录第一个数组中需要遍历的下标int i = low;// 记录第二个数组中需要遍历的下标int j = middle + 1;// 记录在临时数组中存放的下标int index = 0;// 遍历两个数组,取出小的数字,放入临时数组中while (i <= middle && j <= high) {// 第一个数组的数据更小if (arr[i] <= arr[j]) {// 把更小的数据放入临时数组中temp[index] = arr[i];// 下标向后移动一位i++;} else {temp[index] = arr[j];j++;}index++;}// 处理剩余未比较的数据while (i <= middle) {temp[index] = arr[i];i++;index++;}while (j <= high) {temp[index] = arr[j];j++;index++;}// 把临时数组中的数据重新放入原数组for (int k = 0; k < temp.length; k++) {arr[k + low] = temp[k];}
}/*** 归并排序*/
public static void mergeSort(int[] arr, int low, int high) {int middle = (high + low) / 2;if (low < high) {// 处理左边数组mergeSort(arr, low, middle);// 处理右边数组mergeSort(arr, middle + 1, high);// 归并merge(arr, low, middle, high);}
}

五、基数排序

基数排序的原理是将整数按位数切割成不同的数字,然后按每个位数分别比较。为此需要将所有待比较的数值统一为同样的数位长度,数位不足的数在高位补零。

使用 Java 实现的基数排序:

/*** 基数排序*/
public static void radixSort(int[] arr) {// 存放数组中的最大数字int max = Integer.MIN_VALUE;for (int value : arr) {if (value > max) {max = value;}}// 计算最大数字是几位数int maxLength = (max + "").length();// 用于临时存储数据int[][] temp = new int[10][arr.length];// 用于记录在 temp 中相应的下标存放数字的数量int[] counts = new int[10];// 根据最大长度的数决定比较次数for (int i = 0, n = 1; i < maxLength; i++, n *= 10) {// 每一个数字分别计算余数for (int j = 0; j < arr.length; j++) {// 计算余数int remainder = arr[j] / n % 10;// 把当前遍历的数据放到指定的数组中temp[remainder][counts[remainder]] = arr[j];// 记录数量counts[remainder]++;}// 记录取的元素需要放的位置int index = 0;// 把数字取出来for (int k = 0; k < counts.length; k++) {// 记录数量的数组中当前余数记录的数量不为 0if (counts[k] != 0) {// 循环取出元素for (int l = 0; l < counts[k]; l++) {arr[index] = temp[k][l];// 记录下一个位置index++;}// 把数量置空counts[k] = 0;}}}
}

六、八种排序算法的总结

排序法最好情形平均时间最差情形稳定度空间复杂度
冒泡排序O(n)O(n^2)O(n^2)稳定O(1)
快速排序O(nlogn)O(nlogn)O(n^2)不稳定O(nlogn)
直接插入排序O(n)O(n^2)O(n^2)稳定O(1)
希尔排序不稳定O(1)
直接选择排序O(n^2)O(n^2)O(n^2)不稳定O(1)
堆排序O(nlogn)O(nlogn)O(nlogn)不稳定O(nlogn)
归并排序O(nlogn)O(nlogn)O(nlogn)稳定O(n)


http://chatgpt.dhexx.cn/article/ffmTOGcx.shtml

相关文章

十大经典排序算法——java语言

文章目录 一、冒泡排序二、选择排序三、插入排序四、希尔排序五、归并排序六、快速排序七、堆排序八、计数排序九、桶排序十、基数排序 一、冒泡排序 概述&#xff1a; 冒泡排序是一种简单直观的排序算法。它重复的走访要排序的数列&#xff0c;一次比较两个元素&#xff0c;按…

Java常见排序算法

目录 1、归并排序 2、堆排序 3、基数排序 4、冒泡排序 5、希尔排序 6、快速排序 7、插入排序 8、选择排序 1、归并排序 1、基本思想 归并排序&#xff08;MERGE-SORT&#xff09;是利用归并的思想实现的排序方法&#xff0c;该算法采用经典的分治&#xff08;divide-a…

java实现七种经典排序算法

简单算法&#xff1a;冒泡&#xff0c;简单选择&#xff0c;直接插入 改进算法&#xff1a;希尔&#xff0c;堆&#xff0c;归并&#xff0c;快速 直接插入排序&#xff1a;将一个记录插入到已经拍好的有序列表中&#xff0c;从而得到一个新的、记录数增加1的有序表。 冒泡排…

java实现10种排序算法

1.冒泡排序(Bubble Sort) import java.util.Arrays; //冒泡排序 public class BubbleSort_01 {public static void main(String[] args) {int a[]{3,44,38,5,47,15,36,26,27,2,46,4,19,50,48};//记录比较次数int count0;//i0,第一轮比较for (int i 0; i < a.length-1; i) {…

SQL sever数据库触发器设计

SQL sever数据库触发器设计 一、目的: 能够理解触发器调用的机制。能够使用SQL命令创建DML触发器。能够完成触发器的修改、删除等管理任务。 二、触发器: 定义&#xff1a;触发器&#xff08; T rigger &#xff09;是 SQL server 提供给程序员和数据分析员来保证数据完整性…

MySQL——超详细数据库触发器教程

目录 一、触发器的概念 二、创建触发器 三、查看触发器 四、删除触发器 总结 一、触发器的概念 在实际开发中往往会碰到这样的情况&#xff1a; 当我们对一个表进行数据操作时&#xff0c;需要同步对其它的表执行相应的操作&#xff0c;正常情况下&#xff0c;如果我们使用…

关于数据库触发器(trigger)的简单使用操作

最近在做一些东西&#xff0c;用到关于数据库触发器的简单使用。比如当我们在做用户模块的表设计的时候&#xff0c;我们建了联用户信息表&#xff08;t_user&#xff09;和账号表&#xff08;t_account&#xff09;&#xff0c;账号表&#xff08;t_account&#xff09;用来进…

MySQL数据库触发器

MySQL 数据库中触发器是一个特殊的存储过程&#xff0c;不同的是执行存储过程要使用 CALL 语句来调用&#xff0c;而触发器的 执行不需要使用 CALL 语句来调用&#xff0c;也不需要手工启动&#xff0c;只要一个预定义的事件发生就会被 MySQL自动调用 引发触发器执行的事件一般…

mysql数据库触发器失效,mysql 的数据库触发器解决方法

mysql 的数据库触发器 我要做一个数据库触发器&#xff0c;当删除数据库中的某一张表的时候触发这个一个事件&#xff0c;删除其他表中的某一些数据。 大家给个例子 ------解决方案-------------------- MYSQL官方免费手册中已经有现成的例子了。 CREATE TABLE test1(a1 INT); …

什么是数据库触发器?

目录 什么是数据库触发器&#xff1f; 事件 AFTER触发器 INSTEAD OF触发器 特殊数据库对象 定义 用于触发器 复杂的审计 执行业务规则 派生列值 触发器很棘手&#xff01; 什么是数据库触发器&#xff1f; 数据库触发器是在数据库中发生特定操作时运行的特殊存储过…

Oracle数据库 触发器

文章目录 一、触发器的定义二、触发器的分类三、触发器的功能四、触发器的语法五、触发器的使用案例案例1&#xff1a;向emp1表中插入一条数据后输出 欢迎加入 语句案例2&#xff1a;数据校验&#xff0c;在周四这一天不允许向emp1表中插入/更新数据案例3&#xff1a;创建触发器…

数据库之触发器详解

一、触发器的概念 触发器是与表有关的数据库对象&#xff0c;在满足定义条件时触发&#xff0c;并执行触发器中定义的语句集合。触发器的这种特性可以协助应用在数据库端确保数据的完整性。 举个例子&#xff0c;比如你现在有两个表【用户表】和【日志表】&#xff0c;当一个…

数据库-触发器

目录 1. 触发器概述 2. 触发器的创建 2.1 创建触发器语法 3. 查看、删除触发器 3.2 删除触发器 4. 触发器的优缺点 4.2 缺点 4.3 注意点 在实际开发中&#xff0c;我们经常会遇到这样的情况&#xff1a;有 2 个或者多个相互关联的表&#xff0c;如 商品信息 和 库存信…

触发器(数据库必学)

文章目录 概念注意 优缺点优点缺点 语法参数说明 查看触发器删除触发器实例实际应用注意重新编辑拓展不能对同一张表进行修改 概念 触发器是一种特殊类型的存储过程&#xff0c;它不同于存储过程&#xff0c;主要是通过事件触发而被执行的。而存储过程则需要主动调用其名字执行…

C语言实现字符串逆序、倒置字符串(字符串逆序问题的升级)

一.字符串逆序 问题描述&#xff1a; 输入一个字符串str&#xff0c;将其内容颠倒过来&#xff0c;并输出。 数据范围0<len(str)<10000 输入描述&#xff1a; 输入一个字符串&#xff0c;可以有空格 输出描述&#xff1a; 输出逆序的字符串 输入样例&#xff1a; …

指针实现字符串逆序

代码如下&#xff1a; #include<stdio.h> #include<string.h> void reverse(char* str) { //指针变量分别指向第一个和最后一个元素&#xff0c;借助中间变量temp进行交换。char* left str;char* right str strlen(str) - 1;while (left < right){char temp…

c语言字符串逆序输出reverse,将一个字符串逆序输出

C语言:输入一个字符串,然后逆序输出 输入一个字符串,然后逆序输出,要CSS布局HTML小编今天和大家分享主函数调用fun函数,fun的功能是逆可以将整数当做字符串(字符串长度不超过10)接收,然后反向输出字符数组元素即可。 字符串实际长度可以用strlen函数来计算。 方法程序如下…

字符串逆序输出

字符串逆置 方法1:下标法&#xff0c;定义一个i下标从头开始&#xff0c;使用strlen函数求出字符串长度(不包括\0),定义一个j下标等于字符串长度减一&#xff0c;i&#xff0c;j下标字符进行交换&#xff0c;只需要遍历字符串长度一半即可 方法2:额外创建一个数组&#xff0c…

递归实现字符串逆序

编写一个函数 reverse_string(char * string)&#xff08;递归实现&#xff09;&#xff0c;将参数字符串中的字符反向排列&#xff0c;不是逆序打印。 &#xff08;要求&#xff1a;不能使用C函数库中的字符串操作函数。&#xff09; &#xff08;在本次练习中&#xff0c;由于…

C字符串逆序、C++字符串逆序

1.C字符串逆序&#xff1a; void CReverse(char* ch) {int nLen strlen(ch) - 1;char szStr;for (int i 0; i < nLen - i; i){szStr ch[i];ch[i] ch[nLen - i];ch[nLen - i] szStr;}ch[nLen 1] 0; } 2.C字符串逆序&#xff08;利用栈的先进后出的原理&#xff09; …