排序算法
基本介绍
排序算法(Sort Algorithm),是将一组数据,依指定的顺序进行排列的过程。
排序算法的分类
内部排序:指将需要处理的所有数据都加载到内部存储器(内存)中进行排序。
外部排序:数据量过大,无法全部加载到内存中,需要借助外部存储(文件等)进行排序。
图解:
冒泡排序
基本介绍:冒泡排序(BubbleSorting)的基本思想是通过对待排序序列从前向后(从下标较小的元素开始),依次比较相邻元素的值,若发现逆序则交换,使值较大的元素逐渐从前移向后部,就象水底下的气泡一样逐渐向上冒。
优化:因为排序的过程中,各元素不断接近自己的位置,如果一趟比较下来没有进行过交换,就说明序列有序,因此要在排序过程中设置一个标志flag判断元素是否进行过交换。从而减少不必要的比较。(这里说的优化,可以在冒泡排序写好后,再进行)
图解:
代码实现:
package com.athome.sort;
import java.util.Arrays;
import java.util.Random;
/**
* Description:
* Author:江洋大盗
* Date:2021/1/22 8:39
*/
public class BubbleSort {
public static void main(String[] args) {
//测试冒泡排序
int[] arr = new int[10];
Random random = new Random();
for (int i = 0; i < arr.length; i++) {
arr[i] = random.nextInt(50);
}
System.out.println("排序前:" + Arrays.toString(arr));
bubbleSort(arr);
System.out.println("排序后:" + Arrays.toString(arr));
}
/**
* 冒泡排序,从小到大排序
*
* @param arr 待排序的数组
*/
public static void bubbleSort(int[] arr) {
int temp;//临时变量,交换时使用
boolean flag = false;//使用标识优化算法
for (int i = 0; i < arr.length - 1; i++) {
for (int j = 0; j < arr.length - 1 - i; j++) {
if (arr[j] > arr[j + 1]) {
temp = arr[j + 1];
arr[j + 1] = arr[j];
arr[j] = temp;
flag = true;//如果发生了交换,将flag设置为true
}
}
if (flag) {
flag = false;
} else {
//说明有没发生交换,数组已经有序。
break;
}
}
}
}
选择排序
基本介绍:选择排序也属于内部排序法,是从欲排序的数据中,按指定的规则选出某一元素,再依规定交换位置后达到排序的目的。
选择排序思想:选择排序(select sorting)也是一种简单的排序方法。它的基本思想是:第一次从arr[0]到arr[n-1]中选取最小值,与arr[0]交换,第二次从arr[1]到arr[n-1]中选取最小值,与arr[1]交换,第三次从arr[2]到arr[n-1]中选取最小值,与arr[2]交换,......,第i次从arr[i-1]到arr[n-1]中选取最小值,与arr[i-1]交换,......,第n-1次从arr[n-2]到arr[n-1]中选取最小值,与arr[n-2]交换,总共通过n-1次,得到一个按排序码从小到大排列的有序序列。
图解:
代码实现:
package com.athome.sort;
import java.util.Arrays;
import java.util.Random;
/**
* Description:
* Author:江洋大盗
* Date:2021/1/22 8:46
*/
public class SelectSort {
public static void main(String[] args) {
//测试选择排序
int[] arr = new int[10];
Random random = new Random();
for (int i = 0; i < arr.length; i++) {
arr[i] = random.nextInt(50);
}
System.out.println("排序前:" + Arrays.toString(arr));
selectSort(arr);
System.out.println("排序后:" + Arrays.toString(arr));
}
/**
* 选择排序,从小到大排序
*
* @param arr 待排序数组
*/
public static void selectSort(int[] arr) {
int temp;//交换时使用
int index;//记录下标
for (int i = 0; i < arr.length - 1; i++) {
temp = arr[i];
index = i;
for (int j = i; j < arr.length; j++) {
if (arr[j] < temp) {
temp = arr[j];
index = j;
}
}
if (index != i) {
arr[index] = arr[i];
arr[i] = temp;
}
}
}
}
插入排序
基本介绍:插入式排序属于内部排序法,是对于欲排序的元素以插入的方式找寻该元素的适当位置,以达到排序的目的。
插入排序思想:插入排序(Insertion Sorting)的基本思想是:把n个待排序的元素看成为一个有序表和一个无序表,开始时有序表中只包含一个元素,无序表中包含有n-1个元素,排序过程中每次从无序表中取出第一个元素,把它的排序码依次与有序表元素的排序码进行比较,将它插入到有序表中的适当位置,使之成为新的有序表。
图解:
代码实现:
package com.athome.sort;
import java.util.Arrays;
import java.util.Random;
/**
* Description:
* Author:江洋大盗
* Date:2021/1/22 10:15
*/
public class InsertSort {
public static void main(String[] args) {
//测试插入排序
int[] arr = new int[10];
Random random = new Random();
for (int i = 0; i < arr.length; i++) {
arr[i] = random.nextInt(50);
}
System.out.println("排序前:" + Arrays.toString(arr));
insertSort(arr);
System.out.println("排序后:" + Arrays.toString(arr));
}
/**
* 插入排序,从小到大排序
*
* @param arr 待排序的数组
*/
public static void insertSort(int[] arr) {
int insertValue;//待排序的值
int insertIndex;//待排序的位置
for (int i = 1; i < arr.length; i++) {
insertValue = arr[i];
insertIndex = i - 1;
while (insertIndex >= 0 && insertValue < arr[insertIndex]) {
arr[insertIndex + 1] = arr[insertIndex];
insertIndex--;
}
if (insertIndex != i - 1) {
arr[insertIndex + 1] = insertValue;
}
}
}
}
希尔排序
简单插入排序存在的问题:当需要插入的数是较小的数时,后移的次数明显增多,对效率有影响。
希尔排序介绍:希尔排序是希尔(DonaldShell)于1959年提出的一种排序算法。希尔排序也是一种插入排序,它是简单插入排序经过改进之后的一个更高效的版本,也称为缩小增量排序。
希尔排序基本思想:希尔排序是把记录按下标的一定增量分组,对每组使用直接插入排序算法排序;随着增量逐渐减少,每组包含的关键词越来越多,当增量减至1时,整个文件恰被分成一组,算法便终止。
图解:
代码实现:
package com.athome.sort;
import java.util.Arrays;
import java.util.Random;
/**
* Description:
* Author:江洋大盗
* Date:2021/1/22 10:34
*/
public class ShellSort {
public static void main(String[] args) {
//测试插入排序
int[] arr = new int[10];
Random random = new Random();
for (int i = 0; i < arr.length; i++) {
arr[i] = random.nextInt(50);
}
System.out.println("排序前:" + Arrays.toString(arr));
shellSort(arr);
System.out.println("排序后:" + Arrays.toString(arr));
}
/**
* 希尔排序,从小到大排序
*
* @param arr 待排序数组
*/
public static void shellSort(int[] arr) {
int insertValue;
int insertIndex;
for (int gap = arr.length / 2; gap > 0; gap /= 2) {
for (int i = gap; i < arr.length; i++) {
insertValue = arr[i];
insertIndex = i - gap;
while (insertIndex >= 0 && insertValue < arr[insertIndex]) {
arr[insertIndex + gap] = arr[insertIndex];
insertIndex -= gap;
}
if (insertIndex != i - gap) {
arr[insertIndex + gap] = insertValue;
}
}
}
}
}
快速排序
快速排序介绍:快速排序(Quicksort)是对冒泡排序的一种改进。基本思想是:通过一趟排序将要排序的数据分割成独立的两部分,其中一部分的所有数据都比另外一部分的所有数据都要小,然后再按此方法对这两部分数据分别进行快速排序,整个排序过程可以递归进行,以此达到整个数据变成有序序列。
图解:
代码实现:
package com.athome.sort;
import java.util.Arrays;
import java.util.Random;
/**
* Description:
* Author:江洋大盗
* Date:2021/1/22 12:18
*/
public class QuickSort {
public static void main(String[] args) {
//测试快速排序
int[] arr = new int[10];
Random random = new Random();
for (int i = 0; i < arr.length; i++) {
arr[i] = random.nextInt(100);
}
System.out.println("排序前:" + Arrays.toString(arr));
quickSort(arr);
System.out.println("排序后:" + Arrays.toString(arr));
}
/**
* 重载快速排序,从小到大排序
*
* @param arr 待排序数组
*/
public static void quickSort(int[] arr) {
int left = 0;
int right = arr.length - 1;
quickSort(arr, left, right);
}
/**
* @param arr 待排序数组
* @param left 左角标
* @param right 右角标
*/
public static void quickSort(int[] arr, int left, int right) {
int l = left;//左角标
int r = right;//右角标
int mid = arr[(left + right) / 2];//中间值
int temp;//临时变量
while (l < r) {
//寻找左边的值大于中间值的
while (arr[l] < mid) {
l++;
}
//寻找右边的值小于中间值的
while (arr[r] > mid) {
r--;
}
//如果左角标右角标相等了,说明找不到满足条件的值
if (l >= r) {
break;
}
//执行到这里说明有值满足条件,交换位置
temp = arr[l];
arr[l] = arr[r];
arr[r] = temp;
//防止死循环
if (arr[l] == mid) {
l++;
}
if (arr[r] == mid) {
r--;
}
}
//防止死循环
if (l == r) {
l += 1;
r -= 1;
}
//向左半部分递归
if (left < r) {
quickSort(arr, left, r);
}
//向右半部分递归
if (right > l) {
quickSort(arr, l, right);
}
}
}
归并排序
归并排序介绍:归并排序(MERGE-SORT)是利用归并的思想实现的排序方法,该算法采用经典的分治(divide-and-conquer)策略(分治法将问题分(divide)成一些小的问题然后递归求解,而治(conquer)的阶段则将分的阶段得到的各答案"修补"在一起,即分而治之)。
归并排序基本思想:
动态图解:
代码实现:
package com.athome.sort;
import java.util.Arrays;
import java.util.Random;
/**
* Description:
* Author:江洋大盗
* Date:2021/1/22 10:44
*/
public class MergeSort {
public static void main(String[] args) {
//测试归并排序
int[] arr = new int[10];
Random random = new Random();
for (int i = 0; i < arr.length; i++) {
arr[i] = random.nextInt(50);
}
System.out.println("排序前:" + Arrays.toString(arr));
mergeSort(arr);
System.out.println("排序后:" + Arrays.toString(arr));
}
/**
* 重载归并排序,从小到大排序
*
* @param arr 待排序数组
*/
public static void mergeSort(int[] arr) {
int[] temp = new int[arr.length];//临时数组
int left = 0;
int right = arr.length - 1;
mergeSort(arr, temp, left, right);
}
/**
* 归并排序
*
* @param arr 待排序数组
* @param temp 临时数组
* @param left 左角标
* @param right 右角标
*/
public static void mergeSort(int[] arr, int[] temp, int left, int right) {
if (left < right) {
int mid = (left + right) / 2;
//向左递归分解
mergeSort(arr, temp, left, mid);
//向右递归分解
mergeSort(arr, temp, mid + 1, right);
//合并
merge(arr, temp, left, mid, right);
}
}
/**
* 合并
*
* @param arr 待排序数组
* @param temp 临时数组
* @param left 左角标
* @param mid 左右数据分割点
* @param right 右角标
*/
public static void merge(int[] arr, int[] temp, int left, int mid, int right) {
int l = left;//左边数据的初始坐标
int r = mid + 1;//右边数据的初始坐标
int t = 0;//记录temp数组的角标
//1.将左右两边的数据按照规则添加到临时数组中
while (l <= mid && r <= right) {
if (arr[l] <= arr[r]) {
temp[t++] = arr[l++];
}
if (arr[l] > arr[r]) {
temp[t++] = arr[r++];
}
}
//2.将左右两边剩下的数据填入到temp数组中
while (l <= mid) {
temp[t++] = arr[l++];
}
while (r <= right) {
temp[t++] = arr[r++];
}
//3.将temp中的元素拷贝到数组中,不是拷贝所有
t = 0;
int tempLeft = left;
while (tempLeft <= right) {
arr[tempLeft++] = temp[t++];
}
}
}
基数排序
基数排序(桶排序)介绍:
基数排序(radixsort)属于“分配式排序”(distributionsort),又称“桶子法”(bucketsort)或binsort,顾名思义,它是通过键值的各个位的值,将要排序的元素分配至某些“桶”中,达到排序的作用。
基数排序法是属于稳定性的排序,基数排序法的是效率高的稳定性排序法。
基数排序基本思想:将所有待比较数值统一为同样的数位长度,数位较短的数前面补零。然后,从最低位开始,依次进行一次排序。这样从最低位排序一直到最高位排序完成以后,数列就变成一个有序序列。
图解:
代码实现:
package com.athome.sort;
import java.util.Arrays;
import java.util.Random;
/**
* Description:
* Author:江洋大盗
* Date:2021/1/22 12:41
*/
public class RadixSort {
public static void main(String[] args) {
//测试基数排序
int[] arr = new int[10];
Random random = new Random();
for (int i = 0; i < arr.length; i++) {
arr[i] = random.nextInt(50);
}
System.out.println("排序前:" + Arrays.toString(arr));
radixSort(arr);
System.out.println("排序后:" + Arrays.toString(arr));
}
/**
* 基数排序,从小到大排序
*
* @param arr 待排序的数组
*/
public static void radixSort(int[] arr) {
//1.首先找出数组中的最大值的长度
int max = arr[0];
for (int i : arr) {
if (i > max) {
max = i;
}
}
//最大值的长度
int len = String.valueOf(max).length();
//2.创建桶和记录桶中数据的数组
//注:这里将每个桶的长度设为arr.length是为了防止角标越界
int[][] bucket = new int[10][arr.length];//存放数据的桶
int[] bucketCount = new int[10];//记录每个桶中数据个数
int bucketNum;//桶的编号
int index = 0;//记录下标
//3.开始向桶中放数据
for (int i = 0, n = 1; i < len; i++, n *= 10) {
//(1).从低位到高位的顺序按照数字大小依次放入桶中
for (int value : arr) {
bucketNum = (value / n) % 10;//确定桶的编号
bucket[bucketNum][bucketCount[bucketNum]] = value;
bucketCount[bucketNum]++;
}
//(2).从桶中依次取出数据放回原数组
for (int j = 0; j < bucket.length; j++) {
if (bucketCount[j] != 0) {
for (int k = 0; k < bucketCount[j]; k++) {
arr[index++] = bucket[j][k];
}
}
//取出数据后,将记录桶数据的位置置空
bucketCount[j] = 0;
}
//将记录原数组下标的值置空
index = 0;
}
}
}
基数排序的说明:
基数排序是对传统桶排序的扩展,速度很快.
基数排序是经典的空间换时间的方式,占用内存很大,当对海量数据排序时,容易造成OutOfMemoryError。
基数排序时稳定的。[注:假定在待排序的记录序列中,存在多个具有相同的关键字的记录,若经过排序,这些记录的相对次序保持不变,即在原序列中,r[i]=r[j],且r[i]在r[j]之前,而在排序后的序列中,r[i]仍在r[j]之前,则称这种排序算法是稳定的;否则称为不稳定的]。
有负数的数组,我们不用基数排序来进行排序,如果要支持负数,可以考虑优化算法。
常用排序算法总结和对比
排序算法比较图:
排序算法 | 平均时间复杂度 | 最好情况 | 最坏情况 | 空间复杂度 | 排序方式 | 稳定性 |
---|---|---|---|---|---|---|
冒泡排序 | O(n2) | O(n) | O(n2) | O(1) | In-place | 稳定 |
选择排序 | O(n2) | O(n2) | O(n2) | O(1) | In-place | 不稳定 |
插入排序 | O(n2) | O(n) | O(n2) | O(1) | In-place | 稳定 |
希尔排序 | O(nlogn) | O(nlog2n) | O(nlog2n) | O(1) | In-place | 不稳定 |
归并排序 | O(nlogn) | O(nlogn) | O(nlogn) | O(n) | Out-place | 稳定 |
快速排序 | O(nlogn) | O(nlogn) | O(n2) | O(logn) | In-place | 不稳定 |
堆排序 | O(nlogn) | O(nlogn) | O(nlogn) | O(1) | In-place | 不稳定 |
计数排序 | O(n+k) | O(n+k) | O(n+k) | O(k) | Out-place | 稳定 |
桶排序 | O(n+k) | O(n+k) | O(n2) | O(n+k) | Out-place | 稳定 |
基数排序 | O(n*k) | O(n*k) | O(n*k) | O(n+k) | Out-place | 稳定 |
相关术语解释:
稳定:如果a原本在b前面,而a=b,排序之后a仍然在b的前面;
不稳定:如果a原本在b的前面,而a=b,排序之后a可能会出现在b的后面;
内排序:所有排序操作都在内存中完成;
外排序:由于数据太大,因此把数据放在磁盘中,而排序通过磁盘和内存的数据传输才能进行;
时间复杂度:一个算法执行所耗费的时间。
空间复杂度:运行完一个程序所需内存的大小。 n:数据规模 k:“桶”的个数 In-place:不占用额外内存 Out-place:占用额外内存
结语
只要能收获甜蜜,荆棘丛中也有蜜蜂忙碌的身影,未来的你一定会感谢现在努力的自己。