題目:
Josephus問題的定義如下:假設n個人排成環形,且有以正整數m<=n。從某個制定的人開始,沿環報數,每遇到第m個人就讓其出列,且報數進行下去。這個過程一直進行到所有人都出列為止。每個人出列的次序定義了整數1,2,...,n的(n, m)-Josephus排列。例如,(7,3)-Josephus排列為<3,6,2,7,5,1,4>。
a)假設m為整數。請描述一個O(n)時間的演算法,使之對給定的整數n,輸出(n, m)-Josephus排列。
b)假設m不是個常數。請描述一個O(nlgn)時間的演算法,使給定的整數n和m,輸出(n, m)-Josephus排列。
思考:
利用14.1中的動態順序統計,假設剛剛刪除的是剩餘點中的第start個點(初始時為0),此時還剩下t個點,那麼下一個要刪除的是剩餘點的第(start+m-1)%t個點
步驟1:基礎資料結構
紅/黑樹狀結構
步驟2:附加資訊
size[x]:以x為根的子樹的(內部)結點數(包括x本身),即子樹的大小。size[nil[T]]=0
步驟3:對資訊的維護
size[x] = size[left[x]] + size[right[x]] + 1
插入操作:從插入結點到根結點都要更新O(lgn)
旋轉操作:只需要更新旋轉軸上的兩個點O(1)
刪除操作:從刪除結點的父結點開始到根結點都要更新O(lgn)
代碼:
#include <iostream>using namespace std;#define BLACK 0#define RED 1//紅/黑樹狀結構結點結構struct node{//紅/黑樹狀結構的基礎結構node *left;node *right;node *parent;int key;bool color;int size;//以x為根的子樹的(內部)結點數(包括x本身),即子樹的大小node(node *init, int k):left(init),right(init),parent(init),key(k),color(BLACK),size(1){}};//順序統計量樹結構struct Os_Tree{node *root;//根結點node *nil;//哨兵Os_Tree(){nil = new node(NULL, -1);nil->size = 0;root = nil;};};//對附加資訊的維護void Maintaining(node *x){while(x->key >= 0){x->size = x->left->size + x->right->size + 1;x = x->parent;}}//左旋,令y = x->right, 左旋是以x和y之間的鏈為支軸進行旋轉//涉及到的結點包括:x,y,y->left,令node={p,l,r},具體變化如下://x={x->parent,x->left,y}變為{y,x->left,y->left}//y={x,y->left,y->right}變為{x->parent,x,y->right}//y->left={y,y->left->left,y->left->right}變為{x,y->left->left,y->left->right}void Left_Rotate(Os_Tree *T, node *x){//令y = x->rightnode *y = x->right;//按照上面的方式修改三個結點的指標,注意修改指標的順序x->right = y->left;if(y->left != T->nil)y->left->parent = x;y->parent = x->parent;if(x->parent == T->nil)//特殊情況:x是根結點T->root = y;else if(x == x->parent->left)x->parent->left = y;else x->parent->right = y;y->left = x;x->parent = y;//對附加資訊的維護y->size = x->size;x->size = x->left->size + x->right->size + 1;}//右旋,令y = x->left, 左旋是以x和y之間的鏈為支軸進行旋轉//旋轉過程與上文類似void Right_Rotate(Os_Tree *T, node *x){node *y = x->left;x->left = y->right;if(y->right != T->nil)y->right->parent = x;y->parent = x->parent;if(x->parent == T->nil)T->root = y;else if(x == x->parent->right)x->parent->right = y;else x->parent->left = y;y->right = x;x->parent = y;//對附加資訊的維護y->size = x->size;x->size = x->left->size + x->right->size + 1;}//紅/黑樹狀結構調整void Os_Tree_Insert_Fixup(Os_Tree *T, node *z){node *y;//唯一需要調整的情況,就是違反性質2的時候,如果不違反性質2,調整結束while(z->parent->color == RED){//parent[z]是左孩子時,有三種情況if(z->parent == z->parent->parent->left){//令y是z的叔結點y = z->parent->parent->right;//第一種情況,z的叔叔y是紅色的if(y->color == RED){//將parent[z]和y都著為黑色以解決z和parent[z]都是紅色的問題z->parent->color = BLACK;y->color = BLACK;//將parent[parent[z]]著為紅色以保持性質5z->parent->parent->color = RED;//把parent[parent[z]]當作新增的結點z來重複while迴圈z = z->parent->parent;}else{//第二種情況:z的叔叔是黑色的,且z是右孩子if(z == z->parent->right){//對parent[z]左旋,轉為第三種情況z = z->parent;Left_Rotate(T, z);}//第三種情況:z的叔叔是黑色的,且z是左孩子//交換parent[z]和parent[parent[z]]的顏色,並右旋z->parent->color = BLACK;z->parent->parent->color = RED;Right_Rotate(T, z->parent->parent);}}//parent[z]是右孩子時,有三種情況,與上面類似else if(z->parent == z->parent->parent->right){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(T, z);}z->parent->color = BLACK;z->parent->parent->color = RED;Left_Rotate(T, z->parent->parent);}}}//根結點置為黑色T->root->color = BLACK;}//插入一個結點void Os_Tree_Insert(Os_Tree *T, node *z){node *y = T->nil, *x = T->root;//找到應該插入的位置,與二叉尋找樹的插入相同while(x != T->nil){y = x;if(z->key < x->key)x = x->left;else if(z->key > x->key)x = x->right;}z->parent = y;if(y == T->nil)T->root = z;else if(z->key < y->key)y->left = z;elsey->right = z;z->left = T->nil;z->right = T->nil;//將新插入的結點轉為紅色z->color = RED;//從新插入的結點開始,向上調整Os_Tree_Insert_Fixup(T, z);Maintaining(z);}//對樹進行調整,x指向一個紅黑結點,調整的過程是將額外的黑色沿樹上移void Os_Tree_Delete_Fixup(Os_Tree *T, node *x){node *w;//如果這個額外的黑色在一個根結點或一個紅結點上,結點會吸收額外的黑色,成為一個黑色的結點while(x != T->root && x->color == BLACK){//若x是其父的左結點(右結點的情況相對應)if(x == x->parent->left){//令w為x的兄弟,根據w的不同,分為三種情況來處理//執行刪除操作前x肯定是沒有兄弟的,執行刪除操作後x肯定是有兄弟的w = x->parent->right;//第一種情況:w是紅色的if(w->color == RED){//改變w和parent[x]的顏色w->color = BLACK;x->parent->color = RED;//對parent[x]進行一次左旋Left_Rotate(T, x->parent);//令w為x的新兄弟w = x->parent->right;//轉為2.3.4三種情況之一}//第二情況:w為黑色,w的兩個孩子也都是黑色if(w->left->color == BLACK && w->right->color == BLACK){//去掉w和x的黑色//w只有一層黑色,去掉變為紅色,x有多餘的一層黑色,去掉後恢複原來顏色w->color = RED;//在parent[x]上補一層黑色x = x->parent;//現在新x上有個額外的黑色,轉入for迴圈繼續處理}//第三種情況,w是黑色的,w->left是紅色的,w->right是黑色的else{if(w->right->color == BLACK){//改變w和left[x]的顏色w->left->color = BLACK;w->color = RED;//對w進行一次右旋Right_Rotate(T, w);//令w為x的新兄弟w = x->parent->right;//此時轉變為第四種情況}//第四種情況:w是黑色的,w->left是黑色的,w->right是紅色的//修改w和parent[x]的顏色w->color =x->parent->color;x->parent->color = BLACK;w->right->color = BLACK;//對parent[x]進行一次左旋Left_Rotate(T, x->parent);//此時調整已經結束,將x置為根結點是為了結束迴圈x = T->root;}}//若x是其父的左結點(右結點的情況相對應)else if(x == x->parent->right){//令w為x的兄弟,根據w的不同,分為三種情況來處理//執行刪除操作前x肯定是沒有兄弟的,執行刪除操作後x肯定是有兄弟的w = x->parent->left;//第一種情況:w是紅色的if(w->color == RED){//改變w和parent[x]的顏色w->color = BLACK;x->parent->color = RED;//對parent[x]進行一次左旋Right_Rotate(T, x->parent);//令w為x的新兄弟w = x->parent->left;//轉為2.3.4三種情況之一}//第二情況:w為黑色,w的兩個孩子也都是黑色if(w->right->color == BLACK && w->left->color == BLACK){//去掉w和x的黑色//w只有一層黑色,去掉變為紅色,x有多餘的一層黑色,去掉後恢複原來顏色w->color = RED;//在parent[x]上補一層黑色x = x->parent;//現在新x上有個額外的黑色,轉入for迴圈繼續處理}//第三種情況,w是黑色的,w->right是紅色的,w->left是黑色的else{if(w->left->color == BLACK){//改變w和right[x]的顏色w->right->color = BLACK;w->color = RED;//對w進行一次右旋Left_Rotate(T, w);//令w為x的新兄弟w = x->parent->left;//此時轉變為第四種情況}//第四種情況:w是黑色的,w->right是黑色的,w->left是紅色的//修改w和parent[x]的顏色w->color =x->parent->color;x->parent->color = BLACK;w->left->color = BLACK;//對parent[x]進行一次左旋Right_Rotate(T, x->parent);//此時調整已經結束,將x置為根結點是為了結束迴圈x = T->root;}}}//吸收了額外的黑色x->color = BLACK;}//找最小值 node *Os_Tree_Minimum(Os_Tree *T, node *x) { //只要有比當前結點小的結點 while(x->left != T->nil) x = x->left; return x; } //尋找中序遍曆下x結點的後繼,後繼是大於key[x]的最小的結點 node *Os_Tree_Successor(Os_Tree *T, node *x) { //如果有右孩子 if(x->right != T->nil) //右子樹中的最小值 return Os_Tree_Minimum(T, x->right); //如果x的右子樹為空白且x有後繼y,那麼y是x的最低祖先結點,且y的左兒子也是 node *y = x->parent; while(y != NULL && x == y->right) { x = y; y = y->parent; } return y; } //遞迴地查詢二叉尋找樹 node *Os_Tree_Search(node *x, int k) { //找到葉子結點了還沒找到,或當前結點是所尋找的結點 if(x->key == -1 || k == x->key) return x; //所尋找的結點位於當前結點的左子樹 if(k < x->key) return Os_Tree_Search(x->left, k); //所尋找的結點位於當前結點的左子樹 else return Os_Tree_Search(x->right, k); } //紅/黑樹狀結構的刪除node *Os_Tree_Delete(Os_Tree *T, node *z){//找到結點的位置並刪除,這一部分與二叉尋找樹的刪除相同node *x, *y;if(z->left == T->nil || z->right == T->nil)y = z;else y = Os_Tree_Successor(T, z);if(y->left != T->nil)x = y->left;else x = y->right;x->parent = y->parent;if(y->parent == T->nil)T->root = x;else if(y == y->parent->left)y->parent->left = x;elsey->parent->right = x;Maintaining(y->parent);if(y != z){z->key = y->key;Maintaining(z);}//如果被刪除的結點是黑色的,則需要調整if(y->color == BLACK)Os_Tree_Delete_Fixup(T, x);return y;}//尋找以x為根結點的樹中第i大的結點 node *Os_Tree_Select(node *x, int i) { //令x左子樹中點的個數為r-1, int r = x->left->size +1; //那麼x是x樹中第r大的結點 if(r == i) return x; //第i大的元素在x->left中 else if(i < r) return Os_Tree_Select(x->left, i); //第i大的元素在x->right中 else return Os_Tree_Select(x->right, i - r); } int main(){//產生一棵動態順序統計樹Os_Tree *T = new Os_Tree;int m, n, i;while(cin>>n>>m){//將1.,n依次插入到樹中for(i = 1; i <= n; i++){node *z = new node(T->nil, i);Os_Tree_Insert(T, z);}int t = n, start = 0;//還有剩餘結點while(t){//計算下一個要刪除的結點在剩餘結點中的位置start = (start + m - 1) % t;//找到這個結點node *ret = Os_Tree_Select(T->root, start+1);cout<<ret->key<<' ';//刪除這個結點Os_Tree_Delete(T, ret);t--;}cout<<endl;}return 0;}