给你一个整数数组 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),因此称为非线性时间比较类排序。
线性时间非比较类排序:不通过比较来决定元素间的相对次序,它可以突破基于比较排序的时间下界,以线性时间运行,因此称为线性时间非比较类排序。
稳定:如果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路归并排序。
归并排序的特点是归并要用到额外的空间。
算法步骤:
快排的思想就是,对于一个序列,找到一个元素pivot,把比它小的元素移到它的左边,比它大的元素移到它的右边(怎么移动的,不管)。每趟快排都会有一个元素落在最终的位置。这个元素会把序列分成两半,对每一般都进行快速排序即可。
那么这是个递归。而且right坐标有可能会比left坐标还小。因为往下应用快排的话,pivot是不参与的。所以递归退出条件设置为left >= right。
堆是优先队列的一种实现方式。是一棵完全二叉树。对于小根堆(小顶堆)来讲,父结点的值小于孩子结点的值。对于大根堆(大顶堆)来讲,父结点的值大于孩子结点的值。
由于堆是一棵完全二叉树,所以用数组来存储比较方便。通过推断,我们得知左孩子的下标是leftChildIndex = parentIndex*2+1
,而右孩子的下标是rightChildIndex = parentIndex*2+2
。由孩子结点反推父结点parent = (childIndex-1)/2
。
我们在调整堆的时候,只看某个结点和它的孩子结点。以大根堆为例,把三者最大的值放在根处。其余两个当孩子。如果改动影响到了孩子结点,就要把对应的动到的孩子结点为根结点的堆也调整一下(因为本次调整之后,孩子结点为根的堆未必还能维持一个堆的正确结构)。
我们在创建堆的时候,直接把一个数组当做一棵完全二叉树。从最后一个非叶子结点开始调整堆,逐渐往前调整。
将一个元素出堆时,以大根堆为例。大根堆是根结点最大。所以把根结点与最后一个结点互换。此时原根结点成了最后一个结点,把最后一个结点弹出。然后对此时的根结点调整堆即可。
一个很好的教程.
桶排序的思想就是创建一系列的桶,让桶内的元素有序。然后从头到脚收集所有的桶中的元素即可。 是不是有点像计数排序?没错。计数排序就是桶排序的一种特定形式。计数排序的桶容量(注意,这里说的容量是可收集的元素的范围,并不是刻意收集多少元素)是1.而桶排序的容量一般设置为5.
既然桶的容量是5,则桶内也要进行排序。由于桶内元素数值相差不多,则我们可以认为元素是基本有序的,所以桶内我们用插入排序效率较高。
由于桶应当是给多少元素就盛多少元素,故我用了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;
}
}
}