归并排序基础思想就是 分治
左边处理一下,得到左边的信息;右边处理一下,得到右边的信息;最后再处理,横跨左右两边的信息。
代码演示:
```cpp
//二路归并
void merge_sort(int *arr,int l,int r){
if(l>=r) return ;
int mid = (l+r)>>2;
merge_sort(arr,l,mid);//left sort
merge_sort(arr,mid+1,r);//right sort
int *temp = (int *)malloc(sizeof(int)*(r-l+1));//开辟额外的存储空间
int k=0,p1=l,p2=mid+1;
while(p1<=mid||p2<=r){
if(p2>r||(p1<=mid&&arr[p1]<=arr[p2])){
temp[k++] = arr[p1++];
}else{
temp[k++] = arr[p2++];
}
}
//将temp拷贝到原来的数组中去
for(int i=l;i<=r;i++) arr[i]=temp[i-l];
free(temp);
return ;
}
当多路排序时 我们使用小顶堆来进行选择每个集合中的最小元素
因为归并排序可以使用外存和快速排序只能放在内存中(内部排序)存在区别
(最后merge_sort处理的也只是文件指针而不是文件数据)
所以归并排序是大数据排序里面使用较多的一种排序算法
例题:
剑指 Offer 51. 数组中的逆序对
class Solution {
public:
//利用归并排序求解逆序数
vector<int> temp;//额外存储区
int countResult(vector<int> &nums,int l,int r){
if(l>=r) return 0;//只包含一个数或者空区间的逆序数数量为0
int ans=0,mid=(l+r)>>1;
ans+=countResult(nums,l,mid);
ans+=countResult(nums,mid+1,r);
int k=0,p1=l,p2=mid+1;//这里k也可以等于l 但是最后遍历赋值时候(18row)改成nums[i]=temp[i];
while(p1<=mid||p2<=r){
if(p2>r||(p1<=mid&&nums[p1]<=nums[p2])) temp[k++]=nums[p1++];
else {
temp[k++] = nums[p2++];
ans+=(mid-p1+1);//在归并排序时候就可以计算左边的所有比nums[p2]大的nums[p1]后面的元素
}
}
for(int i=l;i<=r;i++) nums[i]=temp[i-l];
return ans;
}
int reversePairs(vector<int>& nums) {
//先求左边序列的逆序数再求右边数列的逆序数
//再求两边产生的逆序数
while(temp.size()<nums.size()) temp.push_back(0);//将temp数组的大小扩展成和nums数组的大小一样
return countResult(nums,0,nums.size()-1);
}
};
- 合并K个升序链表
class Solution {
public:
//这里使用堆、链表、归并
//在k各元素中找到最小值,使用小顶堆进行加速
//定义比较规则
struct CMP{
bool operator()(ListNode* p,ListNode* q){
return p->val>q->val;
}
};
ListNode* mergeKLists(vector<ListNode*>& lists) {
//因为在C++中我们有堆(优先队列)priority_queue可以直接使用
//定义小顶堆q
priority_queue<ListNode *,vector<ListNode *>,CMP> q;//队列存储k个指针的位置
//依次将k个链表头部加入优先队列当中去
for(auto x: lists){
if(x == nullptr) continue;
q.push(x);
}
//合并列表虚拟头结点和合并链表末位结点的指针p
ListNode ret,*p=&ret;
while(!q.empty()){
ListNode *cur=q.top();
q.pop();
p->next = cur;
p=cur;
if(cur->next) q.push(cur->next);//如果cur的下一个节点不为空需要将其压入优先队列当中去作为下一轮我们的带选取元素
}
return ret.next;
}
};
- 排序链表(这题我在前面快速排序实现过 可以参考之前博客,这里使用归并排序使用算法)
class Solution {
public:
//链表排序最好的方式是归并排序
ListNode *mergeSort(ListNode *head,int n){
if(head == nullptr || head->next == nullptr) return head;
//拆分成左右两边链表
int l = n/2,r=n-l;
ListNode *lp = head,*rp = lp,*p;//设置中间指针p
//我们需要将链表拆成两半就需要找到右半部分首地址的前一位=》rp指向右半部分链表首地址的前一位
for(int i=1;i<l;i++,rp=rp->next);
//将左右两半部分拆开
p=rp,rp=rp->next;
p->next = nullptr;
lp = mergeSort(lp,l);//left sort
rp = mergeSort(rp,r);//right sort
//将两个有序链表合并成一个有序链表
ListNode ret;
p= &ret;//p指向的就是合并后链表的最后一个节点
while(lp || rp){
if((rp == nullptr)||(lp&&lp->val<=rp->val)){
p->next=lp;
lp = lp->next;
p=p->next;
}else{
p->next=rp;
rp = rp->next;
p=p->next;
}
}
return ret.next;
}
ListNode* sortList(ListNode* head) {
int n=0;
ListNode *p=head;
while(p) p=p->next,n+=1;
return mergeSort(head,n);
}
};
- 两棵二叉搜索树中的所有元素
class Solution {
public:
void getResult(TreeNode* root,vector<int>&arr){
if(root==nullptr) return ;
getResult(root->left,arr);
arr.push_back(root->val);
getResult(root->right,arr);
return ;
}
vector<int> getAllElements(TreeNode* root1, TreeNode* root2) {
//二叉搜索树先通过中序遍历得到两个有序序列 然后再通过归并得到一个大的有序序列
vector<int>lnums,rnums;
getResult(root1,lnums);
getResult(root2,rnums);
vector<int> ret;
int l=0,r=0;
while(l<lnums.size()||r<rnums.size()){
if(r>=rnums.size()||(l<lnums.size()&&lnums[l]<=rnums[r])){
ret.push_back(lnums[l++]);
}else{
ret.push_back(rnums[r++]);
}
}
return ret;
}
};
- 区间和的个数
[难点在于跨左右两半区间的信息]
class Solution {
public:
int coutTwoPart(vector<long long>&sum,int l1, int r1,int l2,int r2,int lower,int upper){
int ans = 0,k1 = l1,k2= l1;
for(int j=l2;j<=r2;j++){
long long a =sum[j] - upper;
long long b = sum[j] -lower;
while(k1<=r1 && sum[k1]<a) k1+=1;
while(k2<=r1 && sum[k2]<=b) k2+=1;//左闭右开
ans += k2-k1;
}
return ans;
}
int mergeSort(vector<long long>&sum,int l,int r,int lower,int upper){
if(l>=r) return 0;//只有一个元素或者没有就找不到满足条件的数值对
int mid = (l+r)>>1,ans = 0;
ans+=mergeSort(sum,l,mid,lower,upper);
ans+=mergeSort(sum,mid+1,r,lower,upper);
ans+=coutTwoPart(sum,l,mid,mid+1,r,lower,upper);//统计横跨两部分区间的答案信息
//合并两部分有序数组
int k=l,p1=l,p2=mid+1;
while(p1<=mid||p2<=r){
if(p2>r||(p1<=mid&&sum[p1]<=sum[p2])){temp[k++]=sum[p1++];}
else {temp[k++] = sum[p2++];}
}
for(int i=l;i<=r;i++) sum[i]=temp[i];
return ans;
}
vector<long long>temp;
int countRangeSum(vector<int>& nums, int lower, int upper) {
//区间和 =》 前缀和: 区间和=前缀和数组中两项相减的值
//题目可改写为在前缀和sum数组中 lower<=sum[i]-sum[j]<=upper
//在归并排序过程中解决问题
vector<long long>sum(nums.size()+1);//前缀和数组(前缀和数组第一位为0所以这里为size+1);
while(temp.size()<sum.size())temp.push_back(0);
sum[0]=0;
for(int i=0;i<nums.size();i++)sum[i+1]=sum[i]+nums[i];
//然后在前缀和数组排序过程中求解答案
return mergeSort(sum,0,sum.size()-1,lower,upper);
}
};
- 计算右侧小于当前元素的个数
```c
class Solution {
public:
struct Data{
Data(int val,int ind):val(val),ind(ind),cnt(0){}
bool operator>(const Data &a){//自己设定比较关系
return val > a.val;
}
int val,ind,cnt;
};
void mergeSort(vector<Data>&arr,int l,int r){
if(l>=r) return ;
int mid =(l+r)>>1;
mergeSort(arr,l,mid);
mergeSort(arr,mid+1,r);
int p1=l,p2=mid+1,k=l;
while(p1<=mid||p2<=r){
if(p2>r||(p1<=mid&&arr[p1]>arr[p2])){
arr[p1].cnt += (r-p2+1);
temp[k++] = arr[p1++];
}
else{
temp[k++] = arr[p2++];
}
}
for(int i=l;i<=r;i++) arr[i] = temp[i];
return ;
}
vector<Data>temp;
vector<int> countSmaller(vector<int>& nums) {
vector<Data> arr;
for(int i=0;i<nums.size();i++)arr.push_back(Data{nums[i],i});
while(temp.size()<arr.size())temp.push_back(Data{0,0});
mergeSort(arr,0,arr.size()-1);
vector<int> ret(nums.size());
for(auto x:arr)ret[x.ind] = x.cnt;
return ret;
}
};
1508. 子数组和排序后的区间和
```c
class Solution {
public:
struct Data{
Data(int i,int j,long long sum):i(i),j(j),sum(sum){}
int i,j,sum;
};
struct CMP{
bool operator()(const Data &a,const Data &b){
return a.sum > b.sum;
}
};
int rangeSum(vector<int>& nums, int n, int left, int right) {
priority_queue<Data,vector<Data>,CMP>q;
for(int i=0;i<n;i++){
q.push(Data{i,i,nums[i]});
}
int ans = 0,mod = 1e9+7;
for(int i=1;i<=right;i++){
Data d = q.top();
q.pop();
if(i>=left) ans =(ans +d.sum)%mod ;
if(d.j+1<n)q.push(Data{d.i,d.j+1,d.sum+nums[d.j+1]});
}
return ans;
}
};
面试题 04.08. 首个共同祖先
class Solution {
public:
TreeNode* lowestCommonAncestor(TreeNode* root, TreeNode* p, TreeNode* q) {
//一遍递归求得
if(root == NULL) return NULL;
if(root == p|| root == q) return root;
TreeNode *l =lowestCommonAncestor(root->left,p,q);
TreeNode *r =lowestCommonAncestor(root->right,p,q);
if(l!=NULL && r!=NULL) return root;
if(l!=NULL) return l;
return r;
}
};
- 层数最深叶子节点的和
class Solution {
public:
void getResult(TreeNode*root ,int k,int &max_k,int &ans){//k为当前节点的深度
if(root == nullptr) return ;
if(k == max_k) ans += root->val;
if(k>max_k){
max_k =k;
ans = root->val;
}
getResult(root->left,k+1,max_k,ans);
getResult(root->right,k+1,max_k,ans);
return;
}
int deepestLeavesSum(TreeNode* root) {
int max_k=0,ans=0;
getResult(root,1,max_k,ans);
return ans;
}
};