《算法技术手册》相关笔记-第四章排序

来源:互联网 发布:淘宝上的6s官换机真相 编辑:程序博客网 时间:2024/06/05 10:51

数据在随机存储器上一般以两种形式保存:基于指针的存储和基于值得存储。


一个不稳定的算法不会关注原始集合中的元素位置之间的关系(也许它会维护相对顺序,也许不会)。

分析技术

讨论排序,肯定得分析一个算法在最好情况、最坏情况和平均情况下的性能。

计算机科学中的一个基本结论是:无论实在平均情况还是在最坏情况下,一个基于比较的排序算法不可能得到比O(n log n)更快的性能。

简要证明:

给定n个元素,有n!种排序。每个使用两两比较的排序算法都相当于下面的二叉决策树。

决策树的叶子就相当于下面的一个比较序列,每个排序至少在树中与一个叶子相对应。

每条路径从根到叶子上的节点就相当于一个比较序列。

这棵树的高度就是从根到叶子的最长路径上的比较节点的数目。



插入排序

插入排序对于基于值的数据效率很低,因此很多内存需要位移,以给新的值腾出位置。

最好情况:n个元素中每个元素都在其正确的位置,因此插入排序将花费线性时间O(n)。

使用环境:小规模数据需要排序,并且初始数据几乎是有序的。

排序详解

[相关内容以后更新]

参考代码

       public void insertionSort(int[] ar,int length){        for (int i=1;i<length i="" int="" j="i-1;" value="ar[i];" while="">=0 && th4_unit.cmp(ar[j],value)>0){                ar[j+1]=ar[j];                j--;            }            ar[j+1]=value;        }    }</length>

中值排序

分治是一种非常常见的方法,它将一个问题分成两个独立的子问题,每个子问题的规模是原始问题规模的一半。

排序详解


图中的每一行都表示对此算法的一次递归调用。

每一步问题的数量都会加倍,但每一个问题的规模都会减少一半。

子问题之间相互独立,一旦递归结束,就可以得到最终的排序结果。

参考代码

[java代码设计中]

快速排序

快速排序与中值排序的思想大致相同,但却比中值排序更简单。

因为快速排序是递归的,所以我们能够认为:t(n)=2*t(n/2)+O(n)

O(n)表示切分数组需要花费线性时间。

如果一个算法能够用这样的t(n)来定义行为,那么其性能为O(n log n)。从大量实践的经验上来看,因为被抽象称为O(n)的常数比较小,快速排序要比中值排序快。快速排序的总开销也比较少,因为它不需要在构建子问题时寻找中值元素。

最坏情况下,最大或者最小的元素被挑选成为中枢元素。当这种情况发生的时候,快速排序将会遍历一遍数组中所有的元素(线性时间),仅仅排序数组中的一个元素。在最坏的情况下,这个过程将会重复n-1次,导致性能退化到O(n*n)。

排序详解

参考代码

public void quitSort(int[] ar,int left,int right){        if(left>=right) return;        //获取切分值所在位置        int p=left;        //切分值放置到最后        th4_unit.swap(ar,p,right);        //从头开始比较,小于等于中枢值的在前段,大于切分值的数据在后端。        int store=left;        for (int i=left;i<right;i++){            if(ar[i]<=ar[right]){                th4_unit.swap(ar,i,store);                store++;            }        }        th4_unit.swap(ar,store,right);        //递归调用        quitSort(ar,left,store-1);        quitSort(ar,store+1,right);    }

int minSize=5;    /**     * 优化后的快速排序     * 当切分的子序列达到一个最小的阈值,采用插入排序     *     * @param  ar, left, right           * @return void     * @exception     *     * @author sunyx     * @date 2016/3/1     * @since JDK 1.8       */    public void quitSort(int[] ar,int left,int right){                if(left>=right) return;        //获取切分值所在位置        int p=left;        //切分值放置到最后        th4_unit.swap(ar,p,right);        //从头开始比较,小于等于中枢值的在前段,大于切分值的数据在后端。        int store=left;        for (int i=left;i<right;i++){            if(ar[i]<=ar[right]){                th4_unit.swap(ar,i,store);                store++;            }        }        th4_unit.swap(ar,store,right);        //递归调用        if(store-1-left<=minSize){            insertionSort(ar,store-1-left);        }else{            quitSort(ar,left,store-1);        }        if(right-store-1<=minSize){            insertionSort(ar,right-store-1);        }else{            quitSort(ar,store+1,right);        }    }

各种各样的优化:

1.利用存储子任务的栈来消除递归。

2.选择基于三中值分区的中枢值。

3.设定一个使用切分时数组长度的最小值,如果小于这个值,就使用插入排序(这个值根据实现和机器架构的不同而不同;例如,在Sun4架构上,这个最小值根据经验,一般设定为4)。

4.当处理子数组的时候,首先将大的子数组压入栈中,这样来最小化栈的总大小,确保小的问题首先被解决。

选择排序

最慢的,即使在最好的情况下(如数组已经有序)它都需要二次方时间。

[代码后续再传]

堆排序

一个堆就是一颗二叉树,它具有以下两个性质:

外形性质

如果深度k-1存在 2^k-1 个节点,那么深度k(k>0)上存在叶子节点。另外,在一个部分填充的深度级上,节点是“从左到右”添加的。

堆的性质

树中每一个节点的值都大于或者等于任意一个子节点的值(如果有的话)。


排序详解



参考代码

public void heapSort(int[] ar,int n){        buildHeaep(ar,ar.length);        for (int i=n-1;i>0;i--){            th4_unit.swap(ar,0,i);            heapify(ar,0,i);        }    }    /**     * 如果ar[i]的两个子节点ar[2*i+1]和 至少有一个比ar[i]大     * 调用heapify交换ar[i]和两个子节点中较大的来更新ar     *     * @param  ar, n     * @return void     * @exception     *     * @author sunyx     * @date 2016/3/2     * @since JDK 1.8     */    public void buildHeaep(int[] ar,int n){        for (int i=n/2-1;i>=0;i--){            heapify(ar,i,n);        }    }    /**     * 交换ar[idx]和两个子节点中较大的来更新ar[]     * 如果发生了交换,heapify需要检查其孙子节点,以正确的维护ar[]的堆性质     *     * @param  ar, idx, max     * @return void     * @exception     *     * @author sunyx     * @date 2016/3/2     * @since JDK 1.8     */    public void heapify(int[] ar,int idx,int max){        int left=2*idx+1;        int right=2*idx+2;        int largest;        if(left<max && ar[left]>ar[right]){            largest=left;        }else{            largest=idx;        }        if(right<max && ar[right]>ar[largest]){            largest=right;        }        if(largest!=idx){            th4_unit.swap(ar,idx,largest);            heapify(ar,largest,max);        }    }
桶排序

给定n个元素的集合,桶排序构造了n个桶来切分输入集合,因此桶排序能够降低处理时其在额外空间的开销。
如果一个散列函数,hash(ar[i]),用来均匀地将n个元素分配到n个桶中,那么桶排序在最坏情况下也能够以O(n)的时间进行排序。
使用桶排序的条件
均匀分布
输入数据需要均匀分布在一个给定的范围内。基于这种分配,算法创建了n个桶来平分输入数据。
有序散列函数
桶必须是有序的。也就是说,如果i<j,桶b[i]中的元素要字典序小于b[j]中的元素。
桶排序不适合排序随机字符串。

排序详解

参考代码

    private Bucket[] buckets; //桶    private int bucketSize;//桶尺寸    //桶内部节点    class Entry{        int value;        Entry next;    }    //桶节点    class Bucket{        int size;        Entry head;    }    public void sort(int[] ar,int bucketSize){        this.bucketSize=bucketSize;        //创建一个桶        buckets=new Bucket[bucketSize];        //遍历ar        for (int i=0;i<ar.length;i++) {            //将ar[i]生成hash值            int key=hash(ar[i]);            //根据hash值将ar[i]存到对应的桶            if(buckets[key]==null){                buckets[key]=new Bucket();                buckets[key].head=new Entry();                buckets[key].head.value=ar[i];                buckets[key].size+=1;            }else{                Entry entry=new Entry();                entry.value=ar[i];                entry.next=buckets[key].head;                buckets[key].head=entry;                buckets[key].size+=1;            }        }        //执行extract        extract(ar);    }    /**     * 将桶中的数据提取出来,存放回ar     * 提取桶时,采用插入排序,因为桶数组中的链表存放的值是无序的     * @param   ar     * @return void     * @exception     *     * @author sunyx     * @date 2016/3/3     * @since JDK 1.8     */    public void extract(int[] ar){        int idxb=0;//桶数组移动的下标        int idxa=0;//ar数组移动的下标        int lenght=ar.length;        //循环插入到ar        while(idxb<lenght && idxa<lenght){            //提取桶数组链表中的数据,采用插入排序            if(buckets[idxb]==null) {                idxb++;                continue;            }            int[] values =insertionSort(buckets[idxb]);            //循环取出的值,依次插入到ar            for (int i=0;i<values.length;i++){                ar[idxa]=values[i];                idxa++;            }            idxb++;        }    }    /**     * 关键,要根据排序的值进行修改     * 其产生的hash值会影响到 桶中数据节点的位置,进而影响到最终的序列。     * 前一个桶数组中的节点都要比后一个桶数组的节点的数据小     *     * @param  value     * @return int     * @exception     *     * @author sunyx     * @date 2016/3/3     * @since JDK 1.8     */    public int hash(int value){        int key=value/bucketSize;        return (key>=bucketSize?bucketSize-1:key);    }    public int[] insertionSort(Bucket bucket){        int[] ar=new int[bucket.size];        int i=0;        Entry entry=bucket.head;        while (entry!=null && i<ar.length){            int j=i-1;            int value=entry.value;            while(j>=0 && th4_unit.cmp(ar[j],value)>0){                ar[j+1]=ar[j];                j--;            }            ar[j+1]=value;            i++;            entry=entry.next;        }        return ar;    }



选择排序算法的标准标准排序算法很少的元素插入排序几乎有序的元素插入排序关注最坏情况堆排序希望能够得到一个好的平均情况下性能快速排序元素是从一个密集集合中抽取出桶排序希望尽可能少地写代码插入排序




0 0
原创粉丝点击