Josephus排列

思考与分析:

对于m为常数,可以用循环链表,用head,tail标志头尾指针使其易于表示循环结构。循环输出后删除结点n次,每次外层循环时,内层都固定循环m次。所以运行时间为O(mn)=O(n).

对于m为非常数。可以用顺序统计树,用size属性记录每个结点在当前树中所在的位置。经过一个取余过程,每次都能正确找到并输出删除每个结点,在经过n次循环,每次循环都要找到一个结点输出并删除它,所以每次循环都要花费O(lgn)时间。总的运行时间是O(nlgn).

代码如下:

m为常数时:

#include <iostream>
using namespace std;
#define n 10
#define m 3
#define LEN sizeof(struct circular_list)
struct circular_list
{
   int key;
   struct circular_list* next;
};
struct circular_list*tail=NULL;
struct circular_list*Insert(struct circular_list*&head,int k)
{
	struct circular_list*z=new struct circular_list[LEN];
	z->key=k;
    if (head==NULL)
    {
		head=tail=z;
		head->next=tail;
    }
	else
	{
		tail->next=z;
		z->next=head;
		tail=z;
	}
	return head;
}
struct circular_list* Delete(struct circular_list*&head,struct circular_list*z)
{
   struct circular_list*p=head;
   while (p->next!=z)
   {
	   p=p->next;
   }
   if (head==tail)
   {
	   p->next=NULL;
   }
   else
   {
	   if (head==p->next)
	   {
	     head=p->next->next;
	   }
	   else if (tail==p->next)
	   {
		 tail=p;
	   }
	   p->next=p->next->next;
   }
   return p->next;
}
void n_m_Josephus(struct circular_list*&head)
{
   struct circular_list*p=head;
   while (p)
   {
	   int i=0;
	   while (i!=m-1)
	   {
		   p=p->next;
		   i++;
	   }
	   struct circular_list*z=p;
	   cout<<z->key<<" ";
       p=Delete(head,z);
   }
}
void main()
{
    int a[n]={0};
	for (int i=0;i<n;i++)
	{
		a[i]=i+1;
	}
	int j=0;
	struct circular_list*head=NULL;
    while (j!=n)
    {
		head=Insert(head,a[j]);
		j++;;
    }
    struct circular_list*p=head;
	do
	{
		cout<<p->key;
		p=p->next;
	} while (p!=head);
	cout<<endl;
	n_m_Josephus(head);
}

m不是常数时

#include <iostream>
#include <time.h>
using namespace std;
#define BLACK 0
#define RED 1
#define Nil -1
#define LEN sizeof(struct OS_Tree)
struct OS_Tree
{
   struct OS_Tree*right,*left;
   struct OS_Tree*parent;
   int key,color,size;//size表示子树的结点数。
};
struct OS_Tree*root=NULL,*nil=NULL;
void LEFT_ROTATE(struct OS_Tree*x)
{//左旋转:分三个步骤①②③来叙述旋转代码的。
	struct OS_Tree*y=x->right;//设置y结点。
	x->right=y->left;//本行代码以及下面的if结构表达的是“y的左孩子成为x的右孩子”。①
	if(y->left!=nil)
	{
       y->left->parent=x;
	}
	y->parent=x->parent;//本行代码以及下面的if-else结构表达的过程是“y成为该子树新的根”。②
	if(x->parent==nil)
	{
       root=y;
	}
	else if(x==x->parent->left)
	{
       x->parent->left=y;
	}
	else x->parent->right=y;
	y->left=x;//本行代码以及下面一行都表达了“x成为y的左孩子”。③
	x->parent=y;
    y->size = x->size;  //对附加信息的维护
    x->size = x->left->size + x->right->size +1;
}
void RIGHT_ROTATE(struct OS_Tree*x)
{//右旋转:分三个步骤①②③来叙述旋转代码的。
	struct OS_Tree*y=x->left;//设置y结点。
	x->left=y->right;//本行代码以及下面的if结构表达的是“y的左孩子成为x的右孩子”。①
	if(y->right!=nil)
	{
		y->right->parent=x;
	}
	y->parent=x->parent;//本行代码以及下面的if-else结构表达的过程是“y成为该子树新的根”。②
	if(x->parent==nil)
	{
		root=y;
	}
	else if(x==x->parent->right)
	{
		x->parent->right=y;
	}
	else x->parent->left=y;
	y->right=x;//本行代码以及下面一行都表达了“x成为y的左孩子”。③
	x->parent=y;
	y->size = x->size;  //对附加信息的维护
    x->size = x->left->size + x->right->size +1;
}
void RB_INSERT_FIXUP(struct OS_Tree*z)
{
   while (z->parent->color==RED)
   {
	   if (z->parent==z->parent->parent->left)
	   {
		   struct OS_Tree*y=z->parent->parent->right;//叔结点
		   if (y->color==RED)//情况一:叔结点为红色
		   {//给p1,y,p2着色以保持性质5。并且解决了z的父结点和z都是红色结点问题
			   z->parent->color=BLACK;
			   y->color=BLACK;
			   z->parent->parent->color=RED;
			   z=z->parent->parent;//把z的祖父结点当成新结点z进入下一次循环
		   }
		   else
		   {
			   if (z==z->parent->right)//情况二:检查z是否是一个右孩子且叔结点为黑色,前提是p1结点不是叶子结点
			   {//使用一个左旋让情况2转变为情况3
				   z=z->parent;
				   LEFT_ROTATE(z);//由于进入if语句后可知旋转结点不可能是叶子结点,这样就不用判断z是否是叶子结点了。
			   }
               z->parent->color=BLACK;//情况三:是z是一个左孩子且叔结点为黑色,改变z的父和祖父结点颜色并做一次右旋,以保持性质5
			   z->parent->parent->color=RED;
			   RIGHT_ROTATE(z->parent->parent);//由于p2可能是叶子结点,所以最好还是用一个if判断
		   }
	   }
	   else//下面else分支类似于上面,注意到else分支的情况2和情况3所做旋转正好是if分支情况的逆。
	   {
		   struct OS_Tree*y=z->parent->parent->left;
		   if (y->color==RED)
		   {
			   z->parent->color=BLACK;
			   y->color=BLACK;
			   z->parent->parent->color=RED;
			   z=z->parent->parent;
		   }
		   else
		   {
			   if (z==z->parent->left)
			   {
				   z=z->parent;
				   RIGHT_ROTATE(z);
			   }
               z->parent->color=BLACK;
			   z->parent->parent->color=RED;
			   LEFT_ROTATE(z->parent->parent);
		   }
	   }
   }
   root->color=BLACK;//最后给根结点着为黑色。
}
void RB_INSERT(struct OS_Tree*z)
{
	struct OS_Tree*y=nil;
	struct OS_Tree*x=root;
	while (x!=nil)
	{
		x->size++;
		y=x;
		if (z->key<x->key)
		{
			x=x->left;
		}
		else x=x->right;
	}
	z->parent=y;
	if (y==nil)
	{
		root=z;
	}
	else if(z->key<y->key)
	{
		y->left=z;
	}
	else y->right=z;
	z->left=nil;//给插入结点左右孩子赋值为空。
	z->right=nil;
	z->color=RED;//给插入结点着为红色。
	z->size=1;
	z->left->size=0;
	z->right->size=0;
	RB_INSERT_FIXUP(z);
}
void RB_TRANSPLANT(struct OS_Tree*u,struct OS_Tree*v)
{
	if (u->parent==nil)
		root=v;
	else if(u==u->parent->left)
		u->parent->left=v;
	else u->parent->right=v;
	v->parent=u->parent;
}
struct OS_Tree*TREE_MINIMUM(struct OS_Tree*x)//求二叉查找树当前结点最小值
{
	while (x!=nil&&x->left!=nil)
	{
		x=x->left;
	}
	return x;
}
struct OS_Tree*TREE_MAXINUM(struct OS_Tree*x)//求二叉查找树当前结点最大值
{
	while (x!=nil&&x->right!=nil)
	{
		x=x->right;
	}
	return x;
}
struct OS_Tree*TREE_PREDECESSOR(struct OS_Tree*x)//查找二叉查找树的前驱
{
	if (x->left!=nil)
	{
		return TREE_MAXINUM(x->left);
	}
	struct OS_Tree*y=x->parent;
	while (y!=nil&&x==y->left)
	{
		x=y;
		y=y->parent;
	}
    return y;
}
struct OS_Tree*TREE_SUCCESSOR(struct OS_Tree*x)//查找二叉查找树的后继
{
	if (x->right!=nil)
	{
		return TREE_MINIMUM(x->right);
	}
	struct OS_Tree*y=x->parent;
	while (y!=nil&&x==y->right)
	{
		x=y;
		y=y->parent;
	}
	return y;
}
//非递归版本的二叉查找树查找函数
struct OS_Tree*ITERATIVE_TREE_SEARCH(struct OS_Tree*x,int k)
{
	while (x!=nil&&k!=x->key)
	{
		if (k<x->key)
		{
			x=x->left;
		}
		else x=x->right;
	}
	return x;
}
void RB_DELETE_FIXUP(struct OS_Tree*x)
{

	 struct OS_Tree*w=NULL;//w是x的兄弟结点
     while (x!=root&&x->color==BLACK)//如果x是黑色并且不是根结点,才进行循环。
     {//x是一个具有双重颜色的结点,调整的目的是把x的黑色属性向上移动。
		 if (x==x->parent->left)
		 {
			 w=x->parent->right;
			 if (w->color==RED)//情况一:x的兄弟结点w是红色的。
			 {//改变w和x.p的颜色+一次旋转使其变为情况二,三,四。
				 w->color=BLACK;
				 x->parent->color=RED;
				 LEFT_ROTATE(x->parent);
				 w=x->parent->right;
			 }
			 if (w->left->color==BLACK&&w->right->color==BLACK)//情况二:x的兄弟结点w是黑色的,而且w的两个子节点都是黑色。
			 {
				 w->color=RED;//从x和w上去掉一重黑色。x还是黑色,而w变为红色。
				 x=x->parent;//x结点向上移动成为新的待调整结点。
			 }
			 else
			 {
				 if (w->right->color==BLACK)//情况三:x的兄弟结点w是黑色的,w的左孩子是红色的,w的右孩子是黑色的。
				 {//交换w和w.left的颜色+旋转使其转变为情况四。
					 w->left->color=BLACK;
					 w->color=RED;
					 RIGHT_ROTATE(w);
					 w=x->parent->right;
				 }
				 w->color=x->parent->color;//以下是情况四:x的兄弟结点w是黑色的,且w的右孩子是红色的。
				 x->parent->color=BLACK;//置x.p和w.right为黑色+旋转使其去掉x的额外黑色。
				 w->right->color=BLACK;
				 LEFT_ROTATE(x->parent);
				 x=root;//x成为根结点,结束循环。
			 }
		 }
		 else//以下和上面的if分支类似,不做累述。
		 {
             w=x->parent->left;
			 if (w->color==RED)
			 {
				 w->color=BLACK;
				 x->parent->color=RED;
				 RIGHT_ROTATE(x->parent);
				 w=x->parent->left;
			 }
			 if (w->left->color==BLACK&&w->right->color==BLACK)
			 {
				 w->color=RED;
				 x=x->parent;
			 }
			 else
			 {
				 if (w->left->color==BLACK)
				 {
					 w->right->color=BLACK;
					 w->color=RED;
					 LEFT_ROTATE(w);
					 w=x->parent->left;
				 }
				 w->color=x->parent->color;
				 x->parent->color=BLACK;
				 w->left->color=BLACK;
				 RIGHT_ROTATE(x->parent);
				 x=root;
			 }
		 }
     }x->color=BLACK;
}
void RB_DELETE(struct OS_Tree*z)
{
    struct OS_Tree*y=z,*x;//y为待删除或待移动结点
	int y_original_color=y->color;//保存y的原始颜色,为做最后的调整做准备。
	struct OS_Tree*t=z->parent;
	if (z->left==nil)
	{
		while (t!=nil)
		{
			t->size--;
			t=t->parent;
		}
		x=z->right;//x指向y的唯一子结点或者是叶子结点,保存x的踪迹使其移动到y的原始位置上
		RB_TRANSPLANT(z,z->right);//把以z.right为根的子树替换以z为根的子树。
	}
	else if (z->right==nil)
	{
		while (t!=nil)
		{
			t->size--;
			t=t->parent;
		}
		x=z->left;//x指向y的唯一子结点或者是叶子结点,保存x的踪迹使其移动到y的原始位置上
		RB_TRANSPLANT(z,z->left);//把以z.left为根的子树替换以z为根的子树。
	}
	else
	{
		y=TREE_MINIMUM(z->right);//找到z.right的后继。
		struct OS_Tree*t=y->parent;
		y->size=z->size-1;//y替换z原来的位置,所以size属性在待删除结点z基础上-1
		while (t!=nil)
		{
			t->size--;
			t=t->parent;
		}
        y_original_color=y->color;//y的新的原始结点被重置。
		x=y->right;//x指向y的唯一子结点或者是叶子结点,保存x的踪迹使其移动到y的原始位置上
		if (y->parent==z)
		{
			x->parent=y;//由于z的父结点是要删除的结点,所以不能指向它,于是指向y
		}
		else
		{
			RB_TRANSPLANT(y,y->right);//把以y.right为根的子树替换以y为根的子树。
			y->right=z->right;
			y->right->parent=y;
		}
		RB_TRANSPLANT(z,y);//把以y为根的子树替换以z为根的子树。
		y->left=z->left;
		y->left->parent=y;
		y->color=z->color;//把已经删除的结点颜色赋值给y,保证了y以上的树结构红黑性质不变。
	}
	if(y_original_color==BLACK) //y的原始颜色为黑色,说明需要调整红黑颜色。
		RB_DELETE_FIXUP(x);
}
//中序遍历
void InOderTraverse(struct OS_Tree *p)
{
    if (p!=nil)
	{
		InOderTraverse(p->left);
		cout<<p->key<<" "<<p->color<<" "<<"秩:"<<p->size<<endl;
		InOderTraverse(p->right);
	}
}
struct OS_Tree*OS_SELECT(struct OS_Tree*&x,int i)//查找顺序统计树给定秩的元素
{
   int r=x->left->size+1;
   if (i==r)
   {
	   return x;
   }
   else if (i<r)
   {
	   return OS_SELECT(x->left,i);
   }
   else return OS_SELECT(x->right,i-r);
}
int ITERATIVE_OS_RANK(struct OS_Tree*&T,struct OS_Tree*x)//确定顺序统计树的秩
{
   int r=x->left->size+1;
   struct OS_Tree*y=x;
   while (y!=root)
   {
	   if (y==y->parent->right)
	   {
		   r=r+y->parent->left->size+1;
	   }
	   y=y->parent;
   }
   return r;
}
void n_m_Josephus(struct OS_Tree*x,int m,int n)
{
	x=root;
	int j=0,i=n,t=1;
	while(root!=nil)
	{
       j=(t+m-1)%i;
	   if(j==0) j=i;
	   struct OS_Tree*y1=OS_SELECT(root,j);//y1表示待输出待删除结点
	   cout<<y1->key<<" ";//输出结点
	   RB_DELETE(y1);//输出后删除掉
	   t=j;
	   i--;
	}
}
void main()
{
	srand( (unsigned)time( NULL ) );
	int m=0,n=0;
	cout<<"下面请您输入n_m_Josephus排列的m和n值"<<endl;
	cout<<"m=";
	cin>>m;
	cout<<"n=";
	cin>>n;
	int *array1=new int[n];
	for (int j=0;j<n;j++)
	{
		array1[j]=j+1;
		cout<<array1[j]<<" ";
	}
	cout<<endl;
	nil=new struct OS_Tree[LEN];
	nil->key=Nil;nil->color=BLACK;
	root=nil;
	int i=0;
	struct OS_Tree*ROOT=new struct OS_Tree[LEN];
	ROOT->key=array1[i++];
	RB_INSERT(ROOT);
	root=ROOT;
    while (i!=n)
    {
		struct OS_Tree*z=new struct OS_Tree[LEN];
		z->key=array1[i];
		RB_INSERT(z);
		i++;
    }
	InOderTraverse(root);
	cout<<"约瑟夫排列:";
	n_m_Josephus(root,m,n);
}

总结:约瑟夫排列a部分用到了第10章知识,而b部分用到了本章知识。这是对学过的知识的简单应用。感觉这个程序还是比较有趣的。

Josephus排列,布布扣,bubuko.com

时间: 2024-08-08 03:04:39

Josephus排列的相关文章

Josephus排列问题

据说著名犹太历史学家 Josephus有过以下的故事:在罗马人占领乔塔帕特後,39 个犹太人与Josephus及他的朋友躲到一个洞中,39个犹太人决定宁愿死也不要被人抓到,于是决定了一个自杀方式,41个人排成一个圆圈,由第1个人开始报数,每报数到第3人该人就必须自杀,然后再由下一个重新报数,直到所有人都自杀身亡为止. 然而Josephus 和他的朋友并不想遵从,Josephus要他的朋友先假装遵从,他将朋友与自己安排在第16个与第31个位置,于是逃过了这场死亡游戏. Python代码如下: #e

Josephus环形排列问题

题目:设n个人围坐在一圆桌周围,依次编号为1,2,...,n,从第s个人从1开始依次报数,数到m的人出列,然后从出列的下一个人重新开始报数,数到m的人又出列,…,如此反复直到只剩一人为止为止.对于任意给定的n,s和m,输出按出列次序得到的n个人员的序列. 代码如下: #include<stdio.h>int a[100]; void dele(int x,int t) //定义数组删除元素函数 { int j; if(x==t) a[x]=0; for(j=x;j<=t;j++) { a

字符串排列组合算法

第二个算法是我笔试题遇到的,当时没有做出来,在网上看到别人写的算法,感觉太精妙了,就在这里分享出来. 全排列 所谓全排列,就是打印出字符串中所有字符的所有排列.例如输入字符串abc,则打印出 a.b.c 所能排列出来的所有字符串 abc.acb.bac.bca.cab 和 cba . #include<stdio.h> #include<string.h> static int number=0; void Swap(char *a ,char *b) { char temp =

BZOJ 4517: [Sdoi2016]排列计数 错排+逆元

4517: [Sdoi2016]排列计数 Description 求有多少种长度为 n 的序列 A,满足以下条件: 1 ~ n 这 n 个数在序列中各出现了一次 若第 i 个数 A[i] 的值为 i,则称 i 是稳定的.序列恰好有 m 个数是稳定的 满足条件的序列可能很多,序列数对 10^9+7 取模. Input 第一行一个数 T,表示有 T 组数据. 接下来 T 行,每行两个整数 n.m. T=500000,n≤1000000,m≤1000000 Output 输出 T 行,每行一个数,表示

lintcode 中等题:next permutation下一个排列

题目 下一个排列 给定一个整数数组来表示排列,找出其之后的一个排列. 样例 给出排列[1,3,2,3],其下一个排列是[1,3,3,2] 给出排列[4,3,2,1],其下一个排列是[1,2,3,4] 注意 排列中可能包含重复的整数 解题 和上一题求上一个排列应该很类似 1.对这个数,先从右到左找到递增序列的前一个位置,peakInd 2.若peakInd = -1 这个数直接逆序就是答案了 3.peakInd>= 0 peakInd这个位置的所,和 peakInd 到nums.size() -1

HDU--5396(区间dp+排列组合)

做这道题的时候,想到会不会是dp,然后发现dp可做,但是一直被自己坑到死. 枚举最后合并的那个位置,然后对于加减号的,分成的前后两个部分都有不同的组合方法, (a1+a2........) +  (b1,b2.............)         对于每个a,被加b的个数的阶乘次 ,对于每个b,被加a的个数的阶乘次 减法同理 乘法特殊一点 (a1+a2........) *  (b1,b2.............)  乘法分配率,直接将两部分的总和相乘即可 想到这些还远远没有结束,因为最

字符串的排列

题目:输入一个字符串,打印出该字符串中字符的所有排列.例如输入字符串abc,则打印出由字符a.b.c所能排列出来的所有字符串abc.acb.bac.bca.cab和cba. 思路: 我们以三个字符abc为例来分析一下求字符串排列的过程.首先我们固定第一个字符a,求后面两个字符bc的排列.当两个字符bc的排列求好之后,我们把第一个字符a和后面的b交换,得到bac,接着我们固定第一个字符b,求后面两个字符ac的排列.现在是把c放到第一位置的时候了.记住前面我们已经把原先的第一个字符a和后面的b做了交

CSU 1555 Inversion Sequence 给出逆序数求排列 splay

题目链接:点击打开链接 题意: 给出逆序数的值,求原序列(一个1-N的排列) 1, 2, 0, 1, 0 表示1的逆序数是1,2的逆序数是2,3的逆序数是0··· 思路: 从最后一个数开始插,每次插到当前序列的第a[i]个数.. splay模拟 == 这个方法比较直(wu)观(nao),别的方法并没有想出来.. #include <cstdio> #include <iostream> #include <cstring> #include <queue>

排列组合

(常考)错位排列 有N封信和N个信封,每封信都不装在自己信封里的排列种数记作Dn,则 D1=0,D2=1,D3=2,D4=9,D5=44,D6=265 一.相邻问题---捆绑法 不邻问题---插空法 对于某几个元素不相邻的排列问题,可先将其他元素排好,再将不相邻元素在已排好的元素之间及两端空隙中插入即可. [例题1]一张节目表上原有3个节目,如果保持这3个节目的相对顺序不变,再添进去2个新节目,有多少种安排方法? A.20 B.12 C.6 D.4 [答案]A. [解析] 以下内容需要回复才能看