最近面临实习面试,由于自己准备投开发岗,据了解在面试中对于数据结构的考察是很重要的,其中对于查找、排序的算法考察尤为重要,所以又重回当年学习的数据结构好好复习。
参考:《数据结构》 严蔚敏版、《考研数据结构》及各排序总结博客,整理出适合自己的排序总结以供参考
http://www.open-open.com/lib/view/open1453126714683.html
http://www.cnblogs.com/leeplogs/p/5863846.html
http://blog.csdn.net/cold702/article/details/7979332
http://blog.csdn.net/zgrjkflmkyc/article/details/11639091
http://blog.csdn.net/whuslei/article/details/6442755
排序前:if Ai==Aj && i
从稳定性来看,所有时间复杂度为O(n^2)的简单排序和基数排序都是稳定的。而快排、堆排、希尔排序等时间性能较好的排序都是不稳定的。(一般来说,排序过程中的“比较”是在“相邻两个记录关键字”间进行的排序都是稳定的)
(有个猜测,方便记忆:一般来说,若存在不相邻元素间交换,则很可能是不稳定的排序。)
O(n^2):直接插入排序,简单选择排序,冒泡排序。
在数据规模较小时(9W内),直接插入排序,简单选择排序差不多。当数据较大时,冒泡排序算法的时间代价最高。性能为O(n^2)的算法基本上是相邻元素进行比较,基本上都是稳定的。
O(nlogn):快速排序,归并排序,希尔排序,堆排序。
1.数据规模较小
(1)待排序列基本有序的情况下,可以选择直接插入排序;
(2)对稳定性不作要求宜用简单选择排序,对稳定性有要求宜用插入或冒泡
2.数据规模不是很大
(1)完全可以用内存空间,序列杂乱无序,对稳定性没有要求,快速排序,此时要付出log(N)的额外空间。
(2)序列本身可能有序,对稳定性有要求,空间允许下,宜用归并排序
3.数据规模很大
(1)对稳定性有求,则可考虑归并排序。
(2)对稳定性没要求,宜用堆排序。
4.序列初始基本有序(正序),宜用直接插入,冒泡
####1、直接插入排序####
针对一个已排好序的序列,将待排数据插入到该序列中的合适位置,插入完成后序列依然有序。
排序算法
(1)从第一个元素开始,该元素可以认为已经被排序,
(2)取出下一个元素,在已经排序的元素序列中从后向前扫描,
(3)如果该元素(已排序)大于新元素,将该元素移到下一位置,
(4)重复步骤3,直到找到已排序的元素小于或者等于新元素的位置,
(5)将新元素插入到下一位置中,
(6)重复步骤2;
稳定性
在插入排序中,K1是已排序部分中的元素,当K2和K1比较时,直接插到K1的后面(没有必要插到K1的前面,这样做还需要移动!!),因此,插入排序是稳定的。
复杂度
一般情况下,插入排序的时间复杂度和空间复杂度分别为 O(n2 ) 和 O(1) 。
JAVA实现:
/**
* Created by hgx on 2017/3/5.
* 直接插入排序
*/
public static int[] insertSort(int[] a){
//从数组第二个元素开始排序
for(int i=1;i=0 && temp
####2、二分插入排序####
二分排序的思想和直接插入排序是一样的,只是寻找插入位置的方式不同。按照二分法寻找位置,大大减少比较次数。二分插入就是首先将队列中取最小位置low和最大位置high,然后算出中间位置mid,将中间位置mid与待插入的数据data进行比较,如果mid大于data,则就表示插入的数据在mid的左边,high=mid-1,如果mid小于data,则就表示插入的数据在mid的右边,low=mid+1。
排序算法
(1)确定查找范围low=0,high=a.length-1,计算中项mid=(low+high)/2。
(2)若a [mid]=temp或low>=high,则结束查找;否则,向下继续。
(3)若a[mid] < temp,说明待查找的元素值只可能在比中项元素大的范围内,则把mid+1的值赋给low,并重新计算mid,转去执行步骤2;若a [mid]>temp,说明待查找的元素值只可能在比中项元素小的范围内,则把mid-1的值赋给high,并重新计算mid,转去执行步骤2。
(4)找到新元素的插入位置后,将其按直接插入排序方法插入即可。
稳定性
二分排序是稳定的排序。
复杂度
从时间上比较,这般插入排序仅仅减少了关键字的比较次数,却没有减少记录的移动次数,故时间复杂度仍为O(n^2)。
JAVA实现:
/**
* Created by hgx on 2017/3/5.
* 二分排序
*/
public class BinInsertSort {
public static int[] binInsertSort(int[] a){
int low,mid,high;
int temp;
//从数组第二个元素开始排序
for (int i=1; ilow; j--){
a[j] = a[j-1];
}
a[low] = temp; //最后将a[i]插入a[low]
}
return a;
}
####3、希尔排序####
将需要排序的序列划分成为若干个较小的子序列,对子序列进行插入排序,通过则插入排序能够使得原来序列成为基本有序。这样通过对较小的序列进行插入排序,然后对基本有序的数列进行插入排序,能够提高插入排序算法的效率。
在希尔排序中首先解决的是子序列的选择问题。对于子序列的构成不是简单的分段,而是采取相隔某个增量的数据组成一个序列。一般的选择原则是:取上一个增量的一半作为此次序列的划分增量。首次选择序列长度的一半为增量。
排序算法
(1)取d=a.length/2作为第一个增量,把待排数组分为d1个组
(2)从第d个元素开始比较,在各组内进行直接插入排序。
(3)取d=d/2重复步骤(1)直至d=1
稳定性
由于多次插入排序,我们知道一次插入排序是稳定的,不会改变相同元素的相对顺序,但在不同的插入排序过程中,相同的元素可能在各自的插入排序中移动,最后其稳定性就会被打乱,所以shell排序是不稳定的。(有个猜测,方便记忆:一般来说,若存在不相邻元素间交换,则很可能是不稳定的排序。)
复杂度
Shell排序算法的时间复杂度分析比较复杂,实际所需的时间取决于各次排序时增量的个数和增量的取值。研究证明,若增量的取值比较合理,希尔排序的平均时间复杂度为O(nlogn)
JAVA实现:
/**
* Created by hgx on 2017/3/5.
* 希尔排序
*/
public class ShellSort {
public static int[] shellSort(int[] a){
int d = a.length/2;
int temp;
while (d>0){
/*
* 以下这行代码需要详细解释。这个算法能够避免麻烦的分组下标表示
* i从0+d开始,因为分组后的第一组是0,d,2d,3d...这样一个序列.所以直接从d开始比较
* 此循环比较算法区别:假设有一个16项数组,下标为0~15,当d=5时,分为5组
* 以下标表示:(0,5,10,15),(1,6,11),(2,7,12),(3,8,13),(4,9,14)
* 如果分别针对每一组插入排序,则下标控制很复杂,所以外层for循环每次对每一分组的前2个数据排序
* 然后前3个,然后前4个...这和组数有关
* 即当i=5时,对(0,5,10,15)中的(0,5)排序
* i=6时,对(1,6,11)中的(1,6)排序.....
* i=10时,对(0,5,10,15)中的(0,5,10)排序......
* 一直到d=1时,此时的数组基本有序,数据项移动次数大大减少
* */
for (int i=d;i= d && temp < a[j-d]) {
a[j] = a[j - d]; //把上一位向后移
j -= d;
}
a[j] = temp;
}
d=d/2;
}
return a;
}
####1、简单选择排序####
每一趟从待排序的记录中选出关键字最小的记录,顺序放在已排好序的子文件的最后,直到全部记录排序完毕。如对于一组关键字{K1,K2,…,Kn},首先从K1,K2,…,Kn中选择最小值,假如它是 Kz,则将Kz与 K1对换;然后从K2,K3,…,Kn中选择最小值 Kz,再将Kz与K2对换。如此进行选择和调换n-2趟,第(n-1)趟,从Kn-1、Kn中选择最小值 Kz将Kz与Kn-1对换,最后剩下的就是该序列中的最大值,一个由小到大的有序序列就这样形成。
稳定性
一般认为,若是从前往后比较来选择第i小的记录则是稳定的,若是从后往前比较则不稳定。
复杂度
选择排序法与冒泡排序法一样,最外层循环仍然要执行n-1次,其效率仍然较差。该算法的时间复杂度为 O(n2)。
JAVA实现:
/**
* Created by hgx on 2017/3/6.
* 简单选择排序
*/
public class SelectSort {
public static int[] selectSort(int[] a){
int temp;
int k,min; //最小数下标K,值为min
for (int i =0;i
####2、堆排序####
堆的概念: 一棵完全二叉树,任一个非终端结点的值均小于等于(或大于等于)其左、右儿子结点的值。堆分为大顶堆和小顶堆,满足Key[i]>=Key[2i+1]&&key>=key[2i+2]称为大顶堆,满足Key[i]<=key[2i+1]&&Key[i]<=key[2i+2]称为小顶堆。由上述性质可知大顶堆的堆顶的关键字肯定是所有关键字中最大的,小顶堆的堆顶的关键字是所有关键字中最小的。
例:
堆排序利用完全二叉树中双亲节点和孩子节点之间的内在关系,在当前无序区中选择关键字最大(或者最小)的记录。也就是说,以最小堆为例,根节点为最小元素,较大的节点偏向于分布在堆底附近。
然后,再对剩下的n-1个元素建成堆,得到n个元素中关键码次大(或次小)的元素。以此类推,直到进行N-1此后,排序结束,便得到一个按关键码有序的序列。
初始序列:30,24,85,16,36,53,91,47
首先,将待排序序列建成大顶堆:
利用此大顶堆进行堆排序:
稳定性
堆排序为不稳定排序,不适合记录较少的排序。
复杂度
从上述过程可知,堆排序其实也是一种选择排序,是一种树形选择排序。只不过直接选择排序中,为了从R[1…n]中选择最大记录,需比较n-1次,然后从R[1…n-2]中选择最大记录需比较n-2次。事实上这n-2次比较中有很多已经在前面的n-1次比较中已经做过,而树形选择排序恰好利用树形的特点保存了部分前面的比较结果,因此可以减少比较次数。对于n个关键字序列,最坏情况下每个节点需比较log2(n)次,因此其最坏情况下时间复杂度为nlogn。
JAVA实现:
/**
* Created by hgx on 2017/3/6.
* 堆排序
*/
public class HeapSort {
public static int[] heapSort(int[] a){
int arrayLength = a.length;
//循环建堆
for (int i =0; i=0;i--){
int k = i;//k保存正在判断的节点
//如果当前K节点的子节点存在
while (k*2+1<=lastIndex){
int biggerIndex = 2*k +1;//k节点的左子节点的索引
//如果biggerIndex小于lastIndex,即biggerIndex+1代表k节点的右子节点存在
if (biggerIndex
####1、冒泡排序####
冒泡排序(Bubble Sort)是一种最直观的排序方法,在排序过程中,将相邻的记录的关键字进行比较,若前面记录的关键字大于后面记录的关键字,则将它们交换,否则不交换。或者反过来,使较大关键字的记录后移,像水中的气泡一样,较小的记录向前冒出,较大的记录像石头沉入后部。故称此方法为冒泡排序法。
稳定性
序过程中只交换相邻两个元素的位置。因此,当两个数相等时,是没必要交换两个数的位置的。所以,它们的相对位置并没有改变,冒泡排序算法是稳定的!
复杂度
冒泡排序算法稳定,O(1)的额外的空间,比较和交换的时间复杂度都是O(n^2),自适应,对于已基本排序的算法,时间复杂度为O(n)。冒泡算法的许多性质和插入算法相似,但对于系统开销高一点点。使用冒泡排序法对n个数据进行排序,共需要进行n-1次的比较。如果本来就是有顺序的数据,也需要进行n-1次比较。冒泡排序法的算法很简单,效率也较差。
JAVA实现:
/**
* Created by hgx on 2017/3/6.
* 冒泡排序
*/
public class BubbleSort {
public static int[] bubbleSort(int[] a){
int temp;
for (int i=0;ia[j+1]){
temp = a[j];
a[j]=a[i];
a[i]=temp;
}
}
}
return a;
}
####2、快速排序####
快速排序(Quick Sorting)是对冒泡排序的一种改进。在冒泡排序中,记录的比较和交换是在相邻的单元中进行的,记录每次交换只能上移或下移一个单元,因而总的比较和移动次数较多。而在快速排序中,记录的比较和交换是从两端向中间进行的,关键字较小的记录一次就能从后面单元交换到前面去,而关键字较大的记录一次就能从前面的单元交换到后面的单元,记录每次移动的记录较远,因此可以减少记录总的比较和移动次数。
选择一个基准元素,通常选择第一个元素或者最后一个元素,通过一趟扫描,将待排序列分成两部分,一部分比基准元素小,一部分大于等于基准元素,此时基准元素在其排好序后的正确位置,然后再用同样的方法递归地排序划分的两部分。
排序方法
设待排序序列为r[s…t],为实现一次划分,可设置两个指针low和high,他们的初值分别为s和t。以r[s]为基准,在划分的过程中:
(1)从high端开始,依次向前扫描,并将扫描到的每一个记录的关键字同r[s](即基准记录)的关键字进行比较,直到r[high].key< r[s].key时,将r[high]赋值到low所指的位置。
(2)从low端开始,依次向后扫描,并将扫描到的每一个记录的关键字同r[s](即基准记录)的关键字进行比较,直到r[low].key> r[s].key时,将r[low]赋值到high所指的位置。
(3)如此交替改变扫描方向,重复上述两个步骤从两端各自向中间位置靠拢,直到low等于或大于high。经过此次划分后得到的左右两个子序列分别为r[s…low-1]和r[low+1…t]。然后对这两个子序列按上述方法进行再次划分,依次重复,直到每个序列只剩一个元素为止。
稳定性
快速排序是一个不稳定的排序方法。
复杂度
若快速排序出现最好的情况(左、右子序列的长度大致相等),则结点数n与二叉树深度h应满足log2(n)<=h<=log2(n+1),所以总的比较次数不会超过(n+1)log2(n).因此,快速排序的最好时间复杂度应为O(nlog2(n))。若快速排序出现最坏的情况(每次能划分成两个子序列,但是其中一个为空),则此时得到的二叉树是一棵单枝树,得到的非空子序列包含有n-i(i代表二叉树的层数),每层划分需要比较n-i+2次,所以总的比较次数为(n2+3n-4)/2.因此,快速排序的最坏时间复杂度为O(n2).
另外,由于快速排序是递归的,每层递归调用时的指针和参数均要用栈来存放,存放开销在理想情况下为O(log2n),即树的高度。在最坏情况下为O(n)。
JAVA实现:
/**
* Created by hgx on 2017/3/6.
* 快速排序
*/
public class QuickSort {
public static int[] quickSort(int[] a, int low, int high){
if (low < high){
int middle = getMiddle(a,low,high);
quickSort(a,0,middle-1);
quickSort(a,middle+1,high);
}
return a;
}
public static int getMiddle(int[] a,int low, int high){
int temp = a[low];
while (low=temp){
high--;
}
a[low] = a[high];
//从左往右找比基准大的元素并交换
while (low
归并(Merge)排序法是将两个(或两个以上)有序表合并成一个新的有序表,即把待排序序列分为若干个子序列,每个子序列是有序的。然后再把有序子序列合并为整体有序序列。
排序方法
1、递归基础:若序列只有一个元素,则它是有序的,不执行任何操作
2、递归步骤:
先把序列划分成长度基本相等的两个序列
对每个子序列递归排序
把排好序的子序列归并成最后的结果
3、合并步骤:
(1)申请空间,使其大小为两个已经排序序列之和,该空间用来存放合并后的序列
(2)设定两个指针,最初位置分别为两个已经排序序列的起始位置
(3)比较两个指针所指向的元素,选择相对小的元素放入到合并空间,并移动指针到下一位置
(4)重复步骤3直到某一指针达到序列尾
(5)将另一序列剩下的所有元素直接复制到合并序列尾
稳定性
归并排序最大的特色就是它是一种稳定的排序算法。归并过程中是不会改变元素的相对位置的。
复杂度
一趟归并需要n次,总共需要logN次,因此为O(N*logN).
数组需要O(n)的额外空间
JAVA实现:
/**
* Created by hgx on 2017/3/6.
* 归并排序
*/
public class MergeSort {
public static int[] mergeSort(int[] a,int left,int right){
if (left < right){
//划分为两部分,每次两部分进行归并
int middle = (left + right)/2;
//两路归并,先递归处理每一部分
mergeSort(a,left,middle);
mergeSort(a,middle+1,right);
//将已排好序的两两归并排序进行合并
merge(a,left,middle,right);
}
return a;
}
private static void merge(int[] a, int left, int middle, int right){
int[] tempArr = new int[a.length];//临时数组tempArr[],用来存放待合并的两路归并排序数组
int rightIndex = middle +1;//右数组第一个元素索引
int tempIndex = left;//记录临时数组的索引
int tmp = left;//缓存左数组第一个元素位置
while (left <= middle && rightIndex<=right){
//从两个数组中取出最小的放入临时数组
if (a[left]<=a[rightIndex]){
tempArr[tempIndex++] = a[left++];
}else {
tempArr[tempIndex++] = a[rightIndex++];
}
}
//剩余部分以此放入临时数组。以下两个while只会执行其中一个
while (left<=middle){
tempArr[tempIndex++] = a[left++];
}
while (rightIndex<=right){
tempArr[tempIndex++] = a[rightIndex++];
}
//将临时数组中的内容拷贝回原数组
while (tmp<=right){
a[tmp]=tempArr[tmp++];
}
}
写不下去了,哈哈,这个就不看了。。。
我明明是要找Android开发岗啊!!!!
shutup!