单链表

单链表中节点的定义

typedef struct LNode{
  int data;//数据域
  struct LNode *next;//定义一个同类型的指针,指向该节点的后继节点
}LNode, *Linklist;

最开始看单链表代码的时候,我就一直有一个非常非常大的疑问,这个疑问就是:LNode和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指针便成头节点后的第一个数据,也就是相当于把人的身体塞进去辽
    }
}

RE.从单链表开始的数据结构生活(bushi-LMLPHP

尾插法建立单链表

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,不然你要是以后循环就找不到循环结束条件了
}

RE.从单链表开始的数据结构生活(bushi-LMLPHP

在单链表中插入一个结点

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;
}
//初始化其实初始化的是头节点,是可以放在创建函数里面的,不过我喜欢将其重新定义为一个函数(好歹也是链表的大哥大,不得给它个面子嘛

头插法创建双向链表

RE.从单链表开始的数据结构生活(bushi-LMLPHP

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

尾插法创建双向链表

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

RE.从单链表开始的数据结构生活(bushi-LMLPHP

插入元素

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

RE.从单链表开始的数据结构生活(bushi-LMLPHP

删除元素

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

RE.从单链表开始的数据结构生活(bushi-LMLPHP

打印列表

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

RE.从单链表开始的数据结构生活(bushi-LMLPHP

04-21 08:55