通过对比采用数组或伪链表解决约瑟夫环问题的时间复杂度来认识伪链表的优势
解决约瑟夫环问题,采用伪链表使程序时间复杂度更小!
问题
有n个人,从1开始编号并且围成一圈,从第i个人开始依次报数,报到出圈数值m的人出圈,然后下一个人接着从1开始报数,重复上述操作,直到所有人出圈为止。编程要求:编程描述出圈过程及输出每个人的出圈顺序。
假设一共有12个人,从第3个人开始报数,出圈数值为6;出圈顺序如图所示:
思路
1.该题若用“循环链表”,太过于麻烦了——循环链表的生成及结点的删除。
2.考虑采用数组,采用数组时,“一个人出圈并非真正地出圈”,即并不是真正地把该元素删除,而且用0表示该人在圈内,当报数报到出圈数值时,将该元素的值变为1,表示该人出圈。报数操作为num += 1- circle[index];关键在于:不是真正地出圈会导致算法时间复杂度很高,程序运行时间长,即假设在有1000个人围成一个圈,且出圈数值为100的前提下:当只剩两个人在圈内时,倒数第二个人出圈时,需要将这个圈“转”100/2次,每一圈需要做1000次加法操作,即num += 1- circle[index];即使是加0也属于加法操作,所以倒数第二人出圈就需要做50*1000次加法操作;倒数第一个人出圈需要做100*1000次加法操作!
采用数组算法代码如下:
#include <stdio.h>
#include <malloc.h>
void Joseph(int personCount,int firstNo,int outNo);
void Joseph(int personCount,int firstNo,int outNo) {
int count = personCount;
int *circle = NULL;
int index = firstNo - 1;
int num = 0;
circle = (int *)calloc(sizeof(int),personCount);
while(count) {
num += 1 - circle[index];
if(num == outNo) {
printf("%d:第%d个人出圈\n",personCount - count + 1,index + 1);
--count;
circle[index] = 1;
num = 0;
}
index = (index + 1) % personCount;
}
free(circle);
}
int main() {
int personCount;
int firstNo;
int outNo;
printf("Input the personCount:");
scanf("%d",&personCount);
printf("Input the firstNo:");
scanf("%d",&firstNo);
printf("Input the outNo:");
scanf("%d",&outNo);
Joseph(personCount,firstNo,outNo);
return 0;
}
上述代码时间复杂度高关键在于:出圈并不是真正地出圈!做了很多无谓的加法操作!所以,要真正地做到出圈,即删除元素!该想法完全适合于链表,但采用链表正如我上面所说太麻烦。
3.所以采用“伪链表”!
伪链表:本质上是一个数组!伪链表之所以“伪”是因为其结点链域中存储的不是真正的指针而是数组元素下标!
对于该问题生成的伪链表如下:因为该伪链表本质就是一个数组,本身的数组元素下标就可用,无需再用数据与存储各元素下标了,所以,去掉了数据域。通过下图所示的伪链表,就可形成一个圈。下标为0的元素空间中存放下标1…下标为11的元素空间中存放下标0,就把12个人一个跟一个地围成了一个圈。
采用伪链表算法代码如下:
#include <stdio.h>
#include <malloc.h>
void Joseph(int personCount,int firstNo,int outNo);
void Joseph(int personCount,int firstNo,int outNo) {
int count = personCount;
int *circle = NULL;
int curIndex = firstNo - 1;
int preIndex = (curIndex + personCount - 1) % personCount;
int num = 0;
int i;
circle = (int *)calloc(sizeof(int),personCount);
for(i = 0;i < personCount;i++) {
circle[i] = (i+1) % personCount;
}
while(count) {
num++;
if(num == outNo) {
printf("%d:第%d个人出圈\n",personCount - count + 1,curIndex + 1);
--count;
circle[preIndex] = circle[curIndex];
num = 0;
}
else {
preIndex = curIndex;
}
curIndex = circle[curIndex];
}
free(circle);
}
int main() {
int personCount;
int firstNo;
int outNo;
printf("Input the personCount:");
scanf("%d",&personCount);
printf("Input the firstNo:");
scanf("%d",&firstNo);
printf("Input the outNo:");
scanf("%d",&outNo);
Joseph(personCount,firstNo,outNo);
return 0;
}
代码优化后,出圈就是真正地出圈,出圈的人不再参与报数,不存在无效的报数,不用再做无谓的加法操作!随着出圈的人越来越多,留在圈内的人越来越少,优化后的代码时间复杂度减小!