老是学了就忘,打算开始写博客自己记录一下,相当于笔记吧。(基于尚学堂韩顺平的java数据结构视频总结)
一:插入排序中的直接插入排序:
直接插入排序,是最基本的插入排序算法,个人感觉就和打牌一样,手里的牌分为两块,一块有序,往往放在左边,另一块是无序的,需要把每张去慢慢调整,所以就做起了把每张牌向左边有序块插入的动作。
举个例子:比如数组{101,34,119,1}
第一次插入:因为要左边有序,所以默认假设数组第一位后的元素是无序的,从数组第二位开始,取第二位34,与第一位101作比较,发现小于,所以插入后是,34,101,119,1,有序块增加一位;
同理第二第三次,每次都与前面的有序块作比较,第二次插入:取119比较前面的有序块,34,101,119,1; 第三次插入:取1,与前面的有序块的每一位挨个比较,(此处的规则在下面说明),总之插入完后,结果为1,34,101,119那么问题来了,需要哪些变量?
通过上面说明,发现需要待插入元素insertVal,待插入的位置insertIndex,但是待插入元素的位置又不好确定,通过例子,发现待插入元素每一次开始比较都要和前一个元素(即有序块的最后一个元素)比较,那么可以默认insertIndex的位置时待插入元素的前一个元素的位置。那如果比较后要插入,那咋办啊?(此处是直接插入排序的核心)
可以用移位法,比如第一次插入,待插入元素刚好比前一个元素小,可以先用中间变量保存indexVal,然后把前一个元素后移,即往后复制一位实现后移,最后把中间变量赋给待插入的位置。那么如果有序块里有若干个元素呢,则用循环,如果符合判断条件,待插入位置后面的有序块的元素都要往后移一位, 使用while循环,但循环执行完,说明待插入的位置已经找到,但是此处也体现了插入排序的缺点,当插入数据量少时,效率慢。
以下是移位的代码块:
int insertVal; //待插入的数
int insertIndex; //待插入的下标,待插入数的前一位
//insertIndex>=0 :防止数组越界 insertVal<arr[insertIndex]:若待插入的数比前面的小,则将前面的数后移
while (insertIndex>=0 && insertVal<arr[insertIndex]){
arr[insertIndex+1]=arr[insertIndex];
insertIndex--; //每一次后移完,则下标减一,再与前面的数比较
}
//当退出while循环时,说明插入的位置已经找到
arr[insertIndex+1]=insertVal;
}
移位的问题解决后,插入排序也就大体完成了,代码如下:
public static void insertSortExample(int[] arr){
int insertVal=0; //待插入的数
int insertIndex=0-1; //待插入的下标,待插入数的前一位
for (int i=1;i<arr.length;i++){ //插入n-1次
insertVal=arr[i];
insertIndex=i-1;
//insertIndex>=0 :防止数组越界 insertVal<arr[insertIndex]:若待插入的数比前面的小,则将前面的数后移
while (insertIndex>=0 && insertVal<arr[insertIndex]){
arr[insertIndex+1]=arr[insertIndex];
insertIndex--; //每一次后移完,则下标减一,再与前面的数比较
}
//判断是否需要赋值 ,代码优化
if((insertIndex+1)!=i){
//当退出while循环时,说明插入的位置已经找到
arr[insertIndex+1]=insertVal;
}
}
// print(arr);
}
二:希尔排序(优化过的直接插入排序,又称缩小增量排序)
对于插入排序的问题,如果有序块的元素逐渐增多,那么待插入元素的比较次数就会增多,效率变慢,因为有序块只有一个,也只能慢慢挨个元素比较。
希尔排序的优化在于,可以进行分组排序,即把数组按一定规则(即按增量(按一定数组间距))分组,然后分组进行排序,排完后缩小增量,也就缩小了间距,可以渐渐合并为一个数组,完成排序。
个人能力有限,解释不清,看例子吧:
举例:{8,9,1,7,2,3,5,4,6,0}
说说增量怎么取,默认每次/2,取得增量,即增量gap=gap/2,gap默认为数组长度。
(1)第一次,增量为5,说明分组按数组的间距为5分,分完后:{8,3},{9,5},{1,4},{7,6},{2,0},共五组,然后先使用交换法比较,即对应增量的两数比较交换。
注意,虽然分组,但是是虚拟组,并不存在,都是按数组下标-5分组的(因为代码中从第6位开始的),代码如下:
int temp=0;
//第一轮轮排序:将十个数据分为了10/2=5即(增量为5的)五组,(8,3),(9,5)(1,4)(7,6)(2,0)
for (int i = 5; i <arr.length ; i++) {
//遍历各组循环中的所有元素(共五组) 步长为:5
for (int j = i-5; j>=0 ; j-=5) {
if(arr[j]>arr[j+5]){ //如果前面的大于后面的
temp =arr[j];
arr[j]=arr[j+5];
arr[j+5]=temp;
}
}
}
System.out.println("第一轮希尔排序后:"+toString(arr));
比较完成后,此数组为:{3,5,1,6,0,8,9,4,7,2},若有疑惑,根据上面的增量即下标+5对比看
(2)第二次,增量为5/2=2,即间距为2,分完后:{3,1,0,9,7},{5,6,8,4,2}
同理,代码如下:
//第二轮:将五组数据继续分为增量为5/2=2的两组
for (int i = 2; i <arr.length ; i++) {
//遍历各组循环中的所有元素(共两组) 步长为:2
for (int j = i-2; j>=0 ; j-=2) {
if(arr[j]>arr[j+2]){ //如果前面的大于后面的
temp =arr[j];
arr[j]=arr[j+2];
arr[j+2]=temp;
}
}
}
System.out.println("第二轮希尔排序后:"+toString(arr));
执行完成后,此数组为:{0,+2, 1,+4, 3,+5, 7,+6, 9,+8} ,+用来标注一下,便于理解。
(3)第三次:增量为2/2=1,说明间距为1,也就是就一个数组,即{0,+2, 1,+4, 3,+5, 7,+6, 9,+8},则比较,得出排序好的数组,代码如下:
//第三轮:将两数据继续分为增量为2/2=1的一组
for (int i = 1; i <arr.length ; i++) {
//遍历各组循环中的所有元素 步长为:1
for (int j = i-1; j>=0 ; j-=1) {
if(arr[j]>arr[j+1]){ //如果前面的大于后面的
temp =arr[j];
arr[j]=arr[j+1];
arr[j+1]=temp;
}
}
}
System.out.println("第三轮希尔排序后:"+toString(arr));
通过上述(1)(2)(3),发现增量也是分的组数(因为增量即间距),而且增量gap也可作循环的判断条件,整合代码如下:
int temp=0;
for (int gap=arr.length/2;gap>0;gap/=2){
//遍历各组循环中的所有元素(共gap组) 步长为:gap
for (int i = gap; i <arr.length ; i++) {
for (int j = i-gap; j >=0 ; j-=gap) {
if(arr[j]>arr[j+gap]){
temp=arr[j];
arr[j]=arr[j+gap];
arr[j+gap]=temp;
}
}
}
}
System.out.println("希尔排序后:"+toString(arr));
但是发现一个问题,使用交换法需要使用三层for循环,比较笨重,于是可以改进,使用移动法即直接插入排序代替交换法,需要注意的是,此时待插入的位置可不是从待插入元素的前一个位置开始,应该为增量(因为按增量分组的),为了方便,默认为待插入元素的下标,代码如下:
for (int gap=arr.length/2;gap>0;gap/=2){
//从第gap个元素开始逐个对其所在的组进行直接插入
for (int i = gap; i<arr.length; i++) {
//直接插入排序
int j=i; //待插入的下标
int temp=arr[i]; //待插入的数
if(arr[j-gap]>arr[j]){
while (j-gap>=0 && temp<arr[j-gap] ){
//移动
arr[j]=arr[j-gap];
j-=gap;
}
arr[j]=temp;
}
}
}
System.out.println("希尔排序后:"+toString(arr));