您的位置:首页 > 其它

对一个数组,按照给定的下标进行排序,仅使用两两交换的方式

2016-12-17 15:11 375 查看
对一个数组,按照给定的下标进行排序,仅使用两两交换的方式,空间复杂度O(1)。例:原数组 A B C D E,现给定新位置为3 0 1 4 2 排序后的结果是D A B E C

初次见到这道题的时候,着实让我头疼了一把,最后经人指点,自己也就有了一个大致的思路,下面将这道题的解法做一下总结。

分析:这道题要用到挖洞法的思想,不过要在挖洞法的基础上再做一些改进。

例1:原数组 A B C D E,现给定新位置为3 0 1 4 2 排序后的结果是D A B E C



大致思路就是上面分析所的,不过这个栗子有点特殊。因为在这个栗子中tmp是最后才进行填入的。我们再来看下一个栗子.

例2:原数组 A B C D E,现给定新位置为3 4 1 0 2 排序后的结果是D A B E C

在这个栗子中,当我们交换pArr[0]与pArr[3]之后数组就变成了D B C A E,这时候恰好pPos[3]=flag了,所以这时候我们就要将tmp填入到pArr[3]这个位置上,然后再重新挖一个洞。

那么问题来了,这时候这个新的洞到哪里去挖呢???因为前面的pArr[0]已经被正确的填入了,所以就要从flag的下一个位置挖吗???

貌似对于例2来说从flag的下一个位置重新挖坑是没有问题,将上面的想法转化成代码如下:

代码:

template<typename T>
void SwapSort(T* pArr,int *pPos,int n)
{
assert(pArr);
assert(pPos);
T tmp;
int flag = 0;             //标记最开始的时候坑的位置
int count = 0;            //记录已经有多少个数已经被填到正确的位置
int i = 0;
while (count<n)
{
if (i == pPos[i])
{
++i;
count++;
}
else
{
tmp = pArr[i];          //在i位置形成坑
flag = i;
while (1)
{
swap(pArr[i],pArr[pPos[i]]);
i = pPos[i];
count++;
if (flag == pPos[i])          //这个位置恰好要填tmp
break;
}
pArr[i] = tmp;
i = flag + 1;
count++;
}
}
}


的确,用上面的代码能跑过例1和例2这两种情况,不过上面的代码还不够全面。我们接下来再例3。

例3:原数组 A B C D E,现给定新位置为2 0 1 4 3 排序后的结果是D A B E C

当我们第一次将tmp填入坑里面的时候pArr=[C A B D E],pPos=[2 0 1 4 3],我们会发现这次如果在按照例2中那种方法重新挖坑的话,原本pArr[1]和pArr[2]已经被填入正确的值了,经过再次挖坑的话又会将已经排好的数再次打乱。

这个原因主要是因为挖坑的位置不对引起的,所以为了避免这种情况,我们在将数填到正确的位置(假设是i)的时候,也将pPos[i]=i进行修改。



代码:
template<typename T>
void SwapSort(T* pArr,int* pPos,int n)
{
int flag = 0;    //记录最开始的时候坑的位置
char tmp;
int i = 0;
while (i<n)
{
while (i == pPos[i])
{
i++;
}
if (i == n)          //表示所有位置都已经正确填入了
break;
tmp = pArr[i];      //在i这个位置挖一个洞
flag = i;
while (1)
{
swap(pArr[i], pArr[pPos[i]]);
int j = pPos[i];        //先把pPos[i]的位置记录下来
pPos[i] = i;            //因为pArr[i]已经正确填入,所以更新pPos
i = j;
if (flag== pPos[i])      //如果这个新坑要填的是tmp
{
pArr[i] = tmp;
pPos[i] = i;
i = flag + 1;
break;
}
}
}
}


首先这道题目确实是很简单的,但是如果要求在O(1)的空间复杂度内解决这个问题的话,还是有一点难度的。 在上面的解决方法中我们利用了挖洞法的思想,实现了对数组的重新排序。接下来我们再来实现另一种更为巧妙的解决方法。

例:原数组 A B C D E,现给定新位置为2 0 1 4 3 排序后的结果是D A B E C。

而解决这个问题最大的难点就在于如何对已经重新排过的元素进行重定位,例:首先将A和C进行交换后pArr={C B A D E},这时候再对下一个位置进行交换,也就是pArr[1]和pArr[0]进行交换,本来是要将A填到pArr[1]的位置,但是由于之前A已经和C交换过了,这时候pArr[0]=C了,再交换pArr[0]pArr[1]的话肯定是要出错的。

其实,针对位置i而言它的pPos[i]和i的大小关系有三种:

1、i=pPos[i],表示这个数已经填到正确的位置上了,不需要再进行交换。

2、i < pPos[i],表示i这个位置所要填入的数在i位置的后面,直接进行交换的话,不会影响i以及i前面的已经处理过了的位置。

3、i>pPos[i],表示i这个位置要填入的数原来的位置在i的前面,但是前面的数已经被处理过了,所以他新的位置是pPos[pPos[i]]。例:首先将A和C进行交换后pArr={C B A D E},这时候要处理i=1这个位置,因为i>pPos[0],表示A已经被换过位置了,所以A的新位置是pPos[pPos[i]]。但是这样单纯的处理的话还不够,因为B的位置就变了,所以我们还有更新pPos[i]这个位置,让它记录B的新位置。

代码:

template<typename T>
void SwapSort(T* pArr, int* pPos, int n)
{
assert(pArr);
assert(pPos);
for (int i =0 ;i < n- 1;++i)
{
if (i < pPos[i])
{
swap(pArr[i], pArr[pPos[i]]);
}
else if (i>pPos[i])
{
swap(pArr[i], pArr[pPos[pPos[i]]]);
pPos[i] = pPos[pPos[i]];
}
}
}


在上面问题的基础上再加一个限制条件:使得数据移动的次数最少。

第一种解法已经能够满足要求了,针对第二种解法,再加一个限制条件即可:

template<typename T>
void SwapSort(T* pArr, int* pPos, int n)
{
assert(pArr);
assert(pPos);
for (int i =0 ;i < n- 1;++i)
{
if (i < pPos[i])
{
swap(pArr[i], pArr[pPos[i]]);
}
else if (i>pPos[i])
{
if (i!=pPos[pPos[i]])
{
swap(pArr[i], pArr[pPos[pPos[i]]]);
pPos[i] = pPos[pPos[i]];
}
}
}
}
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: 
相关文章推荐