数据结构:单链表 [c语言]

在这里插入图片描述

一、单链表的基本概念

  1. 链表不同于顺序表,顺序表底层采用数组作为存储容器,需要分配一块连续且完整的内存空间进行使用,而链表则不需要,它通过一个指针来连接各个分散的结点,形成了一个链状的结构,每个结点存放一个元素,以及一个指向下一个结点的指针,通过这样一个一个相连,如果不存在下一个节点就指向空,直到最后形成了链表。它不需要申请连续的空间,只需要按照顺序连接即可,虽然物理上可能不相邻,但是在逻辑上依然是每个元素相邻存放的,这样的结构叫做链表(单链表)。

  2. 链表可分为带头节点的链表,和不带头节点的链表

  • 带头结点的链表第一个节点不存放元素,且指向链表的首元节点(如果链表不为空的话),首元节点就是链表中存有有效数值的第一个节点
    在这里插入图片描述
  • 不带头节点的链表,首元节点就代替了头节点的位置
    在这里插入图片描述
    接下来我向铁汁们介绍的是带头节点的单链表
  1. 当前节点的前一个节点叫做该节点的"前趋",后一个节点叫做"后继"

二、单链表的存储结构

依赖结构体来实现链表的存储结构
用指针来实现指向下一个节点的操

typedef int E;

struct ListNode{
    E element;//节点所存储的元素
    struct ListNode * next;//指向下一个节点的地址,建立联系,实现逻辑意义上的相邻
};

在这里插入图片描述

三、对单链表的操作

  1. 初始化单链表
void initList(Node node){
    node->next = NULL;//因为链表为空,所以头节点指向空
}
  1. 对单链表进行插入
    想要插入必须先找到当前节点的前驱节点,不然链表会断
    先遍历链表找到指定序号节点的前趋节点(因为有头节点,所以最index == 0时 当前节点为目标节点的前驱节点)
    找到后先创建节点,并对其进行赋值
    让新创建的节点指向前驱节点的后继
    在这里插入图片描述
    再让前驱节点指向新创建的节点
    在这里插入图片描述
    从而便完成了插入的操作
_Bool insertList(Node current,E element,int index){
    if(index < 1) return 0;
    while(--index){//先遍历链表找到指定序号节点的前趋节点
        current = current->next;
        if(current == NULL){//还没遍历到就指向空了说明该序号不存在
            return 0;
        }
    }
    Node node = malloc(sizeof(struct ListNode));
    if(node == NULL) return  0;//没创建成功就返回false
    node->element = element;
    node->next = current->next;
    current->next = node;
    return 1;
}

我们来测试一下

int main() {
    struct ListNode head;
    initList(&head);
    for (int i = 0; i < 3; ++i) {
        insertList(&head, (i+1) * 100, 1);   //依次插入3个元素
    }
    printList(&head);   //打印一下看看
}

得到结果
在这里插入图片描述

  1. 遍历打印单链表
    从头节开始,打印后继节点,如果后继节点为空就停止打印(这样就打印了每一个节点)
    在这里插入图片描述
void printList(Node head) {
    while (head->next) {
        head = head->next;//迭代节点
        printf("%d ", head->element);
    }
    printf("\n");
}
  1. 删除指定节点
    与插入同理,先找到前驱,再让前驱指向,待删除节点的后继后把带删除节点free掉即可
_Bool deleteList(Node pre,int index){
    if(index < 1) return 0;
    while(--index){
        pre = pre->next;
        if(pre == NULL){
            return 0;
        }
    }
    if(pre->next == NULL) return 0;
    Node temp = pre->next;
    pre->next = temp->next;
    free(temp);
    return 1;
}

咱测试一下

int main() {
    struct ListNode head;
    initList(&head);
    for (int i = 0; i < 3; ++i) {
        insertList(&head, (i+1) * 100, 1);   //依次插入3个元素
    }
    printList(&head);   //打印一下看看
    deleteList(&head,1);
    printList(&head);
}

在这里插入图片描述

  1. 查找存有指定元素的序号
int findList(Node head,E element){
    int index = 1;//位序计数器
    while(head->next){//用下一个节点做测试条件
        head = head->next;//进来后将下一个节点赋给当前节点,if测试下一个节点,因为当前节点已经再上一次循环测试过了
        if(head->element == element){
            return index;
        }
        index++;
    }
    return -1;//走到链表尾部就说明没找到
}
  1. 获取指定序号的所存的元素
E * getList(Node head,int index){
    if(index < 1) return 0;
    while(index--){
        head = head->next;
        if(head == NULL){
            return 0;
        }
    }
    return &head->element;
}
  1. 获取链表长度(不包含头节点)
int sizeList(Node head){
    int index = 0;
    while(head->next){//用下一个节点做测试条件
        head = head->next;
        index++;
    }
    return index;
}

咱们测试一下

int main() {
    struct ListNode head;
    initList(&head);
    for (int i = 0; i < 3; ++i) {
        insertList(&head, (i+1) * 100, 1);   //依次插入3个元素
    }
    printList(&head);   //打印一下看看
    printf("%d\n", sizeList(&head));
    deleteList(&head,1);
    printList(&head);
    printf("%d\n",sizeList(&head));
}

在这里插入图片描述

  1. 完整代码如下
#include <stdio.h>
#include <stdlib.h>
typedef int E;

struct ListNode{
    E element;
    struct ListNode * next;//指向下一个节点,建立联系,实现逻辑意义上的相邻
};

typedef struct ListNode* Node;
//头节点初始化
void initList(Node node){
    node->next = NULL;
}
_Bool insertList(Node current,E element,int index){
    if(index < 1) return 0;
    while(--index){
        current = current->next;
        if(current == NULL){
            return 0;
        }
    }
    Node node = malloc(sizeof(struct ListNode));
    if(node == NULL) return  0;
    node->element = element;
    node->next = current->next;
    current->next = node;
    return 1;
}

void printList(Node head) {
    while (head->next) {
        head = head->next;
        printf("%d ", head->element);
    }
    printf("\n");
}

_Bool deleteList(Node pre,int index){
    if(index < 1) return 0;
    while(--index){
        pre = pre->next;
        if(pre == NULL){
            return 0;
        }
    }
    if(pre->next == NULL) return 0;
    Node temp = pre->next;
    pre->next = temp->next;
    free(temp);
    return 1;
}
E * getList(Node head,int index){
    if(index < 1) return 0;
    while(index--){
        head = head->next;
        if(head == NULL){
            return 0;
        }
    }
    return &head->element;
}

int findList(Node head,E element){
    int index = 1;//位序计数器
    while(head->next){//用下一个节点做测试条件
        head = head->next;//进来后将下一个节点赋给当前节点,if测试下一个节点,因为当前节点已经再上一次循环测试过了
        if(head->element == element){
            return index;
        }
        index++;
    }
    return -1;//走到链表尾部就说明没找到
}

int sizeList(Node head){
    int index = 0;
    while(head->next){//用下一个节点做测试条件
        head = head->next;
        index++;
    }
    return index;
}

总结

  1. 链表的删除操作很快,与链表长度无关。想象一下:你拿着一条项链的一个环,如何拆掉这个环?只需要拆开与它相邻的两个环,然后把相邻的两环接到一起即可。无论项链长短,拆除的操作没有区别。
  2. 链表的插入也很快,与链表长度无关。
  3. 链表的查找需要从头遍历,与数组类似,越长速度越慢。但由于没有下标可用,链表的遍历实际上比数组更慢一些。而且在插入、删除节点时,要先获取到必要的指针,才能删除或添加。
  4. 所以,理论上,在需要经常插入节点、删除节点,而且数据量比较大的场合,非常适合使用链表。

好滴!本次分享到这就结束了
如果对铁汁你有帮助的话,记得点赞👍+收藏⭐️+关注➕
我在这先行拜谢了:)

  • 20
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 10
    评论
二.内核链表 内核链表是一种链表,Linux内核中的链表都是用这种形式实现的 1.特性 内核链表是一种双向循环链表,内核链表的节点节点结构中只有指针域 使用内核链表的时候,将内核链表作为一个成员放入到一个结构体中使用 我们在链表中找到内核链表结构的地址,通过这个地址就可以找到外部大结构体的地址,通过大结构体就可以访问其中的成员 优势: 内核链表突破了保存数据的限制,可以用内核链表来保存任何数据(使用一种链表表示各种类型的数据,通用性很强) 内核链表中只有指针域,维护起来更加方便,效率更高 2.使用 内核链表在内核中已经被实现,我们只需要调用其接口直接使用即可 内核链表的实现代码在内核源代码的list.h文件中 3.源代码分析 (1)节点结构: struct list_head { struct list_head *next, *prev;//前置指针 后置指针 }; (2)初始化 #define INIT_LIST_HEAD(ptr) do { \ (ptr)->next = (ptr); (ptr)->prev = (ptr); \ } while (0) (3)插入 //从头部插入 static inline void list_add(struct list_head *new, struct list_head *head)//传入要插入的节点和要插入的链表 { __list_add(new, head, head->next); } //从尾部插入 static inline void list_add_tail(struct list_head *new, struct list_head *head) { __list_add(new, head->prev, head); } (4)通过节点找到外部结构体的地址 //返回外部结构体的地址,第一个参数是节点地址,第二个参数是外部结构体的类型名,第三个参数是节点在外部结构体中的成员名 #define list_entry(ptr, type, member) ((type *)((char *)(ptr)-(unsigned long)(&((type *)0)->member))) (5)遍历内核链表 //遍历内核链表 #define list_for_each(pos, head) \ for (pos = (head)->next; pos != (head); \ pos = pos->next) //安全遍历内核链表 #define list_for_each_safe(pos, n, head) \ for (pos = (head)->next, n = pos->next; pos != (head); \ pos = n, n = pos->next) 二.内核链表 内核链表是一种链表,Linux内核中的链表都是用这种形式实现的 1.特性 内核链表是一种双向循环链表,内核链表的节点节点结构中只有指针域 使用内核链表的时候,将内核链表作为一个成员放入到一个结构体中使用 我们在链表中找到内核链表结构的地址,通过这个地址就可以找到外部大结构体的地址,通过大结构体就可以访问其中的成员 优势: 内核链表突破了保存数据的限制,可以用内核链表来保存任何数据(使用一种链表表示各种类型的数据,通用性很强) 内核链表中只有指针域,维护起来更加方便,效率更高 2.使用 内核链表在内核中已经被实现,我们只需要调用其接口直接使用即可 内核链表的实现代码在内核源代码的list.h文件中 3.源代码分析 (1)节点结构: struct list_head { struct list_head *next, *prev;//前置指针 后置指针 }; (2)初始化 #define INIT_LIST_HEAD(ptr) do { \ (ptr)->next = (ptr); (ptr)->prev = (ptr); \ } while (0) (3)插入 //从头部插入 static inline void list_add(struct list_head *new, struct list_head *head)//传入要插入的节点和要插入的链表 { __list_add(new, head, head->next); } //从尾部插入 static inline void list_add_tail(struct list_head *new, struct list_head *head) { __list_add(new, head->prev, head); } (4)通过节点找到外部结构体的地址 //返回外部结构体的地址,第一个参数是节点地址,第二个参数是外部结构体的类型名,第三个参数是节点在外部结构体中的成员名 #define list_entry(ptr, type, member) ((type *)((char *)(ptr)-(unsigned long)(&((type *)0)->member))) (5)遍历内核链表 //遍历内核链表 #define list_for_each(pos, head) \ for (pos = (head)->next; pos != (head); \ pos = pos->next) //安全遍历内核链表 #define list_for_each_safe(pos, n, head) \ for (pos = (head)->next, n = pos->next; pos != (head); \ pos = n, n = pos->next) 二.内核链表 内核链表是一种链表,Linux内核中的链表都是用这种形式实现的 1.特性 内核链表是一种双向循环链表,内核链表的节点节点结构中只有指针域 使用内核链表的时候,将内核链表作为一个成员放入到一个结构体中使用 我们在链表中找到内核链表结构的地址,通过这个地址就可以找到外部大结构体的地址,通过大结构体就可以访问其中的成员 优势: 内核链表突破了保存数据的限制,可以用内核链表来保存任何数据(使用一种链表表示各种类型的数据,通用性很强) 内核链表中只有指针域,维护起来更加方便,效率更高 2.使用 内核链表在内核中已经被实现,我们只需要调用其接口直接使用即可 内核链表的实现代码在内核源代码的list.h文件中 3.源代码分析 (1)节点结构: struct list_head { struct list_head *next, *prev;//前置指针 后置指针 }; (2)初始化 #define INIT_LIST_HEAD(ptr) do { \ (ptr)->next = (ptr); (ptr)->prev = (ptr); \ } while (0) (3)插入 //从头部插入 static inline void list_add(struct list_head *new, struct list_head *head)//传入要插入的节点和要插入的链表 { __list_add(new, head, head->next); } //从尾部插入 static inline void list_add_tail(struct list_head *new, struct list_head *head) { __list_add(new, head->prev, head); } (4)通过节点找到外部结构体的地址 //返回外部结构体的地址,第一个参数是节点地址,第二个参数是外部结构体的类型名,第三个参数是节点在外部结构体中的成员名 #define list_entry(ptr, type, member) ((type *)((char *)(ptr)-(unsigned long)(&((type *)0)->member))) (5)遍历内核链表 //遍历内核链表 #define list_for_each(pos, head) \ for (pos = (head)->next; pos != (head); \ pos = pos->next) //安全遍历内核链表 #define list_for_each_safe(pos, n, head) \ for (pos = (head)->next, n = pos->next; pos != (head); \ pos = n, n = pos->next) C语言下的单链表,可以增加,删除,查找,销毁节点。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 10
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值