手撕排序算法:插入排序
文章目录
插入排序(Insertion Sort)是一种简单直观的排序算法。它的工作原理是通过构建有序序列,对于未排序数据,在已排序序列中从后向前扫描,找到相应位置并插入,直到整个数组有序。
1.算法思想
第一步:从第一个元素开始,将其视为已排序部分。
第二步:取出下一个元素,与已排序部分的元素进行比较。
第三步:如果该元素小于已排序部分的最后一个元素,则将其插入到已排序部分的适当位置。
重复步骤二和三,直到整个数组都被排序。
2.算法分析
2.1时间复杂度
我们假设「比较」和「移动」的时间复杂度为O(1)的。
「插入排序」中有两个嵌套循环。
外循环正好运行一1次迭代。但内部循环运行变得越来越短:
当i=1,内层循环1次「比较」操作。
当i=2,内层循环2次「比较」操作。
当i=3,内层循环3次「比较」操作。
当i=n-2,内层循环n一2次「比较」操作。
当i=n-1,内层循环n-1次「比较」操作。
因此,总「比较」次数如下:1+2+…+(n一1)=n(n-1)/2。总的时间复杂度为:O(n^2)。
2.2 空间复杂度
由于算法在执行过程中,只有「移动」变量时候,需要事先将变量存入临时变量X,而其它没
有采用任何的额外空间,所以空间复杂度为O(1)。
3.算法的优缺点
3.1 优点
1.简单易懂,易于实现。
2.适用于小型数组或基本有序的数组。
3.稳定性好不会改变
3.2缺点
1.对于大型无序数组,效率较低。
2.不适合大规模数据排序。
4.优化方案
「插入排序」在众多排序算法中效率较低,时间复杂度为O(2)。
考虑,在进行插入操作之前,我们找位置的过程是在有序数组中找的,所以可以利用「二分查
找」来找到对应的位置。然而,执行「插入」的过程还是O(),所以优化的也只是常数时
间,最坏时间复杂度是不变的。
「改进思路」执行插入操作之前利用「二分查找」来找到需要插入的位置。
5.代码实现
void insertsort(int* nums,int numsSize) {
for (int i = 1; i < numsSize; i++) {
int key = nums[i]; // 记录当前要插入的元素
int j = i - 1;
while (j >= 0 && nums[j] > key) {
nums[j+1] = nums[j];
j--;
}
nums[j + 1] = key;
}
}
- 主循环 (
for
循环):- 从
i = 1
开始遍历数组,因为第一个元素可以视为已排序的部分。 key = nums[i]
表示当前要插入的元素。
- 从
- 插入过程 (
while
循环):j = i - 1
初始化为当前元素的前一个位置,即已排序部分的末尾。while (j >= 0 && nums[j] > key)
循环条件是j
大于等于0
并且当前位置的元素大于key
。- 在循环内部,将比
key
大的元素向右移动一个位置,为key
腾出插入的空间。
- 插入操作:
nums[j + 1] = key
将key
插入到合适的位置,即j + 1
处。
- 循环结束:
- 当内部的
while
循环条件不满足时,表示已经找到了key
的正确位置,此时将key
插入到nums[j + 1]
处。
- 当内部的
6.实战
6.1力扣1491.去掉最低工资和最高工资后的工资平均值
给你一个整数数组salary,,数组里每个数都是唯一的,其中salary[i]是第i个员工的工资。请你返回去掉最低工资和最高工资以后,剩下员工工资的平均值。
void insertsort(int* nums,int numsSize) {
for (int i = 1; i < numsSize; i++) {
int key = nums[i]; // 记录当前要插入的元素
int j = i - 1;
while (j >= 0 && nums[j] > key) {
nums[j+1] = nums[j];
j--;
}
nums[j + 1] = key;
}
}
double average(int* salary, int salarySize) {
insertsort(salary,salarySize);
int sum = 0;
for(int i = 1; i < salarySize - 1; i++){
sum += salary[i];
}
return (double)sum / (salarySize - 2);
}
6.2力扣1619.删除某些元素后的数组均值
给你一个整数数组「r,请你删除最小5%的数字和最大5%的数字后,剩余数字的平均值。
与标准答案误差在105的结果都被视为正确结果。
void insertsort(int* nums,int numsSize) {
for (int i = 1; i < numsSize; i++) {
int key = nums[i]; // 记录当前要插入的元素
int j = i - 1;
while (j >= 0 && nums[j] > key) {
nums[j+1] = nums[j];
j--;
}
nums[j + 1] = key;
}
}
double trimMean(int* arr, int arrSize) {
insertsort(arr,arrSize);
int sum = 0;
int count = 0;
for(int i = arrSize / 20; i < arrSize - arrSize / 20; i++){
sum += arr[i];
count++;
}
return (double)sum / count;
}
6.3 力扣1984.学生分数的最小差值
给你一个下标从0开始的整数数组nums,其中nums[i]表示第i名学生的分数。另给你一个整数k。
从数组中选出任意k名学生的分数,使这k个分数间最高分和最低分的差值达到最小化。
返回可能的最小差值。
void insertsort(int* nums,int numsSize) {
for (int i = 1; i < numsSize; i++) {
int key = nums[i]; // 记录当前要插入的元素
int j = i - 1;
while (j >= 0 && nums[j] > key) {
nums[j+1] = nums[j];
j--;
}
nums[j + 1] = key;
}
}
int minimumDifference(int* nums, int numsSize, int k) {
insertsort(nums,numsSize);
int ret = 10000000;
for(int i = 0; i + k - 1 < numsSize; i++){
int l = i;
int r = i + k - 1;
if(nums[r] - nums[l] < ret){
ret = nums[r] - nums[l];
}
}
return ret;
}