最近在刷算法導論,在第二章思考題2-4的d問題,提示使用遞歸排序計算序列的逆序數。基本思想如下,歸併排序的內容見我上一篇文章https://blog.csdn.net/weixin_44004576/article/details/102635900
遞歸每次將序列分解爲左右兩個序列,在合併的時候(此時假設左右序列都是完成排序,從小到大的順序),如果左側序列中的元素大於右側序列中的元素,則意味着左側序列剩餘所有元素都對右側序列指針目前所指向的元素構成逆序數對,因此在逆序數上加上目前左側序列剩餘元素的數量即可
算法導論的解釋中如下圖所示:下圖爲計算逆序數的整個計算框架
下圖爲合併過程的計算框架:
我做的改進僅僅是不使用哨兵,使用邊界條件判斷左右序列指針是否到達邊界。(特別說明,我的代碼中將p、q、r的順序跟算法導論中的順序是反的,貼算法導論的計算流程僅僅是圖省事,懶得自己寫流程,會在代碼中註釋)。另外由於排序的過程會導致如數序列的順序被修改,所以在最終實現的時候採用適配器模式,將序列拷貝進去,修改的是新拷貝的變量,而不是原序列。
下面代碼是適配器模式的逆序數計算器,核心是調用inversion_merge函數,需要注意的一點是p和q是需要計算序列中的所有邊界索引數,從0開始計數,到size-1爲止,所以在調用時,q應該設置成被計算序列長度減1:
template<typename T>
int inversion_cal(std::vector<T> input, int p, int q)
{
int inversion_number = 0;
inversion_number=inversion_merge<int>(input, p, q);
return inversion_number;
}
下面是遞歸調用inversion_merge過程,裏面主要是遞歸和和合並,相關解釋參考我的上一篇文章:
template <typename T>
int inversion_merge(std::vector<T>& input,int p,int q)
{
int inversion_number = 0;
if (p < q)
{
int r = floor((p + q) / 2);
inversion_number += inversion_merge(input, p, r);
inversion_number += inversion_merge(input, r + 1, q);
inversion_number += merge<int>(input, p, r, q);
}
return inversion_number;
}
最核心的部分就是合並不是計算逆序數,代碼如下:
template<typename T>
int merge(std::vector<T> &input, int p, int r, int q)
{
int n1 = r - p + 1;
int n2 = q - r;
int inversion_number = 0;
std::vector<T> l_vector, r_vector;
//copy data from origin array
for (int i = 0; i < n1 + n2; i++)
{
if (i < n1)
l_vector.push_back(input[p + i]);
else
r_vector.push_back(input[p + i]);
}
int i = 0;
int j = 0;
for (int k = 0; k <= q - p; k++)
{
if (i < n1 && j < n2)
{
if (l_vector[i] < r_vector[j])
{
input[p + k] = l_vector[i];
i++;
}
else
{
input[p + k] = r_vector[j];
inversion_number += (n1 - i); //當左側序列指針所指大於右側序列指針所指,將左側序列所剩的所有元素的數量加到逆序數上即可
j++;
}
}
else if (i < n1 && j == n2)
{
input[p + k] = l_vector[i];
i++;
}
else if (i == n1 && j < n2)
{
input[p + k] = r_vector[j];
j++;
}
else if (n1 == 1 && n2 == 0)
{
input[p + k] = l_vector[0];
}
}
return inversion_number;
}