说起来排序,始终会出现这样的问题
这是一个普通的数组,如果我们想从小到大排序的话,无论是简单的选择排序还是冒泡排序都会有额外的工作。
举例子,使用冒泡排序:
第一轮
显然现在已经是有序的了,但是冒泡排序还需要进行七轮,虽然不会移位,但是还是会逐个比较,这明显是额外的,多余的工作。
这就是这次要说的重点,算法这东西学的是原理,考的是应用,要会的是根据实际情况调整,优化,使得算法更加高效。
那么这种情况该如何解决?我们可以在冒泡排序每轮的时候检测当前是否已经是有序,如果已经是有序,那么就结束排序。
如何判断有序:
根据冒泡算法原理,当前轮如果没有元素交换发生,那么就是有序的
实际代码:(实际例子就使用java写了,比较通用的写法,其他程序也差不多,推荐的在线网站https://tool.lu/coderunner/)
//这里的冒泡算法就是现成的前两天的那个 所以相关的注释就先不要了 仅重视这次有关的值和变量
class Untitled {
public static void main(String[] args) {
int[] array = new int[]{9,1,2,3,4,5,6,7,8}; //我们的需要排序的数组
output(array);
int temp=0; //临时变量
boolean b = true; //是否有序的标志
for(int i=array.length-1;i>0;i--)
{
b = true; //每次初始都假设有序
for(int j=0;j<i;j++) //内层遍历从0到该位置的所有元素
{
if(array[j]>array[j+1]) //大于就交换 否则不变
{
temp = array[j+1];
array[j+1]=array[j];
array[j]=temp;
b = false; //交换就证明不是有序的
}
}
if(b) //如果该轮没发生交换 则有序
{
System.out.println("已经有序了!");
output(array);
break;
}
}
}
//这个函数也是直接拿出来的 注意需要加上static标志符
public static void output(int[] array)
{
String s1=""; //定义一个空字符串
StringBuffer sb=new StringBuffer(s1);
for(int i=0;i<array.length;i++)
{
sb.append(array[i]+","); //遍历int数组,追加到sb;
}
String s2=sb.toString(); //返回缓冲区对象的toString()
System.out.println(s2);
}
}
//输出结果
9,1,2,3,4,5,6,7,8,
已经有序了!
1,2,3,4,5,6,7,8,9,
这个没有什么大的难度,就是加了一个标志
(补充:为什么java的main函数是静态的,而且静态函数不能调用非静态函数?)
静态变量和函数都是在运行时就直接分配了内存可以使用的,不是静态的需要new之后才可以用。
main作为程序入口,肯定是直接用方便啊,要不去哪里new它
非静态的物需要new,所以静态函数不能直接使用非静态函数也是跟上边一个道理
鸡尾酒排序
上边已经了解了这样的一个简单问题,下面一个更有意思的情况
使用冒泡方法试一试
第一轮
以此类推
......
命名第一轮之后只有一个数位置偏了,其他的都很有序但是没得办法,此时真的是要多难受有多难受
我们希望能把1直接拿到前面,这个样子
这就是传说中的鸡尾酒排序的道理,我们正常是从左到右这样一轮一轮,为了考虑像本文这样的特殊情况,我们就不每次都是从左到右的循环了,第一次从左到右,第二次从右到左,反复交替。
此时再次从左往右就发现已经没有元素交替了,已经成功了,哈哈
(这个需要注意的是,从左往右是大的右移,从右往左比较时,是小的往左移动)
那么算法应该如何实现?
- 每一轮从左到右,从右到左各一次,那么长度为l的数组仅需要l/2次循环就可以排序完成(就是基本的冒泡排序)
- 每次循环判断是否已经有序
实际代码:(实际例子就使用java写了,比较通用的写法,其他程序也差不多,推荐的在线网站https://tool.lu/coderunner/)
class Untitled {
public static void main(String[] args) {
int[] array = new int[]{9,2,3,4,5,6,7,8,1}; //我们的需要排序的数组
output(array);
int temp=0; //临时变量
boolean b = true;
for(int i=0;i<array.length/2;i++)
{
System.out.println("第"+(i+1)+"轮");
//从左往右,当i=0时需要定位的元素pos为array.length-1
//随着i增加 pos应该变小 所以表达式上界应该为array.length-1-i
//假如上界为array.length-i i=0时j最大为array.length-1 j+1就超界了
for(int j=i;j<array.length-1-i;j++)
{
if(array[j]>array[j+1]) //大于就交换 否则不变
{
temp = array[j+1];
array[j+1]=array[j];
array[j]=temp;
b = false; //交换就证明不是有序的
}
}
if(b) //如果该轮没发生交换 则有序
{
System.out.println("已经有序了!");
output(array);
break;
}
b = true; //恢复标志
//此次是从右往左遍历,比较谁更小
//当i=0时 排位的目标为index 0 所以以此类推循环的下界为i+1
//上界应不包括上次已经排序好的位置,即array.length-2-i
for(int j=array.length-2-i;j>i;j--)
{
if(array[j]<array[j-1])
{
temp = array[j-1];
array[j-1]=array[j];
array[j]=temp;
b = false;
}
}
if(b) //如果该轮没发生交换 则有序
{
System.out.println("已经有序了!");
output(array);
break;
}
}
}
public static void output(int[] array)
{
String s1=""; //定义一个空字符串
StringBuffer sb=new StringBuffer(s1);
for(int i=0;i<array.length;i++)
{
sb.append(array[i]+","); //遍历int数组,追加到sb;
}
String s2=sb.toString(); //返回缓冲区对象的toString()
System.out.println(s2);
}
}
# 输出
9,2,3,4,5,6,7,8,1,
第1轮
第2轮
已经有序了!
1,2,3,4,5,6,7,8,9,
实际上这里比较繁琐的就是确定循环的上下界,因为每次循环都需要扣掉已经排序过的了。
一般这种我们我都画图解决,然后假设的方式,以后遇到就很容易用脑袋转过来弯
对于大循环是i for(int i=0;i<length/2;i++)
第一轮i=0,排序最后一位 我们比较的是 j和j+1位置的元素 所以j的最大取值是j=length-2 这样定位到的就是最后一位
第二轮i=1,所以j的最大取值应该是j=length-3 所以使用表达式为 j<length-1-i
看下限,第一轮从0开始 第二轮就是从1开始 所以j=i是下界
即(int j=i;j<length-1-i;j++)
当从右往左遍历时候,由于上边已经过了一轮了,所以需要从length-2开始,在考虑i的循环次数,即初始j=length-2-i
由于我们使用的是j和j-1比较,所下界界第一轮i=0时为1,第二轮i=1时候为2 即j>i
即(int j=length-2-i;j>i;j--)
说来说去还是麻烦了,最好的方法就是自己假设一下上界和下界,然后将i的值代入一下就能看出来是否有问题了。
程序总是想起来简单,动手编编就都是问题了,贵在动手。
忘了说了...为啥叫鸡尾酒排序,大家看看鸡尾酒再想想鸡尾酒调制的时候就懂了