zoukankan      html  css  js  c++  java
  • 数据结构_排序算法总结



    作者丨fredal
    https://www.jianshu.com/p/28d0f65aa6a1


    所有内部排序算法的一个总结表格


    640?wx_fmt=other


    640?wx_fmt=gif简单选择排序


    首先在未排序序列中找到最小(大)元素,存放到排序序列的起始位置,然后,再从剩余未排序元素中继续寻找最小(大)元素,然后放到已排序序列的末尾。以此类推,直到所有元素均排序完毕。


    最差:O(n2)


    最优:O(n2)


    平均:O(n2)


    public void selectSort(int[] a){
        for(int i=0;i<a.length-1;i++){
            int min=i;//最小元素所在位置
            for(int j=i+1;j<a.length;j++){
                if(a[j]<a[min]) min=j;
            }
            {int temp=a[min];a[min]=a[i];a[i]=temp;}//交换元素,把最小元素放在头部
        }
     }


    640?wx_fmt=gif冒泡排序


    冒泡排序算法的运作如下:


    1. 比较相邻的元素。如果第一个比第二个大,就交换他们两个。

    2. 对每一对相邻元素作同样的工作,从开始第一对到结尾的最后一对。这步做完后,最后的元素会是最大的数。

    3. 针对所有的元素重复以上的步骤,除了最后一个。

    4. 持续每次对越来越少的元素重复上面的步骤,直到没有任何一对数字需要比较。

    最差:O(n2)

    最优:O(n)

    平均:O(n2)

    public static void bubbleSort(int[] a){
        for(int i=0;i<a.length-1;i++){
            for(int j=0;j<a.length-i-1;j++){
                if(a[j]>a[j+1]) {int temp=a[j];a[j]=a[j+1];a[j+1]=temp;}//交换元素位置 保证大的在后面
            }
        }
     }


    640?wx_fmt=gif插入排序


    一般来说,插入排序都采用in-place在数组上实现。具体算法描述如下:


    1. 从第一个元素开始,该元素可以认为已经被排序

    2. 取出下一个元素,在已经排序的元素序列中从后向前扫描

    3. 如果该元素(已排序)大于新元素,将该元素移到下一位置

    4. 重复步骤3,直到找到已排序的元素小于或者等于新元素的位置

    5. 将新元素插入到该位置后

    6. 重复步骤2~5

    最差:O(n2)

    最优:O(n)

    平均:O(n2)

    640?wx_fmt=gif
    直接插入排序:


    public static void insertSort(int[] a){
        for(int i=1;i<a.length;i++){
            for(int j=0;j<=i;j++){
                if(a[j]>=a[i]){//找到了第一个比自己大的元素,插入到该元素之前
                    int temp=a[i];
                    for(int k=i-1;k>=j;k--) a[k+1]=a[k];//每个元素向后移动一格
                    a[j]=temp;
                    break;
                }
            }
        }
     }


    640?wx_fmt=gif折半插入排序: 


    就是寻找第一个比自己大的元素的时候用折半查找进行优化:


    public static void halfInsertSort(int[] a){
        for(int i=1;i<a.length;i++){
            int low=0;
            int high=i;
            while(low<=high){
                int half=(low+high)/2;
                if(a[half]<a[i]){//插入点在高半区
                    low=half+1;
                }else{//插入点在低半区
                    high=half-1;
                }
            }
            //low或者high指向的元素就是第一个比自己大的元素 插入到之前
            int temp=a[i];
            for(int k=i-1;k>=low;k--) a[k+1]=a[k];//每个元素向后移动一格
            a[low]=temp;
        }
     }


    640?wx_fmt=gif快速排序


    快速排序使用分治法策略来把一个序列(list)分为两个子序列(sub-lists)。


    步骤为:


    1. 从数列中挑出一个元素,称为"基准"(pivot)

    2. 重新排序数列,所有元素比基准值小的摆放在基准前面,所有元素比基准值大的摆在基准的后面(相同的数可以到任一边)。在这个分区结束之后,该基准就处于数列的中间位置。这个称为分区(partition)操作。

    3. 递归地(recursive)把小于基准值元素的子数列和大于基准值元素的子数列排序。

    最差:O(n2)

    最优:O(n log n)

    平均:O(n log n)


    //这里left取0,right取a.length-1
     public static void quickSort(int[] a,int left,int right){
        if(left<right){//递归出口条件
            int i=left;//左指针
            int j=right;//右指针
            int x=a[left];//选择第一个元素作为标尺
            while(i<j){
                while(i<j && a[j]>=x) j--;//从右向左找第一个小于x的数
                if(i<j) a[i++]=a[j];
                while(i<j && a[i]<x) i++;//从左向右找第一个大于等于x的数
                if(i<j) a[j--]=a[i];
            }
            a[i]=x;//插入标尺
            quickSort(a,left,i-1);//递归左边
            quickSort(a, i+1, right);//递归右边
        }
     }


    当前选取第一个元素作为标尺是比较不好的,一种安全的做法是随记选取,但是也不好.我们一般采用三数中值分割法,就是使用左端,右端,中心位置上的三个元素的中位数作为标尺进行排序.


    public static void quickSortS(int[] a,int left,int right){
        if(left<right){//递归出口条件
                int i=left;//左指针
                int j=right;//右指针
                int center=(left+right)/2;
                //三数中值分割法选取标尺
                if((a[left]<=a[center]&&a[center]<=a[right])||(a[right]<=a[center]&&a[center]<=a[left])){
                    int tmp=a[left];
                    a[left]=a[center];
                    a[center]=tmp;//记得交换噢
                }else if((a[left]<=a[right]&&a[right]<=a[center])||(a[center]<=a[right]&&a[right]<=a[left])){
                    int tmp=a[left];
                    a[left]=a[right];
                    a[right]=tmp;
                }
                int x=a[left];//标尺
                while(i<j){
                    while(i<j && a[j]>=x) j--;//从右向左找第一个小于x的数
                    if(i<j) a[i++]=a[j];
                    while(i<j && a[i]<x) i++;//从左向右找第一个大于等于x的数
                    if(i<j) a[j--]=a[i];
                } 
                a[i]=x;//插入标尺
                quickSortS(a,left,i-1);//递归左边
                quickSortS(a, i+1, right);//递归右边
            }
      }


    640?wx_fmt=gif希尔排序


    希尔排序,也称递减增量排序算法,是插入排序的一种更高效的改进版本。希尔排序是非稳定排序算法。


    根据步长进行分组,对每一组进行插入排序.


    最差:O(因步长各异,最好的n log2 n)


    最优:O(n)


    平均:O(因步长各异)


    public static void shellSort(int[] a){
        int igap=a.length;//初始化步长,第一次步长为数组长度的一半
        for(int gap=igap/2;gap>0;gap/=2){//步长
            for(int i=0;i<gap;i++){//共做步长次插入排序
                for(int j=i+gap;j<a.length;j+=gap){//直接插入排序算法
                    for(int k=i;k<=j;k++){
                        if(a[k]>=a[j]){//找到了第一个比自己大的元素,插入到该元素之前
                            int temp=a[j];
                            for(int p=j-1;p>=k;p--) a[p+1]=a[p];//每个元素向后移动一格
                            a[k]=temp;
                            break;
                        }
                    }
                }
            }
        }
     }


    640?wx_fmt=gif归并排序


    原理如下(假设序列共有n个元素):


    1. 将序列每相邻两个数字进行归并操作,形成floor(n/2)个序列,排序后每个序列包含两个元素

    2. 将上述序列再次归并,形成floor(n/4)个序列,每个序列包含四个元素

    3. 重复步骤2,直到所有元素排序完毕

    最差:O(n logn)


    最优:O(n)


    平均:O(n log n)


    public static void mergeSort(int[] a,int first,int last){
        if(first<last){
            int[] temp=new int[a.length];
            int middle=(first+last)/2;
            mergeSort(a, first, middle);//递归左边
            mergeSort(a, middle+1, last);//递归右边
            //将两个有序数列合并
            int i=first;
            int j=middle+1;
            int k=0;
            while(i<=middle&&j<=last){
                if(a[i]<=a[j]){
                    temp[k++]=a[i++];
                }else{
                    temp[k++]=a[j++];
                }
            }
            while(i<=middle){
                temp[k++]=a[i++];
            }
            while(j<=last){
                temp[k++]=a[j++];
            }
            //将排好的temp重新赋给a
            for(i=0;i<k;i++){
                a[first+i]=temp[i];
            }
        }
     }


    640?wx_fmt=gif树形选择排序


    树形选择排序(Tree Selection Sort),又称锦标赛排序(Tournament Sort),是一种按照锦标赛思想进行选择排序的方法。


    首先对n个记录的关键字进行两两比较,然后在其中[n/2](向上取整)个较小者之间再进行两两比较,如此重复,直至选出最小关键字的记录为止。


    这个过程可以用一颗有n个叶子节点的完全二叉树表示,每个非终端节点都是左右孩子中的较小值,这样根节点就是所有叶子节点中的最小值了.把这个值输出后,将叶子节点中的最小值改为"最大值",然后从该叶子节点开始,和其左右兄弟进行比较,修改从叶子节点到根节点的路径上各节点的值,则根节点的值为次小值.同理,可从小到大排出所有值.


    虽然说树,但是通常由数组实现,父节点i的左子节点在位置(2i+1);父节点i的右子节点在位置(2i+2);子节点i的父节点在位置floor((i-1)/2)


    时间复杂度为O(n log n)


    缺点是辅助存储空间太多,并且与"最大值"进行了多余的比较.


    public static int[] TreeSelectSort(int[] data){
            int dlong=data.length;
            int tlong=2*dlong-1;
            int low=0;
            int[] tree=new int[tlong];
            int[] ndata=new int[dlong];

            for(int i=0;i<dlong;i++){
                tree[tlong-i-1]=data[i];
            }

            for(int i=tlong-1;i>0;i-=2){
                tree[(i-1)/2]=(tree[i]<tree[i-1]?tree[i]:tree[i-1]);
            }

            int minIndex;
            while(low<dlong){
                int min=tree[0];
                ndata[low++]=min;
                minIndex=tlong-1;
                //找到最小值
                while(tree[minIndex]!=min){
                    minIndex--;
                }
                tree[minIndex]=Integer.MAX_VALUE;
                //找到其兄弟节点
                while(minIndex>0){//有父节点
                    if(minIndex%2==0){//是右节点
                        tree[(minIndex-1)/2]=(tree[minIndex]<tree[minIndex-1]?tree[minIndex]:tree[minIndex-1]);
                        minIndex=(minIndex-1)/2;
                    }else{//是左节点
                        tree[minIndex/2]=(tree[minIndex]<tree[minIndex+1]?tree[minIndex]:tree[minIndex+1]);
                        minIndex=minIndex/2;
                    }
                }
            }
            return ndata;

        }


    640?wx_fmt=gif堆排序


    堆排序是对树形选择排序的一种优化,建立在"二叉堆"这种数据结构上,二叉堆属于完全二叉树,除此之外还需满足所有父节点都要大于或小于左右子树.


    父节点大于左右孩子的叫最大二叉堆,父节点小于左右孩子的叫最小二叉堆.


    以最大二叉堆为基础进行排序,就是先构建成一个最大堆,接着取出根节点,然后将剩下的数组元素在建成一个最大二叉堆,再取出根节点,如此循环直到所有元素都被取光.


    和树形选择排序一样也是由数组实现,参考上一节.


    最坏,最优和平均的时间复杂度都是nlogn.


    public static void heapSort(int[] a){
            //建立最大堆
            int size=a.length;
            for(int i=(size-1-1)/2;i>=0;i--){
                maxHeap(i,a,size);
            }

            for(int i=a.length-1;i>0;i--){
                //将根节点上的最大值不断与最后一个交换,并将最后一个筛选出来,指向最后的指针向前推进
                int temp=a[i];
                a[i]=a[0];
                a[0]=temp;
                size--;
                //保证根节点最大特性,其他节点都已经保持了
                maxHeap(0, a,size);
            }
        }

        //保持堆的最大特性
        private static void maxHeap(int i,int[] a,int size) {
            int left=2*i+1;
            int right=2*i+2;
            int largest=i;
            //分别与左右子树比较,取最大值
            if(left<=size-1&&a[left]>a[i]){
                largest=left;
            }
            if(right<=size-1&&a[right]>a[largest]){
                largest=right;
            }
            if(largest!=i){
                //交换根节点与最大值
                int temp=a[i];
                a[i]=a[largest];
                a[largest]=temp;
                //递归
                maxHeap(largest, a,size);
            }

        }


    640?wx_fmt=gif桶排序


    桶排序(Bucket Sort)的原理很简单,它是将数组分到有限数量的桶子里。


    假设待排序的数组a中共有N个整数,并且已知数组a中数据的范围[0, MAX)。在桶排序时,创建容量为MAX的桶数组r,并将桶数组元素都初始化为0;将容量为MAX的桶数组中的每一个单元都看作一个"桶"。


    在排序时,逐个遍历数组a,将数组a的值,作为"桶数组r"的下标。当a中数据被读取时,就将桶的值加1。例如,读取到数组a[3]=5,则将r[5]的值+1。


    桶排序的时间复杂度为O(n+k),k为取值范围,在特殊情况下提供了排序的下界.


    public static void bucketSort(int[] a,int max){
            int[] buckets;
            if(a==null||max<1){
                return;
            }
            buckets=new int[max];//创建一个容量为max的数组 并将其数据都初始化为0

            //计数
            for(int i=0;i<a.length;i++){
                buckets[a[i]]++;
            }
            //排序
            for(int i=0,j=0;i<max;i++){
                while((buckets[i]--)>0){
                    a[j++]=i;
                }
            }

            buckets=null;
        }


    640?wx_fmt=gif基数排序(多关键字排序)


    基数排序已经不再是一种常规的排序方式,它更多地像一种排序方法的应用,基数排序必须依赖于另外的排序方法。基数排序的总体思路就是将待排序数据拆分成多个关键字进行排序,也就是说,基数排序的实质是多关键字排序。


    多关键字排序时有两种解决方案:


    最高位优先法(MSD)(Most Significant Digit first)


    最低位优先法(LSD)(Least Significant Digit first)


    这里我们采用LSD,对关键字的排序采用桶排序,那么实质上就变成了多次桶排序.


    时间复杂度为O(d(n+k),d为关键字个数,k为取值范围


    数字是四位数之内,所以四个关键字为个十百千位的值.


    //假定四位数字内比较,有四个关键字,个十百千位,同一关键字使用桶排序
        public static void radixSort(int[] a,int max,int d){
            int rate=1;//表示关键字层级
            int[] buckets=new int[max];//存放个位数,十位数,百位数....
            List<Integer>[] temp=new List[max];//存放缓存数字
            for(int i=0;i<d;i++){

                //清空操作
                 Arrays.fill(buckets, 0);
                 Arrays.fill(temp, null);

                 //计算每个待排序数据的子关键字
                for(int j=0;j<a.length;j++){
                    int subKey=(a[j]/rate)%max;//取得个位数,十位数,百位数...
                    if(temp[subKey]==null) temp[subKey]=new ArrayList<Integer>();//用list数组来存放缓存数字
                    temp[subKey].add(a[j]);
                    buckets[subKey]++;//计数+1
                }

                //进行排序,就是按顺序取
                for(int j=0,k=0;j<max;j++){
                    int t=0;
                    while((buckets[j]--)>0){
                        a[k++]=temp[j].get(t);
                        t++;
                    }
                }

                rate*=max;
            }
        }


    由于数组操作起来是比较麻烦,非要通过数组存储的话只能存储次数不能存储整个数字,那取出来的时候我还没想到好的方法把它还原成数字了.所以我就采取了很笨的方法,再用一个缓存list数组存数字...


    当然这很浪费空间,也很麻烦,直接用链表简单很多


    public static void linkedRadixSort(int[] a,int max,int d){
            ArrayList<ArrayList> list=new ArrayList<ArrayList>();
            int rate=1;

            for(int i=0;i<d;i++){    
                list.clear();
                for(int j=0;j<max;j++){
                    list.add(new ArrayList<Integer>());
                }
                for(int j=0;j<a.length;j++){
                    int num=a[j];
                    int subKey=(num/rate)%max;
                    list.get(subKey).add(num);
                }    
                for(int j=0,k=0;j<max;j++){
                    while(list.get(j).size()>0 && list.get(j)!=null){
                        a[k]=(Integer) list.get(j).remove(0);
                        k++;
                    }    
                }

                rate*=max;
            }
        }


    虽然说是用链表的,但是收集起来的时候仍然是放数组的,仍要开辟许多空间.于是我们采用链式基数排序


    所谓链式,就是用链表存储,前一个分组的尾指针指向下一个分组的头指针这样,这样链表很容易做到.


    public static void linkedRadixSortS(Integer[] a,int max,int d){
            List<Integer> slist=new ArrayList<Integer>();
            ArrayList<ArrayList> list=new ArrayList<ArrayList>();
            int rate=1;
            slist.addAll(Arrays.asList(a));

            for(int i=0;i<d;i++){    
                list.clear();
                for(int j=0;j<max;j++){
                    list.add(new ArrayList<Integer>());
                }
                while(slist.size()>0){
                    int num=slist.remove(0);
                    int subKey=(num/rate)%max;
                    list.get(subKey).add(num);
                }    
                for(int j=0;j<max;j++){
                    slist.addAll(list.get(j));    
                }
                rate*=max;
            }


            a=slist.toArray(a);
        }


    上面三种只是空间上的优化,对于时间复杂度是没有影响的.


    640?wx_fmt=gif外部排序


    之前所有的排序算法都属于内部排序,需要将输入数据装入内存中.然而在一些应用程序中,它们的输入数据量太大装不进内存,这时候就需要外部排序了.


    基本外部排序算法使用归并排序.设有四盘磁带,Ta1,Ta2,Tb1,Tb2,磁带a和磁带b或者用作输入磁带,或者用于输出磁带.从输入磁带一次读入M(我们取3)个记录,在内部将这些记录排序,然后再交替地写到Tb1或Tb2上,将每组排过序的记录叫做一个顺串.


    640?wx_fmt=other


    640?wx_fmt=other


    现在Tb1和Tb2都包含了一些顺串.我们将每个磁带的第一个顺串取出将两者合并,把结果写到Ta1上,该结果是一个二倍长的顺串.然后我们再从每盘磁带取出下一个顺串,合并然后写到Ta2上.继续这个过程直达Tb1或Tb2为空.继续这个过程直到得到长为N的一个顺串,这个算法需要log(N/M)趟工作,见下图


    640?wx_fmt=other


    640?wx_fmt=other


    如果我们有额外的磁带,那么我们可以减少将输入数据排序的趟数,将基本的(2-路)合并扩充为(k-路)合并就可以做到这一点.这就是所谓的多路合并


    640?wx_fmt=other


    640?wx_fmt=other


    640?wx_fmt=other


    上面讨论的k-路合并方案需要使用2k盘磁带,其实我们通过只使用k+1盘磁带也可以完成排序的工作,就是所谓的多相合并.以三盘磁带完成2-路合并为例:


    640?wx_fmt=other


    这儿顺串最初的分配是一个关键的问题,其实采用斐波那契数是最优的.
    对于顺串的构造还可以采用置换选择排序,采用堆的思想构建,具有特别的价值.


    640?wx_fmt=other


    接下来实现完整的外部排序


    package com.fredal.structure;

    import java.io.File;
    import java.io.FileInputStream;
    import java.io.FileNotFoundException;
    import java.io.FileOutputStream;
    import java.io.IOException;
    import java.util.ArrayList;
    import java.util.Arrays;
    import java.util.Iterator;
    import java.util.Random;

    public class ExternalSort {

        public static int BUFFER_SIZE = 10;

        public File sort(File file) throws IOException {
            ArrayList<File> files = split(file);
            return process(files);
        }

        // recursive method to merge the lists until we are left with a
        // single merged list
        private File process(ArrayList<File> list) throws IOException {
            if (list.size() == 1) {
                return list.get(0);
            }
            ArrayList<File> inter = new ArrayList<File>();
            for (Iterator<File> itr = list.iterator(); itr.hasNext();) {
                File one = itr.next();
                if (itr.hasNext()) {
                    File two = itr.next();
                    inter.add(merge(one, two));
                } else {
                    return one;
                }
            }
            return process(inter);
        }

        /**
         * Splits the original file into a number of sub files. 
         */

        private ArrayList<File>  split(File file) throws IOException {
            ArrayList<File> files = new ArrayList<File>();
            int[] buffer = new int[BUFFER_SIZE];
            FileInputStream fr = new FileInputStream(file);
            boolean fileComplete = false;
            while (!fileComplete) {
                int index = buffer.length;
                for (int i = 0; i < buffer.length && !fileComplete; i++) {
                    buffer[i] = readInt(fr);
                    if (buffer[i] == -1) {
                        fileComplete = true;
                        index = i;
                    }
                }
                if (buffer[0] > -1) {
                    Arrays.sort(buffer, 0, index);
                    File f = new File("set" + new Random().nextInt());
                    FileOutputStream writer = new FileOutputStream(f);
                    for (int j = 0; j < index; j++) {
                        writeInt(buffer[j], writer);
                    }
                    writer.close();
                    files.add(f);
                }

            }
            fr.close();
            return files;
        }

        /**
         * Merges two sorted files into a single file.
         * 
         * @param one
         * @param two
         * @return
         * @throws IOException
         */

        private File merge(File one, File two) throws IOException {
            FileInputStream fis1 = new FileInputStream(one);
            FileInputStream fis2 = new FileInputStream(two);
            File output = new File("merged" + new Random().nextInt());
            FileOutputStream os = new FileOutputStream(output);
            int a = readInt(fis1);
            int b = readInt(fis2);
            boolean finished = false;
            while (!finished) {
                if (a != -1 && b != -1) {
                    if (a < b) {
                        writeInt(a, os);
                        a = readInt(fis1);
                    } else {
                        writeInt(b, os);
                        b = readInt(fis2);
                    }
                } else {
                    finished = true;
                }

                if (a == -1 && b != -1) {
                    writeInt(b, os);
                    b = readInt(fis2);
                } else if (b == -1 && a != -1) {
                    writeInt(a, os);
                    a = readInt(fis1);
                }
            }
            os.close();
            return output;
        }

        private void writeInt(int value, FileOutputStream merged)
                throws IOException 
    {
            merged.write(value);
            merged.write(value >> 8);
            merged.write(value >> 16);
            merged.write(value >> 24);
            merged.flush();
        }

        private int readInt(FileInputStream fis) throws IOException {
            int buffer = fis.read();
            if (buffer == -1) {
                return -1;
            }
            buffer |= (fis.read() << 8);
            buffer |= (fis.read() << 16);
            buffer |= (fis.read() << 24);
            return buffer;
        }

        /**
         * @param args
         * @throws IOException
         */

        public static void main(String[] args) throws IOException {
            File file = new File("mainset");
            Random random = new Random(System.currentTimeMillis());
            FileOutputStream fw = new FileOutputStream(file);
            for (int i = 0; i < BUFFER_SIZE * 3; i++) {
                int ger = random.nextInt();
                ger = ger < 0 ? -ger : ger;
                fw.write(ger);
                fw.write(ger >> 8);
                fw.write(ger >> 16);
                fw.write(ger >> 24);
            }
            fw.close();
            ExternalSort sort = new ExternalSort();
            System.out.println("Original:");
            dumpFile(sort, file);
            File f = sort.sort(file);
            System.out.println("Sorted:");
            dumpFile(sort, f);

        }

        private static void dumpFile(ExternalSort sort, File f)
                throws FileNotFoundException, IOException 
    {
            FileInputStream fis = new FileInputStream(f);
            int i = sort.readInt(fis);
            while (i != -1) {
                System.out.println(Integer.toString(i));
                i = sort.readInt(fis);
            }
        }

    }
  • 相关阅读:
    【转】【SEE】基于SSE指令集的程序设计简介
    【转】【Asp.Net】asp.net服务器控件创建
    ControlTemplate in WPF ——ScrollBar
    ControlTemplate in WPF —— Menu
    ControlTemplate in WPF —— Expander
    ControlTemplate in WPF —— TreeView
    ControlTemplate in WPF —— ListBox
    ControlTemplate in WPF —— ComboBox
    ControlTemplate in WPF —— TextBox
    ControlTemplate in WPF —— RadioButton
  • 原文地址:https://www.cnblogs.com/hgmyz/p/12351530.html
Copyright © 2011-2022 走看看