排序算法一直是一个很困惑我的问题,早在刚开始接触 数据结构的时候,这个地方就很让我不解。就是那种,总是感觉少了些什么的感觉。一开始,重新来过,认真来学习这一部分,也总是学着学着就把概念记住了。过了一会,让我重新来写这些算法,又变得不会。
我明白我的问题,就是感觉自己懂了,其实,还是不懂。只要逻辑乱一次,就基本oh my gad了。
然后我就看视频,找一些算法的更通俗易懂的一些理解。还是感觉差了些什么。
不过,最后还是找到一些自己的理解方式。
做关于排序的算法题:
首先你要明白它是怎么排序的。以选择排序为例:
ok,我们知道选择排序就是每一轮寻找一个最小的值。
继续:看这个选择排序的过程,共进行了6轮、每一轮又进行了多少次比较?
这个问题,就是一个嵌套循环的问题:
写出这个循环的嵌套才能进行下一步:口诀就是外层循环表示行,内层循环表示列。我看了很对其它的解释,个人感觉所有嵌套循环,记住这个特点就足够了。
回到这个算法:
//很明显,外层循环有6轮,那么外层循环的判定就是arr.length-1。这个很简单。
//表示的范围就是【0~6】
for(int i=0;i<arr.length-1;i++){
//内层循环表示列。
for(){
}
}
重点来了,怎么写内循环的问题!!!
这里一定要学好,因为不同的排序算法,基本上它的内循环都不同。但是它们都会存在一定的规律和共性。
我们在看这个算法。
每一轮都是找最小值。第一轮,从7个元素找;第二轮从6个元素里面找;第三轮就是5个。
那么内循环表示列:就相当于,第一轮7列(7个元素),第二轮6列(除第一个元素之外。)
for(int i=0;i<arr.length-1;i++){
//内层循环表示列。
//它的范围就是:0~6(7列)、1~6(除了第一个元素0之外的6列)、2~6(5列)
for(int j=i;j<arr.length-1;j++){
}
}
然后,循环嵌套的问题我们就解决了,基本上,选择排序算法我们的问题就解决一半了!
我们再来看不同排序算法间嵌套循环的共性:以插入排序的嵌套循环为例。
你可以自己尝试一下,写出这个循环的嵌套语句:
//6行,就是外循环6次
for(int i=0;i<arr.length-1;i++){
//内循环,观察这个算法的要求。
//第一行:从第二列开始和前一个比较。
//第二行:从第三列和前面两个比较。
//第三行:就是第四个,和前面三个比较。这里不太好画图,自己画一下图,就是一种滑坡的形状。
//那么初始条件,我们可以设为i+1。(从第二列开始)。那么判定条件是什么呢?
//第一次判定只比较一次,第二次判定比较两次,第三次就是比较三次。那就是j>0咯。
//它的范围就是:1(和0比较)、2~1(和0,1比较)、3~1(和0,1,2比较)
for(int j=i+1;j>0;j++){
}
}
这个算法和选择排序算法的嵌套循环的共性是什么呢?
重点在内循环。
我的理解是:
在外循环 i表示行数,内循环 j表示列的前提下。那么一定会在内循环的初始条件或判定条件用到i,而且有且只能用到一次。要么i用在初始条件,要么用在判定条件。
比如:看选择排序的算法:
内循环:for(int j=i;j<arr.length-1;j++)
i用在初始条件。
插入排序的算法:
内循环:for(int j=i+1;j>0;j--)
i用在初始条件。
冒泡排序:这个很简单,就是从第一个往后面比较,小的往前,大的往后。
内循环:for(int j=0;j<arr.length-i-i;j++)
i用在判定条件。
上面三个算法的外循环都是一样的。
我之前做这个算法题时,就这样写过:
错误的写法,选择排序:
for(int j=i;j<arr.length-i;j++)
如果是这样写,那么它的范围就是:
0~6(第一轮比较和之前是一样的比较的比较7次)
1~5(这里只比较了5次)
2~4
3~3(到这里就结束了,不会运行。)
造成的问题就是:只有效比较了三行(也不是很有效,只有效比较了第一行)。后面三行就根本不会比较,只打印出来。
而我们需要比较是6行。和i的关系是紧密联系的!
故此:我得出结论,在排序算法或者嵌套循环有关,在内循环里面如果要使用i。那么只能使用一次。
故此:我得出结论,在排序算法或者嵌套循环有关,在内循环里面如果要使用i。那么只能使用一次。
欧克,关于循环嵌套的问题就到这。想自己训练一下,可以尝试打印9*9乘法口诀表,或者打印三角形的形状。这些都运用了嵌套循环。
在来看算法问题:
以插入排序的算法为例:
它的核心就是从第二个元素开始往前比较。你要从内存空间的角度去思考。这里没有涉及堆和栈,堆和栈也很重要,不过在面向对象的时候才会学习堆和栈的空间变化。
它的空间变化:只以前四个数为例。
原数据:56 、34、45、12、67、52、4
从算法是赋值的角度变化:
两两比较,小的排前面。在算法角度就是,两个交换了位置。具体变化:56、34、45、12
第一行:34 34 45 12 ~ temp=56
34 56 45 12
第二行:34 45 45 12 ~ temp=56
34 45 56 12
第三行:34 45 12 12 ~temp=56
34 45 12 56
34 12 12 56 temp=45
34 12 45 56
12 12 45 56 temp=34
12 34 45 56
我们第三行的最终比较结果就是12、34、45、56、67、52、4。
那么就可以写算法了:
//外循环6行
for(int i=0;i<arr.length-1;i++){
//内循环
for(int j=i+1;j>0;j++){
if(arr[j] < arr[j-1]){ //看上面的规律,是不是两两比较。然后小的排前面。
temp = arr[j];
arr[j-1] = arr[j];
arr[i] = temp;
}
}
Systemo.out.println(Arrays.toString(arr)); //打印每一行的结果
}
算法的主要问题在哪,主要就是j、j+1或者j-1这样的判断。不知道怎么用,会不会出事。这就是第二个困扰的问题。
这个问题解决了,基本上算法排序就比较容易理解了。
其实,关于j 、j+1这样的用法和嵌套循环也很类似。就是j+1的范围不能超过原来数组的长度。j-1不能小于原来数组的长度。超出了,基本上这个算法就出错了。
建议:打草稿咯,像我上面那个例子一样,去画一下内存的变化。就不会出错。
这里,关于插入排序的算法就很有意思了,它的核心算法从每一轮找最小的值,这个最小的值不能通过两两比较交换位置。(透露一下:通过比较返回索引值。直到找到最小的值所在索引,然后和第一个数进行交换。)
选择排序核心算法:
for(int i=0;i<arr.length-1;i++){
//内层循环表示列。
//它的范围就是:0~6(7列)、1~6(除了第一个元素0之外的6列)、2~6(5列)
index=i;
for(int j=i;j<arr.length-1;j++){
if(arr[index] > arr[j+1]){
index=j+1;
}
}
temp = arr[index]; //将第一个位置和找到索引最小的元素进行交换
arr[i] = arr[index];
arr[index] = temp;
System.out.println(Arrays.toString(arr));
}
选择排序的算法去画画它的内存变化吧。
之后我会不停添加其它的算法来验证我的理解和猜想。暂时先写到这了。
补充:嵌套循环还有一个条件,在循环用的一个数组的情况下,内循环的范围不能超过外循环,外循环的范围不能超过原数组。