约瑟夫生者死者游戏问题

C++使用单向循环链表解决
需要实现节点的插入和删除
——我为++做实事


1 、节点定义:

struct node
{
    int number; // 表示序号
    node *next;
};

2 、节点的插入:

为每个节点(人)发放生死序号:index
考虑链表为空和不为空的情况

void insert_Node(node *&head, int index)
{
    node *newNode = new node;
    // 如果链表为空
    if (head == nullptr)
    {
        newNode->number = index;
        newNode->next = newNode; // next指针指向自己
        head = newNode;          // 把自己设为头节点
    }
    else
    {
        newNode->number = index;
        newNode->next = head; // next指针指向头节点
        node *N = find_tail(head);
        N->next = newNode; // 尾节点的next指向自己
    }
}

3 、删除节点的操作:

当我们删除链表中的节点时,一般只需要修改附近节点的 next 指针即可,不过此题使用了 new 来分配空间,所以还需要 delete 掉节点,防止内存泄漏。如果用数组模拟链表就不用考虑真的“删除”节点这个问题。

3 .1 有以下几种情况:
如果是头节点
	是否有且只有头节点
	除了头节点还有其他节点
如果不是头节点
3.2 需要注意释放内存

delete node
代码:

void del_Node(node *&head, node *victim)
{
    if (victim == head) 			// 如果删除的是头节点
    {
        node *tail = find_tail(head);
        if (head == tail) 			// 链表只有一个节点
        {
            delete head;    		// 释放内存
            head = nullptr; 		// 设置为空
        }
        else						// 除了头节点还有其他节点
        {
            head = head->next; 		// 修改头节点
            tail->next = head; 		// 尾节点指向新的头节点
            delete victim;     		// 释放内存
        }
    }
    else							// 删的不是头节点
    {
        node *before_victim = head;
        while (before_victim->next != victim)
            before_victim = before_victim->next;
            
        before_victim->next = victim->next; // 删除 victim,修改前面节点的next指针
        delete victim;                      // 释放内存
    }
}

4. Main 函数

一定要初始化头节点为空,作为链表的一个标志
代码逻辑:
为 30 个人发号码牌
循环 15 次,每次丢出去一个人(好残忍)
每次从当前的人这里,往前走 8 步,找到 victim
删除 victim
指针移动到这个 victim 下一位,下次循环就从这人开始,继续走 8 步……
代码:

int main()
{
    node *head = nullptr;			// 一定要初始化头节点为空
    for (int i = 1; i <= 30; i++)
        insert_Node(head, i);

    int all_people = 30, half_people = all_people / 2;
    node *victim = head;
    while (half_people--)
    {
        for (int i = 0; i < 8; i++)
            victim = victim->next;

        cout << victim->number << " ";
        node *next_vic = victim->next;
        del_Node(head, victim);
        victim = next_vic;

        cout << endl;
        // print(head);
    }
    return 0;
}

最后是些无关紧要的辅助函数

1、find_tail 函数

// 返回链表最后一个节点的指针
node *find_tail(node *head)
{
    node *N = head;
    while (N->next != head) // find尾节点
    {
        N = N->next;
    }
    return N;
}

2、打印链表函数(debug 用可删)

// 打印当前剩下的所有人
void print(node *head)
{
    node *c = head;
    if (c == nullptr)
        return; // 空链表处理
    do
    {
        cout << c->number << " ";
        c = c->next;
    } while (c != head); // 打印直到头节点
    cout << endl;
}

小韩碎碎念 —有些 bug 值得注意:

1、对于链表的修改,包括插入和删除,传进函数一定要用 &,引用传递,否则只会在函数内部修改,不会对原始链表有任何影响。

2、关于 while 和 do-while 的一个 bug:
源代码:

void print(node * head)
{
    node *c = head;
    while(c->next != head)
    cout << c->number << " ";
}

问题:
print 函数的逻辑问题print 函数缺少打印链表最后一个节点的代码,因为它只循环到 c->next != head,而没有打印尾节点。因此,你会错过打印最后一个节点
改成 do-while 循环后的代码:

void print(node * head) 
{ 
	node *c = head; 
	if (c == nullptr) 
		return; // 空链表处理 
	do 
	{ 
		cout << c->number << " "; 
		c = c->next; 
	} while (c != head); // 打印直到头节点 
	cout << endl; 
}
posted @ 2024-11-08 12:19  Yuxi001  阅读(6)  评论(0编辑  收藏  举报