leetCode 707---单链表的创建及相关方法实现(C语言版)

本文详细介绍如何使用C语言实现单链表,包括创建、获取、插入和删除节点等核心操作,以及完整的代码示例。

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

题目描述:

使用C语言实现单链表,单链表中的节点应该具有两个属性:val 和 next。val 是当前节点的值,next 是指向下一个节点的指针/引用。如果要使用双向链表,则还需要一个属性 prev 以指示链表中的上一个节点。假设链表中的所有节点都是 0-index 的。

在链表类中实现这些功能:

get(index):获取链表中第 index 个节点的值。如果索引无效,则返回-1。
addAtHead(val):在链表的第一个元素之前添加一个值为 val 的节点。插入后,新节点将成为链表的第一个节点。
addAtTail(val):将值为 val 的节点追加到链表的最后一个元素。
addAtIndex(index,val):在链表中的第 index 个节点之前添加值为 val  的节点。如果 index 等于链表的长度,则该节点将附加到链表的末尾。如果 index 大于链表长度,则不会插入节点。如果index小于0,则在头部插入节点。
deleteAtIndex(index):如果索引 index 有效,则删除链表中的第 index 个节点

思考设计:

首先要确定是否设置头节点,本题使用头节点。我一直对头节点和头指针这两个东西很迷,这几天我再单独写篇文章来和大家交流一下。

  • 节点设计

单链表的节点包括数据域和指针域,代码如下:

/**
 * 构造节点
 */
typedef struct
{
    int val;                   //数据域,存储有我们需要使用的数据
    struct MylinkedList *next; //指针域,存储有指向下一个节点的指针
} MyLinkedList;
  • 方法实现

1、myLinkedListCreate():构造函数,初始化链表

众所周知,我们使用头结点来代表整个列表。下面的obj就是头节点,可以代替整个链表。

/** 
 * 构造一个有头结点的空链表 
 */
MyLinkedList *myLinkedListCreate()
{
    MyLinkedList *obj = (MyLinkedList *)malloc(sizeof(MyLinkedList)); //在内存空间中开辟一块和节点数据结构大小一样的内存空间,构造头节点
    obj->next = NULL;                                                 //头节点的指针域置空
    return obj;
}

2、myLinkedListGet(MyLinkedList *obj, int index),获取链表中第 index 个节点的值。如果索引无效,则返回-1

链表中第0个节点指的是头节点后面那一个,其他的以此类推。注意index取负数、链表为空、index大于链表节点数的情况

/** 
 * 获取链表中第 index 个节点的值。如果索引无效,则返回-1 
 */
int myLinkedListGet(MyLinkedList *obj, int index)
{
    int counter = 0; //计数器
    int value = 0;
    MyLinkedList *p = obj->next; //定义一个指针,并指向头节点的下一个节点,即第0个节点
    while (p && counter < index)  //当P存在而且没有遍历到第index节点时继续移动指针,直到counter=index时跳出循环;若P为空也会跳出循环,此时counter不一定等于index
    {
        p = p->next;
        counter++;
    }
    if (!p || counter > index) //跳出while循环后counter<=index,若P为空说明查询节点的index大于链表节点数,返回-1;若用户输入的index是一个负数,虽然P不为空,但这种情况应该返回-1
    {
        return -1;
    }
    else
    {
        value = p->val;
        return value;
    }
}

3、myLinkedListAddAtHead(MyLinkedList *obj, int val):头插法的实现,就是在头节点和第0个节点间插入一个新节点

/** 
 * 头插法的实现
 * 在头节点和第0个节点间插入一个新节点
 */
void myLinkedListAddAtHead(MyLinkedList *obj, int val)
{
    MyLinkedList *p = obj;                                             //将头节点赋值给指针P
    MyLinkedList *head = (MyLinkedList *)malloc(sizeof(MyLinkedList)); //新建一个节点
    head->val = val;                                                   //给新节点数据域赋值
    head->next = p->next;                                              //让新节点指向头节点的下一个节点
    p->next = head;                                                    //让头节点指向新节点完成插入
}

4、myLinkedListAddAtTail(MyLinkedList *obj, int val): 尾插法的实现,在链表最后的节点后再插入新的节点

/** 
 * 尾插法的实现
 * 在链表最后的节点后再插入新的节点
*/
void myLinkedListAddAtTail(MyLinkedList *obj, int val)
{
    MyLinkedList *tail = (MyLinkedList *)malloc(sizeof(MyLinkedList));
    tail->val = val;
    tail->next = NULL; //初始化新节点,指针域需要置空
    MyLinkedList *node = obj;
    while (node->next) //从头节点开始遍历链表,当节点的指针域为空时跳出循环,表明找到链表尾节点
    {
        node = node->next;
    }
    node->next = tail; //新节点成为尾节点
}

5、myLinkedListAddAtIndex(MyLinkedList *obj, int index, int val):在第index节点前插入新节点,若index为负数则插入到链表头部,若index超出链表长度则不插入;这里也要注意链表为空,index越界、index为负数的情况。

/** 
 * 在第index节点前插入新节点,若index为负数则插入到链表头部,若index超出链表长度则不插入
 */
void myLinkedListAddAtIndex(MyLinkedList *obj, int index, int val)
{
    MyLinkedList *insertedNode = (MyLinkedList *)malloc(sizeof(MyLinkedList));
    insertedNode->val = val;
    MyLinkedList *node = obj;
    int counter = 0;
    if (index < counter) // index为负数时插入到链表头部
    {
        insertedNode->next = node->next;
        node->next = insertedNode;
    }
    while (node && counter < index) //counter等于index时,node的下标应该是index -1,这里和myLinkedListGet方法不同。差别在于当counter是0时,本方法node是头节点,但在myLinkedListGet方法中P是第0个节点
    {
        node = node->next;
        counter++;
    }
    if (!node || counter > index)
    {
        return -1;
    }
    insertedNode->next = node->next;
    node->next = insertedNode;
}

6、myLinkedListDeleteAtIndex(MyLinkedList *obj, int index):若index有效的话则删除第index个节点,注意index为负数、链表为空的情况

/** 
 * 若index有效的话则删除第index个节点
 * 删除不同于插入。插入时,涉及第index、index-1两个节点,若第index-1个节点是尾节点,虽然第index个节点不存在,但是依然能插入;
 * 删除时,涉及第index-1、index、index+1三个节点,如果第index-1个节点为尾节点,则第index+1个节点即下面的temNode->next不存在,不能完成指针的赋值
 * 所以在判断循环时,判断条件是 node->next,这样才能保证第index+1个节点即下面的temNode->next存在(特殊条件就是为NULL)
 */
void myLinkedListDeleteAtIndex(MyLinkedList *obj, int index)
{
    MyLinkedList *node = obj;
    int counter = 0;
    while (node->next && counter < index) //找到第index-1 个节点,但需要保证第index+1个节点即下面的temNode->next存在(特殊条件就是为NULL)
    {
        node = node->next;
        counter++;
    }
    if (!(node->next) || counter > index)
    {
        return -1;
    }
    else
    {
        MyLinkedList *temNode = (MyLinkedList *)malloc(sizeof(MyLinkedList));
        temNode = node->next;
        node->next = temNode->next;
        free(temNode);
    }
}

7、myLinkedListFree(MyLinkedList *obj):整表删除,从头节点开始删除

/**
 * 整表删除
 */
void myLinkedListFree(MyLinkedList *obj)
{
    MyLinkedList *node = obj->next; //这里node指向的是头节点的下一个节点,与前面有所不同。整表删除时要将头节点以外的所有节点删除,保留头节点
    MyLinkedList *temNode = (MyLinkedList *)malloc(sizeof(MyLinkedList));
    while (node) //判断除当前结点是否为空,不为空时删除此节点
    {
        temNode = node->next; //删除时要保留被删除节点的下一个节点
        free(node);
        node = temNode;
    }
    obj->next = NULL;
}

8、主函数: 大家可以自己试一试,看看结果对不对。

int main()
{
    MyLinkedList *obj = myLinkedListCreate();

    int param_1 = myLinkedListGet(obj, 0);
    printf("%d\n", param_1); 

    myLinkedListAddAtHead(obj, 0);
    int param_2 = myLinkedListGet(obj, 0);
    printf("%d\n", param_2); 

    myLinkedListAddAtTail(obj, 1); 
    int param_3 = myLinkedListGet(obj, 0);
    printf("%d\n", param_3); 

    myLinkedListAddAtIndex(obj, 1, 2); 
    int param_4 = myLinkedListGet(obj, 1);
    printf("%d\n", param_4); 

    myLinkedListDeleteAtIndex(obj, 2); //02

    myLinkedListFree(obj);
    int param_5 = myLinkedListGet(obj, 0);
    printf("%d\n", param_5); 
}
  • 所有代码:

#include <stdio.h>
#include <stdlib.h>

/**
 * 构造节点
 */
typedef struct
{
    int val;                   //数据域,存储有我们需要使用的数据
    struct MylinkedList *next; //指针域,存储有指向下一个节点的指针
} MyLinkedList;

/** 
 * 构造一个有头结点的空链表 
 */
MyLinkedList *myLinkedListCreate()
{
    MyLinkedList *obj = (MyLinkedList *)malloc(sizeof(MyLinkedList)); //在内存空间中开辟一块和节点数据结构大小一样的内存空间,构造头节点
    obj->next = NULL;                                                 //头节点的指针域置空
    return obj;
}

/** 
 * 获取链表中第 index 个节点的值。如果索引无效,则返回-1 
 */
int myLinkedListGet(MyLinkedList *obj, int index)
{
    int counter = 0; //计数器
    int value = 0;
    MyLinkedList *p = obj->next; //定义一个指针,并指向头节点的下一个节点,即第0个节点
    while (p && counter < index) //当P存在而且没有遍历到第index节点时继续移动指针,直到counter=index时跳出循环;若P为空也会跳出循环,此时counter不一定等于index
    {
        p = p->next;
        counter++;
    }
    if (!p || counter > index) //跳出while循环后counter<=index,若P为空说明查询节点的index大于链表节点数,返回-1;若用户输入的index是一个负数,虽然P不为空,但这种情况应该返回-1
    {
        return -1;
    }
    else
    {
        value = p->val;
        return value;
    }
}

/** 
 * 头插法的实现
 * 在头节点和第0个节点间插入一个新节点
 */
void myLinkedListAddAtHead(MyLinkedList *obj, int val)
{
    MyLinkedList *p = obj;                                             //将头节点赋值给指针P
    MyLinkedList *head = (MyLinkedList *)malloc(sizeof(MyLinkedList)); //新建一个节点
    head->val = val;                                                   //给新节点数据域赋值
    head->next = p->next;                                              //让新节点指向头节点的下一个节点
    p->next = head;                                                    //让头节点指向新节点完成插入
}

/** 
 * 尾插法的实现
 * 在链表最后的节点后再插入新的节点
*/
void myLinkedListAddAtTail(MyLinkedList *obj, int val)
{
    MyLinkedList *tail = (MyLinkedList *)malloc(sizeof(MyLinkedList));
    tail->val = val;
    tail->next = NULL; //初始化新节点,指针域需要置空
    MyLinkedList *node = obj;
    while (node->next) //从头节点开始遍历链表,当节点的指针域为空时跳出循环,表明找到链表尾节点
    {
        node = node->next;
    }
    node->next = tail; //新节点成为尾节点
}

/** 
 * 在第index节点前插入新节点,若index为负数则插入到链表头部,若index超出链表长度则不插入
 */
void myLinkedListAddAtIndex(MyLinkedList *obj, int index, int val)
{
    MyLinkedList *insertedNode = (MyLinkedList *)malloc(sizeof(MyLinkedList));
    insertedNode->val = val;
    MyLinkedList *node = obj;
    int counter = 0;
    if (index < counter) // index为负数时插入到链表头部
    {
        insertedNode->next = node->next;
        node->next = insertedNode;
    }
    while (node && counter < index) //counter等于index时,node的下标应该是index -1,这里和myLinkedListGet方法不同。差别在于当counter是0时,本方法node是头节点,但在myLinkedListGet方法中P是第0个节点
    {
        node = node->next;
        counter++;
    }
    if (!node || counter > index)
    {
        return -1;
    }
    insertedNode->next = node->next;
    node->next = insertedNode;
}

/** 
 * 若index有效的话则删除第index个节点
 * 删除不同于插入。插入时,涉及第index、index-1两个节点,若第index-1个节点是尾节点,虽然第index个节点不存在,但是依然能插入;
 * 删除时,涉及第index-1、index、index+1三个节点,如果第index-1个节点为尾节点,则第index+1个节点即下面的temNode->next不存在,不能完成指针的赋值
 * 所以在判断循环时,判断条件是 node->next,这样才能保证第index+1个节点即下面的temNode->next存在(特殊条件就是为NULL)
 */
void myLinkedListDeleteAtIndex(MyLinkedList *obj, int index)
{
    MyLinkedList *node = obj;
    int counter = 0;
    while (node->next && counter < index) //找到第index-1 个节点,但需要保证第index+1个节点即下面的temNode->next存在(特殊条件就是为NULL)
    {
        node = node->next;
        counter++;
    }
    if (!(node->next) || counter > index)
    {
        return -1;
    }
    else
    {
        MyLinkedList *temNode = (MyLinkedList *)malloc(sizeof(MyLinkedList));
        temNode = node->next;
        node->next = temNode->next;
        free(temNode);
    }
}

/**
 * 整表删除
 */
void myLinkedListFree(MyLinkedList *obj)
{
    MyLinkedList *node = obj->next; //这里node指向的是头节点的下一个节点,与前面有所不同。整表删除时要将头节点以外的所有节点删除,保留头节点
    MyLinkedList *temNode = (MyLinkedList *)malloc(sizeof(MyLinkedList));
    while (node) //判断除当前结点是否为空,不为空时删除此节点
    {
        temNode = node->next; //删除时要保留被删除节点的下一个节点
        free(node);
        node = temNode;
    }
    obj->next = NULL;
}

/**
 * Your MyLinkedList struct will be instantiated and called as such:
 * MyLinkedList* obj = myLinkedListCreate();
 * int param_1 = myLinkedListGet(obj, index);
 
 * myLinkedListAddAtHead(obj, val);
 
 * myLinkedListAddAtTail(obj, val);
 
 * myLinkedListAddAtIndex(obj, index, val);
 
 * myLinkedListDeleteAtIndex(obj, index);
 
 * myLinkedListFree(obj);
*/
int main()
{
    MyLinkedList *obj = myLinkedListCreate();

    int param_1 = myLinkedListGet(obj, 0);
    printf("%d\n", param_1); //-1

    myLinkedListAddAtHead(obj, 0);
    int param_2 = myLinkedListGet(obj, 0);
    printf("%d\n", param_2); //0

    myLinkedListAddAtTail(obj, 1); // 0 1
    int param_3 = myLinkedListGet(obj, 0);
    printf("%d\n", param_3); //0

    myLinkedListAddAtIndex(obj, 1, 2); //021
    int param_4 = myLinkedListGet(obj, 1);
    printf("%d\n", param_4); //2

    myLinkedListDeleteAtIndex(obj, 2); //02

    myLinkedListFree(obj);
    int param_5 = myLinkedListGet(obj, 0);
    printf("%d\n", param_5); //-1
}

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值