Bootstrap

数据结构——链表笔记

链表入门


数据结构中最基础且简单的部分就是线性结构的链表。数据结构之后的内容都能经常见到与链表操作相似的部分。理解和掌握链表,对于数据结构之后的学习至关重要。

数组,可以用于存放数据的有序的元素序列。,
一个数组的例子:

元素11534
下标012

特点如下:

  • 可以存放的元素个数是固定的
  • 存储空间连续,存储密度
  • 访问元素通过索引(下标)访问,时间复杂度为O(1)

可以看出,数组的空间和可以容纳的元素个数在数组创建之初就确定下来了。在确定元素个数时,利用数组无疑是十分高效便捷的。但是,当我们需要存储不定个数据,且数据类型又复杂多样时,又该如何处理呢?这时,就可以引申出链表这一概念,先来看看链表的特点:

  • 长度不固定,可以按照需求增删
  • 存储空间不连续,存储密度小
  • 访问元素从表头开始,循环遍历到需要的元素,时间复杂度为O(n)
  • 在特定元素之间增删元素不需要移动大部分元素,时间复杂度为O(1)

链表的实现

如何把一个个在物理上分散的数据联系起来呢?C中的结构体就能很好地实现链表。首先,结构体可以包含多个成员,每一个成员都可以是不同的数据类型,这样就可以储存多样化的数据。在结构体中再添加指向该结构体类型的指针,这样即可将同类型的结构体相连从而实现链表。

C语言实现:

#include <stdio.h>
#include <stdlib.h>
 
#define NULL 0
 
struct student//定义结构体
{
    long num;
    float score;//储存数据的成员
    struct student *next;//指向同类结构体的指针
};
 
void main()
{
    struct student a, b, c, *head, *p;//*head是头指针,存储链表的第一个元素的地址
    a.num = 99101; a.score = 89.5;
    b.num = 99103; b.score = 90;
    c.num = 99107; c.score = 85;//对结点的 num 和 score 成员赋值
    head = &a;//将结点 a 的起始地址赋给头指针 head
    a.next = &b;//将结点 b 的起始地址赋给 a 结点的 next 成员
    b.next = &c;
    c.next = NULL;// c 结点的 next 成员不存放其他结点地址
    p = head;//使 p 指针指向 a 结点
    do
    {
        printf("%ld %5.1f\n", p->num, p->score);// 输出 p 指向的结点的数据
        p = p->next;//使 p 指向下一结点
    }while(p != NULL);//输出完 c 结点后 p 的值为 NULL
    system("pause");
}

//源码出处:https://www.runoob.com/w3cnote/c-structures-intro.html,我对部分代码加以注释补充说明
处理动态链表所需要的函数
  1. malloc函数
    void *malloc(size);
    该函数的功能是在内存的动态存储区分配一片长度为size的空间,函数的返回值是这块被开辟出的空间的起始地址。若函数因为某些原因开辟空间失败,则会返回NULL.
  2. free函数
    void free(void *p);
    该函数功能为将指针p指向的内存区释放,使得该内存区可被其他变量使用

实现代码如下

#include <stdio.h>
#include <stdlib.h>
 
#define NULL 0
#define LEN sizeof(struct student)//定义结构体大小常数便于使用
 
struct student//定义结构体(节点)
{
    long num;
    float score;
    struct student *next;
};
 
struct student *create()//创建链表
{
    struct student *p1, *p2, *head;
    int num;
    float score;
    int n = 0;//计数器,判断
 
    head = NULL;
 
    p1 = p2 = (struct student *)malloc(LEN);//向指针所指区域开辟空间
 
    printf("please input num and score.\n");
    scanf("%d,%f", &p1->num, &p1->score);//将值传入创建的节点内
 
    while(p1->num != 0)
    {
        n ++;
        if(n == 1)//首次循环时记录表头地址
            head = p1;
        else//之后则正常插入节点
            p2->next = p1;
        p2 = p1;
        p1 = (struct student *)malloc(sizeof(struct student));
 
        printf("please input num and score.\n");
 
        scanf("%d,%f", &p1->num, &p1->score);
    }
    p2->next = NULL;
    return head;
}
 
void printlist(struct student *head)//输出链表内容
{
    struct student *p;
    p = head;
    if(head != NULL)
    {
        do
        {
            printf("num=%d score=%f\n", p->num, p->score);
            p = p->next;
        }while(p != NULL);
    }
}
 
void main()
{
    struct student *head;
    head = create();
    printlist(head);
    system("pause");
}

链表之中还有具体细分的类别:

  • 单链表:

    • 仅包含指向下一节点的指针,意味着单链表只能单向遍历
  • 双链表:

    • 同时包含指向上一个节点的与下一个节点的指针,可以双向遍历
    • 操作更复杂,但使用更便捷
  • 循环链表:

    • 由尾结点与头结点相连,形成类似环状结构
    • 类似单链表,但情况更特殊
;