十大排序算法整理
- 1 概述
- 2 冒泡排序(✓)
- 3 快速排序(✓)
- 4 插入排序(✓)
- 5 希尔排序(✓)
- 6 选择排序(✓)(算法图解解释更好)
- 7 堆排序(✓)
- 8 归并排序(✓)
- 9 计数排序(✓)
- 10 桶排序(✓)
- 11 基数排序(✓)
- [10 大排序算法参考1](https://mp.weixin.qq.com/s/IAZnN00i65Ad3BicZy5kzQ)
- [10 大排序算法参考2](https://gitee.com/zhongfucheng/Java3y#https://mp.weixin.qq.com/s?__biz=MzI4Njg5MDA5NA==&mid=2247484081&idx=1&sn=5ea6ecbcbd41c2cc0b948adf6888f60b&chksm=ebd743b0dca0caa68db0ed08a710506d60e129c8d64508c5ce1e123eafb5eaabdf35fbbed28b&scene=21###wechat_redirect)
1 概述
1.1 分类与时间复杂度


1.2 稳定排序、原地排序、时间复杂度、空间复杂度
-
稳定排序:如果 a 原本在 b 的前面,且 a == b,排序之后 a 仍然在 b 的前面,则为稳定排序。
-
非稳定排序:如果 a 原本在 b 的前面,且 a == b,排序之后 a 可能不在 b 的前面,则为非稳定排序。
-
原地排序:原地排序就是指在排序过程中不申请多余的存储空间,只利用原来存储待排数据的存储空间进行比较和交换的数据排序。
-
非原地排序:需要利用额外的数组来辅助排序。
-
时间复杂度:一个算法执行所消耗的时间。
-
空间复杂度:运行完一个算法所需的内存大小。
-
稳定的好处:如果我们只对一串数字排序,那么稳定与否确实不重要,因为一串数字的属性是单一的,就是数字值的大小。但是排序的元素往往不只有一个属性,例如我们对一群人按年龄排序,但是人除了年龄属性还有身高体重属性,在年龄相同时如果不想破坏原先身高体重的次序,就必须用稳定排序算法.
2 冒泡排序(✓)

-
前后两个数比较,大的交换到后面,最后就能在最后位置得到最大的数
-
找大的放最后
-
重点1:大循环次数 = 数组长度 - 1;小循环次数 = 数组长度 - 1 - i (每次都比第几趟大循环次数少1)
-
重点2:每次排序都把最大的放在最后,所以排序i轮,说明i个数已经排好了,就不需要再比较了,所以 array.length - 1 - i
-
重点3:如果某一次大循环比较没有发生交换,那么此时就表示已经是有序的了,可以直接退出,此时整个数组就是从小到大有序的,比如下面的第二趟排序之后就已经有序了,就不用第三,四趟了

-
如上图,第二趟排序之后,没有发生交换,说明找到20最大值后,已经有序了,之后就不用排序了,直接return退出函数即可
// 冒泡排序
public static void bubbleSort(int[] array) {
if (array == null || array.length < 2) {
return;
}
int temp = 0; // 存放交换值
boolean flag = false; // true表示交换了
// 大循环次数是数组长度 - 1
for (int i = 0; i < array.length - 1; i++) {
// 小循环次数 = 大循环 - i = array.length - 1 - i (每次排序后就会少i个数,因为他们是每次找的最大值,所以不用再排了)
for (int j = 0; j < array.length - 1 - i; j++) {
// 如果前一个 > 后一个 , 则交换位置,否则表示有序了
if (array[j] > array[j + 1]) {
temp = array[j + 1];
array[j + 1] = array[j];
array[j] = temp;
// 发生交换
flag = true;
}
}
// 如果没有交换元素,说明已经有序
if (flag==false) {
return;
} else {
flag = false; // 说明交换了,但是交换了就要继续冒泡排序,所以需要把flag重置,判断下次是否没交换
}
}
}
3 快速排序(✓)

import java.util.Arrays;
public class QuickSortDemo {
public static void main(String[] args){
int[] arr = {6, 1, 2, 7, 9, 3, 4, 5, 10, 8};
quickSort(arr, 0, arr.length-1);
System.out.println(Arrays.toString(arr));
}
public static void quickSort(int[] arr,int left,int right){
if(left>right){
return;
}
// i,j用来暂存left,right
// pivot是基准值,默认数组第一个元素
int i=left;
int j=right;
int pivot = arr[left];
while (i<j) {
// j先开始动,往左边找一个小于pivot的值,所以while的条件是 array[j] >= pivot
// 因为要找小的数,所以如果array[j]>pivot,就继续j--,直到找到小于pivot的数,此时j停下来
while (arr[j]>=pivot && i<j) {
j--;
}
// i从左往右,找一个大于pivot的数,所以while条件是 array[i] <= pivot
// 因为要找大于pivot的数,所以如果小于=pivot就继续找,直到找到大于pivot的数,此时i停下来
while (arr[i]<=pivot && i<j) {
i++;
}
// 此时i,j都已经找到了一个符合条件的数,然后就是交换他们
//如果满足条件则交换,i<j才可以,i=j的时候就表示找到了pivot的位置
if (i<j) {
int temp = arr[i];
arr[i] = arr[j];
arr[j] = temp;
}
// 上面一轮结束仅仅是找到一组符合条件的数,还要继续找,直到i<j不满足为止
// while退出后,彼时 i = j,此时的i,j位置就是pivot应该呆的位置
}
//最后将基准值 与 i和j相等的位置 的 数字交换
arr[left] = arr[i]; // array[left] 此时就是 pivot,此句相当于 pivot = array[i],但是这样修改了pivot,交换2个数要temp,array[left]相当于temp
arr[i] = pivot;
// 此时可以检查 i == j是否成立,来验证猜想
System.out.println("i==j? " + (i==j) );
//递归调用左半数组,j-1,因为j的位置就是pivot的位置,j位置不需要参与排序了
quickSort(arr, left, j-1);
//递归调用右半数组,j+1原因同上
quickSort(arr, j+1, right);
}
}
4 插入排序(✓)

-
思想:把n个待排元素看成一个有序表,一个无序表,开始时有序表只有一个元素(第一个),无序表有n-1元素,每次从无序表中取出第一个元素,与有序表中元素比较,插入正确位置,使其称为新的有序表。
-
https://www.cnblogs.com/xiaoming0601/p/5862793.html
package Sort;
import java.util.Arrays;
public class InsertSortDemo {
public static void main(String[] args) {
int[] array = {11, 22, 9, 2};
insertSort(array);
}
public static void insertSort(int[] arr)
{
int i, j; // j是需要插入的位置
int n = arr.length; // 数组长度
int target; // 未排序序列第一个数,它是需要插入的数
//假定第一个元素(0索引)被放到了正确的位置上
//这样,仅需遍历1 - n-1
for (i = 1; i < n; i++)
{
j = i;
target = arr[i]; // 把需要插入的数保存一下
while (j > 0 && target < arr[j - 1])
// 索引大于0,目标值比他前一个值小,表示没找到他该插入的位置,所以继续比较他前2个(j--)
// 并且需要把他前一个数后移一位,直接覆盖target(target已经被保存了。)
{
arr[j] = arr[j - 1];
j--;
}
arr[j] = target; // target插入找到的位置
System.out.println(Arrays.toString(arr));
}
}
}
5 希尔排序(✓)
-
它是插入排序的高级版,回顾一下插入排序:
-
将数据插入到已有序的数列中
-
排序前:将每个元素看成有序的数列
-
第一趟排序后:得到一个有序数列,其长度为2
-
第二趟排序后:得到一个有序数列,其长度为3
-
第三趟排序后:得到一个有序数列,其长度为4
-
………每一趟插入排序,都可以将一个无序值插入一个有序数列,直至全部值有序
-
-
如果给出的数组足够乱的话,那么插入排序所耗费的时间是O(n^2)
-
既然希尔排序是插入排序的高级版,那它做了哪些优化呢??让我们来看看:
-
希尔排序在排序前:将一个序列分成了好几个序列
-
在第一趟排序时:将这几个序列做插入排序。排序后,部分较大的数字往后靠,部分较小的数字往前靠
-
在第二趟排序时:将这个序列又分了好几个序列做插入排序(但比第一次分的数要少,ps:如果第一次分5个,第二次可能就2个了)。排序后,部分较大的数字往后靠,部分较小的数字往前靠
-
…………….
-
在第n趟排序时:将这个序列又分了好几个序列(直到剩下一个序列),从宏观上看,此序列就基本是有序的了。这时就用简单插入排序将数列直至已序
-
从直观上看希尔排序:
- 就是把数列进行分组(不停使用插入排序),直至从宏观上看起来有序,最后插入排序起来就容易了(无须多次移位或交换)。
-
那么,上面那里说了将一个序列分成好几个序列,那么到底怎么分呢?比如有10个元素的序列,分成几个才合适?每次缩减又是多少呢?从专业的角度上讲,将一个序列分成好几个序列,用一个数来表示:那个数称为增量。显然的是,增量是不断递减的(直到增量为1)。往往的:如果一个数列有10个元素,我们第一趟的增量是5,第二趟的增量是2,第三趟的增量是1。如果一个数列有18个严肃,我们第一趟的增量是9,第二趟的增量是4,第三趟的增量是2,第四趟的增量是1。很明显我们可以用一个序列来表示增量:{n/2,(n/2)/2…1},每次增量都/2
-

public static void shellSort(int[] arrays) {
//增量每次都/2
for (int step = arrays.length / 2; step > 0; step /= 2) {
//从增量那组开始进行插入排序,直至完毕
// i<length:因为step是length一半,i到length长度就是总的次数
for (int i = step; i < arrays.length; i++) {
int j = i; // 因为后面要减step,所以不能直接操作i,用j。
int temp = arrays[j];
// j - step 就是代表与它同组隔壁的元素
while (j - step >= 0 && arrays[j - step] > temp) {
arrays[j] = arrays[j - step];
j = j - step;
}
arrays[j] = temp; // 此时j已经减过step了
}
}
}
6 选择排序(✓)(算法图解解释更好)
- 核心:在算法图解中,选择排序就是每次选最小值放入一个新数组,我们使用的代码没有用新数组,而是直接在原数组操作,但其实原理一样,就是每次选最小(大)的,添加新数组。
- 一个无序序列,每次找到最小的和第一个位置交换(从小到大排序)
- 与冒泡相比,每次只交换一次
- 一开始假设第一个最小,后面都无序,后面找到最小和假设最小交换位置,然后最小的那数就有序,从下个位置开始假设最小,后面无序与其比较找最小加入一开始找到最小的那个有序序列。

import java.util.Arrays;
public class SelectSortDemo {
public static void main(String[] args) {
int[] array = {11, 22, 9, 2};
selectSort(array);
}
public static void selectSort(int[] array){
// 选出未排序中最小的数与第一个数交换
if (array.length == 0){
return;
}
for (int i = 0; i < array.length - 1; i++) { // 假设最小值是i,最后只有一个数的时候它肯定最小,所以只需要 [0 , length-1)。
int minIndex = i;
// 找到每轮真的最小值,与第一个猜想最小值交换位置
for (int j = i+1 ; j < array.length; j++) { // 自己不用和自己比较,所以j=i+1,要比较到最后一个数,所以 j<length
if (array[j] < array[minIndex]) //找到最小的数
minIndex = j; //将最小数的索引保存
}
// 上面一轮结束,就找到了未排序序列中的最小值,把它和假设最小值交换位置。
int temp = array[i]; // 保存假设的最小值
array[i] = array[minIndex]; // 真的最小值给i
array[minIndex] = temp; // i给真的最小值
System.out.println(Arrays.toString(array));
}
}
}
7 堆排序(✓)
什么是堆:
https://baike.baidu.com/item/%E5%A0%86/20606834?fr=aladdin
https://www.jianshu.com/p/6b526aa481b1
https://blog.csdn.net/qq_34270874/article/details/113091364
堆排序
- https://mp.weixin.qq.com/s/TKRtF2dAtH7VuNs-FC4awA
- https://mp.weixin.qq.com/s/B0ImTjuQJiR7ahRzBpslcg
8 归并排序(✓)
- 归并的含义是将两个或两个以上的有序表合并成一个新的有序表。两个就是二路归并,代码以二路归并为例。
- 思路1:新建1个表tmp存放最终的数据,拆分后直接比较大小,小的放入tmp,最后把tmp拷贝给原数组。
- 思路2:新建2个表存放拆分后的数据,比较后直接覆盖原数组。
- 参考

import java.util.Arrays;
public class MergeSortDemo {
public static void main(String[] args) {
int[] arr = {11,44,23,67,88,65,34,48,9,12};
// 先新建一个临时数组存放合并的数据,最后可以就使用tmp,也可以把tmp的元素在复制回原arr,且避免频繁开辟新数组空间
int[] tmp = new int[arr.length];
mergeSort(arr,0,arr.length-1, tmp);
System.out.println(Arrays.toString(tmp));
System.out.println(Arrays.toString(arr));
}
public static void mergeSort(int[] arr, int left, int right, int[] tmp){
if(left<right){
int mid = (left+right)/2; // 二路归并所以 /2
mergeSort(arr,left,mid, tmp); // 继续拆分左边
mergeSort(arr,mid+1, right, tmp); // 继续拆分右边
// 合并拆分后的2个序列,因为上面有递归,所以上面递归结束后,merge从合并成2个 -> 合并成4个 ...
merge(arr, left, mid, right, tmp);
}
}
public static void merge(int[] arr, int left, int mid, int right, int[] tmp){
// tmp数组的索引
int i = 0;
//左边序列x 和 右边序列起始索引y
int x = left;
int y = mid+1;
while(x <= mid && y <= right){
// 比较2个序列元素那个小,就放入新tmp数组,然后索引+1,继续比较
if(arr[x] < arr[y]){
tmp[i] = arr[x];
x++;
i++;
}else{
tmp[i] = arr[y];
y++;
i++;
}
}
// 若左边序列还有剩余,它们肯定是比较大的数,直接放tmp末尾。
while(x <= mid){
tmp[i++] = arr[x++];
}
// 同上面理,如果右边序列有剩余,也是大的,直接放tmp末尾
while(y <= right){
tmp[i++] = arr[y++];
}
// 把tmp的数据复制回原数组arr,i是tmo数组大小
for(int t=0; t<i; t++){
arr[left+t] = tmp[t];
}
}
}
9 计数排序(✓)
-
核心思想:一个待排序列,找最小值和最大值,他们之前的区间就是需要的数组长度,适用于取值范围不大的情况。
10 桶排序(✓)
- 桶排序就是把最大值和最小值之间的数进行瓜分,例如分成 10 个区间,10个区间对应10个桶,我们把各元素放到对应区间的桶中去,再对每个桶中的数进行排序,可以采用归并排序,也可以采用快速排序之类的。之后每个桶里面的数据就是有序的了,我们在进行合并汇总。
- 参考

被折叠的 条评论
为什么被折叠?



