循环链表的三个经典例子

一.约瑟夫问题

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

已知总人数N与报数m

单循环链表实现:创建一循环链表,尾指针指向第一个结点,去掉头结点。然后循环报数下去,当只剩下最后一人时,即当前指针的后继也是该指针时,退出循环。在循环过程中,每报到m时删除对应的结点,重新报数。

void CL::Josephus(int N,int m)
{
    Node *p=r;
    Node *s;
    while (p->next!=p)
    {
        int k=1;
        while (k!=m)//没数到m时就一直轮下去,找到k=m时的上一结点
        {
            p=p->next;
            k++;
        }
        s=p->next;//k=m对应的结点
        p->next=s->next;
        cout<<s->date<<"->";
        delete s;
    }
    cout<<p->date<<endl;
}

二.魔术师发牌问题

一位魔术师掏出一叠扑克牌,魔术师取出其中13张黑桃,洗好后,把牌面朝下。
说:“我不看牌,只数一数就能知道每张牌是什么?”魔术师口中念一,将第一张牌翻过来看正好是A;
魔术师将黑桃A放到桌上,继续数手里的余牌,
第二次数1,2,将第一张牌放到这叠牌的下面,将第二张牌翻开,正好是黑桃2,也把它放在桌子上。
第三次数1,2,3,前面二张牌放到这叠牌的下面,取出第三张牌,正好是黑桃3,这样依次将13张牌翻出,全部都准确无误。
求解:魔术师手中牌的原始顺序是什么样子的

从1开始,每次数的数比上一个数大1,数的数是几,就翻第几张牌。由于被翻了的牌不在扑克牌之中了,所以链表实现时还要忽略已经赋值了的结点。

void CL::Magician()
{
    Init();//清零
    Node *p=r->next;//从头结点开始
    p->date=1;//初始化头结点的date为1
    int count=2;//已经初始化一个了,所以从2开始数
    while (count<=13)//只有13张牌
    {
        for (int i=1;i<=count;i++)
        {
            p=p->next;
            if (p->date!=0)//已经赋值了的要忽略
                i--;
        }
        p->date=count;
        count++;
    }
    Show();
}

三.拉丁方阵

拉丁方阵是一种n×n的方阵,方阵中恰有n种不同的元素,每种元素恰有n个,而且每种元素在一行和一列中 恰好出现一次。
著名数学家和物理学家欧拉使用拉丁字母来作为拉丁方阵里元素的符号,拉丁方阵因此而得名。
比如:
1 2 3
2 3 1
3 1 2

问:构造一个N阶方阵

一共循环N次,第N次从第N个节点开始再循环N次。

void CL::NPrint(int N)
{
    Node *p=r->next;
    for (int i=1;i<=N;i++)
    {
        for (int j=1;j<=N;j++)
        {
            cout<<p->date<<"   ";
            p=p->next;
        }
        cout<<endl;
        p=p->next;
    }
}

三个问题汇合在一个程序的代码如下:

#include <iostream>

using namespace std;

struct Node{
    int date;
    Node *next;
    };
class CL{
private:
    Node *r;
public:
    CL(int N);
    ~CL();
    void Josephus(int N,int m);
    void Magician();
    void Init();
    void NPrint(int N);
    void Show();
    };
CL::CL(int N)
{
   Node *head,*p,*s;
   head=new Node;
   head->next=NULL;
   p=head;
   for (int i=0;i<N;i++)
   {
       s=new Node;
       s->date=i+1;
       s->next=p->next;
       p->next=s;
       p=s;
   }
   p->next=head->next;//指向第一个结点
   r=p;//链表的尾指针
   delete head;//删掉表头
}
CL::~CL()
{
    Node *p,*s;
    p=r->next;//指向头结点
    while (p!=r)
    {
        s=p;
        p=p->next;
        delete s;
    }
    delete p;
    cout<<"已删除整个链表"<<endl;
}
void CL::Josephus(int N,int m)
{
    Node *p=r;
    Node *s;
    while (p->next!=p)
    {
        int k=1;
        while (k!=m)//没数到m时就一直轮下去,找到k=m时的上一结点
        {
            p=p->next;
            k++;
        }
        s=p->next;//k=m对应的结点
        p->next=s->next;
        cout<<s->date<<"->";
        delete s;
    }
    cout<<p->date<<endl;
}
void CL::Init()
{
    Node *p=r->next;
    while (p!=r)
    {
        p->date=0;
        p=p->next;
    }
    p->date=0;
}
void CL::Magician()
{
    Init();//清零
    Node *p=r->next;
    p->date=1;//初始化第一个结点为1
    int count=2;//已经初始化一个了,所以从2开始数
    while (count<=13)
    {
        for (int i=1;i<=count;i++)
        {
            p=p->next;
            if (p->date!=0)
                i--;
        }
        p->date=count;
        count++;
    }
    Show();
}
void CL::Show()
{
    Node *p=r->next;
    while (p!=r)
    {
        cout<<p->date<<"  ";
        p=p->next;
    }
    cout<<p->date<<endl;
}
void CL::NPrint(int N)
{
    Node *p=r->next;
    for (int i=1;i<=N;i++)
    {
        for (int j=1;j<=N;j++)
        {
            cout<<p->date<<"   ";
            p=p->next;
        }
        cout<<endl;
        p=p->next;
    }
}
int main()
{
    CL La(41);
    CL Lb(13);
    CL Lc(5);
    La.Josephus(41,3);
    Lb.Magician();
    Lc.NPrint(5);
    return 0;
}

运行结果

注:不知道为什么,La的析构函数不能正常调用,所以运行结果只有两行删除链表的信息

《循环链表的三个经典例子》

    原文作者:魔术师发牌问题
    原文地址: https://blog.csdn.net/sjhdxpz/article/details/81133748
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞