志不强者智不达,言不信者行不果。这篇文章主要讲述八大排序算图解汇总相关的知识,希望能为你提供帮助。
1.排序的介绍排序也成为排序算法(Sort Algoritm),排序是将一组数据,按指定的顺序进行排列的过程。
2. 排序的分类
- 内部排序法:
将需要处理的所有数据都加载到内部存储器(内存)中进行排序
- 外部排序法
数据量过大,无法全部加载到内存中,需要借助外部存储(如文件)进行排序。
- 常见的排序算法分类如下图:
文章图片
相关术语解释:
1)稳定:如果a原本在b前面,而a=b,排序之后a仍然在b的前面;
2)不稳定:如果a原本在b的前面,而a=b,排序之后a可能会出现在b的后面;
3)内排序:所有排序操作都在内存中完成;
4)外排序:由于数据太大,因此把数据放在磁盘中,而排序通过磁盘和内存的数据传输才能进行;
5)时间复杂度: 一个算法执行所耗费的时间。
6)空间复杂度:运行完一个程序所需内存的大小。
7)n: 数据规模
排序方法 | 时间复杂度(平均) | 时间复杂度(最坏) | 时间复杂度(最好) | 空间复杂度 | 稳定性 | 复杂性 |
---|---|---|---|---|---|---|
直接插入排序 | O(n2) | O(n2) | O(n) | O(1) | 稳定 | 简单 |
希尔排序 | O(nlog2n) | O(n2) | O(n1.3) | O(1) | 不稳定 | 较复杂 |
直接选择排序 | O(n2) | O(n2) | O(n2) | O(1) | 不稳定 | 简单 |
堆排序 | O(nlog2n) | O(nlog2n) | O(nlog2n) | O(1) | 不稳定 | 较复杂 |
冒泡排序 | O(n2) | O(n2) | O(n) | O(1) | 稳定 | 简单 |
快速排序 | O(nlog2n) | O(n2) | O(nlog2n) | O(nlog2n) | 不稳定 | 较复杂 |
归并排序 | O(nlog2n) | O(nlog2n) | O(nlog2n) | O(n) | 稳定 | 较复杂 |
基数排序 | O(d(n+r)) | O(d(n+r)) | O(d(n+r)) | O(n+r) | 稳定 | 较复杂 |
4.1.1 介绍
冒泡排序(Bubble Sort) 最为简单的一种排序,通过重复走完数组的所有元素,通过打擂台的方式两两比较,直到没有数可以交换的时候结束这个数,再到下个数,直到整个数组排好顺序。因一个个浮出所以叫冒泡排序。双重循环时间 复杂度O(n^2)
4.1.2 算法步骤
- 比较相邻两个数据如果。第一个比第二个大,就交换两个数
- 对每一个相邻的数做同样1的工作,这样从开始一队到结尾一队在最后的数就是最大的数。
- 针对所有元素上面的操作,除了最后一个。
- 重复1~3步骤,直到顺序完成。
文章图片
4.1.4 java 代码实现和数据测试
/**
* @author 谢阳
* @version 1.8.0_131
*/
public class BubbleSort
public static void main(String[] args)
//时间复杂度为O(n^2)
int[] arr = new int[80000];
for (int i = 0;
i <
80000;
i++)
arr[i] = (int)(Math.random()*1000000);
long time = System.currentTimeMillis();
//sort1(arr);
//未优化 time = 14536 time = 14184 time = 13270
sort2(arr);
//优化 time = 13075time = 13539 time = 13388
System.out.println("time = " + (System.currentTimeMillis() - time));
//冒泡排序
public static void sort1(int[] arr)
//数组长度
int len = arr.length;
for (int i = 0;
i <
len - 1;
i++)
for (int j = 0;
j <
len - i - 1;
j++)
//前一个元素大于后一个元素则交换
if (arr[j] >
arr[j + 1])
int temp = arr[j];
arr[j] = arr[j + 1];
arr[j + 1] = temp;
//冒泡排序优化
public static void sort2(int[] arr)
int len = arr.length;
//标志位
boolean flag;
for (int i = 0;
i <
len - 1;
i++)
//未交换 为true
flag = true;
for (int j = 0;
j <
len - i - 1;
j++)
//前一个元素大于后一个元素则交换
if (arr[j] >
arr[j + 1])
//有交换则为false
flag = false;
int temp = arr[j];
arr[j] = arr[j + 1];
arr[j + 1] = temp;
//System.out.printf("第%d次交换:%s\\n", i + 1, Arrays.toString(arr));
//标志未变化则有序无需在进行交换
if (flag)
break;
4.2 选择排序
4.2.1 介绍
选择排序(Select Sort) 是直观的排序,通过确定一个 Key 最大或最小值,再从带排序的的数中找出最大或最小的交换到对应位置。再选择次之。双重循环时间复杂度为 O(n^2)
4.2.2 算法步骤
- 首先在未排序序列中找到最小(大)元素,存放到排序序列的起始位置。
- 再从剩余未排序元素中继续寻找最小(大)元素,然后放到已排序序列的末尾。
- 重复第二步,直到所有元素均排序完毕。
文章图片
4.2.4 java 代码实现和数据测试
import java.util.Arrays;
/**
* @author 谢阳
* @version 1.8.0_131
*/
public class SelectSort
public static void main(String[] args) //时间复杂度为O(n^2)
int[] arr = new int[80000];
for (int i = 0;
i <
80000;
i++)
arr[i] = (int)(Math.random()*1000000);
long time = System.currentTimeMillis();
sort(arr);
//time = 5168time = 5228time = 5172
//时间比冒泡快,因为减少了交换数据的次数
System.out.println("time = " + (System.currentTimeMillis() - time));
//int[] arr = -1,-42,213,3,1,-423,23,12,0;
//sort(arr);
//System.out.println(Arrays.toString(arr));
//选择排序
public static void sort(int[] arr)
//定义最小数
int min;
//定义标识符
int index;
//数组长度
int len = arr.length;
for (int i = 0;
i <
len - 1;
i++)
min = arr[i];
index = i;
for (int j = i + 1;
j <
len;
j++)
if (min <
arr[j])
min = arr[j];
index = j;
if (index != i)
arr[index] = arr[i];
arr[i] = min;
//System.out.printf("第%d次交换:%s\\n", i + 1, Arrays.toString(arr));
4.3 插入排序
4.3.1 介绍
插入排序(InsertionSort),一般也被称为直接插入排序。对于少量元素的排序,它是一个有效的算法。双重循环时间复杂度为 O(n^2)
4.3.2 算法步骤
- 将第一待排序序列第一个元素看做一个有序序列,把第二个元素到最后一个元素当成是未排序序列。
- 从头到尾依次扫描未排序序列,将扫描到的每个元素插入有序序列的适当位置。(如果待插入的元素与有序序列中的某个元素相等,则将待插入元素插入到相等元素的后面。)
文章图片
4.3.4 java 代码实现和数据测试
/**
* @author 谢阳
* @version 1.8.0_131
*/
public class InsertSort
public static void main(String[] args)
//时间复杂度为O(n^2)
int[] arr = new int[80000];
for (int i = 0;
i <
80000;
i++)
arr[i] = (int) (Math.random() * 1000000);
long time = System.currentTimeMillis();
sort(arr);
//time = 852 time = 826 time = 836 time = 776
System.out.println("time = " + (System.currentTimeMillis() - time));
//int[] arr = new int[]6, 5, 4, 3, 2, 1;
//sort(arr);
//System.out.println(Arrays.toString(arr));
//插入排序
public static void sort(int[] arr)
int len = arr.length;
int insert;
int index;
for (int i = 1;
i <
len;
i++)
insert = arr[i];
index = i - 1;
while (index >
= 0 &
&
arr[index] >
insert)
arr[index + 1] = arr[index];
index--;
arr[index + 1] = insert;
//System.out.printf("第%d次交换:%s\\n", i + 1, Arrays.toString(arr));
4.4 希尔排序
4.4.1 介绍
希尔排序是希尔(Donald Shell)于1959年提出的一种排序算法。希尔排序也是一种插入排序,它是简单插入排序经过改进之后的一个更高效的版本,也称为缩小增量排序。
4.4.2 算法步骤
- 选择一个增量序列 t1,t2,……,tk,其中 ti > tj, tk = 1;
- 按增量序列个数 k,对序列进行 k 趟排序;
- 每趟排序,根据对应的增量 ti,将待排序列分割成若干长度为 m 的子序列,分别对各子表进行直接插入排序。仅增量因子为 1 时,整个序列作为一个表来处理,表长度即为整个序列的长度。
文章图片
4.4.4 java 代码实现和数据测试
import java.util.Arrays;
/**
* @author 谢阳
* @version 1.8.0_131
*/
public class ShellSort
public static void main(String[] args)
//时间复杂度为O(log2n)int[] arr = new int[80000];
for (int i = 0;
i <
80000;
i++)
arr[i] = (int) (Math.random() * 1000000);
long time = System.currentTimeMillis();
sort2(arr);
//time = 23 time = 20 time = 21
System.out.println("time = " + (System.currentTimeMillis() - time));
//int[] arr = 8, 9, 1, 7, 2, 3, 5, 4, 6, 0;
//sort2(arr);
//希尔排序(交换法效率低) time = 8053略快于冒泡排序
public static void sort1(int[] arr)
int len = arr.length;
int temp;
for (int gap = len / 2;
gap >
0;
gap /= 2)
for (int i = gap;
i <
len;
i++)
for (int j = i - gap;
j >
= 0;
j -= gap)
if (arr[j] >
arr[j + gap])
temp = arr[j];
arr[j] = arr[j + gap];
arr[j + gap] = temp;
System.out.println(Arrays.toString(arr));
//希尔排序(插入法) time = 21
public static void sort2(int[] arr)
//插入值
int insert;
//标识符,插入值的上一个分组内的值坐标
int index;
int len = arr.length;
for (int gap = len / 2;
gap >
0;
gap /= 2) //分组,每次除2
for (int i = gap;
i <
len;
i++)//对分组排序
insert = arr[i];
index = i - gap;
//分组内部插入排序 上个坐标大于等于0 且上个坐标值大于插入值
if (arr[index] >
insert) //优化数据量越大效果越明显
while (index >
= 0 &
&
arr[index] >
insert)
//满足交换 坐标后移
arr[index + gap] = arr[index];
index -= gap;
//交换完成后给对应坐标赋值
arr[index + gap] = insert;
//System.out.println(Arrays.toString(arr));
4.5 快速排序
4.5.1 介绍
快速排序是由东尼·霍尔所发展的一种排序算法。在平均状况下,排序 n 个项目要 Ο(nlogn) 次比较。在最坏状况下则需要 Ο(n2) 次比较,但这种状况并不常见。事实上,快速排序通常明显比其他 Ο(nlogn) 算法更快,因为它的内部循环(inner loop)可以在大部分的架构上很有效率地被实现出来。
4.5.2 算法步骤
- 从数列中挑出一个元素,称为 " 基准" (pivot);
- 重新排序数列,所有元素比基准值小的摆放在基准前面,所有元素比基准值大的摆在基准的后面(相同的数可以到任一边)。在这个分区退出之后,该基准就处于数列的中间位置。这个称为分区(partition)操作;
- 递归地(recursive)把小于基准值元素的子数列和大于基准值元素的子数列排序;
文章图片
4.5.4 java 代码实现和数据测试
import java.util.Arrays;
/**
* @author 谢阳
* @version 1.8.0_131
*/
public class QuickSort
public static void main(String[] args)
int[] arr = new int[80000];
for (int i = 0;
i <
80000;
i++)
arr[i] = (int) (Math.random() * 1000000);
long time = System.currentTimeMillis();
sort1(arr, 0, arr.length - 1);
//time = 20 time = 10 time = 30
//sort2(arr, 0, arr.length - 1);
//time = 30 time = 20time = 32
System.out.println("time = " + (System.currentTimeMillis() - time));
//int[] arr = 9, 8, 7, 6, 5, 4, 3, 2, 1;
//System.out.println(arr.length - 1);
//sort1(arr, 0, arr.length - 1);
//System.out.println(Arrays.toString(arr));
//取左值
private static void sort1(int[] arr, int left, int right)
if (left <
right)
//设置基准值的位置
int pivot = left;
//比较开始值
int index = pivot + 1;
//循环将比pivot小的值放在index-1(后面新的pivot)的左边,大的值放右边
for (int i = index;
i <
= right;
i++)
if (arr[i] <
arr[pivot])
swap(arr, i, index);
index++;
//交换index的值
swap(arr, pivot, index - 1);
//定义新的基础下标
pivot = index - 1;
//左遍历,基准下标左移
sort1(arr, left, pivot - 1);
//右遍历,基准下标右移
sort1(arr, pivot + 1, right);
//取中间值
public static void sort2(int[] arr, int left, int right)
int r = right;
//右标志位
int l = left;
//坐标值位
int pivot = arr[(r + l) / 2];
//中间值(基准值)//循环条件
while (l <
r)
//左值小于中间值,则向右移动一位,直到找到大于中间值或中间值为止,得到该值坐标l
while (arr[l] <
pivot)
l++;
//中间值小于右值,则向左移动一位,直到找到小于中间值或中间值为止,得到该值坐标r
while (pivot <
arr[r])
r--;
//如果r>
=l则退出循环
if (l >
= r)
break;
//交换
swap(arr, l, r);
//如果(交换后)左值等于了中间值,说明中间值左边全部小于中间值,则无需在移动,移动右值
if (arr[l] == pivot)
r--;
//同理,移动左值,避免特殊情况(多个等于中间值)
if (arr[r] == pivot)
l++;
//退出循环时则完成了 左边值 <
= 基准值 <
= 右边值//如果退出循环两个值相等则分别向另一个位置移动一格,以便递归
if (l == r)
l++;
r--;
//右递归
if (l <
right)
sort2(arr, l, right);
//左递归
if (left <
r)
sort2(arr, left, r);
//交换
public static void swap(int[] arr, int x, int y)
int temp = arr[x];
arr[x] = arr[y];
arr[y] = temp;
4.6 归并排序
4.6.1 介绍
归并排序(MERGE-SORT)是利用归并的思想实现的排序方法,该算法采用经典的分治(divide-and-conquer)策略(分治法将问题分(divide)成一些小的问题然后递归求解,而治(conquer)的阶段则将分的阶段得到的各答案" 修补" 在一起,即分而治之)。
文章图片
4.6.2 算法步骤
- 申请空间,使其大小为两个已经排序序列之和,该空间用来存放合并后的序列;
- 设定两个指针,最初位置分别为两个已经排序序列的起始位置;
- 比较两个指针所指向的元素,选择相对小的元素放入到合并空间,并移动指针到下一位置;
- 重复步骤 3 直到某一指针达到序列尾;
- 将另一序列剩下的所有元素直接复制到合并序列尾。
文章图片
4.6.4 java 代码实现和数据测试
/**
* @author 谢阳
* @version 1.8.0_131
*/
public class MergeSort
public static void main(String[] args)
int[] arr = new int[80000];
for (int i = 0;
i <
80000;
i++)
arr[i] = (int) (Math.random() * 1000000);
long time = System.currentTimeMillis();
mergeSort(arr, 0, arr.length - 1);
//time = 26 time = 29 time = 28
System.out.println("time = " + (System.currentTimeMillis() - time));
//int[] arr = 8, 4, 5, 7, 1, 3, 6;
//mergeSort(arr, 0, arr.length - 1);
//System.out.println(Arrays.toString(arr));
//归并排序
public static void mergeSort(int arr[], int left, int right)
//判断当前数组的长度是否满足要求(大于1)
if (left <
right)
//中间值
int mid = (left + right) / 2;
//分治,将一个大数组分成两个小数组
mergeSort(arr, left, mid);
mergeSort(arr, mid + 1, right);
//合并,递归的最底层为两个数据,向上回溯
merge(arr, left, mid, right);
//合并(两个数组)数据并排序
public static void merge(int[] arr, int left, int mid, int right)
//用i、j表示两个数组开始的起始坐标
int i = left;
//[left,mid]
int j = mid + 1;
//[mid+1,right]//创建存放数据的临时数组空间,大小为(right - left) + 1
int[] temp = new int[(right - left) + 1];
//temp的起始位置
int t = 0;
//两个有序数组比较数据大小,按顺序将数据放入临时数组中,如果其中一个数组放置完成则退出循环
while (i <
= mid &
&
j <
= right)
temp[t++] = arr[i] <
arr[j] ? arr[i++] : arr[j++];
//如果数组未遍历完则将剩余数据添加到 temp 后面
while (i <
= mid)
temp[t++] = arr[i++];
//同上
while (j <
= right)
temp[t++] = arr[j++];
//System.out.println("left = " + left + "\\tright = " + right);
t = 0;
//恢复temp起始位置//将合并的有序数据覆盖掉原有数据
while (left <
= right)
arr[left++] = temp[t++];
4.7 基数排序
4.7.1 介绍
基数排序(radix sort)属于“分配式排序”(distribution sort),又称“桶子法”(bucket sort)或bin sort,顾名思义,它是通过键值的各个位的值,将要排序的元素分配至某些“桶”中,达到排序的作用
4.7.2 算法步骤
- 事先准备10个数组(10个桶), 0-9 分别对应 位数的 0-9
- 第一轮按照个位大小 放入到 对应的 各个数组中
- 然后从 0-9 个数组/桶,依次,按照加入元素的先后顺序取出
- 第二轮按照十位排序,将各个数,按照十位大小 放入到 对应的 各个数组中
- 然后从 0-9 个数组/桶,依次,按照加入元素的先后顺序取出
- 重复上述炒作直至最大数位数为止
文章图片
4.7.4 java 代码实现和数据测试
import java.util.Arrays;
/**
* @author 谢阳
* @version 1.8.0_131
*/
public class RadixSort
public static void main(String[] args)
int[] arr = new int[80000];
for (int i = 0;
i <
80000;
i++)
arr[i] = (int) (Math.random() * 1000000);
long time = System.currentTimeMillis();
radixSort(arr);
//time = 31 time = 21 time = 26 数据量越大效果越明显
System.out.println("time = " + (System.currentTimeMillis() - time));
//int[] arr = 53, 3, 542, 748, 14, 214, 9999, 12, 10000, 0;
//radixSort(arr);
//System.out.println(Arrays.toString(arr));
//基数排序
public static void radixSort(int[] arr)
//获取数组长度
int len = arr.length;
//获取数组中最大值
int maxValue = https://www.songbingjia.com/android/getMaxValue(arr, len);
//获取该数的长度
int valueLength = getValueLength(maxValue);
//创建放数的桶,10 表示 0~9 号桶
int[][] bucket = new int[10][len];
//创建统计每个桶中数据对应的计数器
int[] preBucketCount = new int[10];
//创建记录数据所在桶的坐标记录
int bucketIndex;
//循环valueLength次,dev是决定每次排序按相应规则(个、十、百等)排序
for (int i = 0, dev = 1;
i <
valueLength;
i++, dev *= 10) //遍历放入对应桶中
for (int j = 0;
j <
len;
j++)
bucketIndex = arr[j] / dev % 10;
//个位、十位、百位...
bucket[bucketIndex][preBucketCount[bucketIndex]] = arr[j];
//计数器大小等于值的个数
preBucketCount[bucketIndex]++;
int index = 0;
//遍历桶中数据并覆盖掉原有数据
for (int j = 0;
j <
bucket.length;
j++)
for (int k = 0;
k <
preBucketCount[j];
k++)
arr[index++] = bucket[j][k];
//将计数清零
preBucketCount[j] = 0;
//得到数组中最大值
public static int getMaxValue(int[] arr, int len)
if (len == 0)
return 0;
int max = arr[0];
for (int i = 1;
i <
arr.length;
i++)
if (max <
arr[i])
max = arr[i];
return max;
//计算数值的长度
public static int getValueLength(int value)
int count = 0;
while (value != 0)
count++;
value /= 10;
return count;
4.8 堆排序
4.8.1 介绍
堆排序是利用堆这种数据结构而设计的一种排序算法,堆排序是一种选择排序,它的最坏,最好,平均时间复杂度均为O(nlogn),它也是不稳定排序。堆是具有以下性质的完全二叉树:每个结点的值都大于或等于其左右孩子结点的值,称为大顶堆, 注意 : 没有要求结点的左孩子的值和右孩子的值的大小关系。每个结点的值都小于或等于其左右孩子结点的值,称为小顶堆
4.8.2 算法步骤
- 创建一个堆 H[0……n-1];
- 把堆首(最大值)和堆尾互换;
- 把堆的尺寸缩小 1,并调用 shift_down(0),目的是把新的数组顶端数据调整到相应位置;
- 重复步骤 2,直到堆的尺寸为 1。
文章图片
4.8.4 java 代码实现和数据测试
/**
* @author 谢阳
* @version 1.8.0_131
*/
public class HeapSort
public static void main(String[] args)
int[] arr = new int[80000];
for (int i = 0;
i <
80000;
i++)
arr[i] = (int) (Math.random() * 1000000);
long time = System.currentTimeMillis();
heapSort(arr);
//time = 10time = 10 time = 17
System.out.println("time = " + (System.currentTimeMillis() - time));
//int[] arr = 1, 2, -1, 3, 999, -2, 4, 5, 6, 7, 8, 16, 9;
//heapSort(arr);
//System.out.println(Arrays.toString(arr));
public static void heapSort(int[] arr)
int len = arr.length;
//调整为大顶堆
for (int i = len / 2 - 1;
i >
= 0;
i--)
adjustHeap(arr, i, len);
int temp;
while (len >
1)
//交换
temp = arr[0];
arr[0] = arr[len - 1];
arr[len - 1] = temp;
len--;
//调整节点
adjustHeap(arr, 0, len);
//调整大节点在上面
public static void adjustHeap(int[] arr, int i, int length)
//定义变量存储节点值
int temp = arr[i];
//遍历是该节点和下面所有节点成为以该节点为中心的大顶堆
for (int k = i * 2 + 1;
k <
length;
k = k * 2 + 1)
//使arr[k]为该节点temp下面的最大值
if (k + 1 <
length &
&
arr[k] <
arr[k + 1])
k++;
//判断arr[k]是否大于temp,大于则交换位置
if (arr[k] >
temp)
arr[i] = arr[k];
i = k;
else //其他情况则排序好了
break;
arr[i] = temp;
参考资料菜鸟教程
【八大排序算图解汇总】尚硅谷Java数据结构与java算法(Java数据结构与算法)
推荐阅读
- 推荐学java——Spring事务
- 玩转自动化运维全流程
- 别再用YYYY-MM-dd了,不然就卷铺盖走人
- 我们公司是如何做到高效并行测试的()
- Nginx之location模块说明
- 浅析 Web3.0 DApp(去中心化应用程序)设计架构
- GitHub注册-创建数据库-本地项目推送GitHub远程数据库-(入门级教程)
- Kubernetes部署
- 被悟空熊炸出的老博主们