排序算法大汇总

冒泡排序

冒泡排序会两两比较每个元素的大小,并将其排序。每一趟排序都会确定一个元素的位置,把最大的放在最后面,并且在排序的过程中会将小的元素集中到前面,大的元素集中到后面,冒泡排序是稳定的
时间复杂度O(n^2),空间复杂度O(1)
在这里插入图片描述

  public static int[] bubbleSort(int[] nums){
        for (int i = 0; i < nums.length; i++) {
            for (int j = 0; j < nums.length-i-1; j++) {
                if(nums[j]>nums[j+1]){
                    nums[j]=nums[j]+nums[j+1];
                    nums[j+1]=nums[j]-nums[j+1];
                    nums[j]=nums[j]-nums[j+1];
                }
            }
        }
        return nums;
    }

选择排序

选择排序会在每趟选取一个最小的元素,并将其和开头元素进行交换。每一趟排序都会确定一个元素的位置,把最小的放在前面,但由于有两个相同元素时会选择位置在后面的那个先放到前面,因此选择排序是不稳定的
时间复杂度O(n^2),空间复杂度O(1)
在这里插入图片描述

  public static int[] selectSort(int[] nums) {
        for (int i = 0; i < nums.length - 1; i++) {
            int minIndex=i;
            int min=nums[i];
            for (int j = i + 1; j < nums.length; j++) {
                if (min > nums[j]) {
                    minIndex=j;
                    min=nums[j];
                }
            }
            if(minIndex==i)continue;
            nums[i] = nums[i] + nums[minIndex];
            nums[minIndex] = nums[i] - nums[minIndex];
            nums[i] = nums[i] - nums[minIndex];
        }
        return nums;
    }

插入排序

插入排序将列表分为两部分,一个是前面的有序部分,一个是后面的无序部分。从后面的无序部分选取第一个元素插入到前面有序部分中,指导无序部分长度变为0。插入排序稳定
时间复杂度O(n^2),空间复杂度O(1)。虽然时间复杂度一样,但是一般来说插入排序的效率会比冒泡和选择快一点。
在这里插入图片描述

public static int[] insertSort(int[] nums){
        for (int i = 1; i < nums.length; i++) {// 插入排序
            int j=i;
            int temp=nums[i];
            while(j>0&&temp<nums[j-1]){
                nums[j]=nums[j-1];
                j-=1;
            }
            nums[j]=temp;
        }
        return nums;
    }

希尔排序

希尔排序是插入排序的优化版本,他先按相距一定间隔在原数组中选择元素进行排序,之后逐步减少间隔的距离直到距离为一。
希尔排序是稳定的
时间复杂度大概O(n^1.3),空间复杂度O(1)
在这里插入图片描述

public static int[] shellSort(int[] nums) {
        for (int inc = nums.length / 2; inc > 0; inc = inc / 2) {// 间隔距离
            for (int i = inc; i < nums.length; i++) {// 插入排序
                int j=i;
                int temp=nums[i];
                while(j>=inc&&temp<nums[j-inc]){
                    nums[j]=nums[j-inc];
                    j-=inc;
                }
                nums[j]=temp;
            }
        }
        return nums;
    }

快速排序

快速排序选取一个值value作为参照(一般是数组的第一个值),把比value大的放到数组的后半部分,比value小的放到数组的前半部分。然后递归的在前半部分和后半部分继续重复上述的操作。
时间复杂度O(nlogn),空间复杂度O(logn)
快速排序是不稳定的
在这里插入图片描述

   public static int[] fastSort(int[] nums, int bg, int end) {
        if (bg >= end)
            return nums;
        int last = end;
        int first = bg;
        int value = nums[bg];
        while (first < last) {
            if (nums[last] > value) {
                last--;
            } else {// 调头
                nums[first] = nums[last];
                while (first < last) {
                    if (nums[first] < value) {
                        first++;
                    } else {
                        nums[last--] = nums[first];
                        break;
                    }
                }

            }
        }
        nums[first] = value;
        fastSort(nums, bg, last);
        fastSort(nums, last + 1, end);

        return nums;
    }

归并排序

归并排序首先将数组递归的向下对半划分,划分到不能划分(每个子序列只有一个元素)后再将子序列向上合并成有序序列,直到合并为最后一个有需序列。
时间复杂度O(nlogn),空间复杂度O(n)。(需要一个辅助数组来合并两个序列)。
归并排序是稳定的
在这里插入图片描述

 public static int[] mergeSort(int[] nums, int bg, int end) {
        int mid = (bg + end) / 2;
        if (bg >= end)
            return nums;

        mergeSort(nums, bg, mid);
        mergeSort(nums, mid + 1, end);

        return merge(nums, bg, end);

    }

    public static int[] merge(int[] nums, int bg, int end) {
        int temp[] = new int[end - bg + 1];
        int mid = (bg + end) / 2;
        int index = 0;
        int i = bg, j = mid + 1;
        while (i <= mid && j <= end) {
            if (nums[i] > nums[j]) {
                temp[index++] = nums[j++];
            } else {
                temp[index++] = nums[i++];
            }
        }
        while (i <= mid) {
            temp[index++] = nums[i++];
        }
        while (j <= end) {
            temp[index++] = nums[j++];
        }
        for (int a = 0; a < temp.length; a++) {
            nums[bg++] = temp[a];
        }
        return nums;
    }

堆排序

什么是堆

堆是一个完全二叉树,且符合以下条件(小根堆就反一下)

  1. 节点大于等于根节点
  2. 左右子树都是符合上述要求

我们可以很完美的使用一维数组来表示一个堆。

由于完全二叉树的特殊结构,我们可以获得几条很有用的规律,设一颗完全二叉树的节点数量为n。位置表示在一维坐标中的索引(i从0开始)

  1. 第i个节点的左孩子位置为2i+1
  2. 第i个节点的右孩子位置为2i+2
  3. 第i个节点的父节点位置为 ⌊ i / 2 ⌋ \lfloor i/2 \rfloor i/2

综上我们可以用一个一维数组来表示一个堆,并且在数组上模拟对堆的操作
例如,交换第3个节点和他的右孩子的值,就只需要交换数组下标2和6的值即可。

构造堆

向上调整

下面以小根堆为例
在堆的最后添加一个元素,然后比较这个元素和他父节点的值大小,小于父节点就交换两个节点的值,然后继续与上一级的父节点比较。
在这里插入图片描述

向下调整

找到最后一个非叶子节点, ⌊ s i z e / 2 ⌋ \lfloor size/2 \rfloor size/2的位置。对这个节点进行调整,让他变成堆。然后找他上一个子树 ⌊ s i z e / 2 ⌋ \lfloor size/2 \rfloor size/2-1,重复这个操作直到根节点。
在这里插入图片描述

   /**
     * 调整堆
     * @param nums
     * @param size 堆最后一个元素坐标
     * @param last  当前调整位置
     * @return
     */
    public static int[] heapAdj(int[] nums, int size, int last) {

        int maxIndex = last;
        while (last <= (size + 1) / 2 - 1) {
            if (last * 2 + 1 > size) {// 是叶子节点
                return nums;
            } else if (last * 2 + 2 <= size) {// 有两个孩子
                if (nums[last * 2 + 1] >= nums[last * 2 + 2]) {
                    maxIndex = last * 2 + 1;
                } else if (nums[last * 2 + 1] < nums[last * 2 + 2]) {
                    maxIndex = last * 2 + 2;
                }
                if (nums[last] < nums[maxIndex]) {
                    swap(nums, last, maxIndex);
                    heapAdj(nums, size, maxIndex);
                } else
                    break;
            } else if (last * 2 + 1 <= size) {// 只有左孩子
                if (nums[last] < nums[last * 2 + 1]) {
                    maxIndex = last * 2 + 1;
                    swap(nums, last, maxIndex);
                    heapAdj(nums, size, maxIndex);
                } else
                    break;
            }
        }
        return nums;
    }

堆排序

首先将原始数据修改成堆,然后交换第一个元素和最后一个元素。然后再把这个数据调整成堆,重复操作即可。
时间复杂度O(nlogn),空间复杂度O(1)
堆排序不稳定
在这里插入图片描述

  public static int[] heapSort(int[] nums) {
        
        downAdj(nums);// 调整为初始堆

        for (int i = 0; i < nums.length; i++) {// 自顶向下调整为堆
            swap(nums, 0, nums.length - i - 1);
            heapAdj(nums, nums.length - i - 2, 0);
        }

        return nums;
    }

    /**
     * 创建大根堆
     * 
     * @param nums
     * @return
     */
    public static int[] downAdj(int[] nums) {
        int parent = nums.length / 2 - 1;
        for (int i = parent; i >= 0; i--) {
            heapAdj(nums, nums.length - 1, i);
        }
        return nums;
    }

总结

排序算法时间复杂度空间复杂度是否稳定
冒泡排序 n 2 n^2 n21稳定
插入排序 n 2 n^2 n21稳定
选择排序 n 2 n^2 n21不稳定
希尔排序 n 1.3 n^{1.3} n1.31不稳定
快速排序 n l o g n nlogn nlogn1不稳定
归并排序 n l o g n nlogn nlogn n n n稳定
堆排序 n l o g n nlogn nlogn1不稳定

源代码

源代码地址,给个star吧兄弟们!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值