通过交换操作,调整数组元素位置

问题描述:有一个长度为N的整形数组row,由0至N-1这N个数字乱序组成(每个数组出现且仅出现一次)。现在你可以对这个数组的任意两个不同的元素进行交换。问:对于一个给定的这种数组,若要把这个数组变为从小到大排好序的操作(即,对于数组的任意下标,均有 I == row[i] 成立),最少需要进行多少次交换?

首先,举几个简单的例子:

例子1:


下标


0


1


2


3


4



0


3


2


1


4

只需1次交换即可:把row中下标为1的元素和下标为3的元素进行交换,记为swap(row, 1, 3)。

例子2:


下标


0


1


2


3


4



0


2


1


4


3

需要两次交换:

第一次:swap(row, 1, 2)

第一次交换后:


下标


0


1


2


3


4



0


1


2


4


3

第二次:swap(row, 3, 4)

例子3:


下标


0


1


2


3


4



0


4


2


1


3

需要两次交换:

第一次:swap(row, 1, 4)

第一次交换后:


下标


0


1


2


3


4



0


3


2


1


4

第二次:swap(row, 1, 3)

注意,在例子3中,下标为1、3、4的三个元素的初始位置形成了一个“环”。即(接下来的话很重要),位置1上的元素本应该在位置4;位置4上的元素本应该在位置3;位置3上的元素本应该在位置1。这段很重要的话太啰嗦了,简记如下:1-->4-->3-->1。

任何一个乱序的数组,都会包含一个或多个形如“1-->4-->3-->1”的“环”。

注意,这个“环”的开头的结尾肯定是同一个下标,绝不会出现如下的形式:

“1-->4-->3-->……-->3”。这是因为,如果数字3出现了两次,那就意味着原始数组row中的两个不同的位置的元素都“本应该出现在位置3”。

所以,“通过交换的方式对数组进行排序”,其实就是“对上述的‘环’中的下标进行操作”。

下面来计算对每个“环”需要进行多少次交换。

首先定义“环”的长度如下:

“1-->4-->3-->1”的长度为3,

“1-->4-->1”的长度为2

“1-->1”的长度为1(长度为1的情况就是“该元素的处于正确位置”的情况)

对于长度为1的环,所需的交换次数是0,SWAP(1) = 0

对于长度为2的环,所需的交换次数是1,SWAP(2) = 1

对于长度为k的环,交换其中的任何两个元素,把当前的“撕裂”为两个更小的环,且两个小环的长度加起来刚好等于k。例如:

对于环:

……i-->j-->k-->……r-->s-->t-->……

执行swap(row, j, s),会生成如下的两个环(需要思考两分钟):

环1:……i-->j-->t-->……

环2:k-->……r-->s->k

(对于j和s在边界的情况,上述结论也成立。)

所以,对于长度为k的环,所需的交换次数SWAP(k)=SWAP(k1) + SWAP(k2) +1,其中k1+k2=k

根据

SWAP(1) = 0,

SWAP(k)=SWAP(k1) + SWAP(k2) +1,其中k1+k2=k

可以用第二数学归纳法证明(第二数学归纳法是啥,见文末),

SWAP(n) = n - 1

注意,对长度为k的环,交换其中的任意两个元素都可以把环撕裂为两个小环。那么,如果我们把第一个元素交换到“它本应出现的位置”,会怎样呢?

对于环“1-->4-->3-->1”,下标1上的元素本应出现在位置4,所以我们执行swap(row, 1, 4),然后就把“1-->4-->3-->1”撕裂为两个小环:

环1:“1-->3-->1”

环2:“4-->4”

环2是“已经搞定了”的状态,接下来只需处理环1,swap(row, 1, 3)。

上述算法的直观感觉就是,不停地把“当前位置的元素”和“它应该去的地方”的元素进行交换,这样,“当前位置的元素”就去了“它应该去的地方”,同时,“被换过来的元素”又成了“当前位置的元素”,直到“被换过来的元素”就应该放在“当前位置”为止。

代码如下:

int sort(int[] row) {

printArray(row);

int nSwapTimes = 0;

for (int i = 0; i < row.length; ++i) {

for (int j = row[i]; j != i; j = row[i]) {

swap(row, i, j);

++ nSwapTimes;

// 可以在每次交换后把row的当前状态打印出来感受一下

printArray(row);

}

}

return nSwapTimes;

}

上述解法可以推广到如下的问题:

有N对夫妇随机坐成一排,现在要经过若干次交换座椅,使得每对夫妇的座位都挨在一起。求最小的交换次数。座位以整形数组row表示,下标从0至2N-1。每一对夫妇都用有序数对表示:(0, 1)、(2, 3)、(2N-2, 2N-1)。数组row的第i个元素row[i]代表位置i上坐着的人。

为了解决这个问题,需要一个和row的用处刚好相反的辅助数组pos:row[i]代表位置i上坐着的人;pos[i]代表人i所在的位置。

为了方便起见,定义getPartner函数如下

int getPartner(int n) {

return (n % 2 == 1) ? (n - 1) : (n + 1);

}

这个函数返回下标n的配偶(n既可以是人也可以是座位。请思考两分钟,当n是座位时getPartner的含义)。

这个问题和乱序数组的排序问题只有一个区别:

乱序数组排序:下标i所在的元素的期望位置是row[i]

本问题:下标i所在元素的期望位置是getPartner(pos[getPartner(row[i])])

getPartner(pos[getPartner(row[i])])的含义:

最内层row[i],位置i上的人是谁

再加一层getPartner,此人的配偶是谁

再加一层pos,此人的配偶在row中的实际座位

再加一层getPartner,此人的期望座位(思考两分钟)

代码如下:

int nResult = 0;

for (int i = 0; i < row.length; i += 2) {

for (int j = getPartner(pos[getPartner(row[i])]); j != i; j = getPartner(pos[getPartner(row[i])])) {

swap(row, i, j);

swap(pos, row[i], row[j]);

++nResult;

}

}

return nResult;

至此,结束

第一数学归纳法,若对自然数的命题P(n),满足:

1、  P(1)成立。

2、  若P(k)成立,则P(k+1)成立

则P(n)对全体自然数成立。

第二数学归纳法,若对自然数的命题P(n),满足:

1、  P(1)成立。

2、  若P(1),P(2),……,P(k)成立,则P(k+1)成立

则P(n)对全体自然数成立。

原文地址:https://www.cnblogs.com/adgjl/p/9653038.html

时间: 2024-10-15 23:13:36

通过交换操作,调整数组元素位置的相关文章

java实现原数组根据下标分隔成两个子数组并且在原数组中交换两个子数组的位置

此类实现:输出一行数组数据,根据输入的下标,以下标位置为结束,将原数组分割成两组子数组.并交换两个子数组的位置,保持子数组中的元素序号不变.如:原数组为7,9,8,5,3,2 以下标3为分割点,分割为子数组一:7,9,8,5.和子数组二:3,2.经过交换算法后的结果应为:3,2,7,9,8,5 有两种交换算法<1>前插法:将子数组3,2另存在一个临时数组中,将原数组7,9,8,5,3,2每一位向后移两个位置  再将子数组3,2插入到移动好元素位置的原数组中.<2>逆置法:将原数组7

让无序数组元素进行排序,排序完后将排序后元素对应的原先元素的位置输出

题目: 让无序数组元素进行排序,排序完后将排序后元素对应的原先元素的位置输出 (1)方法1 方法1:先将数组元素原先的对应位置记录在另一个数组中       并在进行选择排序的过程中,交换数组元素的同时也交换对应位置数组中的对应元素值 /* 选择法排序 并在排序后的数组元素在原先数组的对应位置输出 方法1:先将数组元素原先的对应位置记录在另一个数组中 并在进行选择排序的过程中,交换数组元素的同时也交换对应位置数组中的对应元素值 */ #include <iostream> using name

数组中元素位置移动实现

题目:有 n 个整数,使其前面各数顺序向后移 m 个位置,最后 m 个数变成最前面的 m 个数 分析:实现移动其实就是对索引进行操作,数组元素没有改变,而索引值发生了改变, 合理运用%运算,原数组索引%len=原数组的索引-->(原数组索引+移动位数)%len=新数组索引 public static void function01() { int[] array={2,3,4,6,7,9}; //复制一个数组,用于数据的存放 int [] arraycopy=Arrays.copyOf(arra

js 实现数组元素交换位置

/** * 数组元素交换位置 * @param {array} arr 数组 * @param {number} index1 添加项目的位置 * @param {number} index2 删除项目的位置 * index1和index2分别是两个数组的索引值,即是两个要交换元素位置的索引值,如1,5就是数组中下标为1和5的两个元素交换位置 */function swapArray(arr, index1, index2) {   arr[index1] = arr.splice(index2

javascript 常见数组操作( 1、数组整体元素修改 2、 数组筛选 3、jquery 元素转数组 4、获取两个数组中相同部分或者不同部分 5、数组去重并倒序排序 6、数组排序 7、数组截取slice 8、数组插入、删除splice(需明确位置) 9、数组遍历 10、jQuery根据元素值删除数组元素的方)

主要内容: 1.数组整体元素修改 2. 数组筛选 3.jquery 元素转数组 4.获取两个数组中相同部分或者不同部分 5.数组去重并倒序排序 6.数组排序 7.数组截取slice 8.数组插入.删除splice(需明确位置) 9.数组遍历 10.jQuery根据元素值删除数组元素的方法 数组常见操作包含了 增.删.查.改.插入.交集.并集 1.数组整体元素修改 //map,给数组每个元素加1 输出[1,2,3] $.map([0,1,2],function(n){ return n+1; })

实现数组元素互换位置(乘机理解java参数传递)

Java中函数参数是按值传递的,在实现数组元素互换位置之前,我想先说一下Java函数参数传递过程.一般情况下我们会把参数分为基本数据类型和引用数据类型,然后分别来讲参数传递,因为他们的外在表现似乎是不同的,然而,他们的本质都是值传递.在讲值传递时,请务必将"实参的副本"这五个字刻在脑海,因为它是理解值传递的关键. // 例子1 int a = 0; void value(int x) { x = 1; } value(a); System.out.println(a); // 结果是

调整数组的中的元素使奇数位于数组的前面,偶数位于数组的后面

分为两部分的问题,最好只用两个指针. 譬如此题:就可以一个指针从头往后扫,而另一个指针从后往前扫,保证第一个指针永远指向奇数,最后一个指针永远指向偶数,然后两者交换,直至最终两个指针相等,即扫描完了所有的元素. 代码如下所示: 1 //调整数组顺序使奇数位于偶数前面 2 #include<iostream> 3 using namespace std; 4 void function1(int* number, unsigned int length) 5 { 6 int p1 = 0; 7

javascript数组操作(创建、元素删除、数组的拷贝)

这篇文章主要介绍了javascript数组操作,包括创建.元素的访问.元素删除.数组的拷贝等操作,还有其它示例,需要的朋友可以参考下 1.数组的创建 复制代码 代码如下: var arrayObj = new Array(); //创建一个数组var arrayObj = new Array([size]); //创建一个数组并指定长度,注意不是上限,是长度var arrayObj = new Array([element0[, element1[, ...[, elementN]]]]); 创

将数组元素循环右移k个位置(Java实现)

用四种方法实现了将数组元素循环右移k个位置,相关的解释作为注释放在代码里面了. package movearrayelement; import java.util.BitSet; public class MoveArrayElement { /** * 每次把数组中所有元素移动一个位置,移动k轮 * @param array * @param k */ public static void moveArrayElement(int[] array, int k) { int length =