冒泡排序BubbleSort
基本思想
通过对待排序序列从前向后(从下标较小的元素开始),依次比较相邻的元素值,
若发现逆序则交换,使值较大的元素逐渐从前往后移动,就像水底的气泡逐渐往上冒
优化
若一趟比较下来没有进行过交换,就说明序列有序,通过设置flag判断元素是否进行过交换,减少不必要的比较
过程
- 一共进行 数组大小-1 次的大循环
- 每一趟排序的次数逐渐减少
- 如果发现某趟排序中,没有发生一次交换,则提前结束排序
选择排序SelectSort
基本思想
每次假定需要排序数组第一位为最小值,找到需要排序数组中最小的值及其下标,将min与数组第一位进行交换。
即每次找到最小值放在数组最前面。总共进行n-1次
过程
- 选择排序一共有 数组大小-1 轮排序
- 每一轮排序,又是一个循环
- 假定当前这个数为最小值
- 然后和后面的每个数进行比较,如果发现有比当前数更小的数,就重新确定最小数,并得到下标
- 当遍历到数组最后,就得到本轮最小数和下标
- 交换
插入排序InsertSort
基本思想
把n个待排序的元素看成一个有序和一个无序表,开始有序表只包含一个元素,无序表中包含n-1个元素,
排序过程中每次从无序表中取出第一个元素,把它的排序吗依次与有序表元素的排序码比较,
将它插入到有序表中的适当位置,使之成为新的有序表
希尔排序ShellSort
基本思想
把记录按下标的一定增量分组,对每组使用直接插入排序算法排序;随着增量逐渐减少,
每组包含的关键词越来越多,当增量减至1时,整个文件恰被分成一组,算法终止
快排排序QuickSort
基本思想
通过一趟排序将要排序的数据分割成独立的两部分,其中一部分的所有数据都比另外一部分所有数据都要小,
然后再按照此方法对这两部分数据分别进行快速排序,整个排序过程通过递归实现,依次达到整个数据变成有序序列
优化
- 基准选取可以优化为随机选取待排序的其中一个
- 分段可以优化为分成三段,一段比基准小,一段等于基准,一段大于基准,后续分别对小于和大于基准的进行快排即可
归并排序MergeSort
基本思想
利用归并的思想实现的排序方法,该算法采用经典的分治策略,分治法将问题分成一些小问题然后递归求解,
而治的阶段则将分的阶段得到的各答案修补在一起,即分而治之
堆排序HeapSort
基本思想
- 将待排序序列构造成一个大顶堆
- 此时整个序列的最大值就是堆顶的根节点
- 将其与末尾元素进行交换,此时末尾就为最大值
- 然后将剩余n-1个元素重新构造成一个堆,就会得到n个元素的次小值。反复执行,即可得到一个有序序列
特点
- 利用数组模拟完全二叉树
- 父节点 i 的两个 子节点位置为2i+1与2i+2
基数排序RadixSort
基本思想
- 将所有待比较数值同一为同样的数为长度,数位较短的数前面补零。然后从最低位开始,依次进行一次排序。这样从最低位排序一直到最高位排序完成以后,数列就变成一个有序数列
- 基数排序属于分配式排序,又称桶子法bucket sort或bin sort,顾名思义,通过键值的各个位的值,将要排序的元素分配至某些桶中,达到排序的作用
- 基数排序法属于稳定排序,是效率高的稳定排序法
- 是桶排序的扩展
- 将整数按位数切割成不同的数字,然后按每个位数分别比较
代码
import java.util.Arrays;
/**
* @author ZhangXiong
* @version v12.0.1
* @date 2020-07-17
*/
public class Sort {
public static void main(String[] args) {
int n = 8000000;
int[] arr = new int[n];
int[] arr1 = new int[n];
for (int i = 0; i < n; i++) {
arr[i] = (int) (Math.random() * n);
arr1[i] = arr[i];
}
long start = System.currentTimeMillis();
// 测试sort
new Sort().heapSort(arr);
long mid = System.currentTimeMillis();
System.out.println("mySort cost: " + (mid - start) + "ms");
Arrays.sort(arr1);
long end = System.currentTimeMillis();
// 打印排序后的数组
//System.out.println(Arrays.toString(arr));
System.out.println("ArraysSort cost: " + (end - mid) + "ms");
System.out.println(Arrays.equals(arr, arr1));
}
/**
* 冒泡排序
* 稳定排序
*
* @param arr
*/
public void bubbleSort(int[] arr) {
for (int i = arr.length - 1; i > 0; i--) {
for (int j = 1; j <= i; j++) {
if (arr[j - 1] > arr[j]) {
swap(arr, j - 1, j);
}
}
}
}
/**
* 选择排序
* 不稳定排序
*
* @param arr
*/
public void selectSort(int[] arr) {
for (int i = 0; i < arr.length; i++) {
int min = i;
for (int j = i + 1; j < arr.length; j++) {
if (arr[j] < arr[min]) {
min = j;
}
}
swap(arr, i, min);
}
}
/**
* 插入排序
* 稳定
*
* @param arr
*/
public void insertSort(int[] arr) {
for (int i = 0; i < arr.length; i++) {
int j = i;
while (j > 0) {
if (arr[j] < arr[j - 1]) {
swap(arr, j - 1, j);
j--;
} else {
break;
}
}
}
}
/**
* 希尔排序
* 不稳定
* 思想:
* 把记录按下标的一定增量分组,对每组使用插排
* 随着增量逐渐减小,每组包含关键词越来越多,当增量减为1的时候,算法终止
*
* @param arr
*/
public void shellSort(int[] arr) {
//增量gap(当前分组数),并逐步的缩小变量
for (int gap = arr.length / 2; gap > 0; gap /= 2) {
// 从第gap个元素,逐个对其所在的组进行直接插入排序
// 从第一组的第二个元素开始插排
for (int i = gap; i < arr.length; i++) {
int j = i;
int tmp = arr[j];
// 对当前分组进行插排
while (j - gap >= 0 && tmp < arr[j - gap]) {
arr[j] = arr[j - gap];
j -= gap;
}
//当退出while循环,就给temp找到插入的位置
arr[j] = tmp;
}
}
}
/**
* 归并排序
* 稳定,外部排序
*
* @param arr
*/
public void mergeSort(int[] arr) {
int[] tmp = new int[arr.length];
mergeSort(arr, 0, arr.length - 1, tmp);
}
/**
* 归并排序 分治
*
* @param arr 待排序数组
* @param l 待分割的左边界
* @param r 代分割的右边界
* @param tmp 外部数组
*/
public void mergeSort(int[] arr, int l, int r, int[] tmp) {
if (l < r) {
int mid = l + (r - l) / 2;
// 递归左分割
mergeSort(arr, l, mid, tmp);
// 递归右分割
mergeSort(arr, mid + 1, r, tmp);
// 合并
merge(arr, l, mid, r, tmp);
}
}
/**
* 归并排序的merge函数
*
* @param arr 原数组
* @param l 待排序区间 左边界
* @param mid 待排序区间 中间分隔点
* @param r 待排序区间 右边界
* @param tmp 外部区间
*/
public void merge(int[] arr, int l, int mid, int r, int[] tmp) {
// 左数组的起始
int i = l;
// 右数组的起始
int j = mid + 1;
// 外部数据的起始
int t = 0;
// 合并两数组到外部数组
while (i <= mid && j <= r) {
if (arr[i] <= arr[j]) {
tmp[t++] = arr[i++];
} else {
tmp[t++] = arr[j++];
}
}
// 将未处理的数组放到tmp尾部
while (i <= mid) {
tmp[t++] = arr[i++];
}
while (j <= r) {
tmp[t++] = arr[j++];
}
// 将排好序的区间从tmp复制到arr的对应区间
t = 0;
while (l <= r) {
arr[l++] = tmp[t++];
}
}
/**
* 快速排序
*
* @param arr
*/
public void quickSort(int[] arr) {
quickSort(arr, 0, arr.length - 1);
}
/**
* 快排
*
* @param arr 待排序数组
* @param l 待排序 左边界
* @param r 待排序 右边界
*/
public void quickSort(int[] arr, int l, int r) {
if (l >= r) {
return;
}
int right = r;
int mid = l;
int tar = arr[l + (int) (Math.random() * (r - l))];
int i = l;
while (i <= right) {
if (arr[i] < tar) {
swap(arr, i++, mid++);
} else if (arr[i] == tar) {
i++;
} else {
swap(arr, i, right--);
}
}
quickSort(arr, l, mid - 1);
quickSort(arr, i, r);
}
/**
* 堆排序
*
* @param arr
*/
public void heapSort(int[] arr) {
for (int i = (arr.length - 1) / 2; i >= 0; i--) {
adjust(arr, i, arr.length - 1);
}
for (int i = arr.length-1; i > 0 ; i--) {
swap(arr,0,i);
adjust(arr,0,i-1);
}
}
/**
* 堆排的调整结构
* @param arr
* @param i
* @param len
*/
public void adjust(int[] arr, int i, int len) {
int tmp = arr[i];
for (int k = i * 2 + 1; k < len; k = k * 2 +1) {
if (k+1 <= len && arr[k] < arr[k+1]){//说明左子节点的值小于右子节点的值
k++;//k指向右子节点
}
if (arr[k] > tmp){//如果子节点大于父节点
arr[i] = arr[k];//把较大的值赋给当前节点
i = k;//i指向k,继续循环比较
}else {
break;
}
}
arr[i] = tmp;
}
/**
* 基数排序
* @param arr
*/
public void radixSort(int[] arr) {
//得到数组中最大的数的位数
int max = arr[0];
for (int i = 1; i < arr.length; i++) {
if (arr[i] > max) {
max = arr[i];
}
}
//得到最大数是几位数
int maxLength = (max + "").length();
//定义一个二维数组,表示10个桶,每个桶就是一个一维数组
//1.二维数组包含10个一维数组
//2.为了防止在放入数的时候,数据溢出,则每个一维数组(桶)大小定为arr.length
//3.明确,基数排序是使用空间换时间的经典算法
int[][] bucket = new int[10][arr.length];
//为记录每个桶有多少个元素,定义一个一维数组记录各个桶的每次放入的数据个数
int[] bucketElementCounts = new int[10];
//使用循环将代码处理
for (int i = 0, n = 1; i < maxLength; i++, n *= 10) {
//针对每个元素的对应位进行排序处理,依次是个十百千···
for (int j = 0; j < arr.length; j++) {
//取出每个元素的个位的值
int digitOfElement = arr[j] / n % 10;
//放到对应的桶中
bucket[digitOfElement][bucketElementCounts[digitOfElement]] = arr[j];
bucketElementCounts[digitOfElement]++;
}
//按照桶顺序依次取出数据放入原来数组
int index = 0;
//遍历每一个桶,并将桶中数据,放到原数组
for (int k = 0; k < bucketElementCounts.length; k++) {
//如果桶中,有数据,放到原数组
if (bucketElementCounts[k] != 0) {
//循环该桶,放入
for (int l = 0; l < bucketElementCounts[k]; l++) {
//取出元素放到arr
arr[index] = bucket[k][l];
index++;
}
}
//第i+1轮处理后需要将每个bucketElementCounts[k] = 0
bucketElementCounts[k] = 0;
}
}
}
/**
* 交换函数
*
* @param arr
* @param i
* @param j
*/
private void swap(int[] arr, int i, int j) {
int tmp = arr[i];
arr[i] = arr[j];
arr[j] = tmp;
}
}
总结
排序比较
相关术语
- 稳定:如果a原本在b前面,且a=b,排序后a仍在b前面
- 不稳定:如果a原本在b前面,且a=b,排序后a可能会在b后面
- 内排序:所有排序操作都在内存中完成
- 外排序:由于数据太大,因此把数据放在磁盘中,而排序通过磁盘和内存的数据传输才能进行
- 时间复杂度:一个算法执行所耗费的时间
- 空间复杂度:运行完一个程序所需内存大小
- n:数据规模