关于庞果网数组排序的问题

    昨天在庞果网看了个题目,难度只有两星(最高为五星),看似简单,120分钟内却没有做出来,深受打击!想了一晚上,终于感觉能给出个可用的算法了。题目如下:
 

题目并不要求排序, 只是让求出如果排序最少需要多少次数据交换,在实际应用中可能用处不大,但这却是一个非常考验逻辑思维的题目。在我成功解决之时,挑战的人数已有1648人,但成功的不过402人,说明这道题难度大于2星,而且我觉得挑战成功的人中用下面笨办法的应该大有人在。
一、解决方案:
    第一种,是一种笨办法,就是根据地址偏移值排序(虽然题目没要求排序),然后计数发生的数据交换次数,可以得到答案。这种方法需要在函数中额外申请一块与原数组大小一样的空间,然后完全拷贝原数组的数据,再进行排序(函数形参中有const,无法直接操作原数组,只能申请额外内存)。后来在度娘上搜索答案,发现仅有的几个解决方法也都是这个笨办法,没什么技术含量,随便会个冒泡排序就可以实现。
    第二种,除了计数器的内存,不申请其他任何内存,然后只对原数组进行遍历比较,得到答案。这种算法在空间和时间上效率都很好(第一种的时空性都不好),但至今没有想出来。
    我的算法接近于第二种(不再考虑第一种,不过可以用它来测试。),但借助了一个与原数组有相同元素个数的bool数组,在时空上没有第二种的那么理想。

因为第一种算法很简单便不再将源码贴在这里,第二种算法没有想出来,所以只贴我目前实现的算法。代码如下:
#define             CSC_ERROR             (-1)
int  CalculateSortCount(const int *pArray,int nArrayCount)
{    //参数判断
    if(pArray == NULL || nArrayCount <= 0)         return CSC_ERROR;
//申请内存
     bool    *pbCheckedFlag = new bool[nArrayCount];
     if (pbCheckedFlag == NULL)  
     {
          return CSC_ERROR;
     }
     else
     { 
          memset(pbCheckedFlag,0,nArrayCount);
     }
    //计数器变量 

     int     nExchangeCount = 0;
     int     nCheckedNumber = 0;
     int     nCurrentIndex = 0;
     int     nCurrentValue = 0;
    //当检查过的数字的个数  < 数组中数字的总个数 时 
     while(nCheckedNumber < nArrayCount)
     { //如果这个位置的数字没有被检查过
            if (!pbCheckedFlag[nCurrentIndex])
            { 
               nCurrentValue = pArray[nCurrentIndex];
               //如果当前位置上的数字不等于他的索引,
               //就将当前值作为地址偏移值来取下一个数据(在实际排序中相当于一次数据交换,使一个元素处于正确位置)
               while(nCurrentValue != nCurrentIndex + 1)
               {

                    pbCheckedFlag[nCurrentValue - 1] = true;
                    nCheckedNumber++;
                    nCurrentValue = pArray[nCurrentValue - 1];
                    nExchangeCount++;
               }
               pbCheckedFlag[nCurrentIndex] = true;
               nCheckedNumber++;
            }
            nCurrentIndex++;
     }
     delete []pbCheckedFlag;
     return nExchangeCount;
}

二、算法流程(从第一个数开始,执行下面的算法):
1、将当前元素值保存在nCurrentValue中。
2、检测nCurrentValue与索引值 + 1(因为数组起始索引为0)是否相等。
    <1>若相等则将对应索引的bool数组中的元素设置为真,索引值自增,然后跳到1执行。
    <2>若不相等,则首先将bool数组中索引为nCurrentValue - 1的元素设为真,nCheckedNumber(检查过的元素个数)自增,并以当前nCurrentValue - 1作为索引值取得值存放在nCurrentValue中,nExchangeCount(需要进行交换的次数)自增,跳到2执行。
3、直到nCheckedNumber(检查过的元素个数) 等于 nArrayCount(原数组中元素的个数)时,可以返回 nExchangeCount(需要进行交换的次数)这个数值了。这时nExchangeCount中的值即所要的答案。


三、算法说明:
(1)情况<1>说明当前元素处在正确位置,无需计数。
(2)情况<2>是在当前元素处在不正确位置时,不断地将其调整到正确位置(并没有真正调整,只是进行了调整次数的计数),直到当前位置上的元素为正确元素。
(3)bool数组的作用是记录当前位置上的元素曾经是否被遍历过,若真,那就不对这个元素的调整计数了,若假就对它进行调整计数。

四、测试序列:9 1 7 3 2 4 6 5 8。(如果进行排序,则排序后结果为:1 2 3 4 5 6 7 8 9。)
1、执行过程:
(1)第1个位置元素为9,不是正确元素,开始寻找:9->8->5->2->1,找到了,nExchangeCount计数4。 
(2)第2个元素为1,但1这个位置(也就是位置2)在bool数组中中被标记为被遍历过了(在第一步中确实遍历过位置2的1这个元素)所以跳过。
(3)第3个位置元素为7,没有遍历过,也不是正确元素,开始寻找:7->6->4->3,找到了,(1)中有4次,这里有3次,所以nExchangeCount计数7 。
(4)这时检查过的元素的个数已为9,结束。 

2、实际排序:(变为蓝色的数字的索引在bool数组中为真了,即表明这个数字被遍历过)
(0)9 1 7 3 2 4 6 5 8 (初始序列)
(1)8 1 7 3 2 4 6 5 9 (第1次使9处于正确位置)
(2)5 1 7 3 2 4 6 8 9 (第2次使8处于正确位置)
(3)2 1 7 3 5 4 6 8 9(第3次使5处于正确位置)
(4)1 2 7 3 5 4 6 8 9 (第4次使2处于正确位置,同时元素1刚好处在了正确位置)
(5)1 2 6 3 58 9 (第5次使7处于正确位置)
(6)1 2 4 3 5 6 8 9 (第6次使6处于正确位置) 
(7)1 2 3 4 5 6 8 9 (第7次使4处于正确位置,同时元素3刚好处在了正确位置,已有序,结束) 

3、测试结果:程序执行结果与实际排序结果相同。 在庞果网提交了代码,也通过了测试。

五、总结:
    总觉得这道题里面有一定的数学理论,但自己的数学修养不够,说不出来。
    实现的这种算法没有第二种(理想的)好(空间上多用了bool数组的内存,时间上有n个bool变量的赋值,n指原数组中元素个数),但比第一种要好多了,内存上节省了n*3个字节(想更节省内存可以采用位操作),赋值操作少了n*2次(实际排序时交换两个数据需要3次赋值操作,而我的算法这里只有一次bool类型的赋值)。
    在解决这个题目时,思维需要不断的在地址(即索引)与地址中的值(即元素值)之间变换,需要较好的逻辑思维。相信能很好地理解这个问题的人也是指针高手。
    也许第二种算法是无法实现的,即使可以实现,估计比较次数也要比现在多出许多,因为时间与空间总是冲突的。如果有能实现第二种算法或者更好的大神请一定告诉我。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值