《算法》读书笔记(未完)

排序

排序模板

package algorithm.sort;
import java.lang.Comparable;
public abstract class SortExample<T extends Comparable<T>> {
    public abstract void sort(T[] a);

    public boolean less(T v, T w){
        return v.compareTo(w)<0;
    }
    public void exch(T[] a,int i,int j){
        T t=a[i];
        a[i]=a[j];
        a[j]=t;
    }
    public void show(T[] a){
        for (T comparable : a) {
            System.out.println(comparable + "");
        }
    }
    public boolean isSorted(T[] a){
        for(int i=1;i<a.length;i++){
            if(less(a[i],a[i=1])) return false;
        }
        return true;
    }
}

插入排序

在尚未排序元素中按顺序遍历每个乱序元素,将每个乱序元素按顺序与有序元素比较,放在合适的位置后,操作下一个乱序元素

package algorithm.sort;
public class Insertion<T extends Comparable<T>> extends SortExample<T>{
    @Override
    public void sort(T[] a){
        int N = a.length;
        for(int i=1;i<N;i++){
            for(int j=i;j>0&&less(a[j],a[j=1]);j--){
                exch(a,j,j-1);
            }
        }
    }
}

希尔排序

希尔排序基于插入排序,插入排序的比较与交换是相邻元素之间的所以大规模的乱序会很慢,希尔排序的思想是将数组中任意间隔为h的元素都是有序的,即是说希尔排序意在构建多个相互独立的有序数组。伴随着间隔逐渐的缩减为1后便是有序数组,而各个间隔为h的独立数组的实现都是插入排序。
对于中等大小的数组,希尔排序的运行时间是可以接受的,代码量小,且不需要使用额外的内存,对于很大的N其它高效算法只会比希尔排序快两倍左右,但其更复杂。
此处希尔排序实现中的h的使用的是实时计算出的1/2(3^k-1)序列,也可以将h的序列存储在数组当中供使用。

package algorithm.sort;
public class Shell<T extends Comparable<T>> extends SortExample<T>{
    @Override
    public void sort(T[] a) {
        int N = a.length;
        int h = 1;
        while(h<N/3)
            h=3*h+1;
        while (h>=1){
            for(int i=h;i<N;i++){
                for(int j=i;j>=h&&less(a[j],a[j-h]);j-=h)
                    exch(a,j,j-h);
            }
            h=h/3;
        }
    }
}

归并排序

归并排序的实现思想为:先将左边排序,再将右边排序,最后左右统一起来,在自顶向下调用的过程中总是先排左再排右最后联合,有种后序遍历的感觉,排序主要右merge算法实现。
归并排序在最坏的情况下的比较次数是~NlgN,这是任何最好的算法在最坏的 情况下的最小的比较次数。

package algorithm.sort;
public class Merge{
    private static Comparable[] aux;
    public static void sort(Comparable[] a){
        aux = new Comparable[a.length];
        sort(a,0,a.length-1);
    }
    private static void sort(Comparable[] a,int lo,int hi){
        if(hi<=lo) return;
        int mid = lo+(hi-lo)/2;
        sort(a,lo,mid);
        sort(a,mid+1,hi);
        merge(a,lo,mid,hi);
    }

    public static void merge(Comparable[] a,int lo,int mid ,int hi){
        int i = lo,j = mid+1;
        for (int k = lo;k<=hi;k++)
            aux[k] = a[k];
        for(int k=lo;k<=hi;k++)
            if(i>mid)
                a[k]=aux[j++];
            else if (j>hi)
                a[k] = aux[i++];
            else if (less(aux[j],aux[i]))
                a[k] = aux[j++];
            else
                a[k] = aux[i++];
    }
    private static boolean less(Comparable v, Comparable w){
        return v.compareTo(w)<0;
    }
}

快速排序

快速排序适用于各种不同的输入数据且在一般应用中比其他排序算法都要快得多。并且快速排序是原地排序,只需要一个很小的辅助栈,不会有太多余的内存开销,将长度为N的数组排序所需要的时间和NlgN成正比。
快速排序有着分治的思想:将一个数组分为两个数组,当两个子数组都有序时,整个数组自然有序了。
快速排序的缺点便是很脆弱,一不小心便会使得性能变得恶劣,主要注意一下几个点:原地切分(不在递归调用中新建数组);越界;保持随机性(消除对输入的依赖);终止循环;终止递归;切分元素值会有重复的情况

package algorithm.sort;
public class Quick <T extends Comparable<T>> extends SortExample<T>{
    @Override
    public void sort(T[] a) {
        sort(a,0,a.length-1);
    }
    private void sort(T[] a,int lo,int hi){
        //该算法实现的核心思想是将数组首位的元素放在合适的位置,使得元素左边的小于元素右边的
        if(hi<=lo)return;
        int j=partition(a,lo,hi);
        sort(a,lo,j-1);
        sort(a,j+1,hi);
    }
    private int partition(T[] a,int lo,int hi){
        //左右扫描指针
        int i=lo,j=hi+1;
        T v = a[lo];
        while(true){
            //从左到右遍历,直到某一值大于首位元素,或者遍历完整个数组
            while(less(a[++i],v))
                if(i==hi)break;
            //从右到左遍历,直到某一值小于首位元素,或者遍历完整个数组
            while(less(v,a[--j]))
                if(j==lo)break;
                
            if(i>=j) break;
            //交换该两元素,使得整个数组呈现左边小右边大的趋势
            exch(a,i,j);
        }
        //此时整个数组已经遍历完毕,扫描指针已经重合,重合的位置有着左边小右边大的特点,将首元素移动该位置,并返回以供归并
        exch(a,lo,j);
        return j;
    }
}

对于小数组而言,可以做如下更改将快速排序切换到插入排序
if(hi<=lo) return;
更改为
if(hi<=lo+M){Insertion.sort(a,lo,hi); return;}
当数据具有大量重复元素时,通过将原切分
a[lo,j-1]<v=a[j]<a[j+1,hi]
更改为
a[lo..lt-1]<v=a[lt,gt]<a[gt+1,hi]
具体实现为三向切分的快速排序

三向切分的快速排序

package algorithm.sort;
public class Quick3way extends Quick {
    @Override
    protected void sort(Comparable[] a, int lo, int hi) {
        if(hi<=lo)return;
        int lt = lo,i=lo+1,gt=hi;
        Comparable v = a[lo];
        while(i<=gt){
            int cmp = a[i].compareTo(v);
            if(cmp<0)exch(a,lt++,i++);
            else if(cmp>0)exch(a,i,gt--);
            else i++;
        }
        sort(a,lo,lt-1);
        sort(a,gt+1,hi);
    }
}

堆/队列

堆常为优先队列的实现采用的数据结构,由于优先队只在乎极值,所以在实现过程中少去了许多比较与交换。

基于堆的优先队列

public class MaxPQ <Key extends Comparable<Key>>{
    //基于堆的完全二叉树,pq[0]未使用
    private Key[] pq;
    private int N=0;
    //创建优先队列
    public MaxPQ(){
        ***
    }
    public MaxPQ(int max){
        pq = (Key[])new Comparable[max+1];
    }
    public MaxPQ(Key[] a){
      ***
    }
    public void insert(Key v){
        pq[++N] = v;
        swim(N);
    }
    //返回最大元素
    public Key max(){
        return pq[1];
    }
    //删除、返回最大元素
    public Key delMax(){
        Key max = pq[1];
        exch(1,N--);
        //N+1处不再使用,设为null以便系统回收空间
        pq[N+1]=null;
        sink(1);
        return max;
    }
    //返回队列是否为空
    public boolean isEmpty(){
        return N==0;
    }
    public int size(){
        return N;
    }
    private boolean less(int i,int j){
        return pq[i].compareTo(pq[j])<0;
    }
    private void exch(int i,int j){
        Key t = pq[i];pq[i] = pq[j];pq[j] = t;
    }

    //从下到上,将更大的节点向上传递
    private void swim(int k){
        while(k>1&&less(k/2,k)){
            exch(k/2,k);
            k = k/2;
        }
    }
    //从上到下,将顶部的节点向下传递
    private void sink(int k){
        while(2*k<=N){
            int j = 2*k;
            //选出当前节点的最大子节点
            if(j<N&&less(j,j+1)) j++;
            //若当前节点不小于选出的最大子节点,则打破循环
            if(!less(k,j)) break;
            exch(k,j);
            k=j;
        }
    }
}

基于堆的索引优先队列

适用于优先队列中的元素已经被多个地方的无关用例代码用整数索引来引用的情况
待修改:

public class IndexMinPQ <Key extends Comparable<Key>>{
    private int N;
    //元素对于的整数索引,索引二叉堆,这个是堆
    private int[] pq;
    //整数索引在pq中的位置,可以直接实现索引的访问与修改;访问索引i:pq[qp[i]]
    private int[] qp;
    //元素
    private Key[] keys;
    public IndexMinPQ(int maxN){
        keys = (Key[]) new Comparable[maxN+1];
        pq = new int[maxN+1];
        qp = new int[maxN+1];
        for(int i = 0;i<=maxN;i++) qp[i] =-1;
    }
    public Boolean isEmpty(){
        return N ==0;
    }
    public boolean contains(int k){
        return qp[k] !=-1;
    }
    public void insert(int k,Key key){
        N++;
        qp[k] = N;
        qp[N] = k;
        keys[k]=key;
        swim(N);
    }
    public Key min(){
        return keys[pq[1]];
    }
    public int delMin(){
        int indexOfMin = pq[1];
        exch(1,N--);
        sink(1);
        keys[pq[N+1]] = null;
        qp[pq[N+1]] = -1;
        return indexOfMin;
    }
    //从下到上,将更大的节点向上传递
    private void swim(int k){
        while(k>1&&less(k/2,k)){
            exch(k/2,k);
            k = k/2;
        }
    }
    //从上到下,将顶部的节点向下传递
    private void sink(int k){
        while(2*k<=N){
            int j = 2*k;
            //选出当前节点的最大子节点
            if(j<N&&less(j,j+1)) j++;
            //若当前节点不小于选出的最大子节点,则打破循环
            if(!less(k,j)) break;
            exch(k,j);
            k=j;
        }
    }
    private boolean less(int i,int j){
        return keys[pq[i]].compareTo(keys[pq[j]])<0;
    }
    private void exch(int i,int j){
        int t1 = pq[i];pq[i] = pq[j];pq[j] = t1;
        int t2 = qp[i];qp[i] = qp[j];qp[j] = t2;
    }
    //返回最小元素的索引
    public int minIndex(){
        return pq[1];
    }
    //将索引为k的元素设为key
    public void change(int k,Key key){
        keys[k] = key;
        //索引为k的元素的位置
        swim(qp[k]);
        sink(qp[k]);
    }
    public void delete(int k){
        int index = qp[k];
        exch(index,N--);
        swim(index);
        sink(index);
        keys[k] = null;
        qp[k]=-1;
    }
}

小结

  • 多数情况下快速排序是最佳选择;当稳定性很重要,且空间又不是问题的同时,归并排序也还行。
  • 原始类型数据的排序更快,非原始类数据排序过程中存储引用所需要的空间以及引用访问的成本再加上调用方法的开销都是比原始类型多得多。
  • Java系统库中的主要排序方法java.tuil.Arrays.sort();根据不同的参数类型,都代表了一系列排序算法
  • 以上算法包括了Java现代系统的核心组成部分,所以实际应用开发中Java的Arrays.sort()实现再加上自己实现的CompareTo()、compare()已经够用,因为这里面已经涵盖了经典的三向快速排序以及归并排序。


    各种排序算法的性能特点

查找

小错误

  • 抽象类中的静态方法不可以被重写
  • 抽象方法不可以是静态的
  • Raw use of parameterized class '*':含有泛型参数的类不能使用其原生态类型,不能不对泛型参数进行定义便将该泛型类进行使用
  • unchecked call to * as a member of the raw type $:泛型的引用后面没有规定类型
  • 泛型数组创建存在的问题
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 216,402评论 6 499
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,377评论 3 392
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 162,483评论 0 353
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,165评论 1 292
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,176评论 6 388
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,146评论 1 297
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,032评论 3 417
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,896评论 0 274
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,311评论 1 310
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,536评论 2 332
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,696评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,413评论 5 343
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,008评论 3 325
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,659评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,815评论 1 269
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,698评论 2 368
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,592评论 2 353