• 博客园logo
  • 会员
  • 众包
  • 新闻
  • 博问
  • 闪存
  • 赞助商
  • HarmonyOS
  • Chat2DB
    • 搜索
      所有博客
    • 搜索
      当前博客
  • 写随笔 我的博客 短消息 简洁模式
    用户头像
    我的博客 我的园子 账号设置 会员中心 简洁模式 ... 退出登录
    注册 登录
Asc.416e74
博客园    首页    新随笔    联系   管理     

C語言之約瑟夫問題--鏈錶的創建和元素刪除.

故事:

約瑟夫在自己的日記中寫道,他和他的40個戰友被羅馬軍隊包圍在洞中。他們討論是自殺還是被俘,最終決定自殺,並以抽籤的方式決定誰殺掉誰。約瑟夫斯和另外一個人是最後兩個留下的人。約瑟夫說服了那個人,他們將向羅馬軍隊投降,不再自殺。約瑟夫把他的存活歸因於運氣或天意,他不知道是哪一個.

這個問題也被稱作約瑟夫問題,或者約瑟夫環,圓桌遊戲等.將其抽象之後形成下面的遊戲規則:

  1. 有一群人圍坐成一圈.
  2. 從第一個人開始報數.
  3. 每次報道3該人自殺.
  4. 下一人衝1開始報數.

使用C語言實現:

#include<stdio.h>
#include<stdlib.h>

// 定義玩家(定義鏈錶節點)
typedef struct _
{
    int no;                 // 記錄玩家的編號(數據域)
    struct _ *next;         // 指向下一個玩家的指針(指針域)
} Player;

// 生成玩家(生成鏈錶)
Player *CreatePlayers(int number)
{
    Player *head;       // 第一位玩家(鏈錶頭節點)
    Player *tail;       // 最後一位玩家(鏈錶尾結點)
    Player *temp;       // 用於申請空間的臨時變量
    head = (Player *)malloc(sizeof(Player));

    // 判斷空間是否申請成功
    if(NULL == head || NULL == tail)
    {
        printf("malloc fail!\n");
        return NULL;
    }

    head->next = NULL;      // 還沒有後續結點,所以下一個結點為空
    head->no = 1;           // 把頭結點設置為第一個結點,後面不用判斷和
    tail = head;            // 讓頭結點和尾結點指向相同的節點

    // 生成遊戲玩家(生成鏈錶,並初始化數據域)
    int i;
    for(i = 2; i <= number; i++)
    {      
        temp = (Player *)malloc(sizeof(Player));        // 生成的新玩家(將要加入的新節點)

        // 判斷空間是否申請成功
        if(NULL == temp)
        {
            printf("malloc fail!\n");
            return 0;
        }

        temp->no = i;           // 數據域初始化
        temp->next = NULL;      // 由於使用的是尾插法,所以新節點的指針指向空,如果是頭插法則指向頭結點
        tail->next = temp;      // 讓玩家加入玩家列表(將節點添加到鏈錶末尾)
        tail = temp;            // 新加入節點之後原來的尾結點已經不是最後一個節點
    }
    tail->next = head;          // 尾結點指向頭結點,形成閉環,如果是單鏈表,不用成環則沒有這條語句
    return head;                // 返回頭結點
}

// 查看所有玩家(遍歷鏈錶,確保自己生成成功)
void ShowAllPlayer(Player *head)
{
    Player *t = head;
    do
    {
        // 因為t指正是從head節點出發,最終回到head節點,使用do/while語句比while更簡潔
        printf("Player No: %d Is Coming!!!\n",t->no);
        t = t->next;
    } while (t != head); 
}

// 遊戲開始
void GameStart(Player *head)
{
    // 讓兩個指針同時指向鏈錶的頭
    Player *temp1 = head;
    Player *temp2 = head;

    int number = 1;         // 玩家總人數(鏈錶節點數量)
    int setpSzie = 3;              // 控制單次循環步長

    while(temp2->next != temp1)
    {
        // 通過遍歷的方式讓temp2成為temp1的上一個節點,同時獲取總的節點數量
        temp2 = temp2->next;
        number++;
    }

    // 遊戲開始,每一次外層循環就會死一個人(每次循環減少一個節點)
    for(number; number > 0; number--)
    {
        // 找出本輪遊戲的出局者
        for(setpSzie = 3;setpSzie > 1; setpSzie--)
        {
            temp1 = temp1->next;
            temp2 = temp2->next;
        }

        temp2->next = temp1->next;                          // 讓temp1的上一個節點指向自己的下一個節點,保證自己的內存被釋放只後鏈錶不會斷裂
        printf("Player No: %d game over\n",temp1->no);      // 玩家淘汰
        free(temp1);                                        // 釋放被淘汰的玩家的節點空間
        temp1 = temp2->next;                                // 指定下一輪遊戲的開始者
    }
    
}

int main(int argc, char const *argv[])
{
    Player *head = CreatePlayers(10);
    ShowAllPlayer(head);
    printf("--------------------------\n");
    GameStart(head);
    return 0;
}

執行結果:

 

  

注意事項:

  1. 使用尾插法創建鏈錶的時候,需要先將節點加入鏈錶,然後再講尾結點的標誌後移,如果先將尾結點移動到新節點會導致原本的尾結點沒有標記
  2. 每次創建新的節點的時候需要將其指針指向null.
  3. 在刪除鏈錶元素的時候要先將當前的前驅節點指向後繼節點,確保節點刪除之後鏈錶不會讓鏈錶斷裂,在添加元素的時候則是讓新節點的指針指向後繼節點,然後讓前驅節點指向需要插入的節點,比較簡單,不做代碼展示.
  4. 如果只需要生成單鏈表,那麼只需要在生成的最後不要讓尾結點指向頭結點即可
  5. 鏈錶的頭結點既可以作為鏈錶的標誌,不存放實際數據存在,也可以存放數據,作為有效節點處理,具體而言看個人喜好.
  6. 雙向鏈錶只需要在指針域使用雙指針,一個指向後繼節點,一個指向前驅節點即可.
  7. 鏈錶的數據域可以存放多個數據,主要在於你結構體如何定義.
  8. 如果是C++等其他語言基本都會有已經封裝好的庫(如STL),雖然可以直接調用,但是使用C語言手動實現,有助於你更好的理解這些數據結構.
posted @ 2020-09-14 17:43  ストッキング  阅读(175)  评论(0)    收藏  举报
刷新页面返回顶部
博客园  ©  2004-2025
浙公网安备 33010602011771号 浙ICP备2021040463号-3