1.冒泡排序
冒泡排序算法需要遍历几次数组。在每次遍历中,比较连续相邻的元素。如果某一对元素是降序,则互换他们的值;否则,保持不变。由于较小的值像“气泡”一样逐渐浮向顶部,而较大的值沉向底部,故称这种技术为冒泡排序或下沉排序。第一次排序后,最后一个元素成为数组中的最大数。第二次遍历后,倒数第二个元素成为数组中的第二大数。整个过程持续到所有的元素都已排好序。
原始的冒泡排序算法如下:
for(int k=1;k<list.length;k++)
for(int i=0;ilist[i+1]){
int temp;
temp = list[i];
list[i] = list[i+1];
list[i+1] = temp;
}
但是,若果在某次遍历中没有发生交换,那么就不必进行下一次遍历了,因为所有的元素都已经排好序了。使用这一条可以改进上述算法:
boolean nextTime = true;
for(int k=1;k<list.length&&nextTime;k++)
for(int i=0;i<list.length-k;i++)
if(list[i]>list[i+1]){
int temp;
temp = list[i];
list[i] = list[i+1];
list[i+1] = temp;
nextTime = true;
}
在最佳情况下,冒泡排序算法只要一次遍历就能确定数组已排好序,不需要进行下一次的遍历。由于第一次的遍历情况比较次数为,冒泡排序的时间为
。
在最坏的情况下,冒泡排序算法需要进行次遍历。第一次遍历需要
次比较;第二次遍历需要
次比较;依次进行,最后一次遍历需要1次比较。因此,比较的总次数为:
因此,在最坏的情况下,冒泡排序的时间为。
2.归并排序
归并排序算法可以递归的描述为:算法将数组分成两半,对每部分递归的应用归并排序。在两部分都排好序后,对他们进行归并。该算法的伪代码如下:
public static void mergeSort(int[] list){
if(list.length>1){
mergeSort(list[0 ... list.length/2]);
mergeSort(list[list.length/2+1 ... list.length]);
merge list[0 ... list.length/2] with list[list.length/2+1 ... list.length];
}
}
递归调用将持续将数组划分为子数组,直到每个子数组只包含一个元素。然后,该算法将这些小的数组归并为稍大的有序子数组,直到最后形成一个有序的数组。
算法的实际实现如下:
public static void mergeSort(int[] list){
if(list.length>1){
int[] firstHalf = new int[list.length/2];
System.arraycopy(list,0,firstHalf,0,list.length/2);
mergeSort(firstHalf);
int secondHalfLength = list.length - list.length/2;
int[] secondHalf = new int[secondHalfLength];
System.arraycopy(list,list.length/2,secondHalf,0,secondHalfLength);
mergeSort(secondHalf);
int[] temp = merge(firstHalf,secondHalf);
System.arraycopy(temp,0,list,0,temp.length);
}
}
private static int[] merge(int[] list1,int[] list2){
int[] temp = new int[list1.length+list2.length];
int current1 = 0; //current index in list1
int current2 = 0; //current index in list2
int current3 = 0; //current index in temp
while(current1<list1.length && current2<list2.length){
if(list1[current1]<list2[current2])
temp[current3++] = list1[current1++];
else
temp[current3++] = list2[current2++];
}
while(current1<list1.length)
temp[current3++]=list1[current1++];
while(current2<list2.length)
temp[current3++]=list2[current2++];
return temp;
}
来看看归并排序算法的算法复杂度。归并排序算法讲数组分为两个子数组,使用相同的算法对子数组进行递归排序,然后将子数组进行归并。
第一项是对前半部分排序所需的时间,第二项是对后半部分排序所需的时间,要归并两个数组,最多需要次比较两个子数组中的元素,以及
次移动将元素移动到临时数组中。故:
复杂度为该算法优于选择排序、插入排序和冒泡排序。
其中,java.util.Arrays类中的sort方法就是使用归并排序算法的变体来实现的。
3.快速排序
快速排序在数组中选择一个称为主元(pivot)的元素,将数组分为两部分,使得第一部分中的所有元素都小于或等于主元,而第二部分中的所有元素都大于主元。对第一部分递归的应用快速排序算法,然后对第二部分递归的应用快速排序算法。该算法伪代码如下:
public static void quickSort(int[] list){
if(list.length>1){
select a pivot;
partition list into list1 and list2 such that
all elements in list1 <=pivot and
all elements in list2 >pivot
quickSort(list1);
quickSort(list2);
}
}
该算法的每次划分都将主元放在了恰当的位置。主元的选择会影响算法的性能。在理想的情况下,应该选择能平均划分两部分的主元。为了简单起见,假定讲数组的第一个元素选择为主元。
算法实现如下类,类中有两个重载的quickSort方法。第一个用来对数组进行排序,第二个是一个辅助方法,用于特定范围内的子数组进行排序。
public class QuickSort {
public static void quickSort(int[] list) {
quickSort(list, 0, list.length - 1);
}
private static void quickSort(int[] list, int first, int last) {
// TODO Auto-generated method stub
if (last > first) {
int pivotIndex = partition(list, first, last);
quickSort(list, first, pivotIndex - 1);
quickSort(list, pivotIndex + 1, last);
}
}
private static int partition(int[] list, int first, int last) {
int pivot = list[first];
int low = first + 1;
int high = last;
while (high > low) {
while (low <= high && list[low] <= pivot)
low++;
while (low <= high && list[high] > pivot)
high++;
if (high > low) {
int temp = list[high];
list[high] = list[low];
list[low] = temp;
}
}
while (high > first && list[high] >= pivot)
high--;
if (pivot > list[high]) {
list[first] = list[high];
list[high] = pivot;
return high;
} else
return first;
}
public static void main(String[] args){
int[] list = {2,3,5,2,7,9,4,12,8,20};
quickSort(list);
for(int i=0;i<list.length;i++)
System.out.print(list[i]+" ");
}
}
方法partition使用主元划分数组list[];将子数组的第一个元素宣威主元,在初始情况下,low指向子数组中的第二个元素,high指向子数组中的最后一个元素。
方法在数组中从左侧开始查找第一个大于主元的元素,然后从数组右侧开始查找第一个小于或者等于主元的元素,最后交换这两个元素。在while循环中重复相同的查找和交换操作,直到所有的元素都查找完为止。
如果主元被移动,方法返回将子数组分为两部分的主元的新下标;否则,返回主元的原始下标。
在最差的情况下,划分由n个元素构成的数组需要进行n次比较和n次移动。因此,划分所需时间为。在最差的情况下,每次主元会将数组划分为一个大的子数组和一个空数组。这个大的子数组的规模是在上次划分的子数组的规模上减少1。该算法需要
的时间。
在最佳情况下,每次主元将数组划分为规模大致相等的两部分。设表示使用快速排序算法对包含n个元的数组排序所需的时间,因此,
和归并排序的分析相似,快速排序的。
归并排序和快速排序都使用了分治法。对于归并排序,大量的工作是将两个子表进行归并,并是在子表都排好序后进行的。对于拍拍,大量的工作是将线性表划分为两个子线性表,划分是在子线性表排好序之前进行的。在最差的情况下,归并排序的效率高于快排,但是,在平均的情况下,两者的效率相同。归并排序在归并两个子数组时需要一个临时数组,而快排不需要额外的数组空间。因此,快排的空间效率高于归并排序。
4.堆排序
堆排序使用的是二叉堆。其中,堆是一棵具有以下属性的二叉树:
(1)它是一棵完全二叉树;
(2)每个节点大于或等于它的任意一个孩子。
除了最后一层没填满以及最后一层的叶子都是偏左放置的,如果一棵二叉树的每一层都是满的,那么这棵二叉树就是完全的。