《算法》第四版之排序

《算法》第四版

一:排序算法

排序算法简而言之,可以按照时间复杂度分为两种。时间复杂度是指当排序的数据规模曾指数增长时,排序算法所耗费的时间是呈指数级别增长,还是常数级别增长,还是lg级别增长。因此这两种时间复杂度分别就是O(n平方)和O(nlg(n))两种。当需要排序的数据达到一定规模时,这两种排序算法消耗的时长,也就判若云泥。

(一):n的平方级别

(1)选择排序

选择排序主要是在[4,2,7,1,8,9,3]数组的遍历中,寻找到[i
,n)的最小值。如果是希望升序排列的话。然后将最小值与i进行交换。
也就是说,在每次遍历时,只需要交换一次即可

private void selectSort(int [] arr,int size){
    for(int i=0;i<size;i++){
    //寻找[i,size)之间的最小值,并且保存为minIndex。
        int minIndex=i;
        for(int j=i;j<size;j++){
            if(arr[minIndex]>arr[j]){
            //当发现有更小的值时,更换minIndex。
                minIndex=j;
            }
        }
        //将最小值与i的值进行交换。实现选择排序。
        swap(arr,i,minIndex);
    }
}

因此选择排序,也可以认为是在当前元素及其之后,选择最小值排序。

(2) 插入排序之交换排序

同样的数组[4,2,7,1,8,9,3],如果希望是升序排列的话。插入排序是指,在[0,i]中寻找i的合适位置。如果寻找到了合适位置j,则交换i和j,直到j的位置为0。由于[0,i)已经是有序的啦,因此当首次不满足arr[i]<arr[i-1]的情况下,就可以break。这里可能需要交换多次。

private void insertSort(int [] arr,int size){
//这里从i=1开始,是因为0号索引位置的元素无法再与它之前的元素比较。
    for(int i=1;i<size;i++){
    //寻找[i,0]之间i的合适位置。
        for(int j=i;j>0;j--){
        //如果寻找到了一次合适位置,就交换一次位置。直到(j-1)为0为止。也就是在[i,0]区间中进行了所有元素与i的比较。
            if(arr[j]<arr[j-1]){
                swap(arr,j,j-1);
            }else{
            //由于(i,0]已经是一个有序的降序排列数组,因此一旦发现第一次出现arr[j]不小于arr[j-1]的情况,就可以break;
                break;
            }
        }
    }
}

一般来说,交换一次两个元素需要三次操作,尽管插入排序可以提前结束内层循环,但是由于上面的插入排序可能会使用多次交换,因此在总的时间耗费上,仍肯能高于选择排序。

(2)插入排序之赋值排序

由于交换是一个相对耗时的操作,因此可以采用赋值比较的方式,优化上面的插入排序。将[i,0]的数组中,i的值保存在一个变量中,然后通过这个变量与arr[j-1]进行比较,如果这个变量小于了arr[j-1],那么就将arr[j]的值赋值为arr[j-1],反之,则(j)这个索引,就是所要求的的位置。

private void insertSort2(int [] arr,int size){
    //这里只需要从索引1开始比较,因为索引0之前没有元素了。
    for(int i=1;i<size;i++){
        int temp=arr[i];
        int j;
        for(j=i;j>0;j--){
            if(temp<arr[j-1]){
                arr[j]=arr[j-1];
            }else{
                break;
            }
        }
        //这里的j已经是所求的位置了,因此直接赋值即可。
        arr[j]=temp;
    }
}

通过赋值来进行比较的插入排序,就不再需要交换元素了。只是额外多需要了两个临时空间,用来存储临时变量。并且依然可以提前结束内层循环。插入排序在排列近乎有序的数组时,几乎可以达到O(n)级别。

(3)希尔排序。

希尔排序是插入排序的延伸。插入排序每次只跟前面的一个元素进行比较,希尔排序则是先将数组按照步长分为逻辑上的若干个小数组,分别进行插入排序。排序后,再继续减小步长的值,再次分组,再次分别使用插入排序。最后再对整个数组进行一次插入排序。关于步长序列的选择,直接影响了希尔排序的最坏情况。比如[1,2,4,8...]最坏的情况是n的平方。[1,3,7...((2的n次方)-1)]这样的序列最坏情况就是n的(1.5次方)。Sedgewick提出的[1,5,19,41,109...]则可以将最坏情况降低为n的(1.3次方)。

public void shellSort(int [] arr,int size){
    for(int step=size/2;step>0;step/=2){
        for(int i=step;i<size;i++){
            int j=i;
            while(j-step>=0&&arr[j]<arr[j-step]){
                swap(arr,j,j-step);
                j-=step;
            }
        }
    }
}
(4)归并排序

归并排序是O(nlg(n))级别的算法,相对上面几种排序,归并排序通过递归来实现。先是将数组一分为二,然后各自再一分为二,直到剩下只有一个元素。然后再分别对每个小数组进行排序。排序完成后,两个有序的小数组,再合并为一个有序的大数组。依次递归,最终完成整个数组的排序。排序过程中需要额外的一个数组的空间。

private void merge1(int [] arr,int size){
    merge2(arr,0,size-1);
}

private void merge2(int [] arr,int l,int r){
    if(l>=r){
        return;
    }
    int middle=l/2+r/2;
    merge2(arr,l,middle);
    merge2(arr,middle+1,r);
    merge3(arr,l,middle,r);
}

private void merge3(int [] arr,int l,int m,int r){
    int [] temp = new int[r-l+1];
    for(int i=l;i<=r;i++){
        temp[i-l]=arr[i];
    }
    int i=l;
    int j=m+1;
    for(int k=l;k<=r;k++){
        if(j>r){
            arr[k]=temp[i-l];
            i++;
        }else if(i>m){
            arr[k]=temp[j-l];
            j++;
        }else if(temp[i-l]>temp[j-l]){
            arr[k]=temp[j-l];
            j++;
        }else{
            arr[k]=temp[i-l];
            i++;
        }
    }
}

(5)快速排序

快速排序也是基于递归来实现,但是快速排序与归并的区别在于,归并在每次二分数组的时候,都可以平衡地将数组一分为二,也就保障了数组在分到最后一层的时候,只有lg(n)的层数。但是快速排序在选定一个元素作为中间值,并且以这个元素来二分数组,大于这个元素的值放在该值的右边,小于这个元素的值放在该元素的左边,如果遇到了有序数组的话。那么这种二分就会导致数组的层级为n,快速排序的时间复杂度也就退化为O(n的平方)。避免这种情况,就是每次在选取元素时,都采用随机选取的方式,这样就将O(n的平方)这种复杂度出现的几率,降低到几乎为0。但是如果遇到大量重复元素出现的数组,还是会让快速排序降低为O(n的平方),此时就需要通过三路快排的方式,保障数组在平分时,能够维持在lg(n)的层级。

public void quickSort(int [] arr,int size){
    quickSort1(arr,0,size-1);
}

public void quickSort1(int [] arr,int l,int r){
    if(l>=r){
        return;
    }
    int p = partition(arr,l,r);
    quickSort1(arr,l,p-1);
    quickSort1(arr,p+1,r);
}


public int partition(int [] arr,int l,int r){
    //这里应该随机在[l,r]之间选取一个元素作为v的值。不然遇到有序数组的情况,时间复杂度就会退化为n的平方。
    int v=arr[l];
    int j=l;
    for(int k=l+1;k<=r;k++){
        if(arr[k]<v){
            swap(arr,j+1,k);
            j++;
        }
    }
    swap(arr,j,l);
    return j;
}
(6)三路快排
private static void quickThree1(int arr [],int size){
        quickThree2(arr,0,size-1);
    }

    private static void quickThree2(int [] arr,int l,int r){
        if(l>=r){
            return;
        }
        int v=arr[l];
        int lt=l;
        int gt=r+1;
        int i=l+1;
        while (i<gt){
            if(arr[i]<v){
                swap(arr,i,lt+1);
                lt++;
                i++;
            }else if(arr[i]>v){
                swap(arr,i,gt-1);
                gt--;
            }else{
                i++;
            }
        }
        swap(arr,l,lt);
        quickThree2(arr,l,lt-1);
        quickThree2(arr,gt,r);
    }
  public static void quickColor(int [] nums,int left,int right){
        //[0,1,2,0,0,1,2,2,1]
        int zero=left;
        int two=right;
        int i=zero;
        while (i<two){
            if(nums[i]==2){
                swap(i,two,nums);
                two--;
            }else if(nums[i]==1){
                i++;
            }else{
                swap(i,zero,nums);
                zero++;
                i++;
            }
        }
    }

二:堆

堆又称为优先队列,但是它并不是队列。为了实现一个堆,我们需要定义两个条件。1,任意节点的优先级不得小于子节点的优先级。2,是一个完全二叉树。如果这棵树有n层,那么(n-1)层必须填满,并且n层的元素,从左往右按顺序排开。堆的主要操作,1,插入一个新元素,2,删除最小元素,如果这是个最小堆的话。如果是插入新元素的话,新建的节点位于第n层,然后,该元素与它的父元素比较,如果优先级高于父元素,那么就交换二者的位置。如果是删除根元素的话,那么需要将第n层的最后一个元素,放到根元素的位置,然后根元素的与子元素进行比较,如果比子节点的元素大,则交换,直到最终不能交换为止。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 159,117评论 4 362
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,328评论 1 293
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 108,839评论 0 243
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 44,007评论 0 206
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,384评论 3 287
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,629评论 1 219
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,880评论 2 313
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,593评论 0 198
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,313评论 1 243
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,575评论 2 246
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,066评论 1 260
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,392评论 2 253
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,052评论 3 236
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,082评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,844评论 0 195
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,662评论 2 274
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,575评论 2 270

推荐阅读更多精彩内容

  • Ba la la la ~ 读者朋友们,你们好啊,又到了冷锋时间,话不多说,发车! 1.冒泡排序(Bub...
    王饱饱阅读 1,763评论 0 7
  • 排序算法说明 (1)排序的定义:对一序列对象根据某个关键字进行排序; 输入:n个数:a1,a2,a3,…,an 输...
    code武阅读 629评论 0 0
  • 总结一下常见的排序算法。 排序分内排序和外排序。内排序:指在排序期间数据对象全部存放在内存的排序。外排序:指在排序...
    jiangliang阅读 1,269评论 0 1
  • 搞懂基本排序算法 上篇文章写了关于 Java 内部类的基本知识,感兴趣的朋友可以去看一下:搞懂 JAVA 内部类;...
    醒着的码者阅读 1,125评论 3 4
  • 她在初二就考上了重点艺术学校。 那就意味着我最好的伙伴将要与我分道扬镳了。心中不舍又不甘。不舍的是一个志同道合的伙...
    茶筏呀阅读 314评论 13 3