数据结构——单链表

本文介绍了单链表的基础结构,包括节点定义和指针使用。详细阐述了动态申请节点、链表打印、尾插、头插、尾删、头删、查找、插入、删除等操作的实现,并分析了操作过程中的条件判断。同时讨论了单链表的优势,如快速插入删除,以及劣势,如低访问效率和空间利用率。
摘要由CSDN通过智能技术生成
  1. 单链表的基础结构

单链表在创建前,我们需要知道,在单链表中我们需要插入数据,在插入数据之后还需要一个链表指针(用于指向下一数据的存储位置),因此我们便能得到单链表的结构如下

typedef int SLTDateType;//便于需要改变数据类型时作统一修改
typedef struct SListNode
{
    SLTDateType data;
    struct SListNode* next;
}SListNode;
  1. 单链表的数据操作

①动态申请一个节点

// 动态申请一个节点
SListNode* BuySListNode(SLTDateType x)
{
    //开辟一块空间用来存放储存数据和下一数据的地址
    SListNode* newnode = (SListNode*)malloc(sizeof(SListNode));
    if (newnode == NULL)//开辟失败进行反馈
    {
        printf("malloc fail");
        return NULL;
    }

    newnode->data = x;
    newnode->next = NULL;

    return newnode;
}

②单链表的打印

// 单链表打印
void SListPrint(SListNode* plist)
{
    SListNode* cur = plist;

    while (cur != NULL)
    {
        printf("%d->",cur->data);
        cur = cur->next;
    }
    printf("NULL");
    printf("\n");
}

用图示来解释结束打印的条件

如果判断条件为cur->next的话那么最后一个数据将不会被打印出来。

③单链表的尾插

// 单链表尾插
void SListPushBack(SListNode** pplist, SLTDateType x)
{
    assert(pplist);

    SListNode* newnode = BuySListNode(x);

    SListNode* cur = *pplist;
    if (cur == NULL)
    {
        *pplist = newnode;
    }
    else
    {
        while (cur->next != NULL)
        {
            cur = cur->next;
        }
        cur->next = newnode;
    }
}

根据图像分析可知我们需要以cur->next作为条件来寻找NULL指针

但没有节点时,会出现明显的问题,这时候我们只需要直接使plist=newnode即可

④单链表的头插

// 单链表的头插
void SListPushFront(SListNode** pplist, SLTDateType x)
{
    assert(pplist);

    SListNode* newnode = BuySListNode(x);

    /*SListNode* tmp = *pplist;
    if (*pplist == NULL)
    {
        *pplist = newnode;
    }
    else
    {
        *pplist = newnode;
        (*pplist)->next = tmp;
    }*/

    newnode->next = *pplist;
    *pplist = newnode;
}

简要分析可知,这里并不需要作条件判断,因为只有plist的指向需要做改变

⑤单链表的尾删

// 单链表的尾删
void SListPopBack(SListNode** pplist)
{
    assert(pplist);
    assert(*pplist);

    SListNode* tail = *pplist;
    if (tail->next == NULL)
    {
        free(tail);
        *pplist = NULL;
    }
    else
    {
        while (tail->next->next != NULL)
        {
            tail = tail->next;
        }
        free(tail->next);
        tail->next = NULL;
    }
}

简要分析,如图,要先通过tail->next->next找到最后一个节点的上一个节点,这之后先将tail->next指向的最后一个节点释放,再将其指向NULL作为新的尾节点

同样的,因为是通过tail->next->next来寻找尾节点,所以当原本仅有一个节点时(如图),我们只需要先直接释放tail再将plist指向NULL即可

⑥单链表的头删

// 单链表头删
void SListPopFront(SListNode** pplist)
{
    assert(pplist);
    assert(*pplist);

    SListNode* tmp = *pplist;

    *pplist = (*pplist)->next;
    free(tmp);
    tmp = NULL;
}

如图所示,要先存储首节点,再将首节点的下一位置存储进plist中作为新的首节点,之后用先前临时存储的首节点使其释放

⑦单链表的数据查找

// 单链表查找
SListNode* SListFind(SListNode* plist, SLTDateType x)
{
    while (plist != NULL)
    {
        if (plist->data == x)
        {
            return plist;
        }
        plist = plist->next;
    }

    return NULL;
}

⑧单链表在pos位置之后插入x

// 单链表在pos位置之后插入x
void SListInsertAfter(SListNode* pos, SLTDateType x)
{
    assert(pos);

    SListNode* newnode = BuySListNode(x);

    newnode->next = pos->next;
    pos->next = newnode;
}

如图我们可以分析,将pos->next的值存入newnode中,再使pos->next指向newnode

⑨单链表删除pos位置之后的值

// 单链表删除pos位置之后的值
void SListEraseAfter(SListNode* pos)
{
    assert(pos);
    assert(pos->next);

    SListNode* tmp = pos->next->next;
    free(pos->next);
    pos->next = NULL;
    pos->next = tmp;
}

如图,其思路与尾删思路大同小异,就不做赘述

⑩单链表的销毁

// 单链表的销毁
void SListDestroy(SListNode* plist)
{
    assert(plist);

    SListNode* cur = plist;
    while (cur != NULL)//依次销毁所有节点直至cur指向NULL
    {
        cur = cur->next;
        free(plist);
        plist = NULL;
        plist = cur;
    }
}

⑪单链表在pos位置之前插入x

// pos之前插入
void SLTInsert(SListNode** pplist, SListNode* pos, SLTDateType x)
{
    assert(pplist);
    assert(pos);

    if (*pplist == pos)
    {
        SListPushFront(pplist,x);
    }
    else
    {
        //找到pos之前的位置
        SListNode* pre = *pplist;
        while (pre->next != pos)
        {
            pre = pre->next;
        }

        //插入值
        SListNode* newnode = BuySListNode(x);
        newnode->next = pre->next;
        pre->next = newnode;
    }
}

如图需要从首节点开始通过pre->next是否为pos为标志寻找pos的上一节点,从而在上一节点实现在其之后插入节点的操作

同样的,因为要使用pre->next所以在pos为首节点位置时,这里的实际意义就是头插一个数据,因此直接进行头插即可

⑫单链表删除pos位置的值

// pos位置删除
void SLTErase(SListNode** pplist, SListNode* pos)
{
    assert(pplist);
    assert(pos);

    if (*pplist == pos)
    {
        SListPopFront(pplist);
    }
    else
    {
        SListNode* del = *pplist;
        while (del->next != pos)
        {
            del = del->next;
        }
        
        SListNode* tmp = pos->next;
        free(pos);
        pos = NULL;
        del->next = tmp;
    }
}

与上一个操作类似,这里也需要从头开始寻找,对pos上一个的节点实现删除其后节点的操作

且如果删除的pos为首节点的话,直接进行头删即可

  1. 单链表的优势与劣势

①优势

  1. 插入和删除操作方便快捷:由于每个节点只有一个指针,插入或删除节点时只需改变节点的指针即可,而不需要移动其他节点,因此插入和删除操作的时间复杂度为O(1)。

  1. 不需要预先分配内存:单链表的节点可以在运行时动态分配内存,因此可以根据需要灵活地创建节点,这使得单链表的使用更加灵活和高效。

  1. 支持动态扩容:由于单链表的节点可以在运行时动态分配内存,因此可以根据需要动态地增加节点的数量,这使得单链表的大小可以根据需要动态扩展。

②劣势

  1. 访问元素时效率低:由于单链表中的节点不是连续存储的,因此访问元素时需要从头开始遍历,时间复杂度为O(n)。这使得单链表在访问元素方面效率较低,尤其是在需要随机访问元素时。

  1. 空间利用率低:由于每个节点都需要存储一个指针,因此单链表的空间利用率相对较低,尤其是在需要存储大量数据时。

  1. 不支持随机访问:由于单链表中的节点不是连续存储的,因此无法通过下标随机访问节点。这使得单链表在需要随机访问节点时效率低下。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值