最近两天,复习了几个经典的排序算法。在这里,写一份小结,粗略回顾。
1)Bubble Sort
顾名思义,就是把最小的数如冒泡一样,在每次的遍历当中,移到最顶的位置。
public void BubbleSort(int[] input){
for(int i=0;i<input.length;i++){
for(int j=input.length-1;j>i;j--){
if(input[j]<input[j-1]){
swap(input,j,j-1);
}
}
}
}
分析该排序算法的思路是考量比较的次数和交换的次数。比较发生了(n-1)+(n-2)+(n-3)+...+1 ~= O(n^2). 交换的次数在最坏的情况下,也就是从大到小的初始顺序下,同样发生了(n-1)+(n-2)+(n-3)+...+1 ~= O(n^2)次。
2)Selection Sort:
冒泡排序中,有许多的交换移动是可以避免的。我们可以选好了当前要确定的位置,然后用选择当前剩下的子数组中最小的值去填充需要确定的位置。省去不必要的交换。因为是选择剩下的子数组的最小值去填充,称谓选择排序。
public void selectSort(int[] input){
for(int i=0;i<input.length;i++){
int min_index = i;
for(int j=i+1;j<input.length;j++){
if(input[min_index]>input[j]){
min_index = j;
}
}
swap(input,i,min_index);
}
}
3)Insertion Sort
插入排序就如整理扑克牌一样,把一张牌直接在一堆排中,找一个位置,使得在这个位置之后的都比这张牌大,在这位置前的都比这张牌小。但我们需要注意的是,这里的插入是指插入到已经排好序的序列中。一开始,我们假设第一张是已经排好序的。从数组的第二个元素也就是坐标为1的元素开始抽牌插入。
public void insertionSort(int[] input){
int temp = 0;// the temporary store for the card I take to insert
for(int i=1; i<input.length;i++){
temp = input[i];
int j=i;
for(;j>0;j--){
if(temp<input[j-1]){
input[j] = input[j-1];
}else{
break;
}
}
input[j] = temp;
}
}
分析算法,可知在最坏的情况下比较和移动的次数都是O(n^2)。
4) Merge Sort
归并排序是一种分治算法(Divide and Conquer),分治算法的典型实现是递归(Recursion)。递归最重要的是想好结束的条件,而画好递归树验证逻辑。思想简单,不停地分隔数组。当分到长度为1时,停止分割,继而进行组合,组合时确保新的数组有序。最终组合成一个大的有序数组。
public int[] divideMerge(int[] input, int begin, int end){
int[] return_list = new int[end-begin+1];
if(begin == end){
return_list[0] = input[begin];
return return_list;
}else{
int mid_index = (begin+end)/2;
int[] temp_array1 = divideMerge(input,begin,mid_index);
int[] temp_array2 = divideMerge(input,mid_index+1,end);
return_list = mergeArray(temp_array1,temp_array2);
}
return return_list;
}
private int[] mergeArray(int[] input1, int[] input2){
int[] return_array = new int[input1.length+input2.length];
int i = input1.length-1;
int j = input2.length-1;
int k = return_array.length-1;
while(i>=0 && j>=0 && k>=0){
if(input1[i]<input2[j]){
return_array[k--] = input2[j--];
}else{
return_array[k--] = input1[i--];
}
}
while(i>=0 && k>=0){
return_array[k--] = input1[i--];
}
while(j>=0 && k>=0){
return_array[k--] = input2[j--];
}
return return_array;
}
写merge算法的时候,注意合并数组的填入顺序应和扫描两个分数组的顺序一致,且和小于的逻辑一直。上述代码采用大于的逻辑来写,反方向写入。
分析:归并排序是第一个空间复杂度是O(n+log2(n))的算法,需要另外开辟存储空间。其中n是开辟的存储结果的数组,而log2(n)是由于递归时需要开辟的栈空间。算法的时间复杂度可以根据调用次数统计。如上述例子,4个长度的数组,需要调用:(0,1) --> (0,0)/ (1,1); (2,3) --> (2,2),(3,3). 再加上总的:(0,4)。总共是7次。为n*log2(n) - 1次。所以复杂度为O(nlog2(n))。(书上的解释是,所有元素扫描一次需要n,接着合并的时候,根据完全二叉树深度可知,归并排序需要进行log2(n)次)。
5)QuickSort
快速排序基于一个不断找轴值,使得轴值左边的数小于轴值,轴值右边的数要大于轴值。但两边各自的数不要求有序。在不停地递归当中,直到轴值begin和pivot相等、 pivot + 1和end相等。这样就确保所有的数字有序。
public void quickSort(int[] input, int begin, int end){
if(begin==end){
return;
}else{
int pivot = partition(input, begin, end);
quickSort(input,begin,pivot);
quickSort(input,pivot+1,end);
}
}
private int partition(int[] input, int begin, int end){
while(begin<end){
while(begin<end && input[end] >= input[begin]){
end--;
}
if(begin<end){
swap(input,begin,end);
}
while(begin<end && input[end] >= input[begin]){
begin++;
}
if(begin<end){
swap(input,begin,end);
}
}
return begin;
}
分析快速排序可知,其算法复杂度是O(nlog2(n))。空间复杂度是:O(log2(n))。因为采用了递归。