以下都假设给定一个长度为n的数组
1,冒泡排序
在冒泡排序中, 我们从数组的开始进行比较,如果第一个元素大于第二个元素,我们就将两个元素互换,接下来我们比较第二个元素和第三个元素,如果第二个元素大于第三个元素我们就把它们交换,以此类推,每次循环都得到一个最大的数。代码如下:
public void bubblesort(int[] array){ for(int i=0;i<array.length-1;i++) for(int j=0;j<array.length-i-1;j++){ if(array[j]>array[j+1]){ int tem=array[j]; array[j]=array[j+1]; array[j+1]=tem; } } }
一共循环了(n-1) + (n-2) +(n-3) .....+1次,时间复杂度为O(n^2)。我们仅用了一个变量tem来保存要交换的数据,因此空间复杂度时O(1)。每次比较时两个元素如果相等两个元素的相对位置不变,因此冒泡排序是稳定的。
2,简单选择排序
简单选择排序和冒泡排序看上去真好相反,冒泡排序的每个循环都会找到一个最大数放在数组的后面,而简单选择排序每次都确定一个最小的元素放在最前面。代码如下:
public void selectsort(int[] array){ for(int i=0;i<array.length-1;i++) for(int j=i+1;j<array.length;j++){ if(array[i]>array[j]){ int tem=array[i]; array[i]=array[j]; array[j]=tem; } } }
和冒泡排序一样一共循环了(n-1) + (n-2) +(n-3) + .....+1次,所以时间复杂度同样为O(n^2)。我们仅用了一个变量tem来保存要交换的数据,因此空间复杂度为O(1)。因为在选择较小元素时,相同元素的相对位置可能发生变化,比如{2,2,1} 第一个2会和1交换位置,因此简单选择排序是不稳定排序。
3,直接插入排序
直接插入排序是每次从右边的无序序列中选择一个元素,插入多左边有序序列的合适位置,过程是从右往左进行的。代码如下:
public void insertsort(int[] array){ for(int i=1;i<array.length;i++) for(int j=i;j>0;j--){ if(array[j-1]>array[j]){ int tem=array[j-1]; array[j-1]=array[j]; array[j]=tem; } } }
整个过程循环了1 + 2 + 3 +...+(n-1) 次,所以时间复杂度为O(n^2)。我们仅用了一个变量tem来保存要交换的数据,因此空间复杂度为O(1)。因为每次循环我们都是通过从后向前遍历已排序序列,然后插入,此时相等元素依然可以保持原有位置,因此直接插入排序时稳定排序。如果从左往右遍历有序序列,就和选择排序一样,变成一个不稳定排序。我们在实现的时候尽量把算法设计成稳定排序。
前面的三种排序时间复杂度都是O(n^2), 在实际应用中效率不高,下面的这两种排序算法,大大地提高了排序的效率,也是我们解题中经常用到的排序算法。
4,快速排序
在快速排序中,我们随机选择一个元素,然后把数组分为两段,左边的元素都小于这个元素,右边的元素都大于这个元素,然后用同样的方法处理左边的序列和右边的序列,知道整个数组变为有序。我们从代码来分析,代码如下:
public void quick_sort(int s[], int l, int r) { if (l < r) { int i = l, j = r, pivot = s[l]; while (i < j) { while(i < j && s[j] >= pivot) j--; if(i < j) s[i++] = s[j]; while(i < j && s[i] < pivot) i++; if(i < j) s[j--] = s[i]; } s[i] = pivot; quick_sort(s, l, i - 1); quick_sort(s, i + 1, r); } }
在平均情况下,快速排序要递归log(n)次,每次都要处理n个元素,所以时间复杂度时O(nlogn)。因为要递归log(n)次,所以要用log(n)的栈来实现递归,所以空间复杂度为O(log n)。在极端的情况下,快排的时间复杂度是O(n^2),例如如果数组本身时倒序的,每次取最小,这样的时间复杂度就是O(n^2),一共循环了(n-1) + (n-2) +(n-3) + .....+1次。在排序过程中,相同大小的元素的相对位置会发生变化,例如{2,2,1}在一次排序后第一个2会和1交换位置,因此快速排序是一个不稳定排序。
5,归并排序
归并排序是采用了分治的思想,每次把数组分成两段,直到子序列的长度为1,每个子序列都是有序的,然后再合并这些有序的子序列,直到整个序列都有序。我们从代码来分析它的时间复杂度以及空间复杂度,代码如下:
//通过递归使每个子序列都有序,然后再将它们合并
public void mergesort(int low,int high) { if(low<high) { int middle=low+(high-low)/2; mergesort(low,middle); mergesort(middle+1,high); mergepart(low,middle,high); } } public void mergepart(int low, int middle, int high) { for(int i=low;i<=high;i++) { temparray[i]=array[i]; } int i=low; int j=middle+1; int k=low; while(i<=middle&&j<=high){ if(temparray[i]<=temparray[j]){ array[k]=temparray[i]; i++; }else{ array[k]=temparray[j]; j++; } k++; } while(i<=middle){ array[k]=temparray[i]; i++; k++; } while(j<=high){ array[k]=temparray[j]; j++; k++; } } }
程序执行过程中,总共递归了log(n)次,每次都要处理n个数据,所以它的时间复杂度为O(nlogn),因为每次都是把子序列分为长度相等的两段,因此它的最坏情况和平均情况的时间复杂度都是O(nlogn)。对于空间复杂度,不同的算法实现空间复杂度也不同。在这个代码实现中,我们用了一个长度为n的辅助数组,每次都把较小的元素放入数组中,最后我们把剩余的元素也放入目标数组中。递归调用了log(n)次,加上我们开辟的长度为n的数组,所以它的空间复杂度为O(n)。对于稳定性,当有序列的左右两子序列合并的时候一般是先遍历左序列,所以左右序列如果有相等元素,处在左边的仍然在前,因此归并排序时稳定的排序.
总结:
冒泡排序: 时间复杂度O(n^2),空间复杂度O(1),稳定排序
简单选择排序: 时间复杂度O(n^2),空间复杂度O(1),不稳定排序
快速插入排序:时间复杂度O(n^2),空间复杂度O(1),稳定排序
快速排序:时间复杂度O(nlogn) 平均情况,O(n^2)最坏情况,空间复杂度O(logn),不稳定排序
归并排序:时间复杂度O(nlogn),空间复杂度要根据算法具体实现来确定,稳定排序