题目:
设将个整数存放到一维数组R中。试设计一个在时间和空间两方面都尽可能高效的算法,将R中保存的序列循环左移个位置,即将R中的数据序列由(,,...,),转变为(,,...,,,,...,)。要求:
(1)给出算法的基本设计思想
(2)根据设计思想,采用C或C++或Java语言描述算法,关键之处给出注释
(3)说明你设计的算法的时间复杂度和空间复杂度
解决方案1
(1)给出算法的基本设计思想
利用多次reverse,第一次反转将
(,,...,,,,...,)
原地反转变为
( ...,,,,...,,)
然后再将
( ...,,)与( ,...,,)
分别原地反转,得到
与
(2)根据设计思想,采用C或C++或Java语言描述算法,关键之处给出注释
void Reverse(int R[], int n){
int i=0, j=n-1, temp;
while(i<j){
temp = R[i];
R[i] = R[j];
R[j] = temp;
i++;
j--;
}
}
void leftShift(int R[], int n, int p){
Reverse(R,n);
Reverse(R,n-p);
Reverse(&R[n-p],p);
}
(3)说明你设计的算法的时间复杂度和空间复杂度
时间复杂度为O(n),空间复杂度为O(1)
解决方案2
(1)给出算法的基本设计思想
借助长度为n的辅助空间tmp,先将R中前p个元素按原顺序移动到tmp中的后p个位置,然后将R中后n-p个元素直接按顺序移动到tmp中的前n-p个位置,最后将tmp中的所有元素按顺序移动到R中
(2)根据设计思想,采用C或C++或Java语言描述算法,关键之处给出注释
void leftShift(int R[], int n, int p){
int tmp[n];
for(int i=0;i<n;i++){
tmp[i] = R[(i+p)%n];
}
for(int i=0;i<n;i++){
R[i] = tmp[i];
}
}
(3)说明你设计的算法的时间复杂度和空间复杂度
时间复杂度为O(n),空间复杂度为O(n)
解决方案3
(1)给出算法的基本设计思想
借助长度为p的辅助空间tmp,先将R中前p个元素按原顺序移动到tmp中,然后将后n-p个元素直接按顺序原地往左移动p个距离,最后将tmp中的p个元素按顺序移动到R中后p个元素的位置
(2)根据设计思想,采用C或C++或Java语言描述算法,关键之处给出注释
void leftShift(int R[], int n, int p){
int tmp[p];
for(int i=0;i<n;i++){
if(i<p){
tmp[i] = R[i];
}
else if( i > n-p){
R[i] = tmp[i-n+p];
}
else {
R[i-p] = R[i];
}
}
}
(3)说明你设计的算法的时间复杂度和空间复杂度
时间复杂度为O(n),空间复杂度为O(p)
解决方案4
(1)给出算法的基本设计思想
所有元素循环左移个位置,意味着每个元素都要向左循环移动个距离,每个元素的旧位置和新位置都是确定的,而且互相之间不会冲突。下标为的位置 的新数据将是原来位于的元素。那么可不可以一步到位,将的元素填入 呢?这样做有个前提,就是要把 原来的数据保存到别的地方,空出了位置才可以将新元素填入。同时的元素被挪走以后,就会空出位置,继续往后面寻找这个位置的新主人,这是一个迭代过程。因为每个位置的旧元素与新元素是确定的,所以不会出现多个元素竞争一个位置,不会出现类似于哈希表冲突一样情况。同时,由于元素总数是确定的,所以我们只需要确保每个位置上的元素都是新元素,那么循环左移的任务就算完成了。
上述的过程可以简述为:不断的一步到位将元素移动p个位置,然后让出原有位置,继续向后寻找这个空出来的位置对应的新元素。能够参与到这个过程的元素,形成了前后相互勾连的关系,我们将具有这个关系的所有元素定义为同一个分组,那么上述这个过程就可以看作是组内元素循环左移1个逻辑单位。
这样大跨度寻找元素的过程,很类似于希尔排序。距离为p的多个元素将会组成一个小组,组内采用循环左移一个单位的算法实现移动。
以为步长的时候,会形成个小组,第个小组的首元素便是。
当是的整数倍的时候,这个小组将会相互独立,组内最后一个元素下标 ,得到的便是同一个小组的首元素的下标。
假设 实数,我们可以将这个个元素按行优先存储到行列的数组中,如下图所示。
可以清楚的看到同一列的元素下标 后得到的下标指向的元素是同一列的元素,最后一行的元素下标 后将会回到同一列第0行。这个时候每一列的元素将会形成一个小组,总共个小组,同一列的元素直接循环上移就可以完成将整个数组R循环左移个位置的任务。这个时候个小组是相互独立的,没有哪个组的元素下标 后会进入别的组。
当不是的整数倍的时候, 设 ,这个时候个小组将会不再独立,某个小组内最后一个元素下标 ,将会得到另外一个小组的首元素下标,如下图所示。
将个元素按每个一行,排列到二维数组中,假设总共有行,因为不是的整数倍,所以最后一行(图中蓝色单元格)的元素将不足个。倒数第二行(图中红色单元格)后面个元素 下标 的结果将分别是 ,也就是说第组的元素链接到了第0组,第q+1组的元素链接到了第1组,...,第p-1组的元素链接到了第p-q-1组 ,而倒数第一行(图中蓝色单元格)的元素 下标 的结果将分别是p-q,p-q+1,...,p-1,也就是说第0组的元素链接到了第p-q组,第1组的元素连接到了第p-q+1组,...,第q-1组的元素链接到了第p-1组。
所以本质上,第q组、第0组、第p-q组并不是独立的,而是属于同一条链的,首位相互链接。
寻找哪些组属于同一个链,跟寻找哪些元素属于同一个组,本质上是相同的问题。前者取决于p是不是p-q的整数倍,后者取决于n是不是p的整数倍。前者的前进步长为p-q,后者的前进步长为p。
一开始有p个组,分别属于p个链。当n是p的整数倍的时候,这个p个链没有相互勾连,是相互独立的,并且每个链都是循环链。当n不是p的整数倍的时候,发现原本的p个链中,有些链是首尾相接,链接到一起的,构成了更大的链,所以独立链的条数也会减少。这些同属于一个链条的分组之间的步长从p缩短到q,可能还会进一步缩短。当发现链是循环链的时候,也就是元素下标+p Mod n回到了最初的位置的时候,就已经收敛了。当所有的链都收敛的时候, 独立链的条数也稳定了下来,同属于一个链的分组之间的距离为t,步长为t,链与链的首元素(元素在R中的下标在0,1,...,t-1)是相邻的。
于是当有多个独立的链存在的时候,每个链都是循环链,同一个链上的元素采用下标+p Mod n的方式是无法跳出当前链、到达别的链的,这个时候需要特殊检测是否循环到了最初的位置,如果是,要将最初的位置上的元素(提前存到了临时变量中)填入最近生成的空位置,这个循环链的元素移动也就完成了。下一步要到另一个链进行步长为p的移动,于是采取从最初的元素右侧的一个元素开始(采取下标+1的方式进入新的独立链),直到移动元素的个数达到n为止。当时,第0行中前个元素一定是分属于个等价组,所以采取下标+1的措施进入另外的独立链是可行的。
每个独立的链是循环链的证明:设m是n与p的最小公倍数,设m=s*n=t*p,m,s,n,t,p全都是整数。
当n是p的整数倍的时候,n就是n与p的最小公倍数,s=1,t=M,R中的n个元素可以按行优先组成一个M行p列的二维数组,这个二维数组是完全填满的,一个不多一个不少,前M-1行中每一行的任意一个元素在R里的下标 的结果就是同一列下一行的元素在R里的下标。最后一行的人一个元素在R中的下标 的结果就是同一列第0行的元素在R中的下标。所以以步长p前进遍历过程中的所有访问到的元素全都是同一列的。
当n不是p的整数倍的时候,将R中的n个元素按照行优先的顺序、每行p个元素,摆放成为二维数组,最后一行将会填不满p个单元,如果这个时候再来一个R继续填入,那么需要s个R才能恰好填满,因为m是n与p的最小公倍数,m一定可以被p整除,所以每一行都是满的,因为m是n的整数倍,所以填满这个p列的二维数组的,一定是s个R,一个元素不多,一个元素不少。前t-1行每个元素在R中的下标 的结果就是同一列下一行的元素在R中的下标,最后一行的每个元素在R中的下标 的结果就是同一列第0行的元素在R中的下标,所以构成一个循环链。但是由于这个t行p列的二维数组是由s个R的元素按先后顺序构成的,所以同一列的元素,将会来自于s个R,从上到下将会分为s个part,每个part来自于一个R,不同的part来自于不同的R。同一个part里的元素在R中的下标 Mod n的结果是相同的,但是不同的part中的元素在R中的下标 Mod n的结果必定不相同(因为m是n和p的最小公倍数,如果相同则m不是n与p的最小公倍数,n和p还有一个公倍数比m还小)。回想到一开始的时候,我们根据下标 Mod p 的结果将R中的n个不同的元素分为p个不同的小组,R中前p个元素便是这p个小组的首元素。所以在s个R构成的t行p列的二维数组中,同一列的s个part的元素,分属于不同的小组,但是属于一个循环链。
(2)根据设计思想,采用C或C++或Java语言描述算法,关键之处给出注释
void leftShift(int R[], int n, int p){
int cur, next, r, start = 0;
cur = start;
r = R[cur];
for(int i=0;i<n;i++){
next = ( cur + p) % n;
if(next == start){// there is a circle, return to the start
R[cur] = r;
start++;
cur = start;
r = R[cur];
}
else {
R[cur] = R[next];
cur = next;
}
}
}
(3)说明你设计的算法的时间复杂度和空间复杂度
时间复杂度为O(n),空间复杂度为O(1)