【学习笔记】链表的增删改查以及经典例题总结(C语言实现)

单向链表


基本概念

  1. 链表由一个个节点通过指针连接组成,每个节点由数据部分和指针(非数据)部分组成。
  2. 数组物理储存方式是连续的,故可以通过下标访问数组,而链表的物理储存方式是不连续的,只能通过指针访问当前节点的下一个节点。
  3. 链表中的第一个节点为表头,最后一个节点指向NULL。(好像没有表尾这个说法。。)

链表的基本操作

插入操作

  1. 头插法:创建一个节点后,将该节点指针指向链表的头结点,头结点指向新的节点。
  2. 尾插法:创建一个节点后,链表中最后一个节点指针指向新的节点,注意新的节点指针要置为NULL。(防止出现空指针和野指针)
  3. 从指定节点后插入节点t:这个比较复杂,大概操作是这样的——定义两个指针p、q,其中p指向头结点、q指向p节点的前一节点。循环遍历链表直到q找到指定节点后结束遍历,此时执行 t -> next = p, q -> next = p; 操作。(如图所示)

【学习笔记】链表的增删改查以及经典例题总结(C语言实现)_第1张图片

【学习笔记】链表的增删改查以及经典例题总结(C语言实现)_第2张图片

  • 小技巧:尾插法的时候,可以定义一个变量指向尾节点,这样子每次插入都可以不用遍历整个链表实现O(1)级别的插入操作了。
  • 小技巧:创建一个指向头结点头结点的虚节点。这样子我们就不用对头结点的删除操作进行特殊处理了。
  • tips:注意每一步的顺序,有些步骤不能打乱,建议认真思考其中的原因,有利于你更加深刻的理解掌握链表的删除操作。

删除操作

删除指定节点通用操作:

定义两个指针p、q,其中p指向头结点、q指向p节点的前一节点。循环遍历链表直到p找到指定节点后结束遍历,此时执行 q = p -> next, free(p); 操作。(如图所示)

【学习笔记】链表的增删改查以及经典例题总结(C语言实现)_第3张图片

【学习笔记】链表的增删改查以及经典例题总结(C语言实现)_第4张图片


查找(修改)操作

  • 定义一个指向头结点的指针p,循环遍历链表直到找到指定节点后结束遍历,然后再进行查找操作(修改操作)。
  • 没了。(建议动手实现一下,有时候不动手都不知道自己那个地方会出问题)

参考代码

#include
#include
#include
//定义链表节点
typedef struct node
{
    int val;
    struct node* next;
}node;
void print_list(node*);
//在指定节点位置后插入新节点
//返回值:1真、0假
int list_insert(node* prehead, node* target, node* t)
{
    node* q = prehead, *p = q -> next;
    while(p && q != target)
    {
        q = p;
        p = p -> next;
    }
    if(q != target) return 0;
    q -> next = t;
    t -> next = p;
    //顺便打印看看链表情况
    print_list(prehead);
    return 1;
}

//尾插法
int list_insertback(node* prehead, node* t)
{
    node* q = prehead, *p = q -> next;
    while(p)
    {
        q = p;
        p = p -> next;
    }
    q -> next = t;
    //顺便打印看看链表情况
    print_list(prehead);
    return 1;
}

//删除指定节点
//返回值:1真、0假
int list_delete(node* prehead, int val)
{
    node* q = prehead, *p = q -> next;
    while(p && p -> val != val)
    {
        q = p;
        p = p -> next;
    }
    if(p -> val != val) return 0;
    q -> next = p -> next;
    free(p);
    //顺便打印看看链表情况
    print_list(prehead);
    return 1;
}

//打印链表所有节点值
void print_list(node* prehead)
{
    node* p = prehead -> next;
    printf("[");
    while(p)
    {
        printf("%d -> ", p -> val);
        p = p -> next;
    }
    printf("null]\n");
}

node* init_node(int val)
{
    node* t = (node*)malloc(sizeof(node));
    t -> val = val;
    t -> next = NULL;
    return t;
}

int main()
{
    srand(time(0));
    node* prehead = init_node(0);
    node* t1 = init_node(2);
    list_insertback(prehead, t1);
    node* t2 = init_node(5);
    list_insertback(prehead, t2);
    t2 = init_node(3);
    list_insert(prehead, t1, t2);
    list_delete(prehead, 2);
    list_delete(prehead, 5);
    return 0;
}

经典例题

在最近练习的链表题中,本蒟蒻发现链表题是有很多很有意思的题目的,而且面试官也很爱出这种题。对于链表题没有通用的模板,建议各位小伙伴萌要多加练习。把以下题型熟练掌握,你就可以征战校招面试的大部分链表题了!

  • LeetCode 707. 设计链表
  • LeetCode 2. 两数相加
  • LeetCode 237. 删除链表中的节点
  • LeetCode 21. 合并两个有序链表
  • LeetCode 61. 旋转链表
  • LeetCode 82.删除排序链表中的重复元素 II
  • LeetCode 206. 反转链表
  • LeetCode 92. 反转链表 II
  • LeetCode 141. 环形链表
  • LeetCode 142. 环形链表 II
  • LeetCode 1823. 找出游戏的获胜者

拓展:双向链表

如果熟练掌握单向链表的话,那么双向链表基本是没问题的。双向链表在节点中多了一个pre指针用来指向上一个节点,插入删除的话就比单向链表多了一部pre指针重定向而已。双向链表的好处是可以找到该节点的前驱节点,坏处是降低了结构体中数据部分的占比,产生而外开销。

你可能感兴趣的:(#,数据结构笔记,链表,c语言,数据结构)