算法笔记三:归并排序

算法思想

将一个大的规模的数据,一分为二,分成两部分,分别对这两部分执行排序,然后将两个已经排好序的部分,执行归并操作。

分解—>解决—>合并

在解决的步骤中,什么条件下是阻止其继续分解的终止阀呢?:

1、分解的只有一个元素了

2、分解到了一定小的规模,采用其他算法,如插入算法来解决排序问题


最好情况:

与最坏情况相同,都是O(n*log2n)


最坏情况:

与最好情况相同,都是O(n*log2n)


下面我们来分析下为什么最好情况和最坏情况都是一样的,都为O(n*log2n)


分解:

对规模为N的数据,其分解过程,就是找到一个分解点X,所以其分解代价为常量O(1)

合并:

合并的过程,就是各个分区中的所有元素遍历一遍的过程,其合并代价为O(n)

解决:

最关键的就在于解决过程的代价,假设我们对任意规模的数据,分解为2份个n/2的小部分,假定每个部分的解决时间是T(n/2),那么自然其总的代价就是2*T(n/2),

我们定义分解的代价O(1) = C(1),合并的代价为O(n)=D(n),那么,总时间就等于:

T=分解的代价+合并俄代价+解决的代价 ->

T=C(1) + D(n) + 2*T(n/2).

由于C(1) + D(n)是一个常量和一个线性函数相加,其结果就是一个线性函数,所以,可以得出:

T(n)=cn + 2*T(n/2);

那么,最关键的问题是,T(n)等于多少呢?看下面这个图:


T(n):

cn ————>cn

T(n/2) T(n/2)


继续展开

cn --——>cn

cn/2 cn/2——>cn

T(cn/4) T(cn/4) T(cn/4)T(cn/4)


再继续展开

cn

cn/2cn/2

cn/4cn/4cn/4 cn/4

T(cn/8) T(cn/8) ……………….. T(cn/8)

可以看到,整棵树的高度为log2n,而在每个高度上,其代价均为cn(比如第三层,4个cn/4 = cn)),所以,总的代价就是cn*log2n,即为:n*log2n


所以,这里无论输入顺序是什么,其解决的代价是常量,而分解成的规模数都是一样的,所以最好和最坏情况,其代价均等于n*log2n


空间代价上,在合并的过程当中,需要另辟合并后的大小的空间,有一定的空间代价

算法实现:

#ifndef __p1__MergeV2__
#define __p1__MergeV2__

#include "stdio.h"

class MergeV2 {
public:
    
    //分解
    void split(int * data,int left,int right){
        int middle = (left + right) / 2;
        int p2_start = middle +1 >= right ? right : middle + 1;
        solve(data,left,middle);
        //请思考这里为什么是middle+1
        solve(data, p2_start, right);
        //执行合并
        merging(data, left, middle, p2_start, right);
    }
    
    //解决
    void solve(int * data,int left,int right){
        if(right == left){
            //终止分解伐,只有一个元素时,停止继续分解
            return;
        }
        //继续分解
        split(data, left, right);
    }
    
    //合并,第一个部分由p1left-->p1right给出,第二个部分由p2left->p2right给出
    void merging(int * data,int p1left,int p1right,int p2left,int p2right){
        int area_sum_size = p1right - p1left + 1 + p2right - p2left + 1;
        int tmp_data[area_sum_size];
        
        int p1_i=p1left,p2_i=p2left,j=0;
        while(p1_i <= p1right || p2_i <= p2right){
            if(p1_i > p1right){
                for(;p2_i <= p2right;){
                    tmp_data[j] = data[p2_i];
                    j++;
                    p2_i++;
                }
                break;
            }
            if(p2_i > p2right){
                for(;p1_i <= p1right;){
                    tmp_data[j] = data[p1_i];
                    j++;
                    p1_i++;
                }
                break;
            }
            
            if(data[p1_i] <= data[p2_i]){
                tmp_data[j] = data[p1_i];
                p1_i++;
            }else{
                tmp_data[j] = data[p2_i];
                p2_i++;

            }
            j++;
        }
        
        //放回原处
        int g_index=0;
        for(int j=p1left;j<=p1right;j++){
            data[j] = tmp_data[g_index];
            g_index++;
        }
        for(int j=p2left;j<=p2right;j++){
            data[j] = tmp_data[g_index];
            g_index++;
        }
    }
    
    //执行排序入口
    void doing(int * data,int size){
        solve(data, 0, size-1);
    }
    
    
};

#endif /* defined(__p1__MergeV2__) */


法总结:

该算法在应对数据规模增长的表现上,nlogn明显好于前面的插入算法的n^2,但同时也带来一个思考,当n比较小时,nlogn > n^2,找出这个临界点数据,可以在解决的过程中,当发现n比这个数小时,停止继续分解,而是采用插入算法来排序,这样就能够优化合并算法的性能;

空间代价上,合并算法在合并的过程当中,也是需要另辟空间的,有比较大的空间代价


评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值