数据结构与算法-排序算法(鸡尾酒排序-特殊情况的排序方法)

说起来排序,始终会出现这样的问题

这是一个普通的数组,如果我们想从小到大排序的话,无论是简单的选择排序还是冒泡排序都会有额外的工作。

举例子,使用冒泡排序:

第一轮

显然现在已经是有序的了,但是冒泡排序还需要进行七轮,虽然不会移位,但是还是会逐个比较,这明显是额外的,多余的工作。

 

这就是这次要说的重点,算法这东西学的是原理,考的是应用,要会的是根据实际情况调整,优化,使得算法更加高效。

那么这种情况该如何解决?我们可以在冒泡排序每轮的时候检测当前是否已经是有序,如果已经是有序,那么就结束排序。

如何判断有序:

根据冒泡算法原理,当前轮如果没有元素交换发生,那么就是有序的

实际代码:(实际例子就使用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的值代入一下就能看出来是否有问题了。

程序总是想起来简单,动手编编就都是问题了,贵在动手。

 

忘了说了...为啥叫鸡尾酒排序,大家看看鸡尾酒再想想鸡尾酒调制的时候就懂了

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值