【说人话的算法小课堂】使用分治法(基于归并排序的思想)求一个排列的逆序数(O(n^2)→O(n log n))

在这里插入图片描述

分治法求逆序数

前置知识:归并排序。
我们已经证明了归并排序的正确性。只要将归并排序算法作少量修改,就可以将求一个排列的逆序数的时间复杂度从O(n^2)降低到O(n log⁡n)。
将一个排列P尽量均匀地分成两部分P_1,P_2。则P的逆序数
τ§=τ(P_1 )+τ(P_2 )+τ_Merge
显然,τ(P_1 )和τ(P_2 )分别代表递归求解的在P_1,P_2范围内的逆序数。而τ_Merge则将遗漏的情况,即构成逆序对(a_i,a_j), i<j, a_i>a_j的两个元素a_i,a_j满足a_i∈P_1,a_j∈P_2的情况全部覆盖。
归并排序的过程中,合并两个有序(升序)数列的步骤是:
设一个临时数列t,用于归并。
指针i和j分别指向两个子数列的开头。子数列的范围分别是_begin到_mid、_mid + 1到_end。
不断从两个子数列中各取一个元素(即指针i和j分别指向的元素)比较,将较小的数放到数列t中,对应的指针递增(当两个元素相等时,默认将地址较低的数组中的数,即i指向的数放入t)。当其中一个子数列取完后,该循环结束。
将没取完元素的子数列的全部元素都复制到数列t中。
设从长度分别为m,n的两个有序数列P_1,P_2中取出的两个数分别为x_i,y_j,下标代表它们在有序数列中的位置。每次比较时如果发现一次x_i>y_j,又已知序列是升序的,就有
x_k>y_j, k=i,i+1,…,m, x_k∈P_1,y_j∈P_2
也就是说找到了m-i+1个逆序对。即τ_Merge=m-i+1。
代码:

template<class _Ty> size_t merge(_Ty* _begin, _Ty* _mid, _Ty* _end) {
    
    
	const auto l = _end - _begin + 1; size_t tau = 0;
	_Ty* t = new _Ty[l], * i = _begin, * j = _mid + 1, * k = t;
	while (i <= _mid && j <= _end) {
    
    
		if (*i <= *j) {
    
     *k = *i; ++i; ++k; }
		else {
    
     *k = *j; ++j; ++k; tau += _mid - i + 1; }
	}
	while (i <= _mid) {
    
     *k = *i; ++i; ++k; }
	while (j <= _end) {
    
     *k = *j; ++j; ++k; }
	std::copy(t, t + l, _begin);
	delete[] t;
	return tau;
}

template<class _Ty> size_t inversion_count(_Ty* _begin, _Ty* _end) {
    
    
	size_t tau = 0;
	if (_begin < _end) {
    
    
		_Ty* _mid = _begin + (_end - _begin) / 2;
		tau += inversion_count(_begin, _mid);
		tau += inversion_count(_mid + 1, _end);
		tau += merge(_begin, _mid, _end);
	}
	return tau;
}

猜你喜欢

转载自blog.csdn.net/COFACTOR/article/details/109005737