前言:整理一下常用排序,包括插入(另带希尔)、选择、冒泡、快排、归并、堆、基数排序。
补充:时间有限,直接附上java代码,有少量注释,链接
参考链接:
1、常见几种java排序算法
2、八大常用排序算法详细分析 包括复杂度,原理和实现
排序对比
注:掌握常用的排序,冒泡、快速、直接插入、直接选择、归并(递归,适合计算逆序)
1、稳定性排序,比如冒泡、插入、归并
2、O(n2),比如冒泡,直接插入、直接选择
3、需要辅助空间,比如归并
4、O(nlogn),比如快排、归并、堆排序
注:快排最好nlogn, 最差n方,平均nlogn,不稳定
快速排序最好,最坏,平均复杂度分析
注:选择排序时间复杂度全部n方,且不稳定,虽说和插入排序相似都是将前半部分有序,但插入稳定,选择不稳定的
注:
堆排序时间复杂度全部nlogn,不稳定
相比,归并排序时间复杂度也全部是nlogn,但稳定
一、插入排序,附带希尔
package sort;
// 通过移动先使前半部有序
public class InsertSort {
// 直接插入排序
public static void insertSort(int[] arr){
// 遍历数组,若前一个数大于当前元素tmp,则将前面序列中大于tmp的每个数依次后移,再将tmp插入到前面未移动的最后元素后一个
for (int i = 0; i < arr.length-1; i++) {
if(arr[i] > arr[i+1]){
int tmp = arr[i+1];
int j;
for(j = i; j >= 0 && arr[j] >tmp; j--){
arr[j+1] = arr[j];
}
arr[j+1] = tmp; // 此时j位置就是前面未移动的最后元素
}
}
}
// 希尔排序,分组排序
public static void shellSort(int[] arr){
int len = arr.length;
int step; // 步长
for(step = len/2; step > 0; step/=2){
for(int i = step; i < len; i++){
for(int j = i -step; j >= 0; j-=step){
if(arr[j] > arr[j+step]){
int tmp = arr[j];
arr[j] = arr[j+step];
arr[j+step] = tmp;
}
}
}
}
}
}
二、选择排序
package sort;
// 通过交换先使前半部有序
public class SelectSort {
public static void sort(int[] arr){
// 选择排序,遍历数组与标记位元素比较,若小于标记位元素就交换,标记位后移,重复,使得序列前部慢慢有序
for(int i = 0; i < arr.length-1; i++){
int flagElement = arr[i]; // 设置标志位
for(int j = i+1; j <arr.length; j++){
if(arr[j] < flagElement){
flagElement = arr[j];// 重置标志位值
arr[j] = arr[i];
arr[i] = flagElement;
}
}
}
}
}
三、冒泡排序,附带改进
package sort;
// 通过交换先使后半部有序,冒泡排序也可以更改,若一次遍历中没有交换,则说明已经排序,后面直接退出
public class BubbleSort {
public static void sort(int[] arr) {
// 两层遍历,若当前元素大于后一个元素,则交换位置,冒泡思想
for (int i = 0; i < arr.length; i++) {
for (int j = 0; j < arr.length - 1 - i; j++) {
if (arr[j] > arr[j + 1]) {
int tmp = arr[j + 1];
arr[j + 1] = arr[j];
arr[j] = tmp;
}
}
}
}
public static void sortPlus(int[] arr) {
int len = arr.length;
boolean alreadySort = false; // 初始无序
for (int i = 0; i < len; i++) {
alreadySort = true; // 先假设本次遍历不存在前后交换
for (int j = 0; j < len - 1; j++) {
if (arr[j] > arr[j + 1]) {
int tmp = arr[j + 1];
arr[j + 1] = arr[j];
arr[j] = tmp;
alreadySort = false; // 确实发生了前后交换,即未排好序
}
}
// 若一次遍历中没有发生交换,则可以认为后面都是有序的,退出
if (alreadySort)
break;
}
}
}
四、快速排序
package sort;
// 快速排序,以基准值辅助交换,划分为左右两个区间,右部分都大于左部分
public class QuickSort {
public static void sort(int[] array, int low, int high){
// 若只有一个元素,不需要排序
if(array == null || low >= high){
return;
}
int i = low, j = high, base = array[low];
while(i < j){
// 右边游标先移动,直到小于base基准值停止
while(i < j && array[j] >= base){
j--;
}
// 在j位置找到一个比base小的值
if(i < j){
array[i] = array[j];
i++; // 排序起始位位置后移一位
}
// 左边游标后移动,直到大于base基准值为止
while(i < j && array[i] <= base){
i++;
}
// 在i位置找到一个比base大的值
if(i < j){
array[j] = array[i];
j--; // 排序的终止位置前移一位
}
}
// 两个游标走到一起,只看j,将基准值与j位置元素对调,则以j位置的基准值就可以将数组分为大于和小于基准值的两部分
array[j] = base;
// 再次调整左右两部分数组
sort(array, low, j-1);
sort(array, j+1, high);
}
}
五、归并排序
package sort;
// 归并排序,分治,辅助数组
public class MergeSort {
public static void sort(int[] array, int low, int high){
if(array == null || low >= high){
return;
}
int mid = (low+high)/2;
sort(array, low, mid); // 将左半部分排序
sort(array, mid+1, high); // 将右半部分排序
merge(array, low, mid, high); // 归并
}
private static void merge(int[] array, int low, int mid, int high) {
// 创建辅助数组
int[] tmp = new int[high-low+1];
// p1表示左边部分数组的起点,p2表示右边部分数组的起点,p临时数组添加元素的指向
int p1 = low, p2 = mid+1, p = 0;
while (p1 <= mid && p2 <= high){
if(array[p1] < array[p2]){
tmp[p++] = array[p1++];
}else{
tmp[p++] = array[p2++];
}
}
// 添加两个数组的剩余部分,谁剩余就添加谁
while(p1 <= mid){
tmp[p++] = array[p1++];
}
while (p2 <= high){
tmp[p++] = array[p2++];
}
// 因为创建了临时数组,所以要覆盖原数组
for (int i = 0; i < tmp.length; i++) {
array[low+i] = tmp[i];
}
}
}
六、堆排序
注:堆排序的核心就在调整堆函数,至于建堆则从最后一个非叶子结点向前调整即可,建堆也就是调整堆的过程
package sort;
class HeapSort {
public static void sort(int[] arr) {
createHeap(arr, arr.length);
// 排序逻辑,将堆顶元素与尾部元素交换,调整堆,反复执行使得 有序
for (int j = arr.length - 1; j > 0; j--) {
swap(arr, 0, j);
adjustHeap(arr, 0, j);
}
}
// 建堆
private static void createHeap(int[] arr, int border) {
for (int i = border / 2 - 1; i >= 0; i--) {
adjustHeap(arr, i, border); // 从最后一个非叶子加点i,调整,使得左子节点值小于右子节点,右子节点值小于父结点值
}
}
// 核心在这,构建大顶堆, 若构建小顶堆,只需改两个符号
private static void adjustHeap(int[] arr, int p, int b) {
int tmp = arr[p]; // P表示父结点,c表示子节点, b表示边界
for (int c = 2 * p + 1; c < b; c = 2 * c + 1) {
if (c + 1 < b && arr[c + 1] > arr[c]) // 若构建小顶堆,则右边>这里变为<
c++;
if (arr[c] > arr[p]) { // 若构建小顶堆,则这里>变为<
arr[p] = arr[c]; // 往上传
p = c; // 改变父结点指向
}
}
arr[p] = tmp;
}
private static void swap(int[] arr, int i, int j) {
int tmp = arr[j];
arr[j] = arr[i];
arr[i] = tmp;
}
}
//
// 下面代码略显啰嗦,但为了方便理解,拆分说明
public class HeapSort {
public static void sort(int[] arr){
// 1. 升序,构建大顶堆
int len = arr.length;
for(int i = len/2-1; i >= 0; i--){
// 从最后一个非叶子加点i,调整,使得左子节点值小于右子节点,右子节点值小于父结点值
adjustHeap(arr, i, len);
}
// 将堆顶元素与尾部元素交换,调整堆,反复执行
for(int j = len-1; j > 0; j--){
swap(arr, 0, j);
adjustHeap(arr, 0, j);
}
}
private static void adjustHeap(int[] arr, int parent, int len) {
int leftChild;
int largetChild;
int rightChild;
// 若左子结点存在,则判断是否需要交换父结点与最大值子节点
while((leftChild = 2 * parent + 1) < len){
largetChild = leftChild; // 默认左子结点为最大值字结点
rightChild = leftChild + 1;
if(rightChild < len && arr[leftChild] < arr[rightChild]){
// 若存在右结点,且左节点值小于右结点值,则最大值子结点为右子节点
largetChild = rightChild;
}
if(arr[largetChild] >arr[parent]){
// 若子节点最大值大于父结点值,则需要交换
swap(arr, largetChild, parent);
}
// 交换过后,子节点就可能不是最大堆了,所以继续向下调整
parent = largetChild;
}
}
private static void swap(int[] arr, int largetIndex, int parent) {
int tmp = arr[parent];
arr[parent] = arr[largetIndex];
arr[largetIndex] = tmp;
}
}
七、基数排序
package sort;
public class RadixSort {
public static void sort(int[] arr, int bitCnt) {
int len = arr.length;
int divsor = 1; // 除数,目的取出对应位的数字
int times = 1; // 排序次数,数组中最大值是bitCnt位
// 注:如下辅助数组可以尝试其他方式,如d长度(最大值位数),基数r(0到9),可以采取int[d][r]
int[][] tmp = new int[10][len]; // 辅助数组存放数组元素
int[] order = new int[10]; // 辅助数组,存放当前位i的元素个数,i = 0,1,...,8,9
while (times <= bitCnt) {
int lsd;
for (int i = 0; i < len; i++) {
lsd = (arr[i] / divsor) % 10;
tmp[lsd][order[lsd]] = arr[i]; // 依次将元素存入对应lsd行数组中
order[lsd]++; // 当前lsd位置存入了一个元素,加一
}
// 从tmp数组中按照逐行逐列取出元素,覆盖原数组
int pos = 0; // 数组指示覆盖游标
for (int i = 0; i < 10; i++) {
if (order[i] != 0) {
// 表示当前lsd有元素
for (int j = 0; j < order[i]; j++) {
arr[pos++] = tmp[i][j]; // 覆盖
tmp[i][j] = 0; // 清空,下次需要用
}
order[i] = 0; // 清空
}
}
// 开启下一次高位比较
times++;
divsor *= 10;
}
}
}
八、对比测试
10w数据:
quickSort耗时: 29 ms
mergeSort: 30 ms
insertSort耗时: 1432 ms
shellSort耗时: 12760 ms
selectSort耗时: 20970 ms
bubble耗时: 24366 ms
arraysSort耗时: 23 ms
heapSort耗时: 19 ms
RadixSort耗时: 14 ms
package sort;
import java.time.Clock;
import java.util.Arrays;
import java.util.Random;
public class Main {
public static void main(String[] args) {
int bitCnt = 5;
//method1();
method2(bitCnt);
}
public static void method1(){
int len = 13;
int[] randArr = getRandomArr(len);
int[] insertArr = randArr.clone();
int[] selectArr = randArr.clone();
int[] bubbleArr = randArr.clone();
int[] quickArr = randArr.clone();
int[] mergeArr = randArr.clone();
int[] heapArr = randArr.clone();
int[] radixArr = randArr.clone();
System.out.println("原始未排序数组:");
printArr(randArr);
System.out.println("插入排序或改进希尔排序:");
//InsertSort.insertSort(insertArr);
InsertSort.shellSort(insertArr);
printArr(insertArr);
System.out.println("结束...");
System.out.println("选择排序:");
SelectSort.sort(selectArr);
printArr(selectArr);
System.out.println("结束...");
System.out.println("冒泡排序或改进冒泡排序:");
//BubbleSort.sort(bubbleArr);
BubbleSort.sortPlus(bubbleArr);
printArr(bubbleArr);
System.out.println("结束...");
System.out.println("快速排序起始:");
QuickSort.sort(quickArr, 0, randArr.length-1);
printArr(quickArr);
System.out.println("结束...");
System.out.println("归并排序起始:");
MergeSort.sort(mergeArr, 0, randArr.length-1);
printArr(mergeArr);
System.out.println("结束...");
System.out.println("堆排序:");
HeapSort.sort(heapArr);
printArr(heapArr);
System.out.println("结束...");
System.out.println("基数排序:");
RadixSort.sort(radixArr, 3);
printArr(radixArr);
System.out.println("结束...");
System.out.println("Arrays自带的排序起始:");
Arrays.sort(randArr);
printArr(randArr);
System.out.println("结束...");
}
public static void method2(int bitCnt){
int len = (int) Math.pow(10, bitCnt);
int[] randArr =getRandomArr(len);
int[] quickArr = randArr.clone();
int[] mergeArr = randArr.clone();
int[] insertArr = randArr.clone();
int[] shellArr = randArr.clone();
int[] selectArr = randArr.clone();
int[] bubbleArr = randArr.clone();
int[] heapArr = randArr.clone();
int[] radixArr = randArr.clone();
int[] arrays = randArr.clone();
long s = Clock.systemDefaultZone().millis();
QuickSort.sort(quickArr, 0, quickArr.length - 1);
System.out.println("quickSort耗时: " + (Clock.systemDefaultZone().millis() - s) + " ms");
s = Clock.systemDefaultZone().millis();
MergeSort.sort(mergeArr, 0, mergeArr.length-1);
System.out.println("mergeSort: " + (Clock.systemDefaultZone().millis() - s) + " ms");
s = Clock.systemDefaultZone().millis();
InsertSort.insertSort(insertArr);
System.out.println("insertSort耗时: " + (Clock.systemDefaultZone().millis() - s) + " ms");
s = Clock.systemDefaultZone().millis();
InsertSort.shellSort(shellArr);
System.out.println("shellSort耗时: " + (Clock.systemDefaultZone().millis() - s) + " ms");
s = Clock.systemDefaultZone().millis();
SelectSort.sort(selectArr);
System.out.println("selectSort耗时: " + (Clock.systemDefaultZone().millis() - s) + " ms");
s = Clock.systemDefaultZone().millis();
BubbleSort.sort(bubbleArr);
System.out.println("bubble耗时: " + (Clock.systemDefaultZone().millis() - s) + " ms");
s = Clock.systemDefaultZone().millis();
Arrays.sort(arrays);
System.out.println("arraysSort耗时: " + (Clock.systemDefaultZone().millis() - s) + " ms");
s = Clock.systemDefaultZone().millis();
HeapSort.sort(heapArr);
System.out.println("heapSort耗时: " + (Clock.systemDefaultZone().millis() - s) + " ms");
s = Clock.systemDefaultZone().millis();
RadixSort.sort(radixArr, bitCnt);
System.out.println("RadixSort耗时: " + (Clock.systemDefaultZone().millis() - s) + " ms");
}
// 生成随机数组
private static int[] getRandomArr(int length) {
if(length <= 0){
return null;
}
int[] array = new int[length];
for (int i = 0; i < length; i++) {
array[i] = new Random().nextInt(length);
}
return array;
}
// 打印数组
private static void printArr(int[] array){
for(int e : array){
System.out.print(e + " ");
}
System.out.println();
}
}