RE.從單連結串列開始的資料結構生活(bushi

扇與她盡失 發表於 2021-04-20

單連結串列

單連結串列中節點的定義

typedef struct LNode{
  int data;//資料域
  struct LNode *next;//定義一個同型別的指標,指向該節點的後繼節點
}LNode, *Linklist;

LNode是一個資料節點,而單連結串列是用指標將許多資料節點連線起來的一個線性表

最開始看單連結串列程式碼的時候,我就一直有一個非常非常大的疑問,這個疑問就是:LNode和LinkList到底有什麼區別,什麼時候要加 * ,什麼時候不加 *等等問題, 但這些問題幾乎沒人人解答,就一直卡著我,導致我特別牴觸單連結串列,看見漫天的指標就煩,也就一直沒提起興趣寫連結串列,但現在可能是程式碼看多了,發現這玩意也沒有那麼難搞

我們定義單連結串列的時候,可以有兩種定義方法:LNode *L,或者是LinkList L;通常來說是用的第二種,為什麼呢?首先,我要說的是,我們定義的連結串列為L,這個L其實是一個LNode型別的指標,是整個連結串列的頭節點,我們一旦有了這個頭節點,就可以對這個連結串列進行一系列操作

而這個連結串列的頭節點,他是一個節點而不是一個表;所以就有了第一個定義方法,LNode *L,但這樣總的看起來就沒有個像表的樣子,給人一種很low的感覺,所以所以我們就定一個LNode形的指標為LinkList,這樣以後一旦我們需要定義一個單連結串列,也就是隻需要直接 LinkList L一下即可,現在你看這個LinkList是不是有內味了(>_<)

而為什麼要在LinkList前面加個 * 呢,我認為是這樣滴:把這個LinkList定義為一個節點型別的指標,是為了之後對函式穿引數時,進行連結串列的修改,因為你如果是在主函式裡面建立的連結串列,你想要利用函式改變其資料,就必須傳連結串列的地址進去,不然是改變不了連結串列的資料滴

初始化帶頭節點的連結串列

void InitLinkList(LinkList &L){
  L = (LinkList)malloc(sizeof(LNode));//這個頭節點本質上還是個節點,所以建立空間的時候還是要建立LNode大小的空間
  L->next = NULL;//別忘了讓頭指標next指向NULL,不然就成了野指標
}

頭插法建立單連結串列

void HeadInsert_LinkList(LinkList &L, int n){//頭插法
    LNode *s;//輔助指標,用來申請新的空間,並將輸入的資料存進連結串列
    for(int i = 1; i <= n; ++i){
        s = (LNode*)malloc(sizeof(LNode));//申請一個LNode節點的空間
        cin>>s->data;//輸入資料域
        s->next = L->next;//將頭結點的next指標指向的點賦給s的next指標,就類似於你要插隊,就先要先把腳插進去,人才能進去
        L->next = s;//再把s指標便成頭節點後的第一個資料,也就是相當於把人的身體塞進去遼
    }
}

img

利用頭插法建立單連結串列,輸入的資料的順序與生成的連結串列中的元素的順序相反。每個節點插入的時間複雜度為O(1),總時間複雜度為O(n)。

尾插法建立單連結串列

void RailInsert_LinkList(LinkList &L, int n){//尾插法
    LNode *s, *r;//s還是和頭插一樣,r用於始終指向尾節點,這樣可以避免每一次尾插都得迴圈跑到最後一個位置再插
    r = L;//因為是從無到有,所以先讓尾指標指向頭指標
    for(int i = 1; i <= n; ++i){
        s = (LNode*)malloc(sizeof(LNode));//開闢空間
        cin>>s->data;//輸入資料域
        r->next = s;//r代表的是目前的尾節點,s是新來的節點,尾插自然讓之前的尾節點(r)的next指向新的尾節點(s)
        r = s;//尾節點改變了,自然要讓s變成尾節點
    }
    r->next = NULL;//注意要給尾節點的next賦NULL,不然你要是以後迴圈就找不到迴圈結束條件了
}

img

尾插法建立單連結串列,輸入的資料的順序與生成的連結串列中的元素的順序相同。每個結點插入的時間複雜度為O(1),總時間複雜度為O(n)。

在單連結串列中插入一個結點

void Insert_LinkList(LinkList &L, int location, int elem){
    LNode *p, *s;//p用於遍歷單連結串列,s用於儲存插入的資料elem
    p = L;//p初始指向頭節點
    int j = 1;//計數,用來找到需要插入的位置location,且這裡一定要是1,不然就會變成插在pos後面了
    while (p != NULL && j < location) {//p不能出連結串列外,且j小於location的值
        p = p->next;
        ++j;
    }
    s = (LNode*)malloc(sizeof(LNode));//申請空間
    s->data = elem;//賦值
    s->next = p->next;//因為p的位置其實是location-1,p的下一個才是需要插入的地方,所以我們就把p->next的值賦值給s的next,和上面說的一樣,先把腳伸進去
    p->next = s;//再把身體塞進去,也就是讓前一個節點(p)指向新插入的節點(s)
}

在單連結串列中刪除一個節點

void Delet_LinkList(LinkList &L, int pos){
    LNode *s, *p;//p指向pos前一個位置, s指向pos的位置
    p = L;//將p賦值為L
    int j = 1;//計數,一定是賦值為1
    while (p!=NULL && j < pos) {
        ++j;
        p = p->next;
    }
    s = p->next;//s指向的是pos
    p->next = s->next;//s->next指向的是pos後面的位置,將pos後面的點接到pos前面的節點去,就相當於刪掉了pos這個節點
}

按順序列印單連結串列

void Print_LinkList(LinkList L){
    LNode* p = L->next;//用於遍歷連結串列,注意是指向L->next
    while (p != NULL) {
        cout<<p->data<<' ';
        p = p->next;
    }
    cout<<endl;
}

測試程式碼

#include <cstdio>
#include <cstring>
#include <string>
#include <cmath>
#include <iostream>
#include <algorithm>
#include <vector>
#include <stack>
#include <queue>
#include <stdlib.h>
#include <sstream>
#include <map>
#include <set>
using  namespace std;
#define inf 0x3f3f3f3f
#define MAX 10000 + 50
#define endl '\n'
//#define mod 1000000007
//const int mod = 1e9 + 7;
#define io ios::sync_with_stdio(false); cin.tie(0); cout.tie(0)
#define mem(a,b) memset((a),(b),sizeof(a))
typedef  long long ll ;
//不開longlong見祖宗!
inline int IntRead(){char ch = getchar();int s = 0, w = 1;while(ch < '0' || ch > '9'){if(ch == '-') w = -1;ch = getchar();}while(ch >= '0' && ch <= '9'){s = s * 10 + ch - '0';ch = getchar();}return s * w;}

typedef struct LNode{
    int data;
    struct LNode *next;
}LNode, *LinkList;

void InitLinkList(LinkList &L){//生成帶頭結點的單連結串列L
    L = (LinkList)malloc(sizeof(LNode));
    L->next = NULL;//千萬別忘了讓頭節點的next指向空
}

void HeadInsert_LinkList(LinkList &L, int n){//頭插法
    LNode *s;//輔助指標,用來申請新的空間,並將輸入的資料存進連結串列
    for(int i = 1; i <= n; ++i){
        s = (LNode*)malloc(sizeof(LNode));//申請一個LNode節點的空間
        cin>>s->data;//輸入資料域
        s->next = L->next;//將頭結點的next指標指向的點賦給s的next指標,就類似於你涉足兩個人之間,要先把腳插進去,人才能進去
        L->next = s;//再把s指標便成頭節點後的第一個資料,也就是相當於把人的身體塞進去遼
    }
}

void RailInsert_LinkList(LinkList &L, int n){//尾插法
    LNode *s, *r;//s還是和頭插一樣,r用於始終指向尾節點,這樣可以避免每一次尾插都得迴圈跑到最後一個位置再插
    r = L;//因為是從無到有,所以先讓尾指標指向頭指標
    for(int i = 1; i <= n; ++i){
        s = (LNode*)malloc(sizeof(LNode));//開闢空間
        cin>>s->data;//輸入資料域
        r->next = s;//r代表的是目前的尾節點,s是新來的節點,尾插自然讓之前的尾節點(r)的next指向新的尾節點(s)
        r = s;//尾節點改變了,自然要讓s變成尾節點
    }
    r->next = NULL;//注意要給尾節點的next賦NULL,不然你要是以後迴圈就找不到迴圈結束條件了
}

void Insert_LinkList(LinkList &L, int location, int elem){
    LNode *p, *s;//p用於遍歷單連結串列,s用於儲存插入的資料elem
    p = L;//p初始指向頭節點
    int j = 1;//計數,用來找到需要插入的位置location,且這裡一定要是1,不然就會變成插在pos後面了
    while (p != NULL && j < location) {//p不能出連結串列外,且j小於location的值
        p = p->next;
        ++j;
    }
    s = (LNode*)malloc(sizeof(LNode));//申請空間
    s->data = elem;//賦值
    s->next = p->next;//因為p的位置其實是location-1,p的下一個才是需要插入的地方,所以我們就把p->next的值賦值給s的next,和上面說的一樣,先把腳伸進去
    p->next = s;//再把身體塞進去,也就是讓前一個節點(p)指向新插入的節點(s)
}

void Print_LinkList(LinkList L){
    LNode* p = L->next;//用於遍歷連結串列,注意是指向L->next
    while (p != NULL) {
        cout<<p->data<<' ';
        p = p->next;
    }
    cout<<endl;
}

int main(){
    LinkList L;//建立以L為頭節點的連結串列
    InitLinkList(L);

    int n;
    cin>>n;
    HeadInsert_LinkList(L, n);//頭插法建立n個數的連結串列
    Print_LinkList(L);

    InitLinkList(L);
    RailInsert_LinkList(L, n);//尾插法建立n個數的連結串列
    Print_LinkList(L);

    int pos, elem;
    cin>>pos>>elem;
    Insert_LinkList(L, pos, elem);//在pos的位置插入elem
    Print_LinkList(L);
  
    return 0;
}

雙向連結串列

簡單介紹:

我們把用連結串列比作下棋,單連結串列就是老實人,只能一步步的下,發現哪一步下錯了,是沒法悔棋的,只能從頭再來

而雙向連結串列就像是個開指令碼的小猴子,發現自己前面有哪一步下錯了,就可以無限悔棋去更正……

雙向連結串列的定義:

typedef struct LNode{
    int val;//值
    struct LNode *next, *pre;//每個節點都有next指標和pre指標,next指標指向下一個節點,pre指標指向上一個節點
}LNode, *De_LinkList;

初始化

void Init_De_LinkList(De_LinkList &L){//
    L = (De_LinkList)malloc(sizeof(LNode));
    L->next = NULL;
    L->pre = NULL;
}
//初始化其實初始化的是頭節點,是可以放在建立函式裡面的,不過我喜歡將其重新定義為一個函式(好歹也是連結串列的大哥大,不得給它個面子嘛

頭插法建立雙向連結串列

頭插法的關鍵是頭不儲存資訊,讓每一個新來的節點都插到頭的後面去

看圖我們可以知道,對於新來的節點cnt,需要先讓cnt的next指標指向nex去(相當於上面單連結串列中說的先把腳伸進去),再讓nex的pre指標指向cnt,再讓cnt的pre指標指向頭節點pr,最後再讓pr的next指標指向cnt即可

需要注意的是,pr = L, nex = pr - next,而在輸入第一個數時,nex就是NULL,需要進行單獨處理,不能放在後面的迴圈取pre,因為NULL哪裡有pre

[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片儲存下來直接上傳(img-EuiVwiIm-1618924434677)(/Users/chelsea/Library/Application Support/typora-user-images/image-20210419164001353.png)]

void HeadCreat_De_LinkList(De_LinkList &L, int len){
    LNode *pr, *cnt, *nex;//pr是前一個節點,也就是頭節點,cnt是新來的節點,nex是頭節點後面的節點
    pr = L;
    cnt = (LNode*)malloc(sizeof(LNode));
    cin>>cnt->val;//輸入新節點的值
    cnt->next = NULL;//對一個點進行特殊處理,他後面是沒有元素的,所以給next賦值為NULL
    cnt->pre = L;//賦pre為頭指標
    L->next = cnt;//更新頭指標的next指標
//用s儲存cnt,就是第一個輸入的元素,也就是連結串列的尾節點,便於倒序輸出
//LNode *s;
//s = cnt;
    for(int i = 2; i <= len; ++i){//迴圈從2開始
        cnt = (LNode*)malloc(sizeof(LNode));//開闢空間
        cin>>cnt->val;//輸入資料
      //給pr和nex賦值
        pr = L;
      	nex = L->next;
      //再就是四步曲,見圖上面的文字
        cnt->next = nex;、
        nex->pre = cnt;
        cnt->pre = pr;
        pr->next = cnt;
    }
//從後往前輸出,用於檢查pre是否好用
//  while (s->pre != NULL) {
//        cout<<s->val<<' ';
//        s = s->pre;
//    }
//    cout<<endl;
}

尾插法建立雙向連結串列

尾插法比頭插法容易實現,他不需要pre,nex,只需要一個連結串列的尾節點tail,然後對每個新來的點,直接插到tail後面去即可

void TailCreat_De_LinkList(De_LinkList &L, int len){
    LNode *s, *tail;//s是輔助節點,用來存新來的節點,tail是連結串列的尾節點
    tail = L;//最開始的時候尾節點就是頭節點
    for(int i = 1; i <= len; ++i){
        s = (LNode*)malloc(sizeof(LNode));//申請空間
        cin>>s->val;//輸入值
        tail->next = s;//直接讓尾節點的next指向新節點
        s->pre = tail;//讓新節點的pre指向尾節點
        tail = s;//因為是尾插,所以尾節點改變了,就要時時更新
    }
    tail->next = NULL;
//下面是檢測程式碼,是從後往前遍歷,證明pre是可以用滴
//    while (tail->pre != NULL) {
//        cout<<tail->val<<' ';
//        tail = tail->pre;
//    }
//    cout<<endl;
}

[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片儲存下來直接上傳(img-2JPuWf9L-1618924434678)(/Users/chelsea/Library/Application Support/typora-user-images/image-20210419175026336.png)]

插入元素

void Insert_De_LinkList(De_LinkList &L, int pos, int val){
    LNode *pr, *cnt, *nex;//和頭插法一樣
    pr = L;
    int j = 1;
    while (pr != NULL && j < pos) {//找到pos前一個位置
        pr = pr->next;
        ++j;
    }
    nex = pr->next;//nex是pos節點
    cnt = (LNode*)malloc(sizeof(LNode));//開闢空間
    cnt->val = val;//賦新節點值
  //四步走,見頭插法
    cnt->next = nex;
    nex->pre = cnt;
    cnt->pre = pr;
    pr->next = cnt;   
}

[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片儲存下來直接上傳(img-nFDtWjD4-1618924434678)(/Users/chelsea/Library/Application Support/typora-user-images/image-20210419180526630.png)]

刪除元素

void Delet_De_LinkList(De_LinkList &L, int pos){
    LNode *cnt, *pr, *nex;//和頭插法差不多
    pr = L;
    int j = 1;
    while (pr != NULL && j < pos) {//pr指向pos的前一個節點
        ++j;
        pr = pr->next;
    }
    cnt = pr->next;//cnt指向pos節點
    nex = cnt->next;//nex指向pos的下一個節點
    if(nex != NULL){//如果刪除的不是最後一個元素
        pr->next = nex;//讓pr的next指向nex,也就是讓pos前節點的next指向pos的後節點
        nex->pre = pr;//讓nex的pre節點指向pr,也就是讓pos的後節點的pre指向pos的前節點
    }
    else {//如果是最後一個元素,那麼nex就是NULL,此時只需要讓pr的next指標指向NULL即可
        pr->next = nex;
    }
}

[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片儲存下來直接上傳(img-Z8bL0Np3-1618924434679)(/Users/chelsea/Library/Application Support/typora-user-images/image-20210419180845392.png)]

列印列表

void Print_De_LinkList(De_LinkList L){
    LNode *p;
    p = L ->next;
    while (p !=NULL) {
        cout<<p->val<<' ';
        p = p->next;
    }
    cout<<endl;
}

程式碼總覽

#include <cstdio>
#include <cstring>
#include <string>
#include <cmath>
#include <iostream>
#include <algorithm>
#include <vector>
#include <stack>
#include <queue>
#include <stdlib.h>
#include <sstream>
#include <map>
#include <set>
using  namespace std;
#define inf 0x3f3f3f3f
#define MAX 10000 + 50
#define endl '\n'
//#define mod 1000000007
//const int mod = 1e9 + 7;
#define io ios::sync_with_stdio(false); cin.tie(0); cout.tie(0)
#define mem(a,b) memset((a),(b),sizeof(a))
typedef  long long ll ;
//不開longlong見祖宗!
inline int IntRead(){char ch = getchar();int s = 0, w = 1;while(ch < '0' || ch > '9'){if(ch == '-') w = -1;ch = getchar();}while(ch >= '0' && ch <= '9'){s = s * 10 + ch - '0';ch = getchar();}return s * w;}

typedef struct LNode{
    int val;
    struct LNode *next, *pre;
}LNode, *De_LinkList;

void Init_De_LinkList(De_LinkList &L){
    L = (De_LinkList)malloc(sizeof(LNode));
    L->next = NULL;
    L->pre = NULL;
}

void HeadCreat_De_LinkList(De_LinkList &L, int len){
    LNode *pr, *cnt, *nex, *s;
    pr = L;
    cnt = (LNode*)malloc(sizeof(LNode));
    cin>>cnt->val;
    cnt->next = NULL;
    cnt->pre = L;
    L->next = cnt;
    s = cnt;
    for(int i = 2; i <= len; ++i){
        cnt = (LNode*)malloc(sizeof(LNode));
        cin>>cnt->val;
        pr = L;nex = L->next;
        cnt->next = nex;
        nex->pre = cnt;
        cnt->pre = pr;
        pr->next = cnt;
    }
//    while (s->pre != NULL) {
//        cout<<s->val<<' ';
//        s = s->pre;
//    }
//    cout<<endl;
}

void TailCreat_De_LinkList(De_LinkList &L, int len){
    LNode *s, *tail;
    tail = L;
    for(int i = 1; i <= len; ++i){
        s = (LNode*)malloc(sizeof(LNode));
        cin>>s->val;
        tail->next = s;
        s->pre = tail;
        tail = s;
    }
    tail->next = NULL;
//    while (tail->pre != NULL) {
//        cout<<tail->val<<' ';
//        tail = tail->pre;
//    }
//    cout<<endl;
}

void Insert_De_LinkList(De_LinkList &L, int pos, int val){
    LNode *pr, *cnt, *nex;
    pr = L;
    int j = 1;
    while (pr != NULL && j < pos) {
        pr = pr->next;
        ++j;
    }
    nex = pr->next;
    cnt = (LNode*)malloc(sizeof(LNode));
    cnt->val = val;
    cnt->next = nex;
    nex->pre = cnt;
    cnt->pre = pr;
    pr->next = cnt;

}

void Delet_De_LinkList(De_LinkList &L, int pos){
    LNode *cnt, *pr, *nex;
    pr = L;
    int j = 1;
    while (pr != NULL && j < pos) {
        ++j;
        pr = pr->next;
    }
    cnt = pr->next;
    nex = cnt->next;
    if(nex != NULL){
        pr->next = nex;
        nex->pre = pr;
    }
    else {
        pr->next = nex;
    }
}

void Print_De_LinkList(De_LinkList L){
    LNode *p;
    p = L ->next;
    while (p !=NULL) {
        cout<<p->val<<' ';
        p = p->next;
    }
    cout<<endl;
}



int main(){
    int n;
    De_LinkList L;

    cin>>n;
    Init_De_LinkList(L);
    HeadCreat_De_LinkList(L, n);
    Print_De_LinkList(L);
    
    cin>>n;
    Init_De_LinkList(L);
    TailCreat_De_LinkList(L, n);
    Print_De_LinkList(L);
    
    int pos, val;
    cin>>pos>>val;
    Insert_De_LinkList(L, pos, val);
    Print_De_LinkList(L);
    
    cin>>pos;
    Delet_De_LinkList(L, pos);
    Print_De_LinkList(L);
    return 0;
}

迴圈連結串列

迴圈連結串列和連結串列差不多,只需要將尾節點指向頭節點即可,這裡我通過實現約瑟夫環和抓兔子這個兩個經典的迴圈連結串列問題來展示迴圈連結串列是如何實現的

約瑟夫環問題:

設有n個人圍坐在圓桌周圍,現從某個位置m(1≤m≤n)上的人開始報數,報數到k的人就站出來。下一個人,即原來的第k+1個位置上的人,又從1開始報數,再報數到k的人站出來。依次重複下去,直到全部的人都站出來為止。試設計一個程式求出這n個人的出列順序。

因為此問題需要不斷迴圈連結串列,並利用點的位置,所以頭節點很礙事,索性就不要原來那種沒有值的頭節點,讓第一個有值的節點成為頭節點

採用尾插法

#include <cstdio>
#include <cstring>
#include <string>
#include <cmath>
#include <iostream>
#include <algorithm>
#include <vector>
#include <stack>
#include <queue>
#include <stdlib.h>
#include <sstream>
#include <map>
#include <set>
using  namespace std;
#define inf 0x3f3f3f3f
#define MAX 10000 + 50
#define endl '\n'
//#define mod 1000000007
//const int mod = 1e9 + 7;
#define io ios::sync_with_stdio(false); cin.tie(0); cout.tie(0)
#define mem(a,b) memset((a),(b),sizeof(a))
typedef  long long ll ;
//不開longlong見祖宗!
inline int IntRead(){char ch = getchar();int s = 0, w = 1;while(ch < '0' || ch > '9'){if(ch == '-') w = -1;ch = getchar();}while(ch >= '0' && ch <= '9'){s = s * 10 + ch - '0';ch = getchar();}return s * w;}

typedef struct LNode {
    int data;
    struct LNode *next;
}LNode, *CLinkList;

void Init_CLinkList(CLinkList &L){//初始化
    L = (CLinkList)malloc(sizeof(LNode));
    L->next = L;
}

void Tail_CLinkList(CLinkList &L, int len){//無頭節點的尾插法
    LNode *s, *tail;//輔助節點
    L->data = 1;//對頭節點單獨處理
    L->next = L;//next指標指向本身,形成迴圈,
    tail = L;
    for(int i = 2; i <= len; ++i){
        s = (LNode*)malloc(sizeof(LNode));
        s->data = i;//按題意賦值
        s->next = L;//尾插法,故讓輸入的節點的next指向第一個節點
        tail->next = s;//原來的尾節點指向新來的節點
        tail = s;//將新來的節點變成尾節點
    }
}

void Prinit_CLinkList(CLinkList L){//列印迴圈連結串列
    LNode *s = L ->next ;
    cout<<L->data<<' ';//先輸出頭節點,不然下面的迴圈的條件就寫不了
    while (s != L){//迴圈結束條件都是根據你的連結串列的寫法而定的
        cout<<s->data<<' ';
        s = s->next;
    }
    cout<<endl;
}

void YSF(CLinkList &L, int n, int m, int k){//約瑟夫環
    int j = 1;//計數,找到m-1的位置
    LNode *s, *p;//輔助節點,s用於約瑟夫環的迴圈,p用於找m節點的位置
    if(m == 1){//如果m=1,也就是第一個位置,就直接讓p=L,不然也是下面的迴圈條件沒法寫
        p = L;
    }
    else p = L->next;
    while (j < m && p != L) {//找到位置m,此時p節點就是m的位置
        ++j;
        p = p->next;
    }
    cout<<"連結串列為 ";
    Prinit_CLinkList(L);
    cout<<"起點為 "<<p->data<<endl;//輸出起點
    s = p;
    cout<<"出隊順序為:";
    while (s->next != s) {//當連結串列只剩下一個節點時結束迴圈,此時s節點的next指向他本身,這就是結束條件
        int sum = 1;//計數,看看到沒到k
        while (sum < k - 1) {//結束之時,sum=k-1,s節點是需要刪除的節點的前一個節點
            ++sum;
            s = s->next;
        }
        p = s->next;//p就是我們要刪掉的節點
        cout<<p->data<<" -> ";//輸出p的值
        s->next = p->next;//刪掉p
        s = s->next;
    }
    cout<<s->data<<endl;//輸出最後一個元素
}

int main(){
    CLinkList L;
    Init_CLinkList(L);
    int n, m, k;
    cin>>n;
    Tail_CLinkList(L, n);
    cin>>m>>k;
    YSF(L, n, m, k);
 		return 0;  
}

抓兔子

圍繞著山頂有10個圓形排列的洞,互利要吃兔子,兔子說:”可以,但必須找到我,我就藏於這10個洞中,你先到1號洞找,第二次隔1個洞(即3號洞)找,第二次隔2個洞(即6號洞)找,以後如此類推,次數不限.”但狐狸從早到晚進進出出了1000次,仍沒有找到兔子.問:兔子究竟藏在那個洞裡,

同樣是迴圈對列,操作和上面差不多都

#include <cstdio>
#include <cstring>
#include <string>
#include <cmath>
#include <iostream>
#include <algorithm>
#include <vector>
#include <stack>
#include <queue>
#include <stdlib.h>
#include <sstream>
#include <map>
#include <set>
using  namespace std;
#define inf 0x3f3f3f3f
#define MAX 10000 + 50
#define endl '\n'
//#define mod 1000000007
//const int mod = 1e9 + 7;
#define io ios::sync_with_stdio(false); cin.tie(0); cout.tie(0)
#define mem(a,b) memset((a),(b),sizeof(a))
typedef  long long ll ;
//不開longlong見祖宗!
inline int IntRead(){char ch = getchar();int s = 0, w = 1;while(ch < '0' || ch > '9'){if(ch == '-') w = -1;ch = getchar();}while(ch >= '0' && ch <= '9'){s = s * 10 + ch - '0';ch = getchar();}return s * w;}
typedef struct LNode{
    int data;
    struct LNode *next;
}LNode, *CLinkList;

void Init_CLinkList(CLinkList &L){//同樣是初始化
    L = (CLinkList)malloc(sizeof(LNode));
    L->next = L;
}

void TailCreat_CLinkList(CLinkList &L, int n){//同樣的尾插法建立迴圈連結串列
    L->data = 1;//先特殊處理第一個節點
    LNode *s, *tail;
    tail = L;//因為只有一個節點,所以即是頭節點又是尾節點
    for(int i = 2; i <= n; ++i){
        s = (LNode*)malloc(sizeof(LNode));
        s->data = i;
        tail->next = s;
        s->next = L;
        tail = s;
    }
}

void Print_CLinkList(CLinkList &L){//列印連結串列,同上
    LNode *s;
    s = L;
    cout<<s->data<<' ';
    s = L->next;
    while (s != L) {
        cout<<s->data<<' ';
        s = s->next;
    }
    cout<<endl;
}

void f(CLinkList L,int n, int k){//開始抓兔子遼
    bool vis[n + 1];//標記陣列,用力記錄這個點有沒有來過
    mem(vis, 0);//清0是個好習慣
    int num = 1, pos = 1, cnt = 2;//num表示本次需要在第幾個坑(即1,3,6,10等等),pos是當前的位置,cnt表示每次num找到後需要重新增加的值
    cout<<"連結串列為: ";
    Print_CLinkList(L);//列印連結串列
    cout<<"抓兔子的過程為:";

    LNode *s;
    s = L;
    while (k--) {//找k此
        while (pos < num) {//找到num的位置
            ++pos;
            s = s->next;
        }
        cout<<s->data<<" -> ";
        vis[s->data] = 1;//標記這個點已經來過
        num += cnt;//更新num值
        ++cnt;//更新cnt值
    }
    cout<<"兔子可能的藏身之處 ";
    for(int i = 1; i <= n; ++i){
        if(!vis[i])cout<<i<<' ';//沒標記過即沒來過
    }
}

int main(){
    int n, k;
    cin>>n>>k;
    CLinkList L;
    Init_CLinkList(L);
    TailCreat_CLinkList(L, n);
    f(L, n, k);
    return 0;
}

總結:

連結串列其實沒那麼難搞,寫來寫去就那幾個函式,搞明白next、pre、邊界等小地方就沒問題遼,最重要的是要搞清楚單連結串列的各種操作,多碼幾遍,然後你就會發現其他的什麼雙向連結串列、迴圈連結串列、迴圈雙向連結串列等等都是小兒科o(︶︿︶)o

在這裡插入圖片描述