算法基础:归并

目录

归并排序

概念

代码实现

优化方法

算法考题


归并排序

概念

归并排序号称最快,其次是快速排序和堆排序。

归并排序(MERGE-SORT)是建立在归并操作上的一种有效的排序算法,该算法采用经典的分治(divide-and-conquer)策略(分治法将问题(divide)成一些小的问题然后递归求解,而治(conquer)的阶段则将分的阶段得到的各答案"修补"在一起,即分而治之),将已有序的子序列合并,得到完全有序的序列;即先使每个子序列有序,再使子序列段间有序,若将两个有序表合并成一个有序表,称为二路归并。

第一步:申请空间,使其大小为两个已经排序序列之和,该空间用来存放合并后的序列

第二步:设定两个指针,最初位置分别为两个已经排序序列的起始位置

第三步:比较两个指针所指向的元素,选择相对小的元素放入到合并空间,并移动指针到下一位置

重复步骤3直到某一指针超出序列尾,将另一序列剩下的所有元素直接复制到合并序列尾

归并排序其实要做两件事:

(1)“分解”——将序列每次折半划分(递归实现)

(2)“合并”——将划分后的序列段两两合并后排序

代码实现

void MergeSort (int arr [], int low,int high) {
    if(low>=high) { return; } // 终止递归的条件,子序列长度为1
    int mid =  low + (high - low)/2;  // 取得序列中间的元素
    MergeSort(arr,low,mid);  // 对左半边递归
    MergeSort(arr,mid+1,high);  // 对右半边递归
    merge(arr,low,mid,high);  // 合并
  }
void Merge(int arr[],int low,int mid,int high){
    //low为第1有序区的第1个元素,i指向第1个元素, mid为第1有序区的最后1个元素
    int i=low,j=mid+1,k=0;  //mid+1为第2有序区第1个元素,j指向第1个元素
    int *temp=new int[high-low+1]; //temp数组暂存合并的有序序列
    while(i<=mid&&j<=high){
        if(arr[i]<=arr[j]) //较小的先存入temp中
            temp[k++]=arr[i++];
        else
            temp[k++]=arr[j++];
    }
    while(i<=mid)//若比较完之后,第一个有序区仍有剩余,则直接复制到t数组中
        temp[k++]=arr[i++];
    while(j<=high)//同上
        temp[k++]=arr[j++];
    for(i=low,k=0;i<=high;i++,k++)//将排好序的存回arr中low到high这区间
	arr[i]=temp[k];
    delete []temp;//释放内存,由于指向的是数组,必须用delete []
}
void MergeSort2(int arr[],int n)//n代表数组中元素个数,即数组最大下标是n-1{
	/*
	int step = 1;
	while(step<n) //当元素个数不是2的幂时可能会出错,未考虑第2个序列个数不足的情况
	{
		for(int i=0;i<=n-step-1;i+=2*step)
			Merge(arr,i,i+step-1,i+2*step-1);
		step*=2;
	}*/
 
	int size=1,low,mid,high;
	while(size<=n-1){
		low=0;
		while(low+size<=n-1){
			mid=low+size-1;
			high=mid+size;
			if(high>n-1)//第二个序列个数不足size
				high=n-1;
			Merge(arr,low,mid,high);//调用归并子函数
			low=high+1;//下一次归并时第一关序列的下界
		}
		size*=2;//范围扩大一倍
	}
}

优化方法

优化一:对小规模子数组使用插入排序

用不同的方法处理小规模问题能改进大多数递归算法的性能,因为递归会使小规模问题中方法调用太过频繁,所以改进对它们的处理方法就能改进整个算法。因为插入排序非常简单, 因此一般来说在小数组上比归并排序更快。 这种优化能使归并排序的运行时间缩短10%到15%。

操作方式:

将退出递归的:

 if(low>=high) { return; }

替换为:

 if(high - low <= 10) { // 数组长度小于10的时候
      InsertSort(int arr[], int low,int high) // 切换到插入排序
      return;
 }

优化二: 测试待排序序列中左右半边是否已有序

优化三:去除原数组序列到辅助数组的拷贝

算法考题

小和问题

题目:在一个数组中,每一个数左边比当前数小的数累加起来,叫做这个数组的小和。求一个数组的小和。例如:
对于数组[1,3,4,2,5]
1左边比1小的数,没有;
3左边比3小的数,1;
4左边比4小的数,1、3;
2左边比2小的数,1;
5左边比5小的数,1、3、4、2;
所以小和为1+1+3+1+1+3+4+2=16

解题思路

本题可以使用暴力遍历的方法来解题,不过时间复杂度是O(n^2)。因此我们可以使用归并排序法来提升算法性能。只需要在归并算法的基础上做略微改动,把所有和分成三部分来分而求解。merge_sort(v,l,mid)+merge_sort(v,mid+1,r)+merge(v,l,mid,r)

遍历依次求每个数的小和过程中有很多比较是重复的,而利用归并排序时利用了并入的两个序列分别有序的特性省去了不必要的比较,如134并入25时,2>1直接推出2后面的数都>1,因此直接1*(r-p2+1)即可。

#include <iostream>
#include <vector>
using namespace std;

int res = 0;
vector<int> tmp;

void merge(vector<int> v, int l, int mid, int r)
{
	int p1 = l, p2 = mid + 1;
	int i = l;
	while (p1 <= mid && p2 <= r)
	{
		if (v[p1] < v[p2])
		{
			//并入的时候,利用有序性
				//eg:134和25,由1<2,推出2后面的数都>1,故直接(r-p2+1)*1
			res += (r - p2 + 1) * v[p1];
			tmp[i++] = v[p1++];
		}
		else
			tmp[i++] = v[p2++];
	}
	while (p1 < mid)
		tmp[i++] = v[p1++];
	while (p2 < r)
		tmp[i++] = v[p2++];
	for (int j = 0; j < v.size(); j++)
		v[j] = tmp[j];
}

void merge_sort(vector<int> v, int l, int r)
{
	if (l >= r)
		return ;
	int mid = l + ((r - l) >> 1);
	merge_sort(v, l, mid); 
	merge_sort(v, mid + 1, r);
	merge(v, l, mid, r);
}

int small_sum(vector<int> v)
{
	if (v.empty() || v.size() <= 1)
		return 0;
	tmp.resize(v.size());
	merge_sort(v, 0, v.size() - 1);
	return res;
}

int main()
{
	vector<int> v;
	v.push_back(1);
	v.push_back(3);
	v.push_back(4);
	v.push_back(2);
	v.push_back(5);

	small_sum(v);
	cout << res << endl;
}

逆序对问题

题目:求解一个数组中逆序对个数。
逆序对介绍
如果存在正整数 i, j 使得 1 ≤ i < j ≤ n 而且 A[i] > A[j],则(A[i], A[j]) 这个有序对称为 A 的一个逆序对,也称作逆序数。
例如:
对于数组[1,3,4,2,5]
逆序对为(3,2)、(4,2)
输出:2

解题思路

同样本题可以使用暴力遍历的方法来解题,不过时间复杂度是O(n^2)。因此我们可以使用上题同样的思路来提升算法性能。只需要将v[p1]<v[p2]判断内merge_sort(v,l,mid)+merge_sort(v,mid+1,r)+merge(v,l,mid,r)
语句改为v[p1]>v[p2]判断内**res+=(mid-p1+1);

class Solution {
public:
    int ret = 0;
    vector<int> tmp;
    
    void merge(vector<int>& nums, int l, int r){
        int mid = l + (r - l) / 2;
        int p1 = l, p2 = mid + 1, p = l;
        while(p1 <= mid && p2 <= r){
            if(nums[p1] <= nums[p2]) tmp[p++] = nums[p1++];
            else{
                tmp[p++] = nums[p2++];
                ret += (mid - p1 + 1);
            }
        }
        while(p1 <= mid) tmp[p++] = nums[p1++];
        while(p2 <= r) tmp[p++] = nums[p2++];
        for(int i = l; i <= r; ++i) nums[i] = tmp[i];
    }

    void merge_sort(vector<int>& nums, int l, int r){
        if(l >= r) return;
        int mid = l + (r - l) / 2;
        merge_sort(nums, l, mid);
        merge_sort(nums, mid + 1, r);
        merge(nums, l, r);
    }

    int reversePairs(vector<int>& nums) {
        tmp.resize(nums.size());    
        merge_sort(nums, 0, nums.size() - 1);
        return ret;
    }
};

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Ym影子

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值