排序算法的稳定性:假定在待排序的记录序列中,存在多个具有相同的关键字的记录,若经过排序,这些记录的相对次序保持不变,即在原序列中,ri=rj,且ri在rj之前,
而在排序后的序列中,ri仍在rj之前,则称这种排序算法是稳定的;否则称为不稳定的。
冒泡排序:稳定
public void bubbleSort(){//O(n^2) //每一趟的比较一次就可能交换一次
for(int i=0;i<nElements;i++){
for(int j=0;j<nElements-i-1;j++){/
if(array[j]>array[j+1]){
swap(j,j+1);
}
}
}
}
private void swap(int one, int two){
int temp;
temp = array[one];
array[one] = array[two];
array[two] = temp;
}
选择排序:不稳定
public void selectSort(){ //O(n^2) 每一趟,比较次数同上,但最多只进行一次交换,所以比冒泡快。
//int maxLocal = 0;
for(int i=0;i<nElements;i++){
int minIndex = i;
for(int j=i+1;j<nElements;j++){
if(array[minIndex] > array[j]){
minIndex = j;
}
}
if(minIndex != i){
swap(minIndex,i);
}
}
}
插入排序:稳定
public void insertSort(){// O(n^2) 比冒泡快一倍,比选择排序略快,数据基本有序时,速度较快,但数据逆序时,不如冒泡快。
int key; //存储要插入的元素
int j; //存储当前元素的位置
int i; //存储当前元素的前一个元素的位置,从后面向前比较
for(j=1;j<nElements;j++){
key = array[j];
i=j-1;
while(i>=0 && key<array[i]){ //如果插入元素前的元素比它大,并且数组未比较到第一个元素前
array[i+1] = array[i]; //将大的元素向后移动,第一次移动时覆盖掉key值所在数组中的位置;
i--; //继续向前比较
} //找到插入位置时;
array[i+1] = key; //插入到不比key大的值的后面。
}
}
二路归并排序:稳定 //O(nlogn)
public int[] mergeSort(int []array1, int []array2){//假定数组全部被数据元素填满,没有空余项;
int []array3 = new int[array1.length + array2.length];//
int array1Index = 0;
int array2Index = 0;
int array3Index = 0;
while(array1Index < array1.length && array2Index < array2.length){
if(array1[array1Index] < array2[array2Index]){
array3[array3Index++] = array1[array1Index++];
}else{
array3[array3Index++] = array2[array2Index++];
}
}
while(array1Index < array1.length){
array3[array3Index++] = array1[array1Index++];
}
while(array2Index < array2.length){
array3[array3Index++] = array2[array2Index++];
}
return array3;
}
多路归并排序的递归实现:
归并排序的定义
归并排序算法采用的是分治算法,即把两个(或两个以上)有序表合并成一个新的有序表,即把待排序的序列分成若干个子序列,每个子序列都是有序的,然后把有序子序列合并成整体有序序列,这个过程也称为2-路归并.注意:归并排序的一种稳定排序,即相等元素的顺序不会改变.
归并排序的原理
常见的排序主要有两种,一种是先把待排序的序列一次分割,使子序列的长度减小至1,然后在合并,另外一种是把待排序两两分组排序然后在合并。
归并排序实现的示例代码:
归并排序的最好、最坏和平均时间复杂度都是O(nlogn),而空间复杂度是O(n),比较次数介于(nlogn)/2和(nlogn)-n+1,赋值操作的次数是(2nlogn)。因此可以看出,归并排序算法比较占用内存,但却是效率高且稳定的排序算法。
希尔排序:不稳定, 比插入排序快
public static void shellSort(int []array){//假定数组长度即数据项的个数,即数组所有数据项均被填满,没有空余项;
int interval = 1; // 间隔,初始值为1;
while(interval < array.length/3){
interval = interval * 3 + 1; //计算间隔的最大值;
}
while(interval > 0){ //对于每一个间隔进行分组;
for(int i = interval; i<array.length;i++){ //对每个元素按组分别进行插入排序;
int temp = array[i]; //将待插入排序的元素取出;
int groupSub = i-interval; //标记当前组的最后一个比较元素的下标(从后向前比较);
while( groupSub >= 0 && array[groupSub] > temp){ //若待排序元素的值比排好序的数组的最后一个元素小且比较元素存在
array[groupSub+interval] = array[groupSub] ;//将比较元素后移;
groupSub = groupSub - interval; //继续跟前面的元素比较
}
array[groupSub+interval] = temp; //找到插入位置,将排序值插入到不符合比较条件的元素的后面一个位置;
}//for
interval = (interval-1)/3; //计算下一个更小的间隔值,重新按其划分,重新分组排序;
}//while
}//shellSort
快速排序:不稳定,平均效果最好
public static void quickSort(int []array,int left, int right){ //O(N*logN)
if(left < right){
int key = array[left];//将枢纽的值设置为当前数组的left标记值,
//而不是0下标值,递归调用时left~right指向将要快排的数组片段
int i = left;
int j = right;
while(i < j){
while(i < j && array[j] > key){//从右端找到比key小的值移到左边,未找到则j下标--左移
j--;
}
if(i < j){ //若不是因为比较过头,则说明找到了比key小的值,则将其填到左边i坑里
array[i++] = array[j];//i从下一个开始继续找
}
while(i<j && array[i] < key){ //从左端找到比key大的值移到右边,未找到则i++右移
i++;
}
if(i < j){ //若不是因为比较过头,则说明找到了比key大的值,则将其填到右边j坑里
array[j--] = array[i];//j从下一个开始继续找
}
}
array[i] = key; //跳出循环,则说明i==j,最后一个坑留给key
quickSort(array, left, i-1 );
quickSort(array, i+1, right);
}
}
快速排序的时间性能取决于快速排序递归的深度,可以用递归树来描述递归算法的执行情况。如图9‐9‐7所示,它是{50,10,90,30, 70,40,80,60,20}在快速排序过程中的递归过程。由于我们的第一个关键字是50,正好是待排序的序列的中间值,因此递归树是平衡的,此时性能也比较好。
图9-9-7 |
- T(n)≤2T(n/2) +n,T(1)=0
- T(n)≤2(2T(n/4)+n/2) +n=4T(n/4)+2n
- T(n)≤4(2T(n/8)+n/4) +2n=8T(n/8)+3n
- ……
- T(n)≤nT(1)+(log2n)×n= O(nlogn)
也就是说,在最优的情况下,快速排序算法的时间复杂度为O(nlogn)。
在最坏的情况下,待排序的序列为正序或者逆序,每次划分只得到一个比上一次划分少一个记录的子序列,注意另一个为空。如果递归树画出来,它就是一棵斜树。此时需要执行n‐1次递归调用,且第i次划分需要经过n‐i次关键字的比较才能找到第i个记录,也就是枢轴的位置,因此比较次数为 ,最终其时间复杂度为O(n2)。
平均的情况,设枢轴的关键字应该在第k的位置(1≤k≤n),那么:
由数学归纳法可证明,其数量级为O(nlogn)。
就空间复杂度来说,主要是递归造成的栈空间的使用,最好情况,递归树的深度为log2n,其空间复杂度也就为O(logn),最坏情况,需要进行n‐1递归调用,其空间复杂度为O(n),平均情况,空间复杂度也为O(logn)。
可惜的是,由于关键字的比较和交换是跳跃进行的,因此,快速排序是一种不稳定的排序方法。