[数据结构10分钟入门] 面向初学者从零实现 -- 单链表

一、链表是什么

链表是一种通过指针串联在一起的线性结构,在内存中是分散存储的(数组在内存中连续分布),链表由一系列节点组成,每个节点都由数据域和指针域组成。主要有三种类型的链表:

1、单链表(本章介绍内容)

2、双链表

3、循环链表

链表与数组对比:

插入/删除 时间复杂度 查询 时间复杂度 适用场景
数组 O(n) O(1) 内容固定,查询较为频繁
链表 O(1) O(n) 内容不固定,较少查询

二、单链表定义

1、一个单链表的节点:

  • 数据域data:存放数据的地方
  • 指针域*next:指向下一个节点的地址

2、将节点链接起来,每个节点的*next指针指向下一节点,最后一个节点的指针指向NULL,就形成了单链表。

3、头指针的作用

设置一个头指针指向整条链表的首节点,这样对首节点的操作将和其余节点一致,无需使用两套处理逻辑。‍

三、节点结构体


typedef struct node {
    int data;   /**数据域*/
    struct node *next;    /**指向下一个节点*/      
}list_node;

因为每个节点的数据结构都是一样的,都为struct node,所以指针域*next的类型也是struct node。

四、创建链表


list_node* list_init()
{
    /**从内存动态申请 #1*/
    list_node *head = (list_node *)malloc(sizeof(list_node));
    /** #2*/
    if(head == NULL) {
        return NULL;
    }
    /**初始化头指针 #3*/
    head->data = 0;
    head->next = NULL;  //链表暂无节点,指针赋为NULL
    /** #4*/
    return head;
}

1、malloc动态申请list_node大小(8个字节)的内存,作为链表的头指针;

2、if(head == NULL) 对malloc的返回值进行判断;

3、初始化头指针,头指针的数据域data没有什么作用,可以用来代表链表的节点数量;

4、返回头指针。

五、插入节点


int list_insert(list_node *head, int data, int pos/** #1*/)
{
    list_node *p = head;

    /**如果要插入的位置比链表长,则属于非法操作 #2*/
    if (pos > p->data) {
        return -1;
    }

    /**动态申请一个节点*/
    list_node *node = (list_node *)malloc(sizeof(list_node));
    if (node == NULL) {
        return -1;
    }
    node->data = data;
    node->next = NULL;

    /**遍历链表,找到要插入的位置 #3*/
    for (int i=0; i<pos; i++) {
        p = p->next;
    }

    /**插入节点 #4*/
    node->next = p->next;
    p->next = node;

    /**链表长度加1*/
    head->data++;
   
    return 0;
}

1、参数pos代表节点插入的位置,首节点的位置为0;

2、对插入位置进行判断,不能大于链表长度,异常返回-1;

3、遍历链表找到新节点要插在哪个节点之后,p指向这个节点;

4、如下图假设node4要插入到node1与node2之间,此时的p指针指向节点为node1,新创建的节点指针node指向node4;

node->next = p->next; 即为 node4->next=node1->next=node2

p->next = node; 即为 node1->next=node4

六、删除节点


int list_delete_node(list_node *head, int pos)
{
    list_node *p = head;

    /**删除的位置比链表长,则属于非法操作*/
    if (pos > p->data) {
        return -1;      
    }
    /**遍历链表,找到要删除的节点的前一个节点的指针*/
    for (int i=0; i<pos; i++) {
        p = p->next;
    }
    /**记录将被删除的节点, 用于最后释放该节点内存 #1*/
    list_node *p2 = p->next;
   
    /**跳过被删除的节点,释放内存,完成删除 #2*/
    p->next = p->next->next;
    free(p2);
   
    head->data--;
    return 0;
}

1、被删除的节点的内存需要进行释放,避免内存泄漏;

2、如下图,假设要删除node2,则只需直接将node1的next指针指向node3

七、测试

目录结构

./
├── build                    //编译目录
├── CMakeLists.txt    //cmake 配置文件
├── list_01.c               //源码
├── list_01.h
└── test.c                   //测试文件

工程采用cmake构建,文件均经过测试,编译步骤如下:

1、mkdir build && cd build

2、cmake ../ && make

3、./list_test

源码地址在公众号同标题文章底部,公众号:Linux杂货铺

posted @ 2022-09-07 19:04  Linux杂货铺  阅读(60)  评论(0编辑  收藏  举报