排序(sorting)是将一个数据元素的任意序列,重新排列成一个按关键字有序的序列。在排序的过程中需要进行两种基本操作:
- 比较两个关键字的大小;
- 将一个关键字从一个位置移动到另一个位置。
根据排序时待排序的数据元素数量的不同,使得排序过程中涉及的存储器不同,可以将排序方法分为两类。一类是整个排序过程在内存储器中进行,称为内部排序;另一类是由于待排序元素数量太大,以至于内存储器无法容纳全部数据,排序需要借助外部存储设备才能完成,这类排序称为外部排序。
如果在待排序的序列中存在多个具有相同关键字的元素。假设Ki=Kj(1≤ i≤ n, 1≤ j≤ n,
i≠j),若在排序之前的序列中Ri在Rj之前,经过排序后得到的序列中Ri仍然在Rj之前,则称所用的排序方法是稳定的;否则,当相同关键字元素的前后关系在排序中发生变化,则称所用的排序方法是不稳定的。
交换类排序主要是通过两两比较待排元素的关键字,若发现与排序要求相逆,则“交换”之。在这类排序方法中最常见的是起泡排序和快速排序,其中快速排序是一种在实际应用中具有很好表现的算法。
冒泡排序的思想非常简单。首先,将 n 个元素中的第一个和第二个进行比较,如果两个元素的位置为逆序,则交换两个元素的位置;进而比较第二个和第三个元素关键字,如此类推,直到比较第 n-1 个元素和第 n 个元素为止;上述过程描述了起泡排序的第一趟排序过程,在第一趟排序过程中,我们将关键字最大的元素通过交换操作放到了具有 n 个元素的序列的最一个位置上。冒泡排序的实现方法如下:
public static void bubbleSort(int[] array){
for(int i=array.length;i>0;i--){
for(int j=0;j1;j++){
int temp=0;
if(array[j]>array[j+1]){
temp=array[j];
array[j]=array[j+1];
array[j+1]=temp;
}
}
printArray(array);
}
}
以数组[5,3,8,1,6,7,2,9,4,10,11]为例,执行过程如下:
[3,5,1,6,7,2,8,4,9,10,11]
[3,1,5,6,2,7,4,8,9,10,11]
[1,3,5,2,6,4,7,8,9,10,11]
[1,3,2,5,4,6,7,8,9,10,11]
[1,2,3,4,5,6,7,8,9,10,11]
[1,2,3,4,5,6,7,8,9,10,11]
[1,2,3,4,5,6,7,8,9,10,11]
[1,2,3,4,5,6,7,8,9,10,11]
[1,2,3,4,5,6,7,8,9,10,11]
[1,2,3,4,5,6,7,8,9,10,11]
效率分析:
空间效率: 仅使用一个辅存单元。
时间效率: 假设待排序的元素个数为 n,则总共要进行 n-1 趟排序,对 j 个元素的子序列进行一趟起泡排序需要进行 j-1 次关键字比较。由此,起泡排序的总比较次数为n(n-1)/2,因此,起泡排序的时间复杂度为Ο(n2)。
快速排序是将分治法运用到排序问题中的一个典型例子,快速排序的基本思想是:通过一个枢轴(pivot)元素将 n 个元素的序列分为左、右两个子序列 Ll 和 Lr,其中子序列 Ll中的元素均比枢轴元素小,而子序列 Lr 中的元素均比枢轴元素大,然后对左、右子序列分别进行快速排序,在将左、右子序列排好序后,则整个序列有序,而对左右子序列的排序过程直到子序列中只包含一个元素时结束,此时左、右子序列由于只包含一个元素则自然有序。
快速排序的实现方法如下:
public static void quickSort(int[] array,int low,int high){
if(lowint i=low,j=high,temp=array[low];
while(i// 从右向左找第一个小于temp的数
while(iarray[j]>=temp){
j--;
}
if(i// 将找到小于temp的数赋值给上次temp取值的‘坑’,并从该位置向右移动
array[i]=array[j];
i++;
}
// 从左向右找第一个大于temp的数
while(iarray[i]// 将找到大于temp的数赋值给上次找的小于temp的值的‘坑’,并从该位置向左移动
if(iarray[j]=array[i];
j--;
}
}
//当i,j指向同一个位置时,该位置即为temp对应值的排序后位置
array[i]=temp;
//将数组分为两个数组,递归执行快速排序。
quickSort(array,low,i-1);
quickSort(array,i+1,high);
}
}
效率分析:
时间效率:快速排序算法的运行时间依赖于划分是否平衡,即根据枢轴元素 pivot 将序列划分为两个子序列中的元素个数,而划分是否平衡又依赖于所使用的枢轴元素。快速排序的最坏情况是每次进行划分时,在所得到的两个子序列中有一个子序列为空。快速排序的最好情况是在每次划分时,都将序列一分为二,正好在序列中间将序列分成长度相等的两个子序列。T(n) = Θ(n log n)
空间效率: 虽然从时间上看快速排序的效率优于前述算法,然而从空间上看,在前面讨论的算法中都只需要一个辅助空间,而快速排序需要一个堆栈来实现递归。若每次划分都将序列均匀分割为长度相近的两个子序列,则堆栈的最大深度为 log n,但是,在最坏的情况下,堆栈的最大深度为 n。