排序算法合集
一、概述
排序算法是计算机科学中的一类常见算法,用于将一组数据按照特定的顺序进行排列;排序算法的应用非常广泛,涉及到数据处理、搜索算法、数据库操作等众多领域。常见的排序算法有以下几种:
- 冒泡排序(Bubble Sort)
- 插入排序(Insertion Sort)
- 选择排序(Selection Sort)
- 快速排序(Quick Sort)
- 归并排序(Merge Sort)
- 堆排序(Heap Sort)
- 希尔排序(Shell Sort)
- 计数排序(Counting Sort)
- 桶排序(Bucket Sort)
- 基数排序(Radix Sort)
奈何本人技术不太行,对于树和堆的理解不够透彻,所以涉及到这两种数据结构的排序算法可能要以后再补充
二、排序算法
文章使用 Java语言 进行演示,使用 int数据 类型作为示范,操作的数据结构均以 数组 为准,排序的结果为 从小到大 排列,同时提供随机初始化数据的方法
相关的全局变量和初始化数据方法如下
java
public static int number;//用例数据量
public static int[] nums;//用例数组
/**
* 用例随机初始化
* @param number 用例数据量
* @param start 用例起始范围
* @param end 用例结束范围
*/
public static void initNums(int number, int start, int end) {
nums = new int[number];
for (int i = 0; i < nums.length; i++) {
nums[i] = (int) (Math.random() * (end - start + 1) + start);
}
}
1.冒泡排序
冒泡排序是一种简单直观的排序算法,它通过依次比较相邻的元素,并在必要时交换位置,使得每一轮循环后最大(或最小)的元素浮到序列的末尾。冒泡排序的过程可以描述如下:
- 从列表的第一个元素开始,依次比较相邻的两个元素。如果前一个元素大于(或小于)后一个元素,则交换它们的位置,使较大(或较小)的元素"冒泡"到后面
- 继续对剩余的元素进行相邻比较和交换操作,直到将最大(或最小)的元素移动到列表的最后位置
- 对除去最后一个元素的剩余列表重复上述步骤,每次循环将会确定一个最大(或最小)的元素到达正确的位置
- 重复上述步骤,直到整个列表排序完成
java
/**
* 冒泡排序:从左到右逐个比较相邻的元素,并进行交换,每一轮将最大的元素移到最右侧
*/
public static void bubbleSort() {
int[] bubbleSortNums = nums.clone();
for (int i = 1; i < bubbleSortNums.length; i++) {
for (int j = 0; j < bubbleSortNums.length - i; j++) {
if (bubbleSortNums[j] > bubbleSortNums[j + 1]) {//已经确保两个数不相同,所以异或不会丢失数据
bubbleSortNums[j] ^= bubbleSortNums[j + 1];
bubbleSortNums[j + 1] ^= bubbleSortNums[j];
bubbleSortNums[j] ^= bubbleSortNums[j + 1];
}
}
}
System.out.print("冒泡排序: ");
System.out.println(Arrays.toString(bubbleSortNums));
}
冒泡排序的时间复杂度为O(N^2^),其中N为待排序元素的数量
2.插入排序
插入排序是一种简单直观的排序算法,它的工作原理类似于整理扑克牌的过程。插入排序的基本思想是将待排序的元素逐个插入到已经排序的序列中的正确位置,从而逐步构建有序序列。下面是插入排序的步骤:
- 将列表的第一个元素视为已排序序列
- 从第二个元素开始,逐个将元素插入到已排序序列的正确位置。插入时,将当前元素与已排序序列中的元素从后向前逐个比较,直到找到合适的位置插入
- 重复上述步骤,直到将所有元素插入到正确的位置。
java
/**
* 插入排序:逐个将元素插入到已排序的序列中的正确位置,构建最终有序序列
*/
public static void insertSort() {
int[] insertSortNums = nums.clone();
int temp = 0;//记录待插入的值
int index = 0;//记录插入的位置
boolean isFlag;
for (int i = 1; i < insertSortNums.length; i++) {
isFlag = false;
for (int j = 0; j < i; j++) {
if (insertSortNums[i] < insertSortNums[j]) {
index = j;
temp = insertSortNums[i];
isFlag = true;
break;
}
}
if (isFlag) {//当需要向前插入的时候才执行,否则不用移动
System.arraycopy(insertSortNums, index, insertSortNums, index + 1, i - index);
// for (int j = i; j > index; j--) {
// insertSortNums[j] = insertSortNums[j - 1];
// }
insertSortNums[index] = temp;
}
}
System.out.print("插入排序: ");
System.out.println(Arrays.toString(insertSortNums));
}
插入排序的时间复杂度为O(N^2^),其中N为待排序元素的数量
3.选择排序
选择排序是一种简单直观的排序算法,其基本思想是每一次从待排序的元素中选择最小(或最大)的元素,放置在已排序序列的末尾,从而逐步构建有序序列。选择排序的步骤如下:
- 遍历待排序序列,将第一个元素标记为当前最小(或最大)元素
- 从剩余的未排序元素中依次找到最小(或最大)的元素,与当前最小(或最大)元素进行比较
- 如果找到比当前最小(或最大)元素更小(或更大)的元素,则更新当前最小(或最大)元素的位置
- 继续遍历剩余的未排序元素,重复上述步骤,直到将所有元素排序完成
- 重复上述过程,直到所有元素都被排序
java
/**
* 选择排序:每一轮从未排序的部分选择最小(或最大)的元素,放到已排序部分的末尾
*/
public static void selectSort() {
int[] selectionSortNums = nums.clone();
for (int i = 0; i < selectionSortNums.length - 1; i++) {//计数
int maxNum = 0;//记录极大值的索引
for (int j = 0; j < selectionSortNums.length - i; j++) {
if (selectionSortNums[maxNum] < selectionSortNums[j]) {
maxNum = j;
}
}
//下面的if是用异或交换元素的重点,防止置0
if (maxNum != selectionSortNums.length - i - 1) {//如果已经在对应位置就不需要移动,否则会导致异或同一个变量置0
selectionSortNums[maxNum] ^= selectionSortNums[selectionSortNums.length - i - 1];
selectionSortNums[selectionSortNums.length - i - 1] ^= selectionSortNums[maxNum];
selectionSortNums[maxNum] ^= selectionSortNums[selectionSortNums.length - i - 1];
}
}
System.out.print("选择排序: ");
System.out.println(Arrays.toString(selectionSortNums));
}
选择排序的时间复杂度为O(N^2^),其中N为待排序元素的数量
4.快速排序
快速排序是一种高效的排序算法,它采用分治法的思想。快速排序的基本思想是选择一个基准元素,将待排序序列划分为左右两个子序列,其中左边子序列的元素都小于等于基准元素,右边子序列的元素都大于等于基准元素,然后递归地对左右子序列进行排序,最终得到有序序列。快速排序的步骤如下:
- 选择一个基准元素(通常是列表的第一个或最后一个元素)
- 遍历整个序列,将小于等于基准元素的元素放到左边,大于等于基准元素的元素放到右边,形成两个子序列
- 递归地对左右子序列进行快速排序
- 将左子序列、基准元素、右子序列按照顺序连接起来,得到最终的有序序列
快速排序(双指针法)动画演示
java
/**
* 快速排序:选择一个基准元素,将数组分为小于基准值和大于基准值的两部分,递归地对这两部分进行排序(默认首元素为基准)
*/
public static void quickSort() {
int[] quickSortNums = nums.clone();
quick(quickSortNums, 0, quickSortNums.length - 1);
System.out.print("快速排序: ");
System.out.println(Arrays.toString(quickSortNums));
}
/**
* 快速排序:选择一个基准元素,将数组分为小于基准值和大于基准值的两部分,递归地对这两部分进行排序(默认首元素为基准)
* @param quickSortNums 待排序数组
* @param start 左起点
* @param end 右起点
*/
public static void quick(int[] quickSortNums, int start, int end) {
if (start < end) {//直到序列内只有一个元素时,无需排序,结束递归
int left = start;
int right = end;
int pivotIndex = start;//基准元素的索引值(选择第一个元素作为基准元素)
//寻找基轴位置
while (left < right) {
//右侧扫描
while (left < right && quickSortNums[right] >= quickSortNums[pivotIndex]) {//找到第一个小于基准的元素
right--;
}//和基轴交换位置
if (left < right) {//保证小于基准的元素在右侧
quickSortNums[right] ^= quickSortNums[pivotIndex];
quickSortNums[pivotIndex] ^= quickSortNums[right];
quickSortNums[right] ^= quickSortNums[pivotIndex];
pivotIndex = right;
}
//左侧扫描
while (left < right && quickSortNums[left] <= quickSortNums[pivotIndex]) {//找到第一个大于基准的元素
left++;
}//和基轴交换位置
if (left < right) {//保证大于基准的元素在左侧
quickSortNums[left] ^= quickSortNums[pivotIndex];
quickSortNums[pivotIndex] ^= quickSortNums[left];
quickSortNums[left] ^= quickSortNums[pivotIndex];
pivotIndex = left;
}
}
//左右子序列递归排序
quick(quickSortNums, start, pivotIndex - 1);
quick(quickSortNums, pivotIndex + 1, end);
}
}
快速排序的时间复杂度为O(NlogN),其中N为待排序元素的数量
5.归并排序
归并排序是一种高效的排序算法,它采用分治法的思想。归并排序的基本思想是将待排序序列分成两个较小的子序列,递归地对子序列进行排序,然后将两个有序子序列合并成一个有序序列。以下是归并排序的算法实现步骤:
- 分解:将待排序序列递归地分解为两个较小的子序列,直到每个子序列只剩下一个元素或为空
- 合并:将两个有序子序列合并成一个有序序列。比较两个子序列的首个元素,将较小的元素放入临时的辅助数组中,并将相应子序列的指针向后移动,重复此过程,直到两个子序列中的元素全部放入辅助数组中
- 将辅助数组中的有序序列复制回原始序列的对应位置
- 递归地对两个子序列重复上述步骤,直到整个序列排序完成
java
/**
* 归并排序:将数组递归地分成两半,对每个子数组进行排序,然后将两个有序子数组合并成一个有序数组
*/
public static void mergeSort() {
int[] mergeSortNums = nums.clone();
merge(mergeSortNums, 0, mergeSortNums.length - 1);
System.out.print("归并排序: ");
System.out.println(Arrays.toString(mergeSortNums));
}
/**
* 归并排序:将数组递归地分成两半,对每个子数组进行排序,然后将两个有序子数组合并成一个有序数组
* @param mergeSortNums 子序列
* @param start 子序列起点
* @param end 子序列终点
*/
public static void merge(int[] mergeSortNums, int start, int end) {
if (start < end) {
int middle = (end - start) / 2 + start;
merge(mergeSortNums, start, middle);
merge(mergeSortNums, middle + 1, end);
//合并两个子序列,暂存到临时数组
int num = start;
int i = start;
int j = middle + 1;
int[] temp = new int[mergeSortNums.length];
while (i <= middle && j <= end) {
if (mergeSortNums[i] <= mergeSortNums[j]) {
temp[num++] = mergeSortNums[i++];
} else {
temp[num++] = mergeSortNums[j++];
}
}
while (i <= middle) {
temp[num++] = mergeSortNums[i++];
}
while (j <= end) {
temp[num++] = mergeSortNums[j++];
}
//拷贝回初始数组
System.arraycopy(temp, start, mergeSortNums, start, end - start + 1);
}
}
归并排序的时间复杂度为O(NlogN),其中N为待排序元素的数量
6.计数排序
计数排序是一种非比较的排序算法,它通过统计待排序序列中每个元素出现的次数,进而确定每个元素在有序序列中的位置。计数排序适用于待排序序列中的元素都是非负整数,并且元素的范围相对较小的情况。计数排序的步骤如下:
- 找出待排序序列中的最大元素,并确定计数数组的大小。计数数组的大小为最大元素值加1
- 统计待排序序列中每个元素出现的次数,并将统计结果存储在计数数组中。计数数组的下标表示元素的值,数组中存储的值表示该元素出现的次数
- 对计数数组进行顺序求和操作。将计数数组中的每个元素与前一个元素相加,得到每个元素在有序序列中的最后位置的索引
- 创建一个与待排序序列大小相同的辅助数组
- 从后往前遍历待排序序列,根据元素的值找到其在计数数组中对应的索引,将元素放入辅助数组中的正确位置
- 将辅助数组中的元素复制回原始序列,完成排序。
java
/**
* 计数排序:对每个输入元素进行计数,然后根据计数值的顺序重构有序数组
* @param start 数据左起始范围
* @param end 数据右起始范围
*/
public static void countSort(int start, int end) {
int[] countSortNums = nums.clone();
int[][] numCount = new int[Math.abs(start) + Math.abs(end) + 1][2];//计数
int temp = start;
for (int i = 0; i < numCount.length; i++) {
numCount[i][0] = temp++;
}
for (int i = 0; i < countSortNums.length; i++) {
numCount[countSortNums[i] + Math.abs(start)][1]++;
}
int index = 0;
int indexNum = 0;
for (int i = 0; i < numCount.length; i++) {
if (numCount[i][1] != 0 && indexNum != 0) {
numCount[i][1] += numCount[index][1];
index = i;
} else if (numCount[i][1] != 0 && indexNum == 0) {
indexNum++;
index = i;
}
}//计算排序位置
int left = 0;
int right = -1;
for (int j = 0; j < numCount.length; j++) {
if (numCount[j][1] != 0) {
right = numCount[j][1];
for (int i = left; i < right; i++) {
countSortNums[i] = numCount[j][0];
}
left = right;
}
}
System.out.print("计数排序: ");
System.out.println(Arrays.toString(countSortNums));
}
三、完整源码
java
package Yskysoar;
import java.util.Arrays;
import java.util.Scanner;
/**
* @author Yskysoar
* @createTime 2023-07-12 19:29
* @description 排序算法
* 1.所有测试对象为数组、链表或者树,默认为数组
* 2.提供用例随机初始化方法
* 3.排序结果从小到大排列
*/
public class SortAlgorithm {
public static int number;//用例数据量
public static int[] nums;//用例数组
public static void main(String[] args) {
Scanner scanner = new Scanner(System.in);
System.out.print("请输入数据量,数据起点,数据终点:");
number = scanner.nextInt();
int start = scanner.nextInt();
int end = scanner.nextInt();
// nums = new int[] {3, 8, 10, 5, 10, 10, 3, 4, 2, 9};
initNums(number, start, end);
System.out.print("初始数据: ");
System.out.println(Arrays.toString(nums));
//冒泡排序
bubbleSort();
//选择排序
selectSort();
//插入排序
insertSort();
//快速排序
quickSort();
//归并排序
mergeSort();
//计数排序
countSort(start, end);
}
/**
* 用例随机初始化
* @param number 用例数据量
* @param start 用例起始范围
* @param end 用例结束范围
*/
public static void initNums(int number, int start, int end) {
nums = new int[number];
for (int i = 0; i < nums.length; i++) {
nums[i] = (int) (Math.random() * (end - start + 1) + start);
}
}
/**
* 冒泡排序:从左到右逐个比较相邻的元素,并进行交换,每一轮将最大的元素移到最右侧
*/
public static void bubbleSort() {
int[] bubbleSortNums = nums.clone();
for (int i = 1; i < bubbleSortNums.length; i++) {
for (int j = 0; j < bubbleSortNums.length - i; j++) {
if (bubbleSortNums[j] > bubbleSortNums[j + 1]) {//已经确保两个数不相同,所以异或不会丢失数据
bubbleSortNums[j] ^= bubbleSortNums[j + 1];
bubbleSortNums[j + 1] ^= bubbleSortNums[j];
bubbleSortNums[j] ^= bubbleSortNums[j + 1];
}
}
}
System.out.print("冒泡排序: ");
System.out.println(Arrays.toString(bubbleSortNums));
}
/**
* 选择排序:每一轮从未排序的部分选择最小(或最大)的元素,放到已排序部分的末尾
*/
public static void selectSort() {
int[] selectionSortNums = nums.clone();
for (int i = 0; i < selectionSortNums.length - 1; i++) {//计数
int maxNum = 0;//记录极大值的索引
for (int j = 0; j < selectionSortNums.length - i; j++) {
if (selectionSortNums[maxNum] < selectionSortNums[j]) {
maxNum = j;
}
}
//下面的if是用异或交换元素的重点,防止置0
if (maxNum != selectionSortNums.length - i - 1) {//如果已经在对应位置就不需要移动,否则会导致异或同一个变量置0
selectionSortNums[maxNum] ^= selectionSortNums[selectionSortNums.length - i - 1];
selectionSortNums[selectionSortNums.length - i - 1] ^= selectionSortNums[maxNum];
selectionSortNums[maxNum] ^= selectionSortNums[selectionSortNums.length - i - 1];
}
}
System.out.print("选择排序: ");
System.out.println(Arrays.toString(selectionSortNums));
}
/**
* 插入排序:逐个将元素插入到已排序的序列中的正确位置,构建最终有序序列
*/
public static void insertSort() {
int[] insertSortNums = nums.clone();
int temp = 0;//记录待插入的值
int index = 0;//记录插入的位置
boolean isFlag;
for (int i = 1; i < insertSortNums.length; i++) {
isFlag = false;
for (int j = 0; j < i; j++) {
if (insertSortNums[i] < insertSortNums[j]) {
index = j;
temp = insertSortNums[i];
isFlag = true;
break;
}
}
if (isFlag) {//当需要向前插入的时候才执行,否则不用移动
/*
public static void arraycopy(Object src, int srcPos, Object dest, int destPos, int length)
src:源数组
srcPos:源数组要复制的起始位置
dest:目的数组
destPos:目的数组放置的起始位置
length:复制的长度
*/
System.arraycopy(insertSortNums, index, insertSortNums, index + 1, i - index);
// for (int j = i; j > index; j--) {
// insertSortNums[j] = insertSortNums[j - 1];
// }
insertSortNums[index] = temp;
}
}
System.out.print("插入排序: ");
System.out.println(Arrays.toString(insertSortNums));
}
/**
* 快速排序:选择一个基准元素,将数组分为小于基准值和大于基准值的两部分,递归地对这两部分进行排序(默认首元素为基准)
*/
public static void quickSort() {
int[] quickSortNums = nums.clone();
quick(quickSortNums, 0, quickSortNums.length - 1);
System.out.print("快速排序: ");
System.out.println(Arrays.toString(quickSortNums));
}
/**
* 快速排序:选择一个基准元素,将数组分为小于基准值和大于基准值的两部分,递归地对这两部分进行排序(默认首元素为基准)
* @param quickSortNums 待排序数组
* @param start 左起点
* @param end 右起点
*/
public static void quick(int[] quickSortNums, int start, int end) {
if (start < end) {//直到序列内只有一个元素时,无需排序,结束递归
int left = start;
int right = end;
int pivotIndex = start;//基准元素的索引值(选择第一个元素作为基准元素)
//寻找基轴位置
while (left < right) {
//右侧扫描
while (left < right && quickSortNums[right] >= quickSortNums[pivotIndex]) {//找到第一个小于基准的元素
right--;
}//和基轴交换位置
if (left < right) {//保证小于基准的元素在右侧
quickSortNums[right] ^= quickSortNums[pivotIndex];
quickSortNums[pivotIndex] ^= quickSortNums[right];
quickSortNums[right] ^= quickSortNums[pivotIndex];
pivotIndex = right;
}
//左侧扫描
while (left < right && quickSortNums[left] <= quickSortNums[pivotIndex]) {//找到第一个大于基准的元素
left++;
}//和基轴交换位置
if (left < right) {//保证大于基准的元素在左侧
quickSortNums[left] ^= quickSortNums[pivotIndex];
quickSortNums[pivotIndex] ^= quickSortNums[left];
quickSortNums[left] ^= quickSortNums[pivotIndex];
pivotIndex = left;
}
}
//左右子序列递归排序
quick(quickSortNums, start, pivotIndex - 1);
quick(quickSortNums, pivotIndex + 1, end);
}
}
/**
* 归并排序:将数组递归地分成两半,对每个子数组进行排序,然后将两个有序子数组合并成一个有序数组
*/
public static void mergeSort() {
int[] mergeSortNums = nums.clone();
merge(mergeSortNums, 0, mergeSortNums.length - 1);
System.out.print("归并排序: ");
System.out.println(Arrays.toString(mergeSortNums));
}
/**
* 归并排序:将数组递归地分成两半,对每个子数组进行排序,然后将两个有序子数组合并成一个有序数组
* @param mergeSortNums 子序列
* @param start 子序列起点
* @param end 子序列终点
*/
public static void merge(int[] mergeSortNums, int start, int end) {
if (start < end) {
int middle = (end - start) / 2 + start;
merge(mergeSortNums, start, middle);
merge(mergeSortNums, middle + 1, end);
//合并两个子序列,暂存到临时数组
int num = start;
int i = start;
int j = middle + 1;
int[] temp = new int[mergeSortNums.length];
while (i <= middle && j <= end) {
if (mergeSortNums[i] <= mergeSortNums[j]) {
temp[num++] = mergeSortNums[i++];
} else {
temp[num++] = mergeSortNums[j++];
}
}
while (i <= middle) {
temp[num++] = mergeSortNums[i++];
}
while (j <= end) {
temp[num++] = mergeSortNums[j++];
}
//拷贝回初始数组
System.arraycopy(temp, start, mergeSortNums, start, end - start + 1);
}
}
/**
* 计数排序:对每个输入元素进行计数,然后根据计数值的顺序重构有序数组
* @param start 数据左起始范围
* @param end 数据右起始范围
*/
public static void countSort(int start, int end) {
int[] countSortNums = nums.clone();
int[][] numCount = new int[Math.abs(start) + Math.abs(end) + 1][2];//计数
int temp = start;
for (int i = 0; i < numCount.length; i++) {
numCount[i][0] = temp++;
}
for (int i = 0; i < countSortNums.length; i++) {
numCount[countSortNums[i] + Math.abs(start)][1]++;
}
int index = 0;
int indexNum = 0;
for (int i = 0; i < numCount.length; i++) {
if (numCount[i][1] != 0 && indexNum != 0) {
numCount[i][1] += numCount[index][1];
index = i;
} else if (numCount[i][1] != 0 && indexNum == 0) {
indexNum++;
index = i;
}
}//计算排序位置
int left = 0;
int right = -1;
for (int j = 0; j < numCount.length; j++) {
if (numCount[j][1] != 0) {
right = numCount[j][1];
for (int i = left; i < right; i++) {
countSortNums[i] = numCount[j][0];
}
left = right;
}
}
System.out.print("计数排序: ");
System.out.println(Arrays.toString(countSortNums));
}
}
本人技术水平一般,各位大佬如果发现我的不足之处或者有好的建议可以指出,谢谢大家