对一个数组,按照给定的下标进行排序,仅使用两两交换的方式
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的下一个位置重新挖坑是没有问题,将上面的想法转化成代码如下:
代码:
的确,用上面的代码能跑过例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进行修改。
首先这道题目确实是很简单的,但是如果要求在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的新位置。
代码:
在上面问题的基础上再加一个限制条件:使得数据移动的次数最少。
第一种解法已经能够满足要求了,针对第二种解法,再加一个限制条件即可:
初次见到这道题的时候,着实让我头疼了一把,最后经人指点,自己也就有了一个大致的思路,下面将这道题的解法做一下总结。
分析:这道题要用到挖洞法的思想,不过要在挖洞法的基础上再做一些改进。
例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]]; } } } }
相关文章推荐
- 对一个数组按给定的下标排序,仅使用两两交换的方式,要求不能对数组进行扩容尽可能使用额外少的空间。原数组为:A,B,C,D,E, 现给定新的位置为3, 0, 1, 4, 2那么排序为D,A,B,E,C
- 【数据结构】对一个数组按给定的下标排序,仅使用两两交换的方式,要求不能对数组进行扩容尽可能使用额外少的空间
- 给定一个字符串数组{"nba","abc","cba","zz","qq","haha"},请按照字典顺序进行从小到大的排序。
- 那些年我们刷过的算法题(排序)---有一个只由0,1,2三种元素构成的整数数组,请使用交换、原地排序而不是使用计数进行排序
- java 给定一个字符串数组。按照字典顺序进行从小到大的排序。
- 编写一个使用数组类模板Array对数组进行排序、求最大值和求元素和的程序,并采用相关数据进行测试。
- 使用指针方式进行数组排序 选择排序:
- 已知一个几乎有序的数组,几乎有序是指,如果把数组排好顺序的话,每个元素移动的距离可以不超过k,并且k相对于数组来说比较小。请选择一个合适的排序算法针对这个数据进行排序。 给定一个int数组A,同时给定
- 把一个二维实型数组a按照第0列的元素进行排序(由小到大排序,用气泡法)
- 给定一组不重叠的间隔,在间隔中插入一个新的间隔(如有必要,合并)。间隔最初按照起始时间进行排序。
- 将数组中的对象按照浏览器的x/y轴的显示方式进行排序
- 给定一个字符数组,存储有R、G、B字符,将所有字符按照RGB顺序排序
- 公司要求开发一个继承System.Windows.Forms.ListView类的组件,要求达到以下的特殊功能:点击ListView各列列头时,能按照点击列的每行值进行重排视图中的所有行 (排序的方式如DataGrid相似)。根据您的知识,
- 完全颠倒一个字符串 和 数组按照中间的位置进行交换
- 给定一个未排序的整数数组,找到第一个缺失的正整数 您的算法应在O(n)时间运行,并使用恒定空间。
- 编写一个使用数组类模板Array对数组进行排序、求最大值和求元素和的程序,并采用相关数据进行测试。
- PHP 对一个给定的二维数组按照指定的键值进行排序
- 使用jQuery匹配文档中所有的li元素,返回一个jQuery对象,然后通过数组下标的方式读取jQuery集合中第1个DOM元素,此时返回的是DOM对象,然后调用DOM属性innerHTML,读取该元素 包含的文本信息
- 使用函数指针,完成一个sort()函数,能对任何类型的数组元素进行排序: 回调函数 以及 memcpy ()原型实现
- PHP 对一个给定的二维数组按照指定的键值进行排序