【数据结构与算法】Java描述:第六节:排序算法(直接插入排序,希尔排序)
排序算法简单来说就是把一串数据,以想要的顺序排列起来,例如从大到小,从小到大,这篇文章来介绍一下 常见排序算法中的 插入排序算法。
一、关于排序算法
1.1 排序算法的稳定性
排序算法有稳定性这么一个评判标准 ,意思就是说 相同元素 的排序处理上是否保持原始顺序不变。
二、 插入排序
2.1 直接插入排序的思想
直接插入排序的思想 很简单 以从小到大为例:第一个元素 与第二个元素对比,比它大 就交换它们的位置 ,例如我们整理手牌。
2.2 代码解释:
外层循环:遍历数组
-
循环变量
i
:从1
开始,直到数组末尾 (i < array.length
)。 -
作用:逐个处理未排序的元素。
i
表示当前需要插入的元素位置。
内层循环:寻找插入位置
-
初始化
j
:j = i - 1
,表示从当前元素的前一个位置开始比较。 -
保存当前值:
tmp = array[i]
,临时存储待插入的值。 -
内层循环条件:
j >= 0
,向前遍历已排序部分。-
比较元素:若
array[j] > tmp
(当前元素比待插入值大),则将array[j]
后移到j+1
的位置,并继续向前比较 (j--
)。 -
找到插入位置:若
array[j] <= tmp
,说明tmp
应插入到j+1
的位置。此时将tmp
放入array[j+1]
并跳出内层循环 (break
)。
-
1. 问题解决:
处理边界情况
-
所有元素都比
tmp
大:若内层循环未触发break
(所有已排序元素均大于tmp
),则j
最终变为-1
。此时需要将tmp
插入到数组开头(array[0]
)。
变量 j
的变化示例
假设数组为 [5, 3, 1]
,分析第一次外层循环 (i=1
):
-
初始状态:
i=1
,j=0
,tmp=3
。 -
内层循环:
-
j=0
:array[0]=5 > 3
→ 后移5
到j+1
,数组变为[5, 5, 1]
,j--
→j=-1
。
-
-
退出内层循环:
j=-1
,执行array[j+1] = tmp
→array[0] = 3
,数组变为[3, 5, 1]
。
2. 关键点总结
-
j
的作用:定位tmp
应插入的位置。内层循环通过递减j
遍历已排序部分。 -
array[j+1] = tmp
的冗余性:无论是否触发break
,外层循环都会执行此操作。虽然重复赋值,但结果正确。 -
时间空间复杂度:
时间复杂度:O(N^2) 最坏情况下:逆序的 5 4 3 2 1*
最好情况下:本身就是有序的 1 2 3 4 5 O(n)
* 如果数据越有序,直接插入排序越快
* 空间复杂度:O(1)
* 稳定性:稳定的排序
public void insertsort(int[] array){ //i 代表 要交换的数据的下标,i在数组内一直向前遍历数组 for (int i = 1; i < array.length ; i++) { int j = i-1;//把 j 放到 i 的左侧 int tmp = array[i];//存储 要交换的值 for (; j>=0; j--) { //内层循环,可能循环多次,目的是把交换后的数据向前比对。1 //如果这个tmp的数据是整个数据最小的,等到 j=-1 ,跳出循环, //使用array[j+1] = tmp; 也就是array[0] = tmp; if(array[j] > tmp){ //交换前后数据 array[j+1] = array[j]; }else { //等到前面的数据不比tmp大了,tmp就固定了 array[j+1] = tmp; break; } } array[j+1] = tmp; //处理边界问题,如果所有的都比tmp大了,就把当前tmp 放到array[0] } }
三、希尔排序
3.1 希尔排序的思路:
希尔排序是对直接插入排序的优化,它的做法是这样的
对一串数据进行分组,像这样分5组的叫 gap = 5,每次对分组内的元素排序,逐渐减小gap,最后gap = 1;
因为我们 知道 直接插入排序 的 特点 是越有序的数组,排列越快,所以产生了希尔排序。
gap = 2 :
代码演示:
/**
* 不稳定的
* 时间复杂度:n^1.3 - n^1.5
* 空间复杂度:O(1)
* @param array
*/
public static void shellSort(int[] array) {
int gap = array.length;
while (gap > 1) {
gap = gap/3+1;//保证了最后一次 gap = 1
shell(array,gap);
}
}
private static void shell(int[] array, int gap) {
for (int i = gap; i <array.length ; i++) {
//i不再从数组第二个开始,而是中间
int tmp = array[i];
int j = i - gap;//每隔gap个数据分在一个组内
for (; j >= 0 ; j-=gap) {//不用细想这句,因为一旦为负,循环进不去,回去
if(array[j] > array[j+gap]){
array[j+gap] = array[j];
}else {
array[j+gap] = tmp;//这句的作用是如果前面比后面的大,
// 就保持原来位置,原来数据,不交换
break;
}
}
array[j+gap] = tmp;
//这句的作用是把已经挪动到后面的数据替换成后面那个较小的数据
//因为正好跳出j的循环时,j-=gap,所以它一定就是刚刚那个j的位置
//想不明白就去调试
}
}
时间复杂度分析:
* 不稳定的
* 时间复杂度:n^1.3 - n^1.5
* 空间复杂度:O(1)