912:排序数组

问题描述

给你一个整数数组 nums,请你将该数组升序排列。

示例

输入:nums = [5,2,3,1]
输出:[1,2,3,5]
输入:nums = [5,1,1,2,0,0]
输出:[0,0,1,1,2,5]

提示:

1 <= nums.length <= 50000
-50000 <= nums[i] <= 50000

思路

都给提示了,还合计啥呢。直接用计数排序啊。
不过正好趁着这个机会把常见的排序算法都复习一遍。

排序详解

十种常见排序算法可以分为两大类:

  • 非线性时间比较类排序:通过比较来决定元素间的相对次序,由于其时间复杂度不能突破O(nlogn),因此称为非线性时间比较类排序。

  • 线性时间非比较类排序:不通过比较来决定元素间的相对次序,它可以突破基于比较排序的时间下界,以线性时间运行,因此称为线性时间非比较类排序。
    912:排序数组_第1张图片
    912:排序数组_第2张图片

  • 稳定:如果a原本在b前面,而a=b,排序之后a仍然在b的前面。

  • 不稳定:如果a原本在b的前面,而a=b,排序之后 a 可能会出现在 b 的后面。

  • 时间复杂度:对排序数据的总的操作次数。反映当n变化时,操作次数呈现什么规律。

  • 空间复杂度:是指算法在计算机内执行时所需存储空间的度量,它也是数据规模n的函数。

冒泡排序

不断的比较相邻的元素,把较小的元素浮动到序列的一端固定下来。相当于是每趟冒泡都会有一个元素在其最终的位置。
算法要点是我们需要设置两层循环。外层循环控制一端。内层循环控制另一端。
我们的外层循环没必要让它循环到nums.length-1, 因为只剩下1个元素时,这个元素就在它该在的位置了。
在这里插入图片描述

插入排序

插入排序就是默认为左端是排好序的,右端是待排序的。
然后不断的从待排序的元素中拿到元素,插入到已排好序的序列中即可。
在这里插入图片描述

选择排序

我们同样是维持一个有序序列和一个无序序列。初始有序序列为空,无序序列为nums。我们每次从无序序列中选择一个最小值放在有序序列的末尾。 同样,选择排序也是每趟都有一个元素在其最终的位置。我们可以认为选择排序是冒泡排序的改进版。
在这里插入图片描述

计数排序

计数排序就适用于本题这种给了数据范围的排序场景。
它是建立了一个表,把数都存在了下标为数的表里,遍历这个表把所有元素再取出来即可。

在这里插入图片描述

希尔排序

希尔排序又叫缩小增量排序。它的精髓就是按照一个会逐渐缩小的增量给数据进行分组。然后让组内有序(组内有序一般用的是插入排序,因为到后来组内基本有序,这种场景插入排序时间复杂度最低)。增量逐渐缩小成1,故所有的元素都有序了。我们的增量一般设置为nums.length/2;

归并排序

归并排序的思想是把数组分成很多段,让每段都是有序的。然后再把这些段合并起来即可。等一下?我们怎么让每段都是有序的呢? 对每段进行归并排序即可。看明白了吧,这是递归定义的。所以记得定义好递归的出口,即某段的长度为1就该退出了。我们一般分成2段,这叫2路归并排序。
归并排序的特点是归并要用到额外的空间。
算法步骤:

  • 把长度为n的输入序列分成两个长度为n/2的子序列;
  • 对这两个子序列分别采用归并排序;
  • 将两个排序好的子序列合并成一个最终的排序序列。

在这里插入图片描述

快速排序

快排的思想就是,对于一个序列,找到一个元素pivot,把比它小的元素移到它的左边,比它大的元素移到它的右边(怎么移动的,不管)。每趟快排都会有一个元素落在最终的位置。这个元素会把序列分成两半,对每一般都进行快速排序即可。
那么这是个递归。而且right坐标有可能会比left坐标还小。因为往下应用快排的话,pivot是不参与的。所以递归退出条件设置为left >= right。
在这里插入图片描述

堆排序

堆是优先队列的一种实现方式。是一棵完全二叉树。对于小根堆(小顶堆)来讲,父结点的值小于孩子结点的值。对于大根堆(大顶堆)来讲,父结点的值大于孩子结点的值。
由于堆是一棵完全二叉树,所以用数组来存储比较方便。通过推断,我们得知左孩子的下标是leftChildIndex = parentIndex*2+1,而右孩子的下标是rightChildIndex = parentIndex*2+2。由孩子结点反推父结点parent = (childIndex-1)/2
我们在调整堆的时候,只看某个结点和它的孩子结点。以大根堆为例,把三者最大的值放在根处。其余两个当孩子。如果改动影响到了孩子结点,就要把对应的动到的孩子结点为根结点的堆也调整一下(因为本次调整之后,孩子结点为根的堆未必还能维持一个堆的正确结构)。

我们在创建堆的时候,直接把一个数组当做一棵完全二叉树。从最后一个非叶子结点开始调整堆,逐渐往前调整。

将一个元素出堆时,以大根堆为例。大根堆是根结点最大。所以把根结点与最后一个结点互换。此时原根结点成了最后一个结点,把最后一个结点弹出。然后对此时的根结点调整堆即可。

一个很好的教程.

桶排序

桶排序的思想就是创建一系列的桶,让桶内的元素有序。然后从头到脚收集所有的桶中的元素即可。 是不是有点像计数排序?没错。计数排序就是桶排序的一种特定形式。计数排序的桶容量(注意,这里说的容量是可收集的元素的范围,并不是刻意收集多少元素)是1.而桶排序的容量一般设置为5.
既然桶的容量是5,则桶内也要进行排序。由于桶内元素数值相差不多,则我们可以认为元素是基本有序的,所以桶内我们用插入排序效率较高。
912:排序数组_第3张图片
由于桶应当是给多少元素就盛多少元素,故我用了java的arrayList。需要排序的时候,把arrayList中的元素读到tmp数组中,然后对tmp进行排序即可。

基数排序

我们先取所有数的个位,按照个位的数值映射,将num收集到0~9的桶中,然后写入nums数组中。再取所有数的十位,按照十位的数值映射,将num手机到0~9的桶中,然后写入nums数组。。。 以此类推。直到某次取到的所有的数都进入到了0这个桶中。(代表到了最大位数)

在这里插入图片描述
由于计数排序是取余数,一般只适用于非0整数。想支持负数怎么办?可以多设置10个桶。 取余后的负数+20就得到了映射。
例如: -3+20等于17, 则-3映射到了17这个桶中。
-2+20等于18,则-2映射到了18这个桶中。
所以说比较大的数映射到了比较大的桶中。我们在遍历收集的时候,先遍历存储负数的桶,再遍历正数的桶即可。 由于没有任何数的余数是10,所以10这个桶是废弃了的,遍历的时候不用处理。

所有代码

class Solution {
    public int[] sortArray(int[] nums) {
        // 核心库
//        Arrays.sort(nums);
//        bubbleSort(nums);
//        insertSort(nums);
//        selectionSort(nums);
//        countingSort(nums);
//        shellSort(nums);
//        mergeSort(nums);
//        quickSort(nums);
//        myHeapSort(nums);
//        bucketSort(nums);
        radixSortAll(nums);
        return nums;
    }
    private int[] bubbleSort(int[] nums){
        // 冒泡
        for(int i = 0; i < nums.length-1; i++){
            for(int j = nums.length-1; j > i; j--){
                if(nums[j] < nums[j-1]){
                    nums[j] ^= nums[j-1];
                    nums[j-1] ^= nums[j];
                    nums[j] ^= nums[j-1];
                }
            }
        }
        return nums;
    }
    private int[] insertSort(int[] nums){
        // 插入排序
        for(int i = 1; i < nums.length; i++){
            int key = nums[i];
            int j;
            // j 指向的是插入位置
            for(j = i; j-1 > -1 && nums[j-1] > key; j--){
                nums[j] = nums[j-1];
            }
            nums[j] = key;
        }
        return nums;
    }
    private int[] selectionSort(int[] nums){
        // 选择排序
        for(int i = 0; i < nums.length-1; i++){
            int min = nums[i];
            int minIndex = i;
            for(int j = i+1; j < nums.length; j++){
                if(min > nums[j]){
                    minIndex = j;
                    min = nums[j];
                }
            }
            if(min != nums[i]){
                nums[i] ^= nums[minIndex];
                nums[minIndex] ^= nums[i];
                nums[i] ^= nums[minIndex];
            }
        }
        return nums;
    }
    private int[] countingSort(int[] nums){
        // 计数排序
        int[] tables = new int[100010];
        for(int num:nums) tables[num+50000]++;
        int realIndex = 0;
        for(int i = 0; i < tables.length; i++){
            while(tables[i]!=0){
                nums[realIndex++] = i-50000;
                tables[i]--;
            }
        }
        return nums;
    }
    private int[] shellSort(int[] nums){
        // 希尔排序 - 中间用了插入排序
        int step = nums.length/2;
        while(step > 0){
            for(int i = 0; i < step; i++){
                // 应用插入排序
                for(int j = i+step; j < nums.length; j+=step){
                    int key = nums[j];
                    int k;
                    for(k = j; k-step > -1 && nums[k-step] > key; k-=step){
                        nums[k] = nums[k-step];
                    }
                    nums[k] = key;
                }
            }
            step>>=1;
        }
        return nums;
    }
    private int[] mergeSort(int[] nums){
        mergeSort(0,nums.length-1,nums);
        return nums;
    }
    private void mergeSort(int left,int right,int[] nums){
        if(left == right) return;
        int middle = left+(right-left)/2;
        mergeSort(left,middle,nums);
        mergeSort(middle+1,right,nums);
        merge(left,right,nums);
    }
    private void merge(int left,int right, int[] nums){
        int[] tmp = new int[nums.length];
        int middle = left + (right-left)/2;
        int ptrLeft = left, ptrRight = middle+1;
        int realIndex = left;
        while(ptrLeft<=middle && ptrRight<=right){
            if(nums[ptrLeft] <= nums[ptrRight]){
                tmp[realIndex++] = nums[ptrLeft++];
            }else{
                tmp[realIndex++] = nums[ptrRight++];
            }
        }
        while(ptrLeft<=middle) tmp[realIndex++] = nums[ptrLeft++];
        while(ptrRight<=right) tmp[realIndex++] = nums[ptrRight++];
        // 回写到原数组
        for(int i = left; i <= right; i++){
            nums[i] = tmp[i];
        }
    }
    private void quickSort(int[] nums){
        quickSort(nums,0,nums.length-1);
    }
    private void quickSort(int[] nums, int left, int right){
        if(left >= right) return;
        int oriLeft = left, oriRight = right;
        int pivot = left;
        while(left < right){
            while(right > left && nums[right] >= nums[pivot]){
                right--;
            }
            int tmp = nums[right]; nums[right] = nums[pivot]; nums[pivot] = tmp;
            pivot = right;
            while(right > left && nums[left] < nums[pivot]){
                left++;
            }
            tmp = nums[left];nums[left] = nums[pivot];nums[pivot] = tmp;
            pivot = left;
        }
        quickSort(nums,oriLeft,pivot-1);
        quickSort(nums,pivot+1,oriRight);
    }
    private void heapSort(int[] nums){
        PriorityQueue<Integer> heap = new PriorityQueue<>();
        for(int num:nums) heap.add(num);
        for(int i = 0; i < nums.length; i++) nums[i] = heap.poll();
    }
    private void myHeapSort(int[] nums){
        buildHeap(nums,nums.length);
        for(int i = nums.length-1; i > -1; i--){
            nums[i] = pollHeap(nums,i+1);
        }
    }
    private Integer pollHeap(int[] tree, int length){
        if(length < 1) return null;
        int res = tree[0];
        tree[0] = tree[length-1];
        heapify(tree, length-1, 0);
        return res;
    }
    private void buildHeap(int[] tree, int length){
        int lastNotLeaf = length/2-1;
        for(int i = lastNotLeaf; i > -1; i--){
            heapify(tree,length,i);
        }
    }
    private void heapify(int[] tree, int length, int parentIndex){
        if(parentIndex >= length) return;
        int leftChildIndex = parentIndex*2+1;
        int rightChildIndex = parentIndex*2+2;
        int maxIndex = parentIndex;
        if(leftChildIndex < length && tree[leftChildIndex] > tree[maxIndex]){
            maxIndex = leftChildIndex;
        }
        if(rightChildIndex < length && tree[rightChildIndex] > tree[maxIndex]){
            maxIndex = rightChildIndex;
        }
        if(maxIndex != parentIndex){
            swap(tree,maxIndex,parentIndex);
            heapify(tree,length, maxIndex);
        }
    }
    private void swap(int[] tree, int i, int j){
        if(i != j && tree[i] != tree[j]){
            tree[i] ^= tree[j];
            tree[j] ^= tree[i];
            tree[i] ^= tree[j];
        }
    }
    private void bucketSort(int[] nums){
        if(nums.length == 0) return;
        int max,min;
        min = max = nums[0];
        for(int i = 1; i < nums.length; i++){
            min = Math.min(nums[i],min);
            max = Math.max(nums[i],max);
        }
        final int BUCKET_SIZE = 5; // 桶收集的数的范围
        int bucketNum = (max-min)/BUCKET_SIZE+1; // 计算出桶的数量
//        ArrayList[] bucket = new ArrayList[bucketNum];
        ArrayList<ArrayList<Integer>> bucket = new ArrayList<>(bucketNum);
        for(int i = 0; i < bucketNum; i++){
            bucket.add(new ArrayList<>());
        }
        for(int num: nums){
            bucket.get((num-min)/BUCKET_SIZE).add(num);
        }
        int index = 0;
        for(int i = 0; i < bucketNum; i++){
            int[] tmp = new int[bucket.get(i).size()];
            for(int j = 0; j < tmp.length; j++){
                tmp[j] = bucket.get(i).get(j);
            }
            insertSort(tmp); // 桶内用插入排序
            for(int t:tmp){
                nums[index++] = t;
            }
        }
    }
    private void radixSort(int[] nums){
        ArrayList<ArrayList<Integer>> bucket = new ArrayList<>();
        for(int i = 0; i < 10; i++){
            // 建10个桶
            bucket.add(new ArrayList<>());
        }
        int radix = 10; // 余数
        int bcsh = 1; // 除数
        boolean flag = true;
        while(flag){
            flag = false;
            for(int num: nums){
                // 放置到桶中
                if((num/bcsh)%radix > 0){
                    flag = true;
                }
                bucket.get((num/bcsh)%radix).add(num);
            }
            int numIndex = 0; // 回写nums数组的下标
            for(int i = 0; i < bucket.size(); i++){
                for(int j = 0; j < bucket.get(i).size(); j++){
                    nums[numIndex++] = bucket.get(i).get(j);
                }
                // 清空桶
                bucket.get(i).clear();
            }
            bcsh *= 10; // 倍增除数
        }
    }
    private void radixSortAll(int[] nums){
        ArrayList<ArrayList<Integer>> bucket = new ArrayList<>();
        for(int i = 0; i < 20; i++){
            // 0~9为正数桶, 11~19为负数桶
            bucket.add(new ArrayList<>());
        }
        boolean flag = true;
        int dev = 1,radix = 10;
        while(flag){
            flag = false;
            for(int num:nums){
                int remains = (num/dev)%radix;
                if(remains != 0) flag = true;
                if(remains < 0) remains = 20 + remains;
                bucket.get(remains).add(num);
            }
            int numIndex = 0;
            // 遍历负数桶
            for(int i = 11; i < bucket.size(); i++){
                for(int j = 0; j <  bucket.get(i).size(); j++){
                    nums[numIndex++] = bucket.get(i).get(j);
                }
                bucket.get(i).clear();
            }
            // 遍历正数桶
            for(int i = 0; i < 10; i++){
                for(int j = 0; j < bucket.get(i).size(); j++){
                    nums[numIndex++] = bucket.get(i).get(j);
                }
                bucket.get(i).clear();
            }
            dev *= 10;
        }
    }
}

你可能感兴趣的:(leetcode高频打卡,leetcode中等题,算法)