常见的排序算法

插入排序

时间复杂度 O(n^2) 空间复杂度 O(1)

选择排序的工作原理是在0~i的索引范围内去进行排序,将新增的数arr[ i ]一个个放进来排序,个人感觉有点类似于冒泡排序,但是和冒泡排序不同是选择排序是在小范围内找出最大最小完成一个小范围的有序,而冒泡排序是在整个数组中每次找到最大或者最小值

int[] arr = {7, 5, 1, 9, 3, 41, 5, -1};
public static void insertSort(int[] arr) {
    for (int i = 1; i < arr.length; i++) {
        int temp = arr[i];//记录当前值
        int index = i - 1; //当前数组内移动指针
        //判断从索引0开始到现在的范围内比temp大的 相当于比temp大的数向前移动腾一个位置出来
        while (index >= 0 && arr[index] > temp) arr[index + 1] = arr[index--];
        arr[index + 1] = temp;//在空出来的位置赋值temp
    }
}
//每次的数组变化
[5, 7, 1, 9, 3, 41, 5, -1]
[1, 5, 7, 9, 3, 41, 5, -1]
[1, 5, 7, 9, 3, 41, 5, -1]
[1, 3, 5, 7, 9, 41, 5, -1]
[1, 3, 5, 7, 9, 41, 5, -1]
[1, 3, 5, 5, 7, 9, 41, -1]
[-1, 1, 3, 5, 5, 7, 9, 41]

希尔排序

时间复杂度 O(nlog n) 空间复杂度 O(1)

希尔排序是在插入排序上的升级版,做到的是一个跳跃式的插入排序,将数组拆分成多个小数组进行插入排序,比如下面的案例就是将arr拆分成了 5 7 4 0 和 1 3 -2 来进行插入排序 之后变为 0, 1, -2, 3, 4, 7, 5 再整体进行一次插入排序即可

    
int[] arr = {5, 1, 7, 3, 4, -2, 0};
    int gap = arr.length / 2;
    while (gap > 0) {
        for (int i = gap; i < arr.length; i++) {
            int index = i;
            int temp = arr[index];
            while (index >= gap && temp < arr[index - gap]) {//跳跃式的插入排序
                arr[index] = arr[index - gap];
                index = index - gap;
            }
            arr[index] = temp;
        }
        gap = gap / 2;
    }
}
//每次变化的arr
[0, 1, -2, 3, 4, 7, 5]
[-2, 0, 1, 3, 4, 5, 7]

冒泡排序

时间复杂度 O(n^2) 空间复杂度 O(1)

冒泡排序是在每一次循环都找出最大值并放到最后面

public static void bubbleSort(int[] arr) {
    boolean flag; // 标记当前循环是否发生了元素交换 用于剪枝
    // 外层循环控制趟数,每一趟将一个最大值移到数组末尾
    for (int i = 0; i < arr.length - 1; i++) {
        flag = false; // 每次循环开始时将标记设置为false
        // 内层循环用于对未排序部分进行冒泡操作
        for (int j = 0; j < arr.length - 1 - i; j++) {
            // 如果前一个元素大于后一个元素,则交换它们
            if (arr[j] > arr[j + 1]) {
                int temp = arr[j];
                arr[j] = arr[j + 1];
                arr[j + 1] = temp;
                flag = true; // 发生了交换,更新标记
            }
        }
        // 如果一趟下来没有发生交换,说明数组已经有序,直接退出循环
        if (!flag) break;
    }
}

选择排序

时间复杂度 O(n^2) 空间复杂度 O(1)

和冒泡排序一个样,找到最小的数和索引在不停的放在为排序的数组的最前面

int[] arr = {12, 2, -1, 9, 2, 4, 6, 0};
for (int i = 0; i < arr.length; i++) {
    int temp = arr[i], index = i;
    for (int j = i + 1; j < arr.length; j++) {//找到最小的数和最小数的索引
        if (arr[j] < temp) {
            temp = arr[j];
            index = j;
        }
    }
    if (index != i) {
        arr[index] = arr[i];//让最小的数和当前未确定的数字进行交换
        arr[i] = temp;
    }
}
//每次排序完的arr
[-1, 2, 12, 9, 2, 4, 6, 0]
[-1, 0, 12, 9, 2, 4, 6, 2]
[-1, 0, 2, 9, 12, 4, 6, 2]
[-1, 0, 2, 2, 12, 4, 6, 9]
[-1, 0, 2, 2, 4, 12, 6, 9]
[-1, 0, 2, 2, 4, 6, 12, 9]
[-1, 0, 2, 2, 4, 6, 9, 12]
[-1, 0, 2, 2, 4, 6, 9, 12]

基数排序

时间复杂度 O(n+z * n) 空间复杂度 O(m*n) m代表桶的个数,一般是10,n表述数组长度 z表示最大数的位数

基数排序的原理是通过比较位数来进行排序 因此基数排序只能适用于正数 可以想从个位数开始进行位数的排序每一次都是对当前位数的数完成了范围内的排序 比如 8 12 3 6 在各位排序完就是 3 12 6 8 可以看出来个位数已经被完全排序好了(12忽略掉,他属于两位数),再比较十位数的大小,个数可以补零进行比较,于是就得到了 3 6 8 12 这个懂了的话桶排序就很好理解了

int[] arr = {53, 3, 542, 748, 14, 214};
public static void radixSort(int[] arr) {
    //找到最大数 用来判断需要循环几次
    int max = Integer.MIN_VALUE;
    for (int num : arr) {
        if (num > max) max = num;
    }
    //创建二维数组桶,因为每个位有0~9共十个数 所以需要创建十个桶,最极限的情况是一个桶装arr里面所有的元素
    int[][] bucket = new int[10][arr.length];
    //还需要定义一个一维数组用来表示桶中有多少个数据
    int[] bucketElementCounts = new int[10];
    for (int i = 1, z = 1; i <=(max + "").length(); i++, z = z * 10) {
        for (int num : arr) {//将当前的数按照 位数放到对应的桶中 i=1表示比较个位 i=2表示十位.....
            int digit = num / z % 10;
            bucket[digit][bucketElementCounts[digit]] = num;
            bucketElementCounts[digit]++;
        }
        //开始排序,直接按顺序放入数据即可
        int index = 0;//插入数据的索引
        for (int j = 0; j < 10; j++) {
            int num = bucketElementCounts[j];
            //说明桶中没有数据
            if (num == 0) continue;
            for (int k = 0; k < num; k++) {//将桶中的数据放进去 并归零记录数据量的数组
                arr[index++] = bucket[j][k];
            }
            bucketElementCounts[j] = 0;
        }
    }
}
//每次排序完的arr
[542, 53, 3, 14, 214, 748] //个位
[3, 14, 214, 542, 748, 53] //十位
[3, 14, 53, 214, 542, 748] //百位

快速排序

时间复杂度 O(nlog n) 空间复杂度 O(n)

快速排序的算法是一种分治的体现,将大问题拆分成若干个小问题去解决

在快排中我们首先需要选择一个基准数,用于区分左右两边(任意选择都可以,但是修改了基准之后代码也会有相应的小改动),这里以选择最左边为例,在我们选择好了基准之后就可以进行数字的交换了,要做到的是循环完之后基准数左边的数字都是小于等于基准数的,右边都是大于等于 顺序为先从右边开始递减寻找比基准小的,当找到这个数(a)的时候就停止循环,此时的left索引记录的数就可以修改成a,再开始从左边找起,当找到这个比基准数大的数(b)的时候就停止循环,此时right索引记录的数就修改为b,一直如此循环,直到right==left,此时结束循环.且right索引下的数字修改为基准数,这样第一次分左右就完成了,接下来就是重复的事情就可以递归完成

 public static void main(String[] args) {
        int[] arr = {3, 24, 16, 1, 32, 4, 7, 4, 98, 2, 0};
        int left = 0, right = arr.length - 1;
        quickSort(arr, left, right);
        System.out.println(Arrays.toString(arr));
    }
​
    public static void quickSort(int[] arr, int start, int end) {
        //结束条件
        if (start >= end) {
            return;
        }
        int right = end, left = start;
        int base = arr[left];
        while (right > left) {
            //找到比基准要小的数字 从右向左
            while (right > left && arr[right] >= base) right--;
            //交换
            arr[left] = arr[right];
            //找到比基准要大的数字 从左向右
            while (right > left && arr[left] <= base) left++;
            //交换
            arr[right] = arr[left];
        }
        //因为循环条件是right > left 所以结束的时候一定right = left
        arr[left] = base;
        //递归解决左半边
        quickSort(arr, 0, left - 1);
        //递归解决右半边
        quickSort(arr, right + 1, end);
    }

归并排序

时间复杂度 O(nlog n) 空间复杂度 O(n)

归并排序是一种基于分治法的经典排序算法。它的基本思想是将数组分成较小的子数组,分别对这些子数组进行排序,然后再将它们合并成一个有序的数组,首先设定两个指针,最初位置分别为两个已经排序序列的起始位置,之后比较两个指针的大小并赋值给临时数组,在将剩下的需要排序的数组放入到临时数组中,这样当前范围内的排序就完成了,再将临时数组赋值给原本的数组,一直重复这样的步骤就可以了

    
public static void main(String[] args) {
        int[] arr = {8, 4, 5, 7, 1, 3, 6, 2};
        mergeSort(arr, 0, arr.length - 1);
    }
​
    //合并
    public static void mergeSort(int[] arr, int left, int right) {
        if (left < right) {
            int mid = (left + right) / 2;
            //向左递归分解
            mergeSort(arr, left, mid);
            //向右递归分解
            mergeSort(arr, mid + 1, right);
            //合并
            merge(arr, left, mid, right);
        }
    }
​
    public static void merge(int[] arr, int left, int mid, int right) {
        int[] temp = new int[right - left + 1]; // 只创建需要的临时数组
        int i = left;//左指针
        int j = mid + 1;//右指针
        int index = 0;//temp的索引
        while (i <= mid && j <= right) {
            //如果左边的有序序列的元素小于等于右边的有序序列的元素就拷贝左边的元素到temp中 反之则反过来
            if (arr[i] <= arr[j]) temp[index++] = arr[i++];
            else temp[index++] = arr[j++];
        }
        //把有剩余的数据的一遍的数据依次填充到temp中
        while (i <= mid) temp[index++] = arr[i++];//左边的有序的填充
        while (j <= right) temp[index++] = arr[j++];//右边的全部填充
        //将temp拷贝到原本的arr中
        System.arraycopy(temp, 0, arr, left, temp.length);
    }
//每次排序完的arr
[4, 8, 5, 7, 1, 3, 6, 2]
[4, 8, 5, 7, 1, 3, 6, 2]
[4, 5, 7, 8, 1, 3, 6, 2]
[4, 5, 7, 8, 1, 3, 6, 2]
[4, 5, 7, 8, 1, 3, 2, 6]
[4, 5, 7, 8, 1, 2, 3, 6]
[1, 2, 3, 4, 5, 6, 7, 8]

计数排序

时间复杂度 O(n+k) 空间复杂度 O(n+k) 其中 k 是输入数组中的最大值

一种很明显的拿空间换时间的排序方法,不同于其他的排序,计算排序是不需要和其他元素进行比较的,其原理在于创建一个最大数为长度的数组,之后遍历整个数组获取每个元素的数量,在对应的数组索引下+1,最后再将整个最大数为长度的数组遍历找出里面的元素,这个数组中对应的数字为多少就代表当前这个元素有多少个 这个排序在重复元素多并且间隔小的时候是非常快的,但遇到元素间距差很大的时候就不太能考虑了

 public static void countingSort(int[] arr) {
        if (arr.length == 0 || arr.length == 1) return;
        //找到数组中的最大值
        int max = Arrays.stream(arr).max().getAsInt();
        //创建对应的数组
        int[] count = new int[max + 1];
        // 统计每个元素的出现次数
        for (int num : arr) count[num]++;
        //赋值
        for (int i = 0, index = 0; i < count.length; i++) {
            if (count[i] == 0) continue;
            for (int j = 0; j < count[i]; j++) arr[index++] = i;
        }
    }

堆排序

时间复杂度 O(nlog n) 空间复杂度 O(1)

堆是具有以下性质的完全二叉树:每个结点的值都大于或等于其左右孩子结点的值,称为大顶堆或者每个结点的值都小于或等于其左右 孩子结点的值,称为小顶堆 大顶堆:arr[i] >= arr[2i+1] && arr[i] >= arr[2i+2] 小顶堆:arr[i] <= arr[2i+1] && arr[i] <= arr[2i+2]

堆排序的步骤分为三步,第一步,构造大(小)顶堆,第二步,将数组未排序的最后一位元素和第一位元素进行交换,最后一步,重复前两步直到最后未排序的最后一位元素的索引-1和第一位元素重合 即下面的 i >1

 public static void main(String[] args) {
        int[] arr = {4, 7, 1, 8, 543, 43, 65, 3};
        HeapSort(arr);
    }

    
    public static void HeapSort(int[] arr) {
        //构建大顶堆
        for (int i = arr.length / 2 - 1; i >= 0; i--) {
            bigHeap(arr, arr.length, i);
        }
        //构建完之后开始交换
        for (int i = arr.length - 1; i > 0; i--) {
            int temp = arr[i];
            arr[i] = arr[0];
            arr[0] = temp;
            //调整大顶堆
            bigHeap(arr, i, 0);//因为每次删除的都是堆顶(根节点),所以需要从根节点 0 开始重新堆化
            // 下面的其他节点在之前都已经被堆化好了
        }
        //小顶堆
        //构建小顶堆
        for (int i = arr.length / 2 - 1; i >= 0; i--) {
            smallHeap(arr, arr.length, i);
        }
        //开始交换
        for (int i = arr.length - 1; i > 0; i--) {
            int temp = arr[i];
            arr[i] = arr[0];
            arr[0] = temp;
            //调整小顶堆
            smallHeap(arr, i, 0);
        }

    }

    /**
     * @Param arr: 需要调整的数组
     * @Param n: 需要调整的节点的数量
     * @Param i: 调整的非子叶节点
     */
    public static void bigHeap(int[] arr, int n, int i) {
        int max = i;
        int left = 2 * i + 1;//左子节点
        int right = 2 * i + 2;//右子节点
        if (left < n && arr[left] > arr[max]) max = left;
        if (right < n && arr[right] > arr[max]) max = right;
        //如果最大值不是根节点就交换
        if (max != i) {
            int temp = arr[i];
            arr[i] = arr[max];
            arr[max] = temp;
            // 递归堆化受影响的子树
            bigHeap(arr, n, max);
        }
    }

    public static void smallHeap(int[] arr, int n, int i) {
        int min = i;
        int left = 2 * i + 1;//左子节点
        int right = 2 * i + 2;//右子节点
        if (left < n && arr[left] < arr[min]) min = left;
        if (right < n && arr[right] < arr[min]) min = right;
        //如果最小值不是根节点就交换
        if (min != i) {
            int temp = arr[i];
            arr[i] = arr[min];
            arr[min] = temp;
            // 递归堆化受影响的子树
            smallHeap(arr, n, min);
        }
    }

睡眠排序

时间复杂度 ? 空间复杂度 O(n) 手动狗头(doge)

 public static void sleepSort(int[] arr) {
        for (int num : arr) {
            new Thread(() -> {
                try {
                    Thread.sleep(num * 10L);
                    System.out.print(num );
                } catch (Exception ignore ) {}
            }).start();
        }
    }

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值