一、交换排序
利用交换数据元素的位置进行排序的方法称为交换排序。常用的交换排序有冒泡排序法和快速排序法。
1. 冒泡排序
基本思想:设数组中a中存放了n个数据元素,循环进行n-1趟排序过程:第一趟时,依次比较相邻的两个数据元素,若为逆序,则交换两个数据元素,这样数值最大的数据将被放置在a[n-1]中,第二趟时,循环次数减1,数据元素个数为n-1,这样整个n个数据元素中次大的数据元素被放置在a[n-1]中。直至所有的元素排列完成。有些待排序的数据元素序列已经基本有序,这样,实际上并不需要全部执行完上述过程。可以在算法中设计一个flag变量,flag变量用于标记本次排序过程中是否有交换动作,若本次排序过程没有交换动作则说明数据元素集合已全部排好。
冒泡算法如下:
public static void bubbleSort(int[] arr){
int flag = 1;
for(int i = 0; i < arr.length-1 && flag == 1; i++){
flag = 0;
for(int j = i+1; j < arr.length; j++){
if (arr[j] < arr[i]){
flag = 1;
int tmp = arr[j];
arr[j] = arr[i];
arr[i] = tmp;
}
}
}
}
总结:冒泡算法的最好情况是数据元素集合已经全部排好序,这是循环n-1次,每次循环都因没有交换动作而推出,因此冒泡排序算法最好情况的时间复杂度为O(n);冒泡排序算法最坏情况是数据元素集合全部逆序存放,这是时间复杂度为O(n^2)。冒泡排序的空间复杂度为O(1)。
冒泡排序在面试中会让你写递归形式:(主要思路就是每次冒泡会将最大的值移动到数组尾端,每次递归的时候传入的长度减1)
public static void bubbleRecursion(int[] arr, int n){
if (n < 2)
return;
for(int i = 0; i < n-1; i++){
if (arr[i] > arr[i+1]){
int tmp = arr[i];
arr[i] = arr[i+1];
arr[i+1] = tmp;
}
bubbleRecursion(arr, n-1);
}
}
2. 快速排序
基本思想:设数组a中存放了n个数据元素,low为数组的低端下标,high为数组的高端下标,从数组a中任取一个元素作为标准元素,以该标砖元素为基准来调整数组a中其他各个元素的位置,使排在标准元素前面的元素均小于标准元素,排在标准元素后面的均大于或等于标准元素。对这两个数组中的元素分别再进行方法类同的递归快速排序,算法的递归出口条件是low>=high。
快速排序算法如下:
public static void quickSort(int[] arr, int low, int high){
if (arr.length == 0 || low > high)
return ;
int index = partition(arr, low, high);
quickSort(arr, low, index-1);
quickSort(arr, index+1, high);
}
public static int partition(int[] arr, int low, int high){
int key = arr[low];
while(low < high){
while(low < high && arr[high] >= key)
high--;
arr[low] = arr[high];
while(low < high && arr[low] <= key)
low++;
arr[high] = arr[low];
}
arr[high] = key;
return high;
}
快速排序算法的时间复杂度和各次标准数据元素的取法关系很大。若每次选取的标准元素都能均分两个子数组的长度,这样的快速排序过程就是一个完全二叉树结果,这时分解次数等于完全二叉树的深度logn,每次快速排序过程中无论把数组怎样划分,全部的比较次数都接近于n-1次,所以最好情况下快速排序算法的时间复杂度为O(nlogn)。
快速排序算法的最坏情况是数据元素全部正序或反序有序,此时每次标准元素都把当前数组分成一个大小比当前数组小1的子数组,若把这样的排序过程画成二叉树结果就是一个二叉退化树。一个二叉退化树的深度是n,所以最坏情况下的快速排序算法的时间复杂度为O(n^2)。
一般情况下,数据元素的分布式随机的,数组分解构成的二叉树深度接近于logn,所以快速排序的算法的平均时间复杂度为O(nlogn)。
快速排序需要堆栈空间临时保存递归调用参数,堆栈空间的使用个数和递归调用的次数有关,因此最好情况下快速排序算法的空间复杂度为O(logn);最坏情况下快速排序算法的空间复杂度为O(n);平均空间复杂度为O(logn)。
快速排序算法是一种不稳定的排序算法。
归并排序
归并排序常用的是二路归并排序。基本思想:设数组a中存放了n个数据元素,初始时我们把它看做n个长度为1的有序子数组,然后从第一个子数组开始,把相邻的子数组两两合并,得到n/2个长度为2的新的有序子数组。对于这些新的有序子数组再两两归并,如此重复,直到得到一个长度为n的有序数组为止。
二路归并排序算法如下:
public static void merge(int[] a, int[] swap, int k){
int n = a.length;
int m = 0, l1 = 0, u1 = 0, l2 = 0, i = 0, j = 0, u2 = 0;
while(l1+k <= n-1){
l2 = l1 + k;
u1 = l2 - 1;
u2 = (l2+k-1 <= n-1) ? l2+k-1 : n-1;
for(i = l1, j = l2; i <= u1 && j <= u2; m++){
if (a[i] < a[j]){
swap[m] = a[i];
i++;
}
else{
swap[m] = a[j];
j++;
}
}
while(i <= u1){
swap[m] = a[i];
i++;
m++;
}
while(j <= u2){
swap[m] = a[j];
m++;
j++;
}
l1 = u2 + 1;
}
for(i = l1; i < n; i++, m++){
swap[m] = a[i];
}
}
public static void mergeSort(int[] a){
int i = 0;
int n = a.length;
int k = 1;
int[] swap = new int[n];
while(k < n){
merge(a, swap, k);
for(i = 0; i < n; i++)
a[i] = swap[i];
k = 2*k;
}
}
对n个元素进行一次二路归并排序时,归并的次数约为logN,任何一次的二路归并排序元素的比较次数都约为n-1,二路归并排序算法的时间复杂度为O(NlogN),二路归并排序使用了n个临时临时内存空间存放数据元素,所以二路归并排序的空间复杂度为O(N)。
由于二路归并排序算法是相邻有序子表的两两归并,对于相同的两个数据元素,能够保证原来在前边的元素排序后仍在前边。因此,二路归并排序算法是一种稳定的排序算法。二路归并排序是唯一一个不仅时间复杂度为O(NlogN),并且还是一个稳定的排序算法。