文章目录
一、排序算法是什么?
1.解释
这里引入百度百科的解释:
- 谓排序,就是使一串记录,按照其中的某个或某些关键字的大小,递增或递减的排列起来的操作。
- 排序算法,就是如何使得记录按照要求排列的方法。排序算法在很多领域得到相当地重视,尤其是在大量数据的处理方面。一个优秀的算法可以节省大量的资源。
- 在各个领域中考虑到数据的各种限制和规范,要得到一个符合实际的优秀算法,得经过大量的推理和分析。
2.内部排序
将需要处理的数据加载到内部存储器(内存)中进行排序
- 插入排序:(直接插入排序,希尔排序)
- 选择排序:(简单选择排序,堆排序)
- 交换排序:(冒泡排序,快速排序)
- 归并排序:(二路归并排序,多路归并排序
涉及比较少) - 基数排序
- 计数排序
- 桶排序
其中1-5 比较类排序
5-7 非比较类排序
3.外部排序
内外存结合
二、排序算法介绍及实现
1.冒泡排序
算法描述
- 比较相邻的元素,如果第一个比第二个大,就交换它们两个
- 对每一对相邻的元素作同样的工作,从开始第一对到结尾的最后一对,最终,末尾的元素肯定是数列中最大的数
- 循环重复以上操作
- 总共进行arrr.length-1次
代码实现:
package com.lingo.sort;
import java.util.Arrays;
public class BubbleSortT {
public static void sort(int[] arr) {
for (int i = 1; i < arr.length; i++) {
for (int j = 0; j < arr.length - i; j++) {
if (arr[j] > arr[j + 1]) {
int temp = arr[j];
arr[j] = arr[j + 1];
arr[j + 1] = temp;
}
}
}
}
public static void main(String[] args) {
int[] arr = {1, 5, 9, 11, 55, 66, 5, 3, 2, 6, 22, 44, 15};
sort(arr);
System.out.println(Arrays.toString(arr));
}
}
输出结果:
[1, 2, 3, 5, 5, 6, 9, 11, 15, 22, 44, 55, 66]
2.选择排序
算法描述
- 首先在未排序序列中找到最小(大)元素,然后再放到排序序列的起始位置(已经循环一遍之后再更换)
- 再从剩余未排序元素中继续寻找最小(大)元素,然后放到已排序序列末尾
- 循环重复以上操作
- 总共进行arrr.length-1次
代码实现:
import java.util.Arrays;
public class SelectionSortT {
public static void sort(int[] arr) {
for (int i = 0; i < arr.length - 1; i++) {
int min = i;
for (int j = i + 1; j < arr.length; j++) {
if (arr[min] > arr[j]) {
min = j;
}
}
if (min != i) {
int temp = arr[i];
arr[i] = arr[min];
arr[min] = temp;
}
}
}
public static void main(String[] args) {
int[] arr = {1, 5, 9, 11, 55, 66, 5, 3, 2, 6, 22, 44, 15};
sort(arr);
System.out.println(Arrays.toString(arr));
}
}
输出结果:
[1, 2, 3, 5, 5, 6, 9, 11, 15, 22, 44, 55, 66]
3.插入排序
算法描述
- 从第一个元素开始,该元素可以被视为已经排序
- 取出下一个元素,在已经排好序的元素序列中从后往前扫描
- 若被扫描的元素大于待排序元素,则继续向前扫描
- 直到被扫描的元素小于或等于待排序元素
- 将该元素插入到被扫描元素的后面
- 循环重复以上操作
代码实现:
import java.util.Arrays;
public class InsertionSortT {
public static void sort(int[] arr) {
for (int i = 1; i < arr.length; i++) {
int temp = arr[i];
int j = i;
while (j > 0 && temp < arr[j - 1]) { //从该处至函数末尾,temp可以换成arr[i]吗?
arr[j] = arr[j - 1];
j--;
}
if (i != j) {
arr[j] = temp;
}
}
}
public static void main(String[] args) {
int[] arr = {9, 5, 1, 11, 55, 66, 5, 3, 2, 6, 22, 44, 15};
sort(arr);
System.out.println(Arrays.toString(arr));
}
}
这里给大家一个思考:
上面的temp可以改成arr[i]吗?
输出结果:
[1, 2, 3, 5, 5, 6, 9, 11, 15, 22, 44, 55, 66]
4.希尔排序
算法描述
- 插入排序 plus
- 希尔提出的排序方法 缩小增量排序
- 缩小增量以gap = gap/2的方式,这种增量选择我们可以用一个序列来表示,{n/2,(n/2)/2…1},称为增量序列
- 先将整个的待排序的序列分割为若干子序列
- 然后对他们分别进行插入排序,
- 待整个序列中的记录 “基本有序”,再对全体记录进行一次直接插
入排序 - 将该元素插入到被扫描元素的后面
- 循环重复以上操作
动图展示
引入一张动图,大家就可清楚了解希尔排序了
代码实现:
import java.util.Arrays;
public class ShellSortT {
public static void sort(int[] arr) {
for (int group = arr.length / 2; group > 0; group = group / 2) {
//若10个数据则被分成5个组
//0与5 1与6
for (int i = group; i < arr.length; i++) {
int temp = arr[i];
int j = i;
while (j - group >= 0 && temp < arr[j - group]) {
arr[j] = arr[j - group];
j = j - group;
}
if (j != i) {
arr[j] = temp;
}
}
}
}
public static void main(String[] args) {
int[] arr = {1, 5, 9, 11, 55, 66, 5, 3, 2, 6, 22, 44};
sort(arr);
System.out.println(Arrays.toString(arr));
}
}
输出结果:
[1, 2, 3, 5, 5, 6, 9, 11, 22, 44, 55, 66]
5.归并排序
算法描述
- 从这里开始,难度系数相对来说就有点高了,需要多琢磨,多debug
- 归并排序(MERGE-SORT)是利用归并的思想实现的排序方法,该算法采用经典的**分治(divide-and-conquer)策略(分治法将问题分(divide)成一些小的问题然后递归求解,而治(conquer)**的阶段则将分的阶段得到的各答案"修补"在一起,即分而治之)。
- 难点:分治和递归
- 申请空间,此空间的大小是两个已经排序的序列之和 该空间是用来存放合并后的序列
- 设定两个“指针”,最初位置是两个已排序序列的起始位置
- 比较两个指针所指向的元素,选择较小的元素,放到合并空间里,并且移动该指针到下一位置
- 重复步骤3,直到某一个指针到达序列尾
- 将另一序列的所有元素直接复制到合并空间尾
动图展示
引入一张动图,大家就可清楚了解希尔排序了
如果还不理解,那就再给你加点料
下面是最全的归并排序的示例图了
主要的思想还是分治思想
代码实现:
import java.util.Arrays;
public class MergeSortT {
public static void sort(int[] arr) {
int[] temp = new int[arr.length];
divide(arr, 0, arr.length - 1, temp);
}
/**
* 分
*
* @param arr
* @param left
* @param right
* @param temp
*/
private static void divide(int arr[], int left, int right, int[] temp) {
if (left < right) {
int mid = left + (right - left) / 2;
//左分
divide(arr, left, mid, temp);
//右分
divide(arr, mid + 1, right, temp);
//合
merge(arr, left, mid, right, temp);
}
}
;
/**
* 治
*
* @param arr
* @param left
* @param mid
* @param right
* @param temp 临时数组
*/
private static void merge(int[] arr, int left, int mid, int right, int[] temp) {
int i = left;
int j = mid + 1;
//临时存放数组下标索引
int t = 0;
//第一种情况:
while (i <= mid && j <= right) {
if (arr[i] <= arr[j]) {
temp[t] = arr[i];
i++;
t++;
} else {
temp[t] = arr[j];
j++;
t++;
}
}
//代码执行到这里说明 i<=mid && j<=right 至少有一条不符合条件了
while (j <= right) {
temp[t] = arr[j];
j++;
t++;
}
while (i <= mid) {
temp[t] = arr[i];
i++;
t++;
}
//拷贝
t = 0;
int tempLeft = left;
while (tempLeft <= right) {
arr[tempLeft] = temp[t];
t++;
tempLeft++;
}
}
public static void main(String[] args) {
int[] arr = {1, 5, 9, 11, 55, 66, 5, 3, 2, 6, 22, 44, 15};
sort(arr);
System.out.println(Arrays.toString(arr));
}
}
输出结果:
[1, 2, 3, 5, 5, 6, 9, 11, 15, 22, 44, 55, 66]
6.快速排序
算法描述
- 通过一次排序将待排序数列分割成两个部分,其中一部分的值要比另一部分要小。接下来,则可分别对这两部分数列进行排序,使得整个序列有序。
- 对于绝大部分数列来说,快速排序总是优于归并排序。
- 快速排序是使用分治法把一个序列分成两个序列,分治思想显然尤为重要
- 快速排序是被研究的最多的一个排序方法
排序流程:
- 从数列中挑选一个元素,称为“基准” pivot(基准可以取第一个也可以取中间,也可以取尾)
- 重新排序序列,所有元素数值比基准小的摆在基准前面,所有元素比基准值大的摆在基准的后面。
- 重复以上步骤
动图展示
引入一张动图,大家就可清楚了解快速排序了
代码实现(选取第一个为基准):
import java.util.Arrays;
public class QuickSortFirstT {
public static void sort(int[] arr) {
sort(arr, 0, arr.length - 1);
}
private static void sort(int[] arr, int left, int right) {
if (left >= right) {
return;
}
int i = left;
int j = right;
int piovt = arr[left];//基准
while (i < j) {
//右指针向左走
while (i < j && piovt < arr[j]) {
j--;
}
//执行到这里 说明 piovt>=arr[j]
if (i < j) {
arr[i++] = arr[j];
}
//左指针向右走
while (i < j && piovt > arr[i]) {
i++;
}
//同理
if (i < j) {
arr[j--] = arr[i];
}
}
//代码执行到这里说明i==j
//空出的位置 我们存放基准 此时 基准左边全是比基准小的值 右边全是比基准大的值
arr[i] = piovt;
//左边的递归排序
sort(arr, left, i - 1);
//右边的递归排序
sort(arr, i + 1, right);
}
public static void main(String[] args) {
int[] arr = {1, 5, 9, 11, 55, 66, 5, 3, 2, 6, 22, 44};
sort(arr);
System.out.println(Arrays.toString(arr));
}
}
输出结果:
[1, 2, 3, 5, 5, 6, 9, 11, 22, 44, 55, 66]
7.堆排序 (Heap Sort)
算法描述
- 堆排序是利用堆这种数据结构而设计的一种排序算法,堆排序是一种选择排序,它的最坏,最好,平均时间复杂度均为O(nlogn),它也是不稳定排序。
- 堆是一种近似的完全二叉树,性质:子节点的值总是大于或者小于其父
节点的值 - 大根堆(大顶堆):父节点永远大于子节点
- 小根堆(小顶堆):父节点永远小于子节点
- 需要注意的是孩子节点的坐标:arr[i] >= arr[2i+1] && arr[i]>= arr[2i+2]
排序流程(以升序举例):
- 首先将待排序序列构建大根堆
- 将堆顶元素与最后一个元素(最后一个叶子节点)交换,得到的剩余n-1个元素的序列重新进行构建大根堆
- 重复以上步骤,直至排序完成
动图展示
引入一张动图,大家就可清楚了解堆排序了
这里给大家引入以为UP主对堆排序的介绍以及实现的文章
大家可以参考学习下:
https://www.cnblogs.com/chengxiao/p/6129630.html
代码实现:
import java.util.Arrays;
public class HeapSortT {
/**
* 调整为大根堆(大顶堆)
*
* @param arr 待调整数组
* @param start 从哪一个节点开始
* @param length 待调整的长度
*/
private static void adjust(int[] arr, int start, int length) {
//获取左孩子节点 这里的i = 2*i+1 解释下
//这里是被交换的孩子节点变成了父节点,此时这棵树已经被调整了 但是被交换位置可能还会有他的子树
//因此 i = 2*i+1 是继续调整原父节点的子树
int temp = arr[start];
for (int i = 2 * start + 1; i < length; i = 2 * i + 1) {
if (i + 1 < length && arr[i] < arr[i + 1]) {
i++;
}
//较大的子节点的值大于父节点,将子节点的值赋值给父节点
//子节点的值无需改变 ,因为temp已经保存了原父节点的值
//之后再进行循环判断时 仍比较的是原temp ****
if (arr[i] > temp) {
arr[start] = arr[i];
//将交换的子节点的坐标赋值给父节点
start = i;
//如果没有进行交换则跳出循环
} else {
break;
}
}
//循环进行完毕之后
//此时的arr[start]应该被赋值temp
arr[start] = temp;
}
public static void sort(int[] arr) {
//从下至上 从左至右 第一个非叶子节点应该为(arr.length/2-1)
//第一将整棵树调整为大根堆的树
for (int i = arr.length / 2 - 1; i >= 0; i--) {
adjust(arr, i, arr.length);
}
//开始交换
for (int i = arr.length - 1; i > 0; i--) {
int temp = arr[i];
arr[i] = arr[0];
arr[0] = temp;
//这里的范围应该为i 会存在写arr.length-i(这里如果lengh = arr.length-i 说明思路正确,但没有认真思考i的值)
adjust(arr, 0, i);
}
}
public static void main(String[] args) {
int[] arr = {1, 3, 3, 2, 4, 4, 4, 8, 7};
sort(arr);
System.out.println(Arrays.toString(arr));
}
}
输出结果:
[1, 2, 3, 3, 4, 4, 4, 7, 8]
8.桶排序 (Heap Sort)
算法描述
- 桶排序 (Bucket sort)或所谓的箱排序,是一个排序算法,工作的原理是将数组分到有限数量的桶子里。
- 每个桶子再个别排序(有可能再使用别的排序算法或是以递归方式继续使用桶排序进行排序)。
- 桶排序是鸽巢排序的一种归纳结果。当要被排序的数组内的数值是均匀分配的时候,桶排序使用线性时间(Θ(n))。但桶排序并不是 比较排序,他不受到 O(n log n) 下限的影响。
- 时间复杂度:O(N + C)
- 额外空间复杂度:O(N + M)
- N:N 次循环,将每个元素装入对应的桶中
- M:M 次循环,对每个桶中的数据进行排序(平均每个桶有 N/M 个元素)
排序流程(以升序举例):
- 首先获取桶的数量
- 将所有的数据依次存放在桶中
- 对每个桶中的数据进行排序
- 合并
代码实现:
import java.util.*;
public class BucketSort {
public static void sort(int[] arr) {
//获取最大值以及最小值
int max = arr[0];
int min = arr[0];
for (int i = 0; i < arr.length; i++) {
max = Math.max(max, arr[i]);
min = Math.min(min, arr[i]);
}
sort(arr, max, min);
}
private static void sort(int[] arr, int max, int min) {
//得到多少个桶 这里也会出现极端情况 桶只存在一个
int bucketNum = (max - min) / arr.length + 1;
List<Integer>[] bucketArray = new ArrayList[bucketNum];
for (int i = 0; i < arr.length; i++) {
int t = (arr[i] - min) / (arr.length);
if (bucketArray[t] == null) {
bucketArray[t] = new ArrayList<>();
}
bucketArray[t].add(arr[i]);
}
//对桶内元素排序 这里可以采用任意的排序方式
for (int i = 0; i < bucketArray.length; i++) {
if (bucketArray[i] != null && bucketArray[i].size() > 0) {
//这里采用Collections.sort() 归并排序的plus
Collections.sort(bucketArray[i]);
}
}
//合并
int index = 0;
for (int i = 0; i < bucketArray.length; i++) {
//可能存在空桶的可能性
if (bucketArray[i] == null) {
continue;
}
for (int j = 0; j < bucketArray[i].size(); j++) {
arr[index] = bucketArray[i].get(j);
index++;
}
}
}
public static void main(String[] args) {
int[] arr = {1, 5, 9, 11, 55, 66, 5, 3, 2, 6, 22, 44};
sort(arr);
System.out.println(Arrays.toString(arr));
}
}
输出结果:
[1, 2, 3, 5, 5, 6, 9, 11, 22, 44, 55, 66]
9.基数排序(Radix Sort)
算法描述
- 基数排序(radix sort)属于“分配式排序”(distribution sort),又称“桶子法”(bucket sort)或bin sort,顾名思义,它是透过键值的部份资讯,将要排序的元素分配至某些“桶”中,藉以达到排序的作用。
- 基数排序法是属于稳定性的排序,其时间复杂度为O (nlog®m),其中r为所采取的基数,而m为堆数,在某些时候,基数排序法的效率高于其它的稳定性排序法。
- 通俗理解:(以空间换时间) 基数排序是先按照低位进行排序,然后收集回来,再按照高位排序,再收集回来,以此类推,直到最高位。有时也会按照优先级。
排序流程:
- 取得数组中的最大数,取得其位数
- arr做原始数组,从最低位开始取每个位组成radix数组
- 收集后对高位做同样处理
- 重复以上步骤
动图展示
引入一张动图,大家就可清楚了解基数排序了
代码实现:
import java.util.Arrays;
public class RadixSortT {
public static void sort(int[] arr) {
//防止数组中数据均存放在一个桶中,导致溢出
int[][] bucket = new int[10][arr.length];
int[] bucketElementCounts = new int[10];
//取出最大数的位数
int max = arr[0];
for (int i = 0; i < arr.length; i++) {
if (arr[i] > max) {
max = arr[i];
}
}
int maxLength = (max + "").length();
for (int i = 0, n = 1; i < maxLength; i++, n *= 10) {
for (int j = 0; j < arr.length; j++) {
int t = arr[j] / n % 10;
bucket[t][bucketElementCounts[t]] = arr[j];
bucketElementCounts[t] = bucketElementCounts[t] + 1;
}
int index = 0;
for (int j = 0; j < bucketElementCounts.length; j++) {
if (bucketElementCounts[j] != 0) {
for (int k = 0; k < bucketElementCounts[j]; k++) {
arr[index] = bucket[j][k];
index++;
}
}
bucketElementCounts[j] = 0;
}
}
}
public static void main(String[] args) {
int[] arr = {1, 5, 9, 11, 55, 66, 5, 3, 2, 6, 22, 44};
sort(arr);
System.out.println(Arrays.toString(arr));
}
}
输出结果:
[1, 2, 3, 5, 5, 6, 9, 11, 22, 44, 55, 66]
三、归纳总结
首先给大家看一下排序的总结表
通过时间复杂可以清晰看出排序的效率其中涉及的思想:分治思想,以空间换时间思想等
当然,里面所涉及的排序代码可能仍存在各种问题,欢迎大家积极同我探讨交流学习