每日一题---OJ题: 环形链表的约瑟夫问题

本文介绍了如何使用环形链表解决约瑟夫问题,通过创建不带头的单向循环链表,按照给定的规则每隔m个节点删除一个节点,直到剩下最后一个节点。关键步骤包括创建链表、遍历节点并执行删除操作。
摘要由CSDN通过智能技术生成

嗨! 小伙伴们,我们又见面啦,今天我们来看看这道题:  环形链表的约瑟夫问题

emmm,题目看似不太好理解,咱们画个图吧!

第一次:   从1号位置开始报数,从1开始,编号为2的人离开,1号->1,2号->2,2号离开。

第二次: 再从剩下的1号,3号,4号,5号里面报数,从3号开始报数,3号->1,4号->2,4号离开。

 

第三次:从剩下的1号,3号,5号里面报数,从5号开始报数,5号->1, 1号->2, 1号离开。

第四次:从剩下的3号,5号里面报数,从3号开始报数,3号->1,5号->2, 5号离开,最后留下来的是3号。

好的,图画清晰了,该怎么做呢?

思路一: 我们可以根据n来创建不带头单向循环链表,逢m就删除当前结点。

我们可以先定义一个不带头单向循环链表,因为建立链表需要创建结点,因此,我们把创建新结点封装成函数,方便之后进行调用。

创建新结点的代码如下:

typedef struct ListNode ListNode;
ListNode* BuyNode(int x) {
    //创建一个新结点
    //向内存中申请一块空间
    ListNode* newNode = (ListNode*) malloc(sizeof(ListNode));   
    if (newNode == NULL) {
    //如果申请失败,直接退出
        exit(1);
    }
    newNode->val = x;         //新结点的数据域为x
    newNode->next = NULL;     //新结点的next指针置为空
    return newNode;           //将新结点返回
}

创建好了新结点,相当于有了一块块单独的羊肉块,我们需要把它们像羊肉串一样串起来 ,放在循环链表中。

怎么创建循环链表呢? 别着急,我们画图分析分析

(这里假设n为5,循环链表总共有5个结点)

Step1:   我们定义一个头结点,一个尾结点,同时指向第一个结点

第一次:   链表的头结点和尾结点都指向第一个结点,新结点的next指针指向NULL。

第二次: 头结点不变,新结点尾插到链表中,作为新的尾结点。

第三次:

第四次:

第五次:

咦? 奇怪,这样就是单链表了呀,怎么循环起来呢?

很简单,就让 newTail 的 next 指针指向 newHead 就可以了啊!

创建不带头单向循环链表的代码如下:

ListNode* CreateList(int n) {
    //创建循环链表
    ListNode* phead = BuyNode(1);       //链表的头结点存在,它的数据域为1
    ListNode* ptail = phead;            //链表的头结点和尾结点指向同一个结点
    for (int i = 2; i <= n; i++) {
        //依次创建第2个结点,第3个结点....第n个结点
        ptail->next = BuyNode(i);
        ptail =  ptail->next;
    }
    //让尾结点的next指针指向头结点,使其首尾相连,循环起来
    ptail->next = phead;
    //返回头结点
    return phead;    
}

准备工作就绪后,下面就是重头戏啦!

首先,我们定义一个pcur变量,用来遍历这个循环链表的每一个结点; 定义一个prev变量,用来保存pcur指向的结点的前驱结点 ; 定义一个计数器count , 用来统计这是第几次。

(如果count的值不为m, 那么pcur继续指向下一个结点, 如果count的值为m , 那么删除当前pcur指向的结点)

第一次: prev初始置为NULL, pcur指向头结点(“1”位置), count为1,pcur将遍历下一个结点。

    int count = 1;                       //用来记录此时的序号
    ListNode* head = CreateList(n);
    ListNode* pcur = head;              //pcur指向头结点
    ListNode* prev = NULL;              //prev置为NULL

第二次: pcur指向下一个结点(“2”位置),prev保存pcur指向结点的前一个结点,count为2,执行删除操作。

此时我们要删除pcur指向的结点, prev指向pcur指向结点的后继结点,pcur将指向下一个结点,count置为初始值1。

            //删除当前节点pcur
            prev->next = pcur->next;
            free(pcur);
            //删除pcur结点后,要让pcur走到新的位置,count置为初始值
            pcur = prev->next;
            count = 1;

或者这样写,也可以:

            //删除当前节点pcur
            prev->next = pcur->next;
            ListNode* del = pcur;            
            //用临时变量del将pcur指向的结点保存下来
            pcur = pcur->next;
            //删除pcur结点后,要让pcur走到新的位置,count置为初始值
            free(del);
            count = 1;

第三次:pcur指向下一个结点(“3”位置),prev和pcur指向同一个结点,count为1,pcur将指向下一个结点。

代码如下:

         prev = pcur;       //prev指针保留pcur的前一个结点
         pcur = pcur->next; //pcur结点继续往后走,
         count++;           //计数器自增一次

第四次: pcur指向下一个结点(“4”位置),prev保存pcur指向结点的前一个结点,count为2

此时我们需要删除pcur指向的结点,prev的next指针指向pcur结点的后继结点,释放pcur结点,pcur为野指针,pcur将走到新的位置,count置为初始值1。

第五次:pcur指向下一个结点(“5”位置),prev和pcur指向同一个结点,count为1,pcur将指向下一个结点。

第六次: pcur指向下一个结点(“1”位置),prev保存pcur指向结点的前一个结点,count为2

此时我们需要删除pcur指向的结点,prev的next指针指向pcur结点的后继结点,释放pcur结点,pcur为野指针,pcur将走到新的位置,count置为初始值1。

第七次:pcur指向下一个结点(“3”位置),prev和pcur指向同一个结点,count为1,pcur将指向下一个结点。

第八次: pcur指向下一个结点(“5”位置),prev保存pcur指向结点的前一个结点,count为2。

此时我们需要删除pcur指向的结点,prev的next指针指向pcur结点的后继结点,释放pcur结点,pcur为野指针,pcur将走到新的位置,count置为初始值1。

第九次:pcur指向下一个结点(“3”位置),prev和pcur指向同一个结点,count为1,pcur将指向下一个结点。

此时下一个结点仍然是“3”位置,因此跳出while循环。

所以,进入while循环的条件是什么呢? 猜对啦,答案就是: pcur->next != pcur;

当链表中只有一个结点,即退出循环。

核心代码如下:


    int count = 1;                       //用来记录此时的序号
    ListNode* head = CreateList(n);
    ListNode* pcur = head;              //pcur指向头结点
    ListNode* prev = NULL;              //prev置为NULL
    while(pcur->next != pcur){
        //当链表中不止有一个结点时,进入循环(当链表中只有1个结点时,就退出循环)
        if(count == m){
            //逢m就删除当前结点
            prev->next = pcur->next;
            ListNode* del = pcur;
            pcur = pcur->next;
            //删除pcur结点后,要让pcur走到新的位置,count置为初始值
            free(del);
            count = 1;
        }else{

         prev = pcur;       //prev指针保留pcur的前一个结点
         pcur = pcur->next; //pcur结点继续往后走,
         count++;           //计数器自增一次
      }
    }

题目要求我们最后返回整型int , 即 pcur指向结点的数据域 , 那么就返回 pcur->val。

好啦,讲到这里就结束啦,附上完整代码:

typedef struct ListNode ListNode;
ListNode* BuyNode(int x) {
    //创建一个新结点
    //向内存中申请一块空间
    ListNode* newNode = (ListNode*) malloc(sizeof(ListNode));   
    if (newNode == NULL) {
    //如果申请失败,直接退出
        exit(1);
    }
    newNode->val = x;         //新结点的数据域为x
    newNode->next = NULL;     //新结点的next指针置为空
    return newNode;           //将新结点返回
}

ListNode* CreateList(int n) {
     //根据n来创建不带头的单向循环链表
    ListNode* phead = BuyNode(1);       //链表的头结点存在,它的数据域为1
    ListNode* ptail = phead;            //链表的头结点和尾结点指向同一个结点
    for (int i = 2; i <= n; i++) {
        //依次创建第2个结点,第3个结点....第n个结点
        ptail->next = BuyNode(i);
        ptail =  ptail->next;
    }
    //让尾结点的next指针指向头结点,使其首尾相连,循环起来
    ptail->next = phead;
    //返回头结点
    return phead;    
}
int ysf(int n, int m ) {
 
    int count = 1;                       //用来记录此时的序号
    ListNode* head = CreateList(n);
    ListNode* pcur = head;              //pcur指向头结点
    ListNode* prev = NULL;              //prev置为NULL
    while(pcur->next != pcur){
        //当链表中不止有一个结点时,进入循环(当链表中只有1个结点时,就退出循环)
        if(count == m){
            //逢m就删除当前结点
            prev->next = pcur->next;
            ListNode* del = pcur;
            pcur = pcur->next;
            //删除pcur结点后,要让pcur走到新的位置,count置为初始值
            free(del);
            count = 1;
        }else{
           
         prev = pcur;       //prev指针保留pcur的前一个结点
         pcur = pcur->next; //pcur结点继续往后走,
         count++;           //计数器自增一次
      }
    }
    //此时pcur结点就是幸存下来的唯一结点
    return pcur->val;
}

OK,这个boss还是被我们打败了,我们下期再见! 

  • 27
    点赞
  • 11
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
这道目的大致思路如下: 1. 首先读入原有文件中的数据,并将其保存到一个数组中; 2. 读入要插入的数据,并将其插入到数组中相应的位置; 3. 将数组中的数据写回到原有文件中。 下面是一个可能的实现: ```c++ #include <iostream> #include <fstream> #include <vector> using namespace std; struct Record { int id; string name; int age; }; int main() { // 读入原有文件中的数据 vector<Record> records; ifstream fin("data.txt"); if (fin.is_open()) { int id, age; string name; while (fin >> id >> name >> age) { records.push_back({id, name, age}); } fin.close(); } // 读入要插入的数据 Record new_record; cin >> new_record.id >> new_record.name >> new_record.age; // 将新数据插入到数组中相应的位置 int pos = -1; for (int i = 0; i < records.size(); ++i) { if (records[i].id > new_record.id) { pos = i; break; } } if (pos == -1) { records.push_back(new_record); } else { records.insert(records.begin() + pos, new_record); } // 将数组中的数据写回到原有文件中 ofstream fout("data.txt"); if (fout.is_open()) { for (const auto& record : records) { fout << record.id << " " << record.name << " " << record.age << "\n"; } fout.close(); } return 0; } ``` 其中,我们定义了一个 `Record` 结构体来表示每一条记录,然后使用一个 `vector` 来保存所有的记录。在读入原有文件中的数据时,我们使用了文件读取流 `ifstream`,在写回到文件中时,我们使用了文件写入流 `ofstream`。读入要插入的数据时,我们直接使用标准输入流 `cin`。 在将新数据插入到数组中时,我们首先需要找到相应的位置。这里我们使用了一种简单的方法,即遍历数组,找到第一个 ID 大于新数据 ID 的位置,然后将新数据插入到该位置。如果没有找到这样的位置,说明新数据 ID 是最大的,我们将其追加到数组末尾即可。在将新数据插入到数组中时,我们使用了 `vector` 的 `insert` 方法。 最后,我们将数组中的数据写回到原有文件中。在写回到文件中时,我们使用了 `ofstream` 的输出流运算符 `<<`。由于每条记录都需要以一行的形式写入文件,因此我们在输出时需要加上换行符 `\n`。 希望这个解答能够帮助到你!
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值