经典排序算法(下)(快速排序、归并排序)

1.快速排序

快速排序每趟选择一个基准元素,用基准元素将序列划分成两部分,所有比基准值小的元素摆放在基准前面,所有比基准值大的元素摆在基准后面,这一趟过程称为分区(partition)操作。每一趟分区操作的目的就是把这趟的基准元素摆到最终位置。
  递归地对基准元素左边的序列和右边的序列分别调用分区操作,则当序列的大小是零或者一时整个序列排序完成,采用的是“分治”的策略。

快速排序-图片来自维基百科

思路总结:

(1)先从序列中取出一个数作为基准数。
(2)分区过程:将比基准数大的数全部放到它的右边,小于或等于它的数全部放到它的左边。
(3)对左右序列重复步骤(2),直到各序列数只有一个或零个

为了便于大家理解“分区”操作,将快排写成两个函数,大家也可以合成一个函数写,参考代码如下:

//分区操作
    public int partition(int a[],int left,int right){
        int l = left,r = right,key = a[left];
        if(left<right){         
            while(l<r){//结束条件为左指针与右指针汇合
                while(l<r&&key<=a[r]){//从右向左遍历数组,找到第一个小于key的值
                    r--;
                }          
                if(l<r){//右边小于key的值与key的位置互换
                a[l++] = a[r]; 
                }   
                 //左右方向互换           
                while(l<r&&key>a[l]){//从左向右遍历,找到第一个大于key的值
                    l++;
                }               
                if(l<r){//左边大于key的值与key互换
                    a[r--] = a[l];
                }
            }
            a[l] = key;//key放到数组最终位置        
        }
        return l;
    }

上面的分区操作的代码可以简单概括为“挖坑填数”,即每次partiton将序列最左边的数选为基准元素key,将key挖出,从右向左开始遍历,让序列中的数与基准元素key值进行比较,若右边有比基准值小的数,则将该数“挖”出来,“填”入坑中,被挖的数成为新的坑,每“挖、填”一次数,改变一次序列的遍历方向,直到序列遍历完成为止,将key(基准元素)填入最终结束的位置,也就确定了基准元素最终在序列中的位置。

//递归调用
public void quickSort(int a[],int left,int right){
        if(left<right){
            int t = partition(a, left, right);
            quickSort(a, left, t-1);//左边的数组快排
            quickSort(a, t+1, right);//右边的数组快排
        }
    }

排序过程如下所示:
初始状态:  a[0]  a[1]  a[2]  a[3]  a[4]  a[5]
初始值:   5   6   4   3   1   2
第一趟排序过程:key = 5  a[0]为初始坑所在位置(用[ ]标识坑的位置)
      [5]  6   4   3   1     (右->左,l=0,r=5,a[r]小于key)
          6   4   3   1  [2] (交换,a[5]值填坑,a[5]变新坑) 
       2      4   3   1  [2] (左->右,l=1,r=5,a[l]大于key)
       2  [6]  4   3   1     (交换,a[1]值填坑,a[1]变新坑)
       2  [6]  4   3      6  (右->左,l=1,r=4,a[r]小于key)
       2   1   4   3  [1]  6  (交换,a[4]值填坑,a[4]变新坑)
       2   1      3  [1]  6  (左->右,l=2,r=4)
       2   1   4     [1]  6  (左->右,l=3,r=4)
       2   1   4   3  []  6  (l=r=4,key填a[l])
       2   1   4   3      6  (找到5的最终位置)
第二趟:   1      4   3    5   6 (找到2的最终位置)
第三趟:   1   2   3       5   6 (找到4的最终位置)

2.归并排序

归并排序先递归分解序列,一分为二进行分组,直到分解到分组只有一个元素为止,认为其有序,再将有序分组两两合并,最后使整个序列有序。(归并可以简单理解为:递分解+两两合

归并排序-图片来自维基百科

将两个有序序列合并的思路:

(1)比较两个序列中第一个数,取出较小者,对应取数序列取数位置后移一位,即下一个数变为第一个数。
(2)重复步骤(3),如果有序列值全部取完,那直接将另一个序列的数据依次取出即可
(3)取出的数依次存入一个新的序列,这个新的序列即为这两个有序序列合并而成的新的有序序列

分解的实现比较简单,通过改变传入数组下标,直接递归调用即可,为了便于大家理解归并排序中递归与合并的概念,将归并写成两个函数,参考代码如下:

//递归分解操作
public void mergeSort(int a[],int begin,int end){//传入的begin、end均为待排序数组的下标值
        if(begin<end){
            int mid = (begin+end)/2;
            mergeSort(a, begin, mid);
            mergeSort(a, mid+1, end);
            merge(a, begin, end);
        }
    }

对于数组进行分解,例如若某个数组长度为8,下标为0~7,则mid = (0+7)/2=3,可将数组分成两个子数组:下标为[03]的数组和下标为[47]的数组。而对于下标[03]的数组,其mid=(0+3)/2=1,又可将其分为下标为[01]的数组和下标为[2~3]的数组。依此类推,直到分解成的数组只有一个元素为止,认为其有序。

//合并操作
public void merge(int a[],int begin,int end){
        int mid = (begin + end)/2;//将传进来原数组对应下标的子数组根据mid分解
        int i = begin, j = mid + 1;   //分解成两个数组:[begin~mid]、[mid+1~end]
        int k = 0;  
        int temp[] = new int[end+1];//申请额外空间来暂存排序后的新的有序数组
        while (i <= mid && j <= end)  //依次比较分解后两个数组内的数,直至其中一个数组到末尾
        {  
            if (a[i] <= a[j])  //将较小值放入临时数组的前面
                temp[k++] = a[i++];  
            else  
                temp[k++] = a[j++];  
        }         
        while (i <= mid)  //若数组[begin~mid]中还有数没取完,则将未取完的数全部追加到临时数组后面
            temp[k++] = a[i++];           
        while (j <= end)  //若数组[mid+1~end]中还有数没取完,则将未取完的数全部追加到临时数组后面
            temp[k++] = a[j++];           
        for (i = 0; i < k; i++)  
            a[begin+i] = temp[i]; //将合并后有序的临时数组中的数依次赋值到原来待合并的数组,完成该次合并      
    }

排序过程如下所示:
初始状态:a[0]  a[1]  a[2]   a[3]   a[4]   a[5]  a[6] a[7]
初始值: 6   5   3   1   8   7  2  4
           3   1   8   7  2  4(0~1合并)
     5   6         8   7  2  4(2~3合并)
                 8   7  2  4(0~3合并)
     1   3   5   6        2  4(4~5合并)
     1   3   5   6   7   8    (6~7合并)
     1   3   5   6          (4~7合并)
                        (0~7合并)
     1   2   3   4   5   6  7  8(排序结果)

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

推荐阅读更多精彩内容

  • 1.插入排序—直接插入排序(Straight Insertion Sort) 基本思想: 将一个记录插入到已排序好...
    依依玖玥阅读 1,225评论 0 2
  • 概述 排序有内部排序和外部排序,内部排序是数据记录在内存中进行排序,而外部排序是因排序的数据很大,一次不能容纳全部...
    蚁前阅读 5,149评论 0 52
  • 概述:排序有内部排序和外部排序,内部排序是数据记录在内存中进行排序,而外部排序是因排序的数据很大,一次不能容纳全部...
    每天刷两次牙阅读 3,723评论 0 15
  • 该系列文章主要是记录下自己暑假这段时间的学习笔记,暑期也在实习,抽空学了很多,每个方面的知识我都会另起一篇博客去记...
    Yanci516阅读 12,149评论 6 19
  • 1.快速排序 快速排序每趟选择一个基准元素,用基准元素将序列划分成两部分,所有比基准值小的元素摆放在基准前面,所有...
    游戏开发小Y阅读 183评论 0 0