关于《链表》的基础解析(残卷)

本文详细介绍了C语言中的链表概念,包括单链表、双链表和循环链表,涉及移除链表元素、设计链表结构、反转链表、两两交换元素及删除倒数第N个元素等基础操作。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

链表作为C语言中的重点,无疑也是初学者认为的难点。本篇我们将对链表及其常见的基础操作进行详细的解析。


目录

1.认识链表

2.移除链表元素

3.设计链表

4.反转链表

5.两两交换链表中的元素

6.删除链表的倒数第N个元素

7.链表相交

8.环形链表


1.认识链表

何为链表?链表是通过指针串联起多个数据项的线性结构;每一个数据项由数据部分(存放项的数据)和指针部分(存放指向下一个数据项的指针)组成,首个数据项由单独定义项指向链头。

链表的类型:

 单链表:上述的简单链表即为单链表。

双链表:每一个数据项有两个指针部分,一个指针部分指向下一个数据项,一个指针部分指向上一个指针项。

循环链表:链表首尾相连。

循环链表可以解决约瑟夫(Josephus)问题。

链表的存储:

数组在内存中是连续分布的,但是链表在内存中并不是连续分布的。

链表通过指针部分的指针连接内存中的各个节点。

链表中的节点在内存中不是连续分布的 ,而是散乱分布在内存中的某地址上,分配机制取决于操作系统的内存管理。

链表的定义:
struct cell {
	int x;
	struct cell* next;
};//单链表节点结构体定义

作为动态数据结构,我们需要引入动态变量。此时使用malloc函数

head = (struct cell*)malloc(sizeof(struct cell));

综上,我们对链表已经有了一个基本的认知。在认识链表时,最好将抽象的文字具象化,把链表的基本结构画出来,有助于理解和解题,下面介绍链表的系列基本操作并附上例题。 

2.移除链表元素

 删除链表元素的基本算法是将该元素上一个指针域指向该元素下一个数据项;代码如下:

p = p->next;//指向下一项
free(p0->next);//删除掉p原先所指的一项
p0->next = p;

 例:给你一个链表的头节点 head 和一个整数 val ,请你删除链表中所有满足 Node.val == val 的节点,并返回 新的头节点 。

示例 1:

输入:head = [1,2,6,3,4,5,6], val = 6
输出:[1,2,3,4,5]

示例 2:

输入:head = [], val = 1
输出:[]

示例 3:

输入:head = [7,7,7,7], val = 7
输出:[]

思路 :在遍历链表的同时判断哪个节点是目标值。

struct ListNode* removeElements(struct ListNode* head, int val) {
    struct ListNode*temp;
    while(head&&head->val==val){
        temp=head;
        head=head->next;
        free(temp);
    }/*对头节点的单独处理,当头节点存在并且头节点的数据值等于目标值时,删除头节点。将新的头节点设置在head->next*/
    struct ListNode*cur=head;
    while(cur&&(temp=cur->next)){
        if(temp->val==val){
            cur->next=temp->next;
            free(temp);
        }   //当找到目标值,根据删除算法删掉目标值
        else{
            cur=cur->next;  //若没有找到目标值,则继续遍历链表
        }
    }
    return head;
}

3.设计链表

设计链表的基础在于创建链表,之后在此基础上进行链表的基本操作。

链表的创建参考 关于《链表》(单向链表的简单操作·上)

例:你可以选择使用单链表或者双链表,设计并实现自己的链表。

单链表中的节点应该具备两个属性:val 和 next 。val 是当前节点的值,next 是指向下一个节点的指针/引用。

如果是双向链表,则还需要属性 prev 以指示链表中的上一个节点。假设链表中的所有节点下标从 0 开始。

实现 MyLinkedList 类:

  • MyLinkedList() 初始化 MyLinkedList 对象。
  • int get(int index) 获取链表中下标为 index 的节点的值。如果下标无效,则返回 -1 。
  • void addAtHead(int val) 将一个值为 val 的节点插入到链表中第一个元素之前。在插入完成后,新节点会成为链表的第一个节点。
  • void addAtTail(int val) 将一个值为 val 的节点追加到链表中作为链表的最后一个元素。
  • void addAtIndex(int index, int val) 将一个值为 val 的节点插入到链表中下标为 index 的节点之前。如果 index 等于链表的长度,那么该节点会被追加到链表的末尾。如果 index 比长度更大,该节点将 不会插入 到链表中。
  • void deleteAtIndex(int index) 如果下标有效,则删除链表中下标为 index 的节点。

示例:

输入
["MyLinkedList", "addAtHead", "addAtTail", "addAtIndex", "get", "deleteAtIndex", "get"]
[[], [1], [3], [1, 2], [1], [1], [1]]
输出
[null, null, null, null, 2, null, 3]

解释
MyLinkedList myLinkedList = new MyLinkedList();
myLinkedList.addAtHead(1);
myLinkedList.addAtTail(3);
myLinkedList.addAtIndex(1, 2);    // 链表变为 1->2->3
myLinkedList.get(1);              // 返回 2
myLinkedList.deleteAtIndex(1);    // 现在,链表变为 1->3
myLinkedList.get(1);              // 返回 3

 思路:本题对于链表的设计基本囊括了链表的常见操作,

  • 获取链表第index个节点的数值
  • 在链表的最前面插入一个节点
  • 在链表的最后面插入一个节点
  • 在链表第index个节点前面插入一个节点
  • 删除链表的第index个节点

那么我们就要注意链表的基本算法,除了删除算法外,插入算法也是基本。链表的插入就是我们常说的链表中添加元素。同样是移动指针域的指向。

r->next=p;
p0->next=r;
p0=r;

 具体的插入与删除算法参考关于《链表》(单向链表的简单操作·下)

在理解题意并掌握基本操作后设计链表:

typedef struct MyLinkedList {
    int val;
    struct MyLinkedList* next;
}MyLinkedList;

/** Initialize your data structure here. */

MyLinkedList* myLinkedListCreate() {
    //这个题必须用虚拟头指针,参数都是一级指针,头节点确定后没法改指向了!!!
    MyLinkedList* head = (MyLinkedList *)malloc(sizeof (MyLinkedList));
    head->next = NULL;
    return head;
}

/** Get the value of the index-th node in the linked list. If the index is invalid, return -1. */
int myLinkedListGet(MyLinkedList* obj, int index) {
    MyLinkedList *cur = obj->next;
    for (int i = 0; cur != NULL; i++){
        if (i == index){
            return cur->val;
        }
        else{
            cur = cur->next;
        }
    }
    return -1;
}

/** Add a node of value val before the first element of the linked list. After the insertion, the new node will be the first node of the linked list. */
void myLinkedListAddAtHead(MyLinkedList* obj, int val) {
    MyLinkedList *nhead = (MyLinkedList *)malloc(sizeof (MyLinkedList));
    nhead->val = val;
    nhead->next = obj->next;
    obj->next = nhead;

}

/** Append a node of value val to the last element of the linked list. */
void myLinkedListAddAtTail(MyLinkedList* obj, int val) {
    MyLinkedList *cur = obj;
    while(cur->next != NULL){
        cur = cur->next;
    }
    MyLinkedList *ntail = (MyLinkedList *)malloc(sizeof (MyLinkedList));
    ntail->val = val;
    ntail->next = NULL;
    cur->next = ntail;
}

/** Add a node of value val before the index-th node in the linked list. If index equals to the length of linked list, the node will be appended to the end of linked list. If index is greater than the length, the node will not be inserted. */
void myLinkedListAddAtIndex(MyLinkedList* obj, int index, int val) {
    if (index == 0){
        myLinkedListAddAtHead(obj, val);
        return;
    }
    MyLinkedList *cur = obj->next;
    for (int i = 1 ;cur != NULL; i++){
        if (i == index){
            MyLinkedList* newnode = (MyLinkedList *)malloc(sizeof (MyLinkedList));
            newnode->val = val;
            newnode->next = cur->next;
            cur->next = newnode;
            return;
        }
        else{
            cur = cur->next;
        }
    }
}

/** Delete the index-th node in the linked list, if the index is valid. */
void myLinkedListDeleteAtIndex(MyLinkedList* obj, int index) {
    if (index == 0){
        MyLinkedList *tmp = obj->next;
        if (tmp != NULL){
            obj->next = tmp->next;
            free(tmp);     
        }
        return;
    }
    MyLinkedList *cur = obj->next;
    for (int i = 1 ;cur != NULL && cur->next != NULL; i++){
        if (i == index){
            MyLinkedList *tmp = cur->next;
            if (tmp != NULL) {
                cur->next = tmp->next;
                free(tmp);
            }
            return;
        }
        else{
            cur = cur->next;
        }
    }
    
}

void myLinkedListFree(MyLinkedList* obj) {
    while(obj != NULL){
        MyLinkedList *tmp = obj;
        obj = obj->next;
        free(tmp);
    }
}

4.反转链表

 如果要实现链表的反转,只需要改变数据项中next指针的指向,而不必重新定义一个新的链表。

例: 给你单链表的头节点 head ,请你反转链表,并返回反转后的链表。

示例 1:

输入:head = [1,2,3,4,5]
输出:[5,4,3,2,1]

示例 2:

输入:head = [1,2]
输出:[2,1]

示例 3:

输入:head = []
输出:[]

5.两两交换链表中的元素

6.删除链表的倒数第N个元素

7.链表相交

8.环形链表

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

无须logic ᭄

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值