Java八大经典排序解析源码

八大排序

一. 时间复杂度

一. 时间复杂度为O(n^2)

1. 冒泡排序(BubbleSort)

思想:连续两个数进行比较大的后移
在这里插入图片描述

import java.util.Arrays;

/**
 * 时间复杂度是O(n^2)
 */
public class BubbleSort {
    public static void bubble(int []arr){
        int len = arr.length;
        //总共比较n-1次
        int tmp = 0;
        for (int i = 0; i < len-1; i++) {
            //每次都会找到大的将它排除在外(每趟排序)
            for (int j = 0; j < len-i-1; j++) {
                if(arr[j]>arr[j+1]){
                    tmp = arr[j];
                    arr[j] = arr[j+1];
                    arr[j+1] = tmp;
                }
            }
        }
    }

    public static void bubbleY(int []arr){
        int len = arr.length;
        boolean flag = false;
        int tmp = 0;
        //总共比较n-1次
        for (int i = 0; i < len-1; i++) {
            //每次都会找到大的将它排除在外
            for (int j = 0; j < len-i-1; j++) {
                if(arr[j]>arr[j+1]){
                    tmp = arr[j];
                    arr[j] = arr[j+1];
                    arr[j+1] = tmp;
                    flag = true;
                }
            }
            System.out.println("第"+(i+1)+"趟结果为:");
            System.out.println(Arrays.toString(arr));
            //如果一趟都没有交换
            if (!flag)
                break;
            else
                flag = false;
        }

    }
    //从前往后找最大,从后往前找最小,记录最后交换的位置,说明之后的已经有序了
    public static void bubbleYY(int []arr) {
        int len = arr.length-1;
        boolean flag;
        int pos = 0;//记录最后交换的位置
        int index = 0;//每趟过后都有值确定
        int tmp = 0;
        //总共比较n-1次
        for (int i = 0; i < len; i++) {
            flag = false;
            pos = 0;
            //每次都会找到大的将它排除在外
            //从前往后找最大
            for (int j = index; j < len; j++) {
                if (arr[j] > arr[j + 1]) {
                    tmp = arr[j];
                    arr[j] = arr[j + 1];
                    arr[j + 1] = tmp;
                    flag = true;
                    pos = j;
                }
            }
            if (!flag)
                break;
            //len的值改变
            len = pos;
            //从后往前找最小
            for(int j = len ;j>index;j--){
                if(arr[j]<arr[j-1]){
                    tmp = arr[j];
                    arr[j] = arr[j-1];
                    arr[j-1] = tmp;
                    flag = true;
                }
            }
            index ++;
            System.out.println("第" + (i + 1) + "趟结果为:");
            System.out.println(Arrays.toString(arr));
            //如果一趟都没有交换
            if(!flag)
                break;
        }
    }
    public static void main(String[] args) {
        long start = System.currentTimeMillis();
        int [] arr = {3,9,-1,10,20,-2};
        bubbleYY(arr);
        long end = System.currentTimeMillis();
//        System.out.println(Arrays.toString(arr));
        System.out.println(end-start+"ms");
    }
}
2. 选择排序

思想:依次选择最小的放在数组前面,每次记录最小值的位置

在这里插入图片描述

import java.util.Arrays;

public class SelectSort {
    public static void selectSort(int[] arr) {
        int len = arr.length;
        int min;
        int index;
        int tmp;
        for (int i = 0; i < len - 1; i++) {//len-1次循环
            //假定当前值是最小值
            min = arr[i];
            index = i;
            for (int j = i + 1; j < len; j++) {
                if (min > arr[j]) {
                    min = arr[j];
                    //并记录位置
                    index = j;
                }
            }
            //一次循环后交换
            if (i != index) {
                arr[index] = arr[i];
                arr[i] = min;
            }
        }
    }

    public static void main(String[] args) {
        int[] arr = {3, 9, -1, 10, 20, -2};
        selectSort(arr);
        System.out.println(Arrays.toString(arr));
    }
}
3. 插入排序

思想:首先前两个比较,然后对排好的进行后续的插入,找到插入的位置

在这里插入图片描述

import java.util.Arrays;

public class InsertSort {
    public static void insertSort(int arr[]) {
        int len = arr.length;
        int tmp;
        for (int i = 1; i < len; i++) {
            tmp = arr[i];//记录无序列表第一个元素
            int j;
            //插入有序列表
            for (j = i - 1; j >= 0; j--) {
                //如果比无序表中的数大时
                if (arr[j] > tmp) {
                    //后移
                    arr[j + 1] = arr[j];
                } else
                    break;
            }
            arr[j + 1] = tmp;
            System.out.println("第"+i+"轮交换后:");
            System.out.println(Arrays.toString(arr));
        }
    }

    public static void main(String[] args) {
        int[] arr = {3, 9, -1, 10, 20, -2};
        insertSort(arr);
        //        System.out.println(Arrays.toString(arr));
    }
}

二. 时间复杂度为O(n*logn)

1. 归并排序

思想:首先先递归分解让数组中每个数成为长度为1的有序区间,然后合并成最大长度为2的有序区间(排序),以此类推直到所有数为一个有序区间

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

import java.util.Arrays;

public class MergetSort {
    public static void mergetSort(int[
        
        
    ] arr) {
        int len;
        if ((len = arr.length) > 0 && arr != null) {
            int[] tmpArr = new int[len];
            mergetSort(arr, 0, len - 1, tmpArr);
        } else {
            System.out.println("数组中没有任何信息!!");
        }
    }

    //递归拆分
    public static void mergetSort(int[] arr, int left, int right, int[] tmpArr) {
        if (left < right) {
            int mid = (right - left) / 2 + left;
            //递归分解
            mergetSort(arr, left, mid, tmpArr);
            mergetSort(arr, mid + 1, right, tmpArr);
            //每次合并
            merget(arr, left, mid, right, tmpArr);
        }
    }

    //合并
    public static void merget(int[] arr, int left, int mid, int right, int[] tmpArr) {
        int i = left;//两个小数组的起始坐标
        int j = mid + 1;
        int tmpIndex = i;
        //排序合并到大数组
        while (i <= mid && j <= right) {
            if (arr[i] < arr[j]) {
                tmpArr[tmpIndex] = arr[i];
                i++;
            } else {
                tmpArr[tmpIndex] = arr[j];
                j++;
            }
            tmpIndex++;
        }
        //拷贝剩下的
        while (i <= mid) {
            tmpArr[tmpIndex] = arr[i];
            i++;
            tmpIndex++;
        }
        while (j <= right) {
            tmpArr[tmpIndex] = arr[j];
            j++;
            tmpIndex++;
        }
        //拷贝到arr数组
        for (int k = left; k <= right; k++) {
            arr[k] = tmpArr[k];
        }
    }

    public static void main(String[] args) {
        int[] arr = {8, 4, 5, 7, 1, 3, 6, 2};
        mergetSort(arr);
        System.out.println(Arrays.toString(arr));
    }
}
2. 快速排序

思想:随机的在数组中选择一个数(划分值),小于这个数的统一放在左边,大于这个数的统一放在右边,接下来递归的调用快速排序。(一次划分过程的时间复杂度为O(n))

在这里插入图片描述

在这里插入图片描述

import java.util.Arrays;

public class QuickSort {
    public static void quickSort(int[] arr) {
        if (arr == null || arr.length == 0) {
            System.out.println("数组中没有值");
            return;
        }
        quickSort(arr, 0, arr.length - 1);
    }

    public static void quickSort(int[] arr, int low, int high) {
        if (low < high) {//结束条件
            int middle = getMiddle(arr, low, high);
            // System.out.println(Arrays.toString(arr));
            //根据基准元素划分,递归调用
            quickSort(arr, low, middle - 1);
            quickSort(arr, middle + 1, high);
        }
    }

    //进行划分(方法1)
    public static int getMiddle1(int[] arr, int low, int high) {
        int tmp = arr[low];//基准元素(随便找)
        while (low < high) {
            while (low < high && arr[high] >= tmp) {
                high--;
            }
            arr[low] = arr[high];
            while (low < high && arr[low] <= tmp) {
                low++;
            }
            arr[high] = arr[low];
        }
        arr[low] = tmp;
        return low;//根据基准的中间位置
    }

    //进行划分(方法2)
    public static int getMiddle(int[] arr,int low,int high) {
        int tmp = arr[high];//基准元素(随便找)
        for (int i = low; i < high; i++) {
            if (arr[i] <= tmp) {
                int a = arr[i];
                arr[i] = arr[low];
                arr[low] = a;
                low++;
            }
        }
        arr[high] = arr[low];
        arr[low] = tmp;
        return low;//根据基准的中间位置
    }

    public static void main(String[] args) {
        int[] arr = {8, 9, 1, 7, 2, 3, 5, 4, 6, 0};
        quickSort(arr);
        System.out.println(Arrays.toString(arr));
    }
}
3. 堆排序

思想:首先把n个数建成大堆(小堆),把堆顶元素与最后位置的数交换,把最大值放在最后成为有序部分,然后构建n-1的大堆。

在这里插入图片描述

import java.util.Arrays;

public class HeapSort {

    public static void main(String[] args) {
        int[] arr = {4, 6, 8, 5, 9};
        heapSort(arr);
        System.out.println(Arrays.toString(arr));
    }

    // 完全二叉树,线性存储
    public static void heapSort(int[] arr) {
        //1. 构建大顶堆(升序) 如果降序构建小顶堆
        int len = 0;
        int tmp = 0;
        for (len = arr.length; len > 1; len--) {// 每次会把大的放在数组末尾排除掉
            int index = len / 2 - 1;// 第一个非叶子节点
            // 调整
            for (int i = index; i >= 0; i--) {
                adjustHeap(arr, i, len);// 调整
            }
            // 交换
            tmp = arr[0];
            arr[0] = arr[len - 1];
            arr[len - 1] = tmp;
        }
    }

    /**
     * @param arr   : 要调整的数组
     * @param index : 非叶子节点
     * @param len   : 还需要调整的长度
     */
    public static void adjustHeap(int arr[], int index, int len) {
        int tmp = arr[index];// 要调整子树的根
        for (int k = 2 * index + 1; k < len; k = 2 * k + 1) {
            if (k + 1 < len && arr[k] < arr[k + 1]) {// index 的左右子孩子
                k++;
            }
            // 和根比
            if (arr[k] > tmp) {
                arr[index] = arr[k];
                index = k;// !!!
            } else {
                break;
            }
        }
        arr[index] = tmp;
    }
}
4. 希尔排序

思路:插入排序的改良,插入排序步长为1和前面一个一个比较,而希尔步长是调整的,从大到小调整,根据步长比较交换。关键在于步长的选择

在这里插入图片描述

在这里插入图片描述

import java.text.SimpleDateFormat;
import java.util.Arrays;
import java.util.Date;

/**
 * 基本插入排序会有一些问题比如
 * 23456781,需要后移次数很多,影响效率
 */
public class ShellSort {
    //1. 希尔排序在交换时用的交换法(效率不够高)
    public static void shellSort(int[] arr) {
        int len = arr.length;
        int stepSize = len / 2;//gap步长
        int tmp;
        while (stepSize > 0) {
            //分stepSize组
            for (int i = stepSize; i < len; i++) {
                //比较交换(不是单纯的两两比较交换)
                for (int j = i - stepSize; j >= 0; j -= stepSize) {
                    if (arr[j] > arr[j + stepSize]) {
                        tmp = arr[j];
                        arr[j] = arr[j + stepSize];
                        arr[j + stepSize] = tmp;
                    }
                }
            }
            //            System.out.println("步长为" + stepSize + "交换后数组为:");
            //            System.out.println(Arrays.toString(arr));
            stepSize = stepSize / 2;
        }
    }

    //2. 希尔排序在交换时用的移动法(速度会高很多)
    public static void shellSortG(int[] arr) {
        int len = arr.length;
        int stepSize = len / 2;//gap步长
        int tmp;
        while (stepSize > 0) {
            //分stepSize组,逐个对其所在的组进行直接插入排序
            for (int i = stepSize; i < len; i++) {
                tmp = arr[i];
                int j;
                for (j = i - stepSize; j >= 0;j -= stepSize) {
                    //移动
                    if (arr[j] > tmp) {
                        arr[j + stepSize] = arr[j];
                    } else {
                        break;
                    }
                }
                //找到插入的位置
                arr[j + stepSize] = tmp;
            }
            //            System.out.println("步长为" + stepSize + "交换后数组为:");
            //            System.out.println(Arrays.toString(arr));
            stepSize = stepSize / 2;
        }
    }

    public static void main(String[] args) {
        //        int[] arr = {8, 9, 1, 7, 2, 3, 5, 4, 6, 0};
        int[] arr = new int[80000];
        for (int i = 0; i < 80000; i++) {
            arr[i] = (int) (Math.random()*80);
        }
        Date date = new Date();
        SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
        System.out.println(dateFormat.format(date));
        shellSortG(arr);
        Date date1 = new Date();
        System.out.println(dateFormat.format(date1));
        //        System.out.println(Arrays.toString(arr));
    }
}

三. 时间复杂度趋近于O(n)

不是基于比较的排序,思想来源于桶排序

1. 计数排序

思想:先建立桶,然后把要排的实例放到对应的桶,然后顺序倒出就是排好序的

import java.util.Arrays;

//基于桶排序的思想
public class CountSort {
    /**
     * 计数排序,用于待排序的数位数相同;
     * 比如比较员工身高 在100-300之间
     * @param arr
     */
    public static void countSort(int[] arr){
        //1.建立桶
        int[] dp = new int[201];//身高在100 ~300
        //2. 入桶
        for (int i = 0; i < arr.length; i++) {
            dp[arr[i]-100]++;
        }
        //3. 出桶,已经排列好了
        int count =0;
        for (int i = 0; i <= 200; i++) {
            if(dp[i]!=0){
                for (int j = 0; j < dp[i]; j++) {
                    arr[count] = i+100;
                    count++;
                }
            }
        }
    }
    public static void main(String[] args) {
        int[] arr = {178,156,165};
        countSort(arr);
        System.out.println(Arrays.toString(arr));
    }
}
2. 基数排序

思想:比如排序的数是十进制的建立0-9号桶,根据个位数进桶,然后从0-9依此倒出,然后按十位进入对应桶,然后再倒出,然后再根据最高位入桶,然后倒出后就是有序的

import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;

public class RadixSort {
    /**
     * 使用于正整数情况,而且位数可以不相同,如果都相同时用计数排序
     * @param arr  待排序数组
     */
    public static void radixSort(int[] arr) {
        //1. 找到最大值,看有几位就是需要排几次,几次入桶
        int max = 0;
        int count = 0;
        for (int i = 0; i < arr.length; i++) {
            if (max < arr[i]) {
                max = arr[i];
            }
        }
        //看有几位
        while (max > 0) {
            max /= 10;
            count++;
        }
        //建立桶(动态的二维数组)
        List<ArrayList<Integer>> dp = new ArrayList<>();
        for (int i = 0; i < 10; i++) {
            ArrayList<Integer> list = new ArrayList<>();
            dp.add(list);
        }
        //进桶count次
        int bit;
        for (int i = 0; i < count; i++) {
            //比较所有数组元素从个位到count位(入桶)
            for (int j = 0; j < arr.length; j++) {
                //获取对应位的值 0 : 个位 1 : 十位
                bit = arr[j] % (int)Math.pow(10,i+1) / (int)Math.pow(10,i);
                ArrayList<Integer> list = dp.get(bit);
                list.add(arr[j]);
                // dp.set(bit,list);
            }
            //按次序倒出桶
            int tmp = 0;
            for (int j = 0; j < 10; j++) {
                ArrayList<Integer> list = dp.get(j);
                if(list.size()>0){
                    while(list.size()>0) {
                        arr[tmp] = list.remove(0);
                        tmp ++;
                    }
                }
            }
        }
    }

    public static void main(String[] args) {
        int []arr = {123,22,44,35,12,90,9,0};
        radixSort(arr);
        System.out.println(Arrays.toString(arr));
    }
}

二. 经典排序算法的空间复杂度

1. O(1)

插入排序,选择排序,冒泡排序, 堆排序(递归版O(logn)), 希尔排序

2. O(logn)~O(n)

快速排序,基于划分情况

3. O(n)

归并排序,(手摇算法->O(1))

4. O(m)

计数排序,基数排序(选择桶的个数决定的)

三. 经典排序算法的稳定性

假定待排序的记录序列中,存在多个具有相同的关键字的记录,若经过排序,这些记录的相对次序保持不变,称为稳定的,否则为不稳定的。

1. 稳定排序:

冒泡排序 插入排序 归并排序 计数排序 基数排序 桶排序

2. 不稳定排序

选择排序,2221

快速排序,43335

希尔排序,5115 步长2

堆排序, 555

四. 补充说明

1. 排序算法无绝对优劣
2. 为什么叫快速排序

并不代表比堆排序,归并排序优良,只是常量系数比较小

3. 工程上的排序
  1. 是综合排序
  2. 数组较小时,插入排序
  3. 数组较大时,快速排序或其它O(n*logn)的排序。

参考视频:牛客
欢迎访问我的个人博客:个人博客

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
选择排序算法准则: 每种排序算法都各有优缺点。因此,在实用时需根据不同情况适当选用,甚至可以将多种方法结合起来使用。 选择排序算法的依据 影响排序的因素有很多,平均时间复杂度低的算法并不一定就是最优的。相反,有时平均时间复杂度高的算法可能更适合某些特殊情况。同时,选择算法时还得考虑它的可读性,以利于软件的维护。一般而言,需要考虑的因素有以下四点: 1.待排序的记录数目n的大小; 2.记录本身数据量的大小,也就是记录中除关键字外的其他信息量的大小; 3.关键字的结构及其分布情况; 4.对排序稳定性的要求。 设待排序元素的个数为n. 1)当n较大,则应采用时间复杂度为O(nlog2n)的排序方法:快速排序、堆排序或归并排序序。 快速排序:是目前基于比较的内部排序中被认为是最好的方法,当待排序的关键字是随机分布时,快速排序的平均时间最短; 堆排序 : 如果内存空间允许且要求稳定性的, 归并排序:它有一定数量的数据移动,所以我们可能过与插入排序组合,先获得一定长度的序列,然后再合并,在效率上将有所提高。 2) 当n较大,内存空间允许,且要求稳定性 =》归并排序 3)当n较小,可采用直接插入或直接选择排序。 直接插入排序:当元素分布有序,直接插入排序将大大减少比较次数和移动记录的次数。 直接选择排序 :元素分布有序,如果不要求稳定性,选择直接选择排序 5)一般不使用或不直接使用传统的冒泡排序。 6)基数排序 它是一种稳定的排序算法,但有一定的局限性:   1、关键字可分解。   2、记录的关键字位数较少,如果密集更好   3、如果是数字时,最好是无符号的,否则将增加相应的映射复杂度,可先将其正负分开排序
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值