为了明天即将到来的面试,为了加强记忆,在这里紧急的总结下各自排序的相关知识,
话说CSDN博客里可不可以@啊。
虽然这样的帖子成千上万,不过写出来是给自己看得。
首先,第一个: 插入排序(insertion sort)
这个原理很简单,用两个指针,一个指向要排序的数字,另外一个遍历数组,来找出第一个比《要排序的数字》大或者小的数字,将要排序的数字放入合适的位置。如此遍历数组中所有的数,直到数组有序。
对于随机乱序的数组,该算法的时间复杂度为O(n^2),并且稳定。希尔排序为该排序的变种。
比较适合针对本身已经有序的数组,插入N个元素(N个数远远小于数组长度)后,使原数组仍然有序。
给一个该排序的Java实现:
public int[] insertionSort(int[] array){
int tmp = 0;
for (int i = 1; i < array.length; i++){
int j;
tmp = array[i];
for(j=i ; j > 0 && tmp < array[j-1] ; j--){
array[j] = array[j-1];
}
array[j] = tmp;
}
return array;
}
然后,我们复习一下稍微复杂点的,冒泡排序(bubble sort)
不过好像也复杂不到哪里去,bubble最大的优点就是易于实现,原理简单,以及稳定(其实我觉得如果要使用不如用插入,实现也简单,算法复杂度也一样,不过真的很少用吧),该算法时间复杂度为 O(n^2),排序中算是比较慢的。感觉主要用途是教学..
原理也很简单,想象一下水中的气泡,大的气泡会浮的快些,小的慢看起来慢些,如果同一时间上浮,那么最后这些气泡会是有序的。
在这里,我们将选取一个元素,然后从该元素开始遍历整个数组,如果发现两个元素顺序相反,我们就将其调换,由此,我们可知,冒泡排序效率之所以低就是因为交换的次数过多导致的。
实现代码如下
public int[] bubbleSort(int[] array){
int tmp = 0;
for (int i = 1; i < array.length; i++){
for(int j = 0; j < array.length - i; j++){
if (array[j] > array[j+1]){
tmp = array[j+1];
array[j+1] = array[j];
array[j] = tmp;
}
}
}
return array;
}
以前经常会把前两种排序弄混,虽然原理很相似,不过注意,插入排序会直接把数组分成有序的部分和无序的部分,然后从无序的部分不断的提取元素插入有序数组。而插入排序则是不断的对无序元素进行调换,最后使元素有序。
然后,我们看一下最简单的不稳定排序 - 选择排序
选择排序的原理就是,每次选出未排序序列中的最大/最小值,将其放入数组的最后/前。n趟之后,数组有序。
这种排序方法的比较方法是恒定的O(n^2),其不稳定的地方主要体现在交换次数上,最好的情况及数组有序,我们需要交换0次,而最差情况是数组逆序,需要交换n-1次,因此,在n值较小时,选择排序效率高于冒泡排序。
java code:
public int[] selectionSort(int[] array){
int min = 0;
for (int i = 0; i < array.length; i++){
for(int j = i; j < array.length; j++){
min = array[i];
if (array[j] < min){
int tmp = array[j];
array[j] = min;
min = tmp;
}
array[i] = min;
}
}
return array;
}
接下来是排序中的重头戏,个人认为是当今算法界的明星。排序方法中最常用,同时也是通常是最有效率的算法- 快速排序:
该算法是冒泡排序的改进,其原理采用了算法学中的经典思想:分而治之的思想: 首先,选出一个数(这个数一般选自数组的head或者tail,我们称之为pivot)同时,根据这个数字,我们把数组分成 比该数大 或者 比该数小 两个子数组,然后递归调用,知道数组不可再分,即使数组有序。
这个图非常好:
快速排序也是一种不稳定的排序方法,很容易想象到,在这个排序过程中,pivot在有序数组中的位置决定了其程序的执行时间,最好的情况应该是,每一次方法的调用,都会很幸运的选到这个数组的中位数,理想情况下,其复杂度为O(nlogn)。而最坏情况,每一次我们都只能选到最大值或者最小值,此时,排序便会退化为冒泡排序,执行时间O(n^2)。
另外,快速排序中必须要知道的一点就是partition,也就是分区的方法。这个算法有同学描述为挖坑添数法,个人觉得十分形象:
具体步骤为定义i,j两个指针,一个指向数组head 一个指向数组tail,当我们取出了pivot之后,我们即认为原来pivot所占的位置已经空了,我们需要另外一个数字来占据它。如果我们选取了head作为pivot,那么我们首先向前移动指向tail的指针j,直到我们遇到第一个比pivot小的数,用其添掉原来pivot的坑,而这时这个较小的数字原来所占据的位置又变成了一个坑,此时,我们便移动指向head的指针i,寻找第一个比pivot大的数,并用其添坑。做完后,j继续移动,如此反复知道i= j,最后一个坑我们便用pivot来添,而i或者j的值,便是partition的分界线。
好了上代码:
public int[] quickSort(int[] array){
sortPartition(array,0, array.length-1);
return array;
}
public void sortPartition(int[] array, int start, int end){
int i = start;
int j = end;
if(i >= j){
return;
}
int pivot = array[start];
while (i < j){
while (i < j && array[j] >= pivot){
j--;
}
if(i < j){
array[i] = array[j];
}
while (i < j && array[i] < pivot){
i++;
}
if(i < j){
array[j] = array[i];
}
}
array[i] = pivot;
sortPartition(array, start, i-1);
sortPartition(array, i+1, end);
}
好了,然后是感谢时间,
感谢@MoreWindows的白话经典算法系列,我在复习快速排序算法的时候,参考了他的这篇文章:
http://blog.csdn.net/morewindows/article/details/6684558
下一次,计划总结一下堆排序,顺便分析一下堆这个数据结构。另外总结一下外部排序的算法。
另外,祈了个福,希望明天面试顺利!!