数据结构之九大排序(JAVA)


最近面临实习面试,由于自己准备投开发岗,据了解在面试中对于数据结构的考察是很重要的,其中对于查找、排序的算法考察尤为重要,所以又重回当年学习的数据结构好好复习。

参考:《数据结构》 严蔚敏版、《考研数据结构》及各排序总结博客,整理出适合自己的排序总结以供参考

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


排序基本概念


分类

  1. 插入排序:直接插入排序、二分法插入排序、希尔排序。
  2. 选择排序:简单选择排序、堆排序。
  3. 交换排序:冒泡排序、快速排序。
  4. 归并排序
  5. 基数排序

数据结构之九大排序(JAVA)_第1张图片
其中快排的空间复杂度应该是log2 n


稳定性

排序前: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

数据结构之九大排序(JAVA)_第2张图片

####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)_第3张图片

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)_第4张图片

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)_第5张图片

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]称为小顶堆。由上述性质可知大顶堆的堆顶的关键字肯定是所有关键字中最大的,小顶堆的堆顶的关键字是所有关键字中最小的。
例:
数据结构之九大排序(JAVA)_第6张图片

堆排序利用完全二叉树中双亲节点和孩子节点之间的内在关系,在当前无序区中选择关键字最大(或者最小)的记录。也就是说,以最小堆为例,根节点为最小元素,较大的节点偏向于分布在堆底附近。
然后,再对剩下的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)是一种最直观的排序方法,在排序过程中,将相邻的记录的关键字进行比较,若前面记录的关键字大于后面记录的关键字,则将它们交换,否则不交换。或者反过来,使较大关键字的记录后移,像水中的气泡一样,较小的记录向前冒出,较大的记录像石头沉入后部。故称此方法为冒泡排序法。
数据结构之九大排序(JAVA)_第7张图片

稳定性
序过程中只交换相邻两个元素的位置。因此,当两个数相等时,是没必要交换两个数的位置的。所以,它们的相对位置并没有改变,冒泡排序算法是稳定的!

复杂度
冒泡排序算法稳定,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)是对冒泡排序的一种改进。在冒泡排序中,记录的比较和交换是在相邻的单元中进行的,记录每次交换只能上移或下移一个单元,因而总的比较和移动次数较多。而在快速排序中,记录的比较和交换是从两端向中间进行的,关键字较小的记录一次就能从后面单元交换到前面去,而关键字较大的记录一次就能从前面的单元交换到后面的单元,记录每次移动的记录较远,因此可以减少记录总的比较和移动次数。

选择一个基准元素,通常选择第一个元素或者最后一个元素,通过一趟扫描,将待排序列分成两部分,一部分比基准元素小,一部分大于等于基准元素,此时基准元素在其排好序后的正确位置,然后再用同样的方法递归地排序划分的两部分。

数据结构之九大排序(JAVA)_第8张图片

排序方法
设待排序序列为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)排序法是将两个(或两个以上)有序表合并成一个新的有序表,即把待排序序列分为若干个子序列,每个子序列是有序的。然后再把有序子序列合并为整体有序序列。

数据结构之九大排序(JAVA)_第9张图片

排序方法
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++];
        }
    }

基数排序(BaseSort)

写不下去了,哈哈,这个就不看了。。。
我明明是要找Android开发岗啊!!!!
shutup!

你可能感兴趣的:(JAVA)