約瑟夫生者死者遊戲問題

Yuxi001發表於2024-11-08

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; 
}

相關文章