Java排序算法总结之(二)——基于交换排序(冒泡排序、快速排序)

排序方法可以分为两种:内部排序 和 外部排序

内部排序的方法很多,常用的大致可以分为:

  1. 插入排序(直接插入排序、折半插入排序、希尔排序)
  2. 交换排序(冒泡排序、快速排序)
  3. 选择排序(简单选择排序、堆排序)
  4. 归并排序
  5. 基数排序

交换排序

1.冒泡排序

基本操作:

  1. 比较相邻的元素。如果第一个比第二个大,就交换他们两个。
  2. 对每一对相邻元素作同样的工作,从开始第一对到结尾的最后一对。这步做完后,最后的元素会是最大的数。
  3. 针对所有的元素重复以上的步骤,除了最后一个。
  4. 持续每次对越来越少的元素重复上面的步骤,直到没有任何一对数字需要比较。
冒泡排序简介易懂,是入门的程序设计算法之一。

Java代码:

public class BuddleSort {
	public static int[] sort(int[] s){
		for(int i = 0; i < s.length - 1; i++){//进行 n-1 趟排序
			for (int j = 0; j < s.length - i - 1; j++) {
				if(s[j] > s[j + 1]){
					int temp = s[j];
					s[j] = s[j + 1];
					s[j + 1] = temp;
				}
			}
		}
		return s;
	}
}
效率分析:
空间:辅助空间O(1)

时间:按上述代码执行,即使是正序排列的数列,也要 n-1 趟排序,需要进行 n(n-1)/2 次比较,时间复杂度为O(n^2)。

然而当序列为正序时,冒泡排序如果能在内部循环第一次运行时,使用一个旗标来表示有无需要交换的可能,也可以把最好的复杂度降低到O(n)。

改进后的代码:

public class BuddleSort {
	public static int[] sort(int[] s){
		boolean flag = true;//
		for(int i = 0; i < s.length - 1 && flag; i++){//判断的条件需要加上对旗标的判断
			flag = false;//旗标先置为false,若下列交换不发生,证明已经排序好,则退出循环
			for (int j = 0; j < s.length - i - 1; j++) {
				if(s[j] > s[j + 1]){
					int temp = s[j];
					s[j] = s[j + 1];
					s[j + 1] = temp;
					flag= true;
				}
			}
		}
		return s;
	}
}
稳定性:true.因为每次只比较交换相邻两数据,当两数相等时不做调整,故为稳定的。

若在每次走访数列时,把走访顺序反过来,也可以稍微地改进效率。有时候称为鸡尾酒排序,因为算法会从数列的一端到另一端之间穿梭往返。

2.鸡尾酒排序

示例:
     Java排序算法总结之(二)——基于交换排序(冒泡排序、快速排序)_第1张图片
Java代码:
public class CocktailSort {
	public static int[] sort(int[] s) {
		int start = 0, end = s.length - 1;
		int i, temp;
		
		while (start < end) {
			flag = false;
			for (i = start; i < end; i++) {
				if (s[i] > s[i + 1]) {
					temp = s[i];
					s[i] = s[i + 1];
					s[i + 1] = temp;					
				}
			}
			end--;
			for (i = end; i > start; i--) {
				if (s[i - 1] > s[i]) {
					temp = s[i];
					s[i] = s[i - 1];
					s[i - 1] = temp;					
				}
			}
			start++;
		}
		return s;
	}
}

以序列(2,3,4,5,1)为例,鸡尾酒排序只需要访问一次序列就可以完成排序,但如果使用冒泡排序则需要四次。但是在乱数序列的状态下,鸡尾酒排序与冒泡排序的效率都很差劲。

3.快速排序

基本思想:使用分治法(Divide and conquer)策略来把一个序列(list)分为两个子序列(sub-lists)。
  1. 从数列中挑出一个元素,称为"基准"(pivot),
  2. 重新排序数列,所有元素比基准值小的摆放在基准前面,所有元素比基准值大的摆在基准的后面(相同的数可以到任一边)。在这个分区结束之后,该基准就处于数列的中间位置。这个称为分区(partition)操作。
  3. 递归地(recursive)把小于基准值元素的子数列和大于基准值元素的子数列排序。
具体做法:
  1. 设两个指针 low 和 high ,设枢轴值pivotkey为数列中的某一个数,一般设为初始low指针指向的数(位于当前数列的左部,左部存放小于枢轴值的数);
  2. 从 high 所指的起 向前搜索找到第一个值小于pivotkey的值,置于枢轴值的位置(即当前low指向的位置);
  3. 接着从low所指的位置起向后搜索,找到第一个大于pivotkey的值,置于之前high所指向的位置;
  4. 重复2,3步,直到low = high,将枢轴值赋予该处,此时,枢轴值左边都比其小,右边都比它大;
  5. 对左右两部分递归实现排序
示例:
    Java排序算法总结之(二)——基于交换排序(冒泡排序、快速排序)_第2张图片
代码:(更详细的说明,请转到-->白话经典算法系列之六 快速排序 快速搞定)
public class QuickSort {
	
	public static int[] sort(int[] s, int l, int h){
		if (l < h) {//判断左右指针的关系,作为递归的出口
			int low = l;//左指針
			int high = h;//右指針
			int pivotKey = s[l];
			while(low < high){
				while (low < high && s[high] > pivotKey) {
					high--;
				}
				if (low < high) {
					s[low++] = s[high];
				}
				while (low < high && s[low] < pivotKey) {
					low++;
				}
				if (low < high) {
					s[high--] = s[low];
				}
			}
			s[low] = pivotKey;
			sort(s, l, low - 1);
			sort(s, low + 1, h);
		}
		return s;
	}
}
效率分析:
空间:快速排序需要一个栈空间来实现递归,最好情况是O(nlog n)和最坏情况是O(n^2*log n)的空间需求
时间:最差时间复杂度O(n^2)最优时间复杂度O(nlog n)平均时间复杂度O(nlog n)
稳定性:false.在比较过程中,若遇到与枢轴值相等的元素,那么排序后出现在其左侧和右侧都有可能,故是不稳定的

参考文献:

  1. 冒泡排序-Wikipedia
  2. 鸡尾酒排序-Wikipedia
  3. 数据结构-严蔚敏版
  4. 快速排序-Wikipedia

你可能感兴趣的:(Java排序算法总结)