快速排序、归并排序

作者: 游戏开发小Y | 来源:发表于2017-01-18 10:41 被阅读0次

    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   2  (右->左,l=0,r=5,a[r]小于key)
           2   6   4   3   1  [2] (交换,a[5]值填坑,a[5]变新坑) 
           2   6   4   3   1  [2] (左->右,l=1,r=5,a[l]大于key)
           2  [6]  4   3   1   6  (交换,a[1]值填坑,a[1]变新坑)
           2  [6]  4   3   1   6  (右->左,l=1,r=4,a[r]小于key)
           2   1   4   3  [1]  6  (交换,a[4]值填坑,a[4]变新坑)
           2   1   4   3  [1]  6  (左->右,l=2,r=4)
           2   1   4   3  [1]  6  (左->右,l=3,r=4)
           2   1   4   3  [5]  6  (l=r=4,key填a[l])
           2   1   4   3   5   6  (找到5的最终位置)
    第二趟:   1   2   4   3    5   6 (找到2的最终位置)
    第三趟:   1   2   3   4    5   6 (找到4的最终位置)

    2.归并排序

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

    Paste_Image.png

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

    (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
         6   5   3   1   8   7  2  4(0~1合并)
         5   6   3   1   8   7  2  4(2~3合并)
         5   6   1   3   8   7  2  4(0~3合并)
         1   3   5   6   8   7  2  4(4~5合并)
         1   3   5   6   7   8  2  4(6~7合并)
         1   3   5   6   7   8  2  4(4~7合并)
         1   3   5   6   2   4  7  8(0~7合并)
         1   2   3   4   5   6  7  8(排序结果)

    相关文章

      网友评论

        本文标题:快速排序、归并排序

        本文链接:https://www.haomeiwen.com/subject/yqwvbttx.html