十大排序应用
1. 冒泡排序
75. 颜色分类-思路
不好的处理方法:冒泡排序 ,时间复杂度为O(n^2)。
注意本题,只有三色球,即只有0,1,2三个数进行从小到大不减排序。归为经典的荷兰国旗问题,题目本质是要我们将数分成三段——双指针处理。(思想:F3)
循环中的++i
和i++
结果一样的原因,但是性能不一样:
在大量数据的时候++i的性能要比i++的性能好原因: i++由于是在使用当前值之后再+1,所以需要一个临时的变量来转存。 而++i则是在直接+1,省去了对内存的操作的环节,相对而言能够提高性能。
283. 移动零-思路
注意前提:题目给的数组是含有零元素,且非零元素有序。
D1:两次遍历。第一次记录非零元素个数n,顺便排序;第二次将[n+1,len-1]赋值为0;
class Solution {
public void moveZeroes(int[] nums) {
if(nums==null) return;
int len=nums.length;
int i,j=0;
//第一次遍历,给非零元素排序
for(i=0;i<len;i++){
if(nums[i]!=0){
nums[j++]=nums[i];
}
}
//第二次遍历,给非零元素之后的空位赋值为0
for(i=j;i<len;i++){
nums[i]=0;
}
}
}
D2:一次遍历。设两个指针同时从下标为0开始,非零元素则i,j交换,0元素则不进行任何操作。
class Solution {
public void moveZeroes(int[] nums) {
int n=nums.length;
int i=0,j=0;
if(nums==null) {
return;
}
for(i=0;i<n;i++){
if(nums[i]!=0){
swap(nums,i,j++);
}
}
}
public void swap(int[] nums,int i,int j){
int temp=nums[i];
nums[i]=nums[j];
nums[j]=temp;
}
}
2. 选择排序
3. 插入排序
LC 912. 排序数组
LC 147. 对链表进行插入排序
4. 希尔排序
LC 912. 排序数组
LC 506. 相对名次
5. 归并排序
LC 剑指Offer 51. 数组中的逆序对
LC 面试题10.01. 合并排序的数组
6. 快速排序
思想:
- 选择基准值(一般将数组第一个数作为基准值);
- 双指针分别从左右两边移动,左边寻找比基准值大的数,右边找比基准值小的数。 两者交换数值;
- 左指针==右指针时,将 基准值 与 指针所指位置 交换数值;
- 随后,将数组分成两部分,分别重复上述操作即可。
代码实现:
算法分析:
指针移动问题
注意:先移动右指针
原因:如果先移动左指针就导致每次交换基准值时,都将会把比基准值大的数移动到了左边数组的第一个数,导致左边数组的值不完全小于右边数组的值,递归快排都将出错。
例题:
LC 912. 排序数组
LC 169.多数元素
LC 215. 数组中的第K个最大元素
方案一:经典快排后得到一个从小到大递增数组,返回下标为len-k+1的数即为所求。
结果:超时。
class Solution {
public int findKthLargest(int[] nums, int k) {
int n=nums.length;
QuickSort(nums,0,n-1);
return nums[n-k];
}
public void QuickSort(int[] nums,int low,int high){
if(low<high){
int piont=Partition(nums,low,high);
QuickSort(nums,0,piont-1);
QuickSort(nums,piont+1,high);
}
}
public int Partition(int[] nums,int low,int high){
//找快排中的轴心
int piont=nums[low];
while(low<high){
while(low<high && nums[high]>=piont){
high--;
}
nums[low]=nums[high];
while(low<high && nums[low]<=piont){
low++;
}
nums[high]=nums[low];
}
nums[low]=piont;
return low;
}
}
方案二:快排改进。由此可以发现每次经过「划分」操作后,我们一定可以确定一个元素的最终位置,即 xxx 的最终位置为 q,并且保证 a[l⋯q−1] 中的每个元素小于等于 a[q],且 a[q] 小于等于 a[q+1⋯r]中的每个元素。所以只要某次划分的 q为倒数第 k 个下标的时候,我们就已经找到了答案。 我们只关心这一点,至于 a[l⋯q−1] 和 a[q+1⋯r]是否是有序的,我们不关心。(思想)
我们知道快速排序的性能和「划分」出的子数组的长度密切相关。直观地理解如果每次规模为 n 的问题我们都划分成 1 和 n−1,每次递归的时候又向 n−1的集合中递归,这种情况是最坏的,时间代价是 O(n ^ 2)。
我们可以引入随机化 来加速这个过程,它的时间代价的期望是 O(n)。
顺序统计量:期望为线性时间的选择算法
(暂时没弄明白)
选择算法指的是:在一个长度为n的数列中找到第i 小或第i 大的元素。
一般选择问题看起来要比找最小值这样的简单问题更难。但令人惊奇的是,这两个问题的渐近运行时间却是相同的:Θ ( n )。参考文章将介绍一种解决选择问题的分治算法。
参考文章:https://blog.csdn.net/hy592070616/article/details/120470336
方案三:堆排序。
class HeapSort{
public static int[] HeapSortMain(int[] arr) {
int len = arr.length;
buildMaxHeap(arr, len); // 将数组整理成堆
for (int i = len - 1; i > 0; i--) {
swap(arr, 0, i); // 把堆顶元素(当前最大)交换到数组末尾
len--; // 逐步减少堆有序的部分
heapify(arr, 0, len); // 下标 0 位置下沉操作,使得区间 [0, i] 堆有序
}
return arr;
}
//将数组整理成堆(堆有序)
private void buildMaxHeap(int[] arr, int len) {
for (int i = (int) Math.floor(len / 2); i >= 0; i--) {
heapify(arr, i, len);
}
}
// i为当前下沉元素的下标
private void heapify(int[] arr, int i, int len) {
int left = 2 * i + 1;
int right = 2 * i + 2;
int largest = i;
if (left < len && arr[left] > arr[largest]) {
largest = left;
}
if (right < len && arr[right] > arr[largest]) {
largest = right;
}
if (largest != i) {
swap(arr, i, largest);
heapify(arr, largest, len);
}
}
private void swap(int[] arr, int i, int j) {
int temp = arr[i];
arr[i] = arr[j];
arr[j] = temp;
}
}
7. 堆排序
LC 215. 数组中的第K个最大元素
LC 剑指Offer 40.最小的k个数
4种解法秒杀TopK(快排/堆/二叉搜索树/计数排序)
8. 计数排序
LC 912. 排序数组
LC 1122. 数组的相对排序
9. 桶排序
LC 908.最小差值I
LC 164.最大间距
10. 基数排序
LC 164.最大间距
LC 561.数组拆分I