在网上逛的时候,见有些程序写得实在太烂,那谁谁谁不是说过“这不是挖社会主义墙角,hao社会主义羊毛吗!”比如说下面的约瑟夫环问题吧,下面这个从网上摘下来的算法写得惨不忍睹。
约瑟夫问题的算法,精华在于建立链表和节点出列两个方面,而下面的算法在建立链表的时候居然又用了一个数组,本来很简单的问题搞得很复杂,而且还要限定变量范围,大幅度降低了程序的通用性。在节点出列算法中,我看了半天,又是数组又是链表的,愣是没看明白咋回事。于是俺冲冠一怒,自己写一个,作为拨乱反正的第一声呐喊吧。
从网上摘下来的算法:
约瑟夫(Joseph)环
题目描述:编号为1,2,...,n的n个人按顺时针方向围坐一圈,每人持有一个密码(正整数)。一开始,
任选一个正整数作为报数上限值m,从第一个人开始按顺时针方向自1开始顺序报数,报到m时停止报数。
报m的人出列,将他的密码作为新的m值,从他在顺时针方向上的下一个人开始重新从1报 数,如此下去,
直至所有人全部出列为止。试设计一个程序求出出列顺序。
*需求分析
利用向循环链表存储结构模拟此过程。程序输入、输出值均为正整数(其中人数限?amp;lt;=30),
程序的功能是按照出列的顺序印出各人的编号。
*测试数据:m=20,n=7,7个人的密码为:3,1,7,2,4,8,4;
*测试结果:出列顺序为6,1,4,7,2,3,5.
*附录(源程序):
-
//约瑟夫(Joseph)环
-
#include<iostream.h>
-
#include<conio.h>
-
//定义单链存储结构
-
typedef struct LNode{
-
int data;
-
struct LNode *next;
-
}LNode,*LinkList;
-
void main()
-
{
-
int m,n,t[30],j(0); //定义初始报数上限值m、人数n
-
cout<<"输入初始报数上限值m(正整数):";
-
cin>>m;
-
cout<<"输入人数n(正整数,<=30):";
-
cin>>n;
-
cout<<"输入各人的密码(以空格符为分隔号):"<<endl;
-
for(int i=0;i<n;i++)
-
cin>>t[i];
-
LinkList p,head;
-
head=new LNode;
-
head->data=0;
-
head->next=0;
-
p=head;
-
//初始化单向循环链表
-
for(i=1;i<n;i++)
-
{
-
struct LNode *s=new LNode;
-
s->data=i;
-
//s->next=0;
-
p->next=s;
-
p=p->next;
-
}
-
p->next=head;
-
//处理出列顺序
-
while(p->data!=p->next->next->data&&j<m)
-
{
-
if(j==m-1){
-
int n=p->next->data;
-
m=t[n];
-
j=0;
-
cout<<n+1<<endl;
-
if(p->data==p->next->next->data)
-
break;
-
p->next=p->next->next;
-
}
-
if(p->data==p->next->next->data){
-
cout<<p->next->data+1<<endl; //倒数第二个出列者
-
break; }
-
p=p->next;
-
j++;
-
}
-
cout<<p->data+1<<endl; //最后一个出列者
-
//暂停
-
getch();
-
}
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
下面是俺的算法,这里给出两个版本,一个增强版,一个简化版,主要区别在于输出详细程度不一样。而且俺给的注释十分详细
增强版
-
//Joseph Ring 2005 Plus! 简体中文正式版
-
//解决约瑟夫环问题
-
//CopyRight 2005 09 23 of Empire_Lenin @ USTC EEIS
-
//预编译命令
-
#include "iostream.h"//c++程序的标准输入输出头文件
-
#include "conio.h"//程序最后用到getch();语句,这是它的头文件
-
//定义单链存储结构
-
typedef struct LNode{
-
unsigned long serial;//各节点序号,用于输出出列顺序
-
unsigned long password;//各节点密码
-
struct LNode *next;//链表指针
-
}LNode,*LinkList;
-
//约瑟夫环主程序
-
void main()
-
{
-
unsigned long m,n,i; //定义初始报数上限值m、人数n、循环控制变量i为无符号长整型变量
-
cout<<"~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"<<endl;
-
cout<<" Joseph Ring 2005 Plus! 简体中文正式版"<<endl;
-
cout<<" CopyRight 2005 09 23 of Empire_Lenin @ USTC EEIS"<<endl;
-
cout<<"~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"<<endl<<endl;
-
cout<<"输入随机数m(正整数):";
-
cin>>m;
-
cout<<"输入人数n:";
-
cin>>n;
-
cout<<"输入各人的密码(密码为正整数,回车确认每一个输入,空格亦可):"<<endl;
-
LinkList p,head;//定义指针变量p和头指针head
-
head=new LNode;//开辟头结点,本问题亦可不设头结点,此处为了更容易实现本思想,故设之
-
head->serial=0;//以下两行对头结点元素赋值,头结点元素值无意义,赋值为了保证程序的健壮
-
head->next=0;
-
p=head;//定义指针变量p初始为指向头结点
-
LinkList s;//定义指针变量s
-
//初始化单向循环链表
-
for(i=1;i<=n;i++)
-
{
-
s=new LNode;//开辟新节点,并将新节点的地址赋给LNode型指针变量s
-
s->serial=i;//对新开辟的节点的序号元素赋值
-
cin>>s->password;//输入新开辟节点的密码
-
p->next=s; //新节点链接入链表
-
p=p->next; //指针变量p前进
-
}
-
p->next=head->next;//将表尾节点链接到表头,完成单向循环链表的建立
-
//下面六行语句用于检测链表建立是否和法,正式编译程序时可以略去
-
unsigned long j=1;//无符号长整型变量虽然耗费存储空间,但为保证程序健壮性起见,仍设之
-
cout<<"根据你的输入,该单向循环链表中每个节点的serial number和password分别如下 :"<<endl;
-
while(j<=n){
-
cout<<"Node No. "<<j<<" 's serial number is : "<<p->next->serial<<" "<<"password is : "<<p->next->password<<endl;
-
p=p->next;
-
++j;
-
}//检测完毕,结果在屏幕上输出,若链表建立正确,则若最终结果有误,错误必出在下面while语句。本段程序可删。
-
//链表初始化完成,下面输出出列节点序列
-
cout<<endl<<"建立链表成功,出列顺序如下 :"<<endl<<endl;
-
p=head;//置LNode型指针变量于头结点处
-
unsigned long counter1=1;//计数器,用于显示节点出列序号
-
//下面程序段以一个while嵌套语句完成按要求寻找节点、删除、返回数据等操作
-
//此种写法思路简洁明确,易读性高,完全优于用数组画蛇添足
-
while(n!=0){//节点出列完也就是链表长度递减到0之前,一直执行下述循环
-
while(m>n) m=m-n;//如果m值大于链表长度,求出在链表长度范围内的m值
-
unsigned long counter2=1;//计数器,用于标记和m值匹配的、指针变量p的移动步数
-
while(m!=counter2){//找到和m值匹配的节点
-
p=p->next;
-
++counter2;
-
}
-
//下面的cout语句较为拉杂,目的是为了更详细的在屏幕上显示每一步结果,在简化版中将会使用直接打印出列序列的语句
-
cout<<"第"<<counter1<<"个出列的节点是链表上第"<<p->next->serial<<"个节点。"<<endl;
-
m=p->next->password;//在节点删除之前将该节点的密码赋给m,用以执行下次循环
-
p->next=p->next->next;//删除该节点
-
--n;//链表长度自减一
-
++counter1;//计数器累进
-
}//while语句结束,当不满足循环条件时跳出
-
//由于是命令行程序,故使用下面语句防止结果显示后程序马上退出造成看不见结果
-
cout<<"按任意键退出本程序 :) ";
-
getch();//暂停
-
}//整个程序到此完毕
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
简化版
-
//Joseph Ring 2005 Lite! 简体中文正式版
-
//解决约瑟夫环问题
-
//CopyRight 2005 09 23 of Empire_Lenin @ USTC EEIS
-
//预编译命令
-
#include "iostream.h"//c++程序的标准输入输出头文件
-
#include "conio.h"//程序最后用到getch();语句,这是它的头文件
-
//定义单链存储结构
-
typedef struct LNode{
-
unsigned long serial;//各节点序号,用于输出出列顺序
-
unsigned long password;//各节点密码
-
struct LNode *next;//链表指针
-
}LNode,*LinkList;
-
//约瑟夫环主程序
-
void main()
-
{
-
unsigned long m,n,i; //定义初始报数上限值m、人数n、循环控制变量i为无符号长整型变量
-
cout<<"~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"<<endl;
-
cout<<" Joseph Ring 2005 Lite! 简体中文正式版"<<endl;
-
cout<<" CopyRight 2005 09 23 of Empire_Lenin @ USTC EEIS"<<endl;
-
cout<<"~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"<<endl<<endl;
-
cout<<"输入随机数m(正整数):";
-
cin>>m;
-
cout<<"输入人数n:";
-
cin>>n;
-
cout<<"输入各人的密码(密码为正整数,回车确认每一个输入,空格亦可):"<<endl;
-
LinkList p,head;//定义指针变量p和头指针head
-
head=new LNode;//开辟头结点,本问题亦可不设头结点,此处为了更容易实现本思想,故设之
-
head->serial=0;//以下两行对头结点元素赋值,头结点元素值无意义,赋值为了保证程序的健壮
-
head->next=0;
-
p=head;//定义指针变量p初始为指向头结点
-
LinkList s;//定义指针变量s
-
//初始化单向循环链表
-
for(i=1;i<=n;i++)
-
{
-
s=new LNode;//开辟新节点,并将新节点的地址赋给LNode型指针变量s
-
s->serial=i;//对新开辟的节点的序号元素赋值
-
cin>>s->password;//输入新开辟节点的密码
-
p->next=s; //新节点链接入链表
-
p=p->next; //指针变量p前进
-
}
-
p->next=head->next;//将表尾节点链接到表头,完成单向循环链表的建立
-
//链表初始化完成,下面输出出列节点序列
-
cout<<endl<<"建立链表成功,出列顺序如下 :"<<endl<<endl;
-
p=head;//置LNode型指针变量于头结点处
-
unsigned long counter1=1;//计数器,用于显示节点出列序号
-
//下面程序段以一个while嵌套语句完成按要求寻找节点、删除、返回数据等操作
-
//此种写法思路简洁明确,易读性高,完全优于用数组画蛇添足
-
while(n!=0){//节点出列完也就是链表长度递减到0之前,一直执行下述循环
-
while(m>n) m=m-n;//如果m值大于链表长度,求出在链表长度范围内的m值
-
unsigned long counter2=1;//计数器,用于标记和m值匹配的、指针变量p的移动步数
-
while(m!=counter2){//找到和m值匹配的节点
-
p=p->next;
-
++counter2;
-
}
-
//此为简化版,使用直接打印出列序列的语句
-
if(counter1>=10&&counter1%10==0) cout<<endl;//每输出十个节点换行
-
else cout<<p->next->serial<<" ";//输出出列节点序号
-
m=p->next->password;//在节点删除之前将该节点的密码赋给m,用以执行下次循环
-
p->next=p->next->next;//删除该节点
-
--n;//链表长度自减一
-
++counter1;//计数器累进
-
}//while语句结束,当不满足循环条件时跳出
-
//由于是命令行程序,故使用下面语句防止结果显示后程序马上退出造成看不见结果
-
cout<<endl<<"按任意键退出本程序 :) ";
-
getch();//暂停
-
}//整个程序到此完毕
下面是俺给出的一个验证素数的程序
-
//CopyRight 2005 09 22 of Empire_Lenin @ USTC EEIS
-
//检验一个数是不是素数
-
//素数是除了0和1及其自身外不能被别的任何正数整除的数
-
#include "conio.h"
-
#include "math.h"
-
#include "iostream.h"
-
void main()
-
{
-
unsigned long n;
-
unsigned long i;
-
cout<<"~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"<<endl<<endl;
-
cout<<" 本程序用于判断一个数是不是素数\n";
-
cout<<" CopyRight 2005 09 22 of Empire_Lenin @ USTC EEIS\n\n";
-
cout<<"~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"<<endl<<endl;
-
cout<<"请输入一个正整数:";
-
cin>>n;
-
cout<<endl;
-
for(i=2;i<=sqrt(n);++i){
-
if(n%i!=0) continue;
-
else cout<<"这个数不是素数.\n\n"<<"原因是 "<<i<<"*"<<n/i<<"="<<n<<endl<<endl;break;
-
}
-
if(i>sqrt(n)) cout<<"这是个素数.\n\n";
-
getch();
-
}