交换排序的基本思想是:两两比较待排序元素,如果发现次序相反则进行交换,直到没有反序为止。
应用交换排序基本思想的两种排序是冒泡排序与快速排序。
一.冒泡排序
1.基本思想
确定一个比较范围,在每次排序中遍历这个范围内的元素,通过比较与交换使这个范围中最大或者最小的元素冒泡到最右侧,逐次缩小这个比较范围,直到整个序列变为已排序序列。
2.算法描述
假定序列S={s0....sn}为含有n个可排序元素的待排序序列,i为控制比较范围的变量,j为比较范围中的元素下标,temp为临时变量。
(1).排序开始,初始化i=n-1,重复(2)、(3)、(4)并递减i,直到i<0结束。
(2).本次冒泡开始,初始化j=0,确定比较范围为j~i
(3).比较相邻元素,如果符合条件则交换
(4).本次冒泡结束。
(5).排序结束。
3.具体实现
只考虑增序排列,java代码如下:
public static void bubbleSort(int[] array) {
int len = array.length;
int temp;
// 外层循环,确保每个元素都被遍历到
for (int i = len-1; i > 0; i--) {
// 内层循环确定比较范围
for (int j = 0; j < i; j++) {
// 比较并交换
if (array[j + 1] < array[j]) {
temp = array[j + 1];
array[j + 1] = array[j];
array[j] = temp;
}
}
}
}
4.算法分析
(1).最好时间复杂度: 最好情况即序列为已排序状态,O(n).
(2).最坏时间复杂度:最坏情况为反序,O(n^2)
(3).平均时间复杂度:O(n^2)
冒泡排序由于元素交换次数(移动次数)较多,性能比直接插入排序要差。
二.快速排序
1.基本思想
快速排序使用了分治策略,即分治法(Divide-and-ConquerMethod)。
分治法的基本思想是:将原来的问题分解为若干个规模更小但是结构与原问题相似的子问题,递归地解决这些小问题,然后将这些小问题的解组合为原问题的解。当然由于递归在某写层面上问题,我们可以通过一些手段消除递归,但是分解->分解->组合->组合的这种结构是不会变的。利用分治法的场景很多,如在面对大数据时我们采用的MapReduce等。
2.算法描述
假定当前待排序序列的无序子序列为S[low...high],使用分治法描述快排算法如下:
(1).分解。在S[low...high]中选定一个元素为基准元素,以该元素为基准将当前序列分为S[low...pivotpos-1]与S[pivotpos+1...high]两个序列,并使左侧序列中所有元素
小于基准元素,而右侧序列所有元素大于等于基准元素,
(2).求解。递归排序这两个序列(在该过程中会重复分解-求解-组合的过程)。
(3).组合。当这两个序列的递归调用结束时,两个序列都是有序的,从而整个序列也是有序的。
3.具体实现
/** * 快速排序 * @param array */ public static void quickSort(int[] array){ int len = array.length; if(len > 0){ quickSort(array,0,len-1); } } /** * 快速排序 * @param array * @param left * @param right */ private static void quickSort(int[] array,int left,int right){ if(left < right){ int pivot = selectPivot(array,left,right); quickSort(array,left,pivot); quickSort(array,pivot+1,right); } } /** * 获取枢纽元的下标并分组 * @param array * @param left * @param right * @return */ private static int selectPivot(int[] array,int left,int right){ int temp = getMiddleNode(array,left,right); //以枢纽元为枢纽进行分组 while(left<right){ while(left < right && array[right] >= temp){ right--; } array[left] = array[right]; while (left < right && array[left] <= temp) { left++; } array[right] = array[left]; } array[left] = temp; return left; } /** * 简单选取left为枢纽元下标,可以改变策略 * @param array * @param left * @param right * @return */ private static int getMiddleNode(int[] array,int left,int right){ return array[left]; }
4.算法分析
(1).时间复杂度
最坏情况,即枢纽元始终是最小元素,O(n^2)
最好情况,即枢纽元正好位于中间,O(nlogn)
平均情况,O(nlogn)
(2).空间复杂度
快速排序在计算机系统中需要一个Stack来进行函数的递归调用。根据树的定义:
如果每次分解比较均匀,则该树的高度为O(logn),即空间复杂度为O(logn)
如果每次划分都不均匀,则该树的高度为O(n),即空间复杂度为O(n)
(3).关于枢纽元的选择
当前无序区中选择的枢纽元是否合理决定着再次分解的均匀程度,也决定了快速排序最终的性能,所以有两种方式常用方式:
a.三数中值分割法
即在示例程序中getMiddleNode方法中求array[left]、array[(left+right)/2]、array[right]三个数的中值作为枢纽元
b.随机数枢纽元
即在示例程序中getMiddleNode方法中求取位于left与right之间的随机数(left=<random<=right),以array[random]为枢纽元
总结:冒泡排序是最基本的交换排序算法,性能不如直接插入排序,较为稳定。快速排序可以在O(nlogn)的时间复杂度上执行,也较为稳定。