[C语言]单链表及其基本操作

目录

引入:

​链表的创建:

链表的插入:

链表的删除:

单链表的整表删除:


引入:

当需要存储一些数据的时候,首先想到的肯定是定义一个数组来存储这些数据。但它有很多缺点:

第一、在输入数据之前不知道这组数据有多少个,所以我们很难确定要定义多大的一个数组,而且数组在定义之后就不能改变其大小,定义多了造成空间的浪费,定义少了又导致程序不能正常运行。

第二、在数组中间插入或删除一个数据时需要移动大量元素,需要耗费很多时间。

为了解决这个问题,我们想一下能不能构造一种数据结构:当存储一些数据时,能不能让每一个元素都知道他下一个元素的位置,这样,就可以通过前一个元素访问到下一个元素,以此类推,就可以把一整组数据遍历完。

如图:

扫描二维码关注公众号,回复: 13562399 查看本文章

 

 为了表示每个数据元素a i与其后继数据a i+1之间的逻辑关系,对于数据元素a i来说,除了存储本身的信息之外,还需存储一个指示其直接后继的信息(即后继元素的存储位置)。我们把存储数据元素信息的域称为数据域,把存储直接后继元素位置的域称为指针域,这两部分组成的一个整体,叫做结点

一般定义一个结构体来存储一个结点,上图:

struct ListNode
{
    int data;
    struct ListNode *next;
};

 当多个结点链结在一起时就组成了一个链表。

对于链表来说,也需要有头有尾。在链表的头部我们需要一个指针head指向链表的最开始,并且链表的最后一个结点的指针域为“空”。

上图:

链表的创建:

1、定义一个头指针;

2、创建第一个结点(即头结点)使头指针指向头结点,头结点的指针域为空;

3、创建结点,使上一个结点的指针指向这个结点,这个结点的指针域为空(循环);

注:头结点是为了更方便地对链表进行操作,在链表的第一个结点前设置一个结点,称为头结点,头结点的数据域可以不存储任何信息,也可以存储链表的长度等附加信息。)

代码实现:

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

struct ListNode{
    int data;
    struct ListNode *next;
};//创建一个结构体来表示链表的结点

int main()
{
    int n,a;
    struct ListNode *head;//声明一个头指针
    struct ListNode *p,*q,*t;

    t=(struct ListNode*)malloc(sizeof(struct ListNode));//创建头结点
    q=t;//指针q指向头结点
    head=t;//将头指针指向头结点
    t->next=NULL;//头结点的指针域为空
    
    scanf("%d",&n);
    for(int i=0;i<n;i++){
        scanf("%d",&a);
        p=(struct ListNode*)malloc(sizeof(struct ListNode));//动态申请空间来存放头结点,并用指针p指向这个结点
        p->data=a;//将数据存储到当前结点的数据域中
        p->next=NULL;//该结点的指针域为空
        q->next=p;//将上一个结点的指针指向当前结点
        q=p;//q指向当前结点(方便于下次循环时将本次循环创建的结点的指针指向下次循环创建的结点)
    }
    return 0;
}

上面这段代码在加入一个新的结点时,始终让新的结点都放到最后,这种算法称之为尾插法。

在添加新的结点时还有一种思路就是将新的结点插入到链表的最前端,这种算法简称为头插法。

来吧,展示!

/*该段代码只有创建新的结点那一部分于上一个的代码不相同*/
#include<stdio.h>
#include<stdlib.h>
struct ListNode{
    int data;
    struct ListNode *next;
};
int main()
{
    int n,a;
    struct ListNode *head;
    struct ListNode *p,*q,*t;
    t=(struct ListNode*)malloc(sizeof(struct ListNode));
    head=t;
    q=t;
    t->next=NULL;
    scanf("%d",&n);
    for(int i=0;i<n;i++){
        scanf("%d",&a);
        p=(struct ListNode*)malloc(sizeof(struct ListNode));//动态申请空间来存放头结点,并用指针p指向这个结点
        p->data=a;//将数据存储到当前结点的数据域中
        p->next=q->next;//该结点的指针指向头结点的指针指向的位置
        q->next=p;//将头结点的指针指向该结点
    }
    return 0;
}

链表的插入:

在将一个新的结点插入到链表中间的某一个位置时,只需要对需要插入的地方的前一个结点的指针域进行修改即可,无需对后面的元素操作。

若要将一个新的结点s插入到一个链表的第i个元素时(假设原链表中元素的个数大于等于i个),算法思路如下:

1、声明一个指针p指向链表的第一个结点,初始化j从1开始计数;

2、当j<i时就遍历链表,让p的指针向后移动,不断指向下一个节点;

3、将s的指针指向p的指针指向的结点,将p的指针指向s。s->next=p->next;p->next=s;

图解:

代码展示:

/*在第i个结点后插入一个新的结点s*/
struct ListNode* insert(struct ListNode* head,int i,int number)
{
    struct ListNode *p,*s;
    p=head;
    int j=1;
    while(j<i){
        p=p->next;
        ++j;
    }//寻找第i个结点

    s=(struct ListNode*)malloc(sizeof(struct ListNode));//生成新的结点
    s->data=number;
    s->next=p->next;//将p的后继结点赋值给s的后继
    p->next=s;//将s赋值给p的后继
    return head;
}

链表的删除:

单链表删除第i个结点的算法思路:

1、声明一个指针指向第一个结点,初始化j=1;

2、当j<i时遍历链表,让p的指针向后移动,不断指向下一个节点;

3、将要删除的点p->next赋值给q;将q->next赋值给p->next;释放q结点。

代码展示:

/*删除链表中第i个元素*/
struct ListNode* deleteone(struct ListNode* head,int i)
{
    struct ListNode *p,*q;
    p=head;
    int j=1;
    while(j<i){
        p=p->next;
        ++j;
    }//寻找第i个元素
    
q=p->next;//将要删除的点p->next赋值给q
    p->next=q->next;//将q->next赋值给p->next
    free(q);//释放q结点
    return head;
}

单链表的整表删除:

算法思路:

1、声明两个指针p和q;

2、将第一个结点赋值给p;

3、将下一个结点赋值给q,释放p,将q赋值给p,将下一个结点赋值给q(依次循环直至将整个链表全部删除);

代码展示:

struct ListNode* listdeleteall(struct Listnode *head)
{
    struct ListNode*p,*q;
    p=head;//将第一个结点赋值给p
    while(p){
        q=p->next;//将下一个结点赋值给q
        free(p);//释放p
        p=q;
    }
    return head;
}

猜你喜欢

转载自blog.csdn.net/qq_61007453/article/details/121639300