让单链表不再云里雾里

发布于:2025-03-05 ⋅ 阅读:(23) ⋅ 点赞:(0)

一日不见,如三月兮!接下来与我一起创建单链表吧!

目录

单链表的结构:

创建单链表:

增加结点:

插入结点:

删除结点:

打印单链表:

单链表查找:

单链表指定位置插入:

单链表指定位置删除:

答疑解惑:

结语:


单链表的结构:

单链表有不带头单向不循环,和带头单向非循环两种类型,我们将探索的是不带头单向不循环的链表;它有两种结构:逻辑结构和物理结构,如下图:

因为物理结构便于理解,所以我们参照它来进行代码书写,物理结构中我们访问的都是地址,所以我们会使用指针,且蓝色所指表示它们两的地址相同!

创建单链表:

首先我们要定义一个结构体,然后我们要访问数值,所以会定义一个整型变量,为了找下一个数值的地址,我们还需创建一个指针

typedef int SLTDataType;
typedef struct SListNode
{
    SLTDataType data;
    struct SListNode* next;//方便查找下一个结点
}SLTNode;

增加结点:

因为每次插入,都需要书写一个代码,为了节省空间,我们将它包装成一个函数,方便我们进行调用

SLTNode* Buynewnode(SLDataType x)
{
    SLTNode* newnode = (SLTNode*)malloc(sizeof(SLTNode));//开辟空间
    if(newnode == NULL)//判断是否开辟成功
    {
        perror("malloc fail");
        return;
    }
    newnode->data = x;//将数据赋值
    newnode->next = NULL;
    
    return newnode;//返回地址
}

插入结点:

头插:

首先将一开始头的数据地址赋给新的结点的下一个(newnode->next)地址,再将头指针变为新结点

void SLTPushFront(SLTNode** pphead,SLTDataType x)
{
    assert(pphead);
    SLTNode* newnode = Buynewnode(x);
    newnode->next = *pphead;
    *pphead = newnode;
}

尾插:

首先判断链表是否为空的情况,若为空,则将新结点赋给头结点即可,如果不是,再进行找尾,当找到尾时,将新结点的地址赋给tail的下一个。

void SLTPushBack(SLTNode** pphead, SLTDataType x)
{
    assert(pphead);
    SLTNode* newnode = Buynewnode(x);
    //判断是否为空
    if(*pphead == NULL)
    {
        *pphead = newnode;
    }
    else
    {
        //找尾
        while(tail->next != NULL)
        {
            tail = tail->next;
        }
        tail->next = newnode;//链接新结点
     }
}

删除结点:

头删:

先判断链表是否为空,若不为空,将第二个的数据地址赋给头指针即可,但我们怎么找到第二个的数据地址呢?我们可以先将头指针赋给一个指针进行保存,再将创建的指针找到其下一个的地址(即第二个数据的地址)赋给头指针,在释放第一个结点的地址即可。

void SLTPopFront(SLTNode** pphead)
{
    assert(pphead);
    assert(*pphead);
    SLTNode* begin = *pphead;//保存头指针
    *pphead = begin->next;
    free(begin)//释放第一个结点的空间
    begin = NULL;
}

尾删:

首先判断单链表是否为空,其次判断一下是否只有一个结点,若只有一个,直接将头指针释放置空即可,剩下的情况我们只要找到尾将它删除即可,有人想偷懒,将上面的找尾代码拷贝,然后将其置空,果真这么简单吗?会导致什么结果呢?又该怎么处理呢?

如果将tail置空,会使前一个结点的下一个指针变为野指针,tail置空是将局部变量置空,而非将结构体变量置空,若要改变这个情况,我们再定义一个结构体指针,找到tail的前面一个头结点,再将其下一个置空就行了

void SLTPopBack(SLTNode** pphead)
{
    //为空
    assert(pphead);
    assert(*pphead);
    //只有一个结点
    if((*pphead)->next == NULL)
    {
        free(*pphead);
        *pphead = NULL;
    }
    else
    {
        SLTNode* prev = NULL;
        SLTNode* tail = *pphead;
        //找尾
        while(tail->next != NULL)
        {
            prev = tail;//找到tail前一个结点
            tail = tail->next;
        }
        //释放掉尾结点
        free(tail)
        tail = NULL;
        prev->next = NULL;
    }
}

打印单链表:

将头指针赋值给一个指针变量,这样可以防止头指针被覆盖(这个作用这里体现不明显),然后循环遍历就OK了

void SLTPrint(SLTNode* phead)
{
    SLTNode* begin = phead;
    //循环遍历
    while(begin)
    {
        printf("%d->", begin->data);
        begin = begin->next;
    }
    printf("NULL\n");
}

单链表查找:

将头指针赋值给一个指针变量,然后遍历,用if语句判断

SLTNode* SLTFind(SLTNode* phead, SLTDataType x)
{
    SLTNode* cur = phead;
    while(cur)
    {
        if(cur->data == x)
        {
            //找到返回
            return cur;
        }
        cur = cur->next;
    }
    return NULL;
}

单链表指定位置插入:

我们先定义一个指针变量,用来找我们指定插入的位置地址,找到这个指针变量的前一个位置的地址,再将新结点的地址赋给前一个的下一个地址(next),再将指针变量赋给新节结点的下一个地址

void SLTInsert(SLTNode** pphead, SLTNode* pos, SLTDataType x)
{
    assert(pos);//判断pos是否为空
    assert(pphead);
    SLTNode* newnode = Buynewnode(x);
    if(*pphead == pos)
    {
        newnode->next = pos;
        *pphead = newnode//将新结点赋给头指针
    }
    else
    {
        SLTNode* prev = *pphead;
        //找到pos前面一个地址
        while(prev->next != pos)
        {
            prev = prev->next;
        }
        prev->next = newnode;
        newnode->next = pos;
     }
}

单链表指定位置删除:

我们先定义一个指针变量,用来找我们指定插入的位置地址,找到这个指针变量的前一个位置的地址,再将指定位置的下一个,赋给前一个位置的下一个,再将指定位置释放置空

void SLTErase(SLTNode** pphead, SLTNode* pos)
{
    assert(pos);//判断pos是否为空
    assert(pphead);
    //只有一个结点
    if(*pphead == pos)
    {
        *pphead = pos->next;
         free(pos);
         pos = NULL;
    }
    else
    {
        SLTNode* prev = *pphead;
        //找pos前一个位置
        while(prev->next != pos)
        {
            prev = prev->next;
        }
        prev->next = pos->next;
        free(pos);//释放指定位置空间
        pos = NULL;
     }
}

答疑解惑:

为什么要传二级指针? 因为要修改结构体指针,所以要用二级指针,比如修改变量,用一级指针修改才行!

什么时候用assert?当一个变量一定不为空时,则用assert,比如插入数据时,*pphead可以为空,所以不用断言,亦或者打印时,也可以打印空数据,所以也不用断言。

结语:

谢谢大家观看!期待下次见面哦!