归并排序算法、多路归并排序

1. 归并排序

base case:if(left>=right) return;
归并排序左半边,归并排序右半边,合并

void merge_sort(vector<int> &nums,vector<int> reg,int start,int end){
    if(start>=end) return ;
    int mid=(end-start)/2+start;
    int start1=start,end1=mid;
    int start2=mid+1,end2=end;
    merge_sort(nums,reg,start1,end1);
    merge_sort(nums,reg,start2,end2);//分治左、右半边
    int t=start;
    while(start1<=end1&&start2<=end2){
        reg[t++]=nums[start1]>nums[start2]?nums[start1++]:nums[start2++];
    }
    while(start1<=end1)
        reg[t++]=nums[start1++];
    while(start2<=end2)
        reg[t++]=nums[start2++];
    while(start<=end)
        nums[start]=reg[start++];//合并
}

2.k路归并排序、败者树

k路归并排序就是将路合并,可以通过增大k来减少外存的读写次数,从而减少归并时间,但是k增大,增大了比较的次数,通过败者树来确定k,从而不影响内部排序效率。

2.1败者树

在这里插入图片描述
如上所示,败者树是一颗完全二叉树,每个结点是一个选手,每个结点的父节点是比赛的结果的败者,数组b中的数据项发生了改变,需要重构败者树,重构的方式是将新进入选择树的结点与其父节点进行比赛,将败者存在父节点中,胜者再与上一级的父节点比赛,比赛沿着根节点的路径不断进行,直到ls[1]处。把败者存放在ls[1]处,胜者存放在ls[0]处。

败者树的数据项发生变化:
在这里插入图片描述
对于败者树,数组的长度和原始数组的长度相等。
代码:数组b用来存储原始数组,数组ls用来生成败者树。ls存储的是b的索引,即子节点败者的索引
注释:数组b[s]的父节点是ls[t],t=(s+len)/2;
思想:b[s]与b[ls[t]]比较,败者存储在ls[t]中,s作为胜者继续向上比较。如果s败了,s和ls[t]交换作为胜者向上比较,如果s胜了,作为胜者向上比较。
ls[t]的父节点是ls[t/2]。
最终使得ls[0]=s作为最终的胜者存储。
一言以蔽之:胜者不断和父节点比较。
过程:

  1. 原始数据后面添加一个-1,作为初始化。
  2. ls数组的长度和原始数组相同,初始值为-1所在的索引。
  3. 从最后一个叶子节点(原始数据的最后一个元素)开始调整。
  4. 调整的方式是逐步和父节点比较,失败就交换。最后得到胜者。
#include<iostream>
#include<vector>
using namespace std;
void swap(int& a,int& b){
	int temp=a;
	b=a;
	a=temp;
}
void adjust(vector<int> &b,vector<int> &ls,int s,int len){
    int t=(s+len)/2;
    while(t>0){
        if(b[s]>b[ls[t]]){
        swap(s,ls[t]);
        }
        t/=2;
    }
    ls[0]=s;
}
vector<int> creatls(vector<int>& nums){
    int len=nums.size();
    vector<int> b(len+1,-1);//最后一个元素-1是为了初始化,即-1永不败,也就是-1所在的索引永远不会出现在ls中
    for(int i=0;i<len;i++)
        b[i]=nums[i];
    vector<int> ls(len,len);//ls的长度和原始数据的长度相同,初始值为-1所在的索引
    for(int i=len-1;i>=0;i--){//从最后一个叶子结点调整败者树
        adjust(b,ls,i,len);
    }

    return ls;
}
int main()
{
    vector<int> b={10,9,20,6,12};
    vector<int> ls=creatls(b);
    for(int i=0;i<ls.size();i++)
        cout<<ls[i];
}

2.2 归并排序

  • 不断取出s=ls[0],表示第s个数组的元素是当前最小的,存入结果中
  • 判断第s个数组是否遍历完,如果遍历完,存入INT_MAX,否则存入下一位到b[s]
  • adjust(b,ls,s,k),b[s]改变,调整失败树。
    终止条件:b[s]==INT_MAX,表示所有的数据遍历完了,结束。
#include<iostream>
#include<vector>
using namespace std;
#define MAX INT_MAX
void swaq(int& a,int& b){
    int temp=a;
    a=b;
    b=temp;
}
void adjust(vector<int>& b,vector<int>& ls,int s,int k){
    int t=(s+k)/2;
    while(t>0){
        if(b[s]>b[ls[t]])
            swaq(s,ls[t]);
        t/=2;
    }
    ls[0]=s;
}
void creatls(vector<int>& b_,vector<int>& ls,int k){
    vector<int> b(k+1,-1);
    for(int i=0;i<b_.size();i++)
        b[i]=b_[i];
    for(int i=k-1;i>=0;i--)
        adjust(b,ls,i,k);
}
void kmerge(vector<vector<int>>& nums,vector<int>& b,vector<int> &ls,int k){
    vector<int> index(k,0);//index[i]表示第i个数组的下标
    vector<int> res;
    int s=ls[0];//s表示第几个数组
    while(b[s]!=MAX){
               for(int i=0;i<b.size();i++)
                cout<<b[i]<<" " ;
            cout<<endl;
            res.push_back(nums[s][index[s]]);//按序到达
            index[s]++;//第s个数组的下标
            if(index[s]>=nums[s].size()){
                b[s]=MAX;
            }
            else{
                b[s]=nums[s][index[s]];
            }
            adjust(b,ls,s,k);
            s=ls[0];

    }
    for(int i=0;i<res.size();i++)
        cout<<res[i]<<" ";
}
int main(){
    vector<vector<int>> nums{
    vector<int>{1,3,6,9},
    vector<int>{2,3,4,5},
    vector<int>{3,5,5,6,12},
    vector<int>{3,5,6,7},
    vector<int>{3,4,6,6}
    };
    int k=5;
    vector<int> b(k,0);
    for(int i=0;i<b.size();i++)
        b[i]=nums[i][0];
    vector<int> ls(k,k);
    creatls(b,ls,k);
    kmerge(nums,b,ls,k);
    return 0;
}

调整adjust是和父节点比较
创建creatls是从最后一个节点调整到根节点
k路归并kmerge是不断取出最小的元素。注意:放入元素就要调整,整体结束条件和单独数组结束条件。

  • 2
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
归并排序是一种分治算法,将待排序的序列分成若干个子序列,每个子序列都是有序的,然后再将有序的子序列合并成整体有序序列。而二路归并排序归并排序的一种实现方式,它是将待排序序列分成两个子序列,每个子序列都是有序的,然后再将两个有序子序列合并成整体有序序列。 二路归并排序归并排序的一种特殊情况,它只是将待排序序列分成两个子序列,而不是像归并排序那样分成多个子序列。因此,二路归并排序的实现相对简单,但是它的时间复杂度和归并排序一样,都是O(nlogn)。 下面是归并排序和二路归并排序的Python实现: 归并排序: ```python def merge_sort(arr): if len(arr) <= 1: return arr mid = len(arr) // 2 left = merge_sort(arr[:mid]) right = merge_sort(arr[mid:]) return merge(left, right) def merge(left, right): result = [] i, j = 0, 0 while i < len(left) and j < len(right): if left[i] < right[j]: result.append(left[i]) i += 1 else: result.append(right[j]) j += 1 result += left[i:] result += right[j:] return result ``` 二路归并排序: ```python def merge_sort(arr): if len(arr) <= 1: return arr mid = len(arr) // 2 left = arr[:mid] right = arr[mid:] left = merge_sort(left) right = merge_sort(right) return merge(left, right) def merge(left, right): result = [] i, j = 0, 0 while i < len(left) and j < len(right): if left[i] < right[j]: result.append(left[i]) i += 1 else: result.append(right[j]) j += 1 result += left[i:] result += right[j:] return result ```

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值