题意: 给定一个包含1-n的数列,通过交换任意两个元素给数列重新排序。求最少需要多少次交换,能把数组排成按1-n递增的顺序,(数组中的元素互不重复)。
举例: 原数列为2,4,3,6,5,7;第一次交换(3,4)得2,3,4,6,5,7;第二次交换(5,6)得2,3,4,5,6,7。完成排序需2次。
思想: 构造“循环节”,找原位置与排序后位置不一致的形成交换环。元素2位置无需变动自形成一个环;元素4位置需变动(应在元素3位置),因此将元素4与经排序后要放入的位置处元素(‘3’)形成一个环;同理6,5形成一个环;7自形成一个环。一共四个环。我们都知道在一个环内将其元素按有序排列所要的交换次数为:环内元素个数-1。因此求解此题次数n = 数列元素个数-环数。
证明:假设有m个环,完成任务总的交换次数 = 环1元素个数-1+环2元素个数-1+....+环m元素个数-1 = (环1元素个数+环2元素个数+.....+环m元素个数 = 数列元素总个数)- (1+1+....+1 = m)。
代码:
#include<bits/stdc++.h>
using namespace std;
/**
* 交换任意两数的本质是改变了元素位置,
* 故建立元素与其目标状态应放置位置的映射关系
*/
int getMinSwaps(vector<int> v)
{
vector<int> v1(v); //将A内元素复制到B。
sort(v1.begin(), v1.end());
map<int,int> m;
int len = v.size();
for (int i = 0; i < len; i++)
{
m[v1[i]] = i; // 建立每个元素与其应放位置的映射关系
}
int loops = 0; // 循环节个数
vector<bool> flag(len, false); //初始化
//找出循环节的个数
for (int i = 0; i < len; i++)
{
if (!flag[i])
{
int j = i;
while (!flag[j]) //对环处理
{
flag[j] = true;
j = m[v[j]]; //原序列中j位置的元素在有序序列中的位置
}
loops++;
}
}
return len - loops;
}
vector<int> v;
int main()
{
int n,k;
cin>>n;
while(n--){
cin>>k;
v.push_back(k);
}
int num = getMinSwaps(v);
cout<<"交换次数:"<<num<<endl;
return 0;
}

PS: 对于只在相邻两元素间交换的逆序对解法可参考我的另一篇文章:https://blog.youkuaiyun.com/lfb637/article/details/78309507
7702

被折叠的 条评论
为什么被折叠?



