1. 程式人生 > 實用技巧 >C語言之約瑟夫問題--鏈錶的建立和元素刪除.

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語言手動實現,有助於你更好的理解這些資料結構.