数据结构与算法:单向链表(c语言实现),代码注释,易错点分析

一、具体代码

//头文件
#pragma once
#include<stdio.h>
#include<stdlib.h>

typedef int SLDataType;

typedef struct SListNode
{
    SLDataType data;
    struct SListNode * pNext;
}SLNode, *pSLNode;

pSLNode Built_ListNode(SLDataType value);
void SList_Pushback(pSLNode *ppHead, SLDataType value);
void SList_Pushfront(pSLNode *pphead, SLDataType value);
void SList_Popfront(pSLNode *pphead);
void SList_Popback(pSLNode *pphead);
pSLNode SList_find(pSLNode phead, SLDataType value);
void SList_Insert(pSLNode *pphead, pSLNode pos, SLDataType value);
void SList_Earse(pSLNode *pphead, pSLNode pos);
void Print_List(pSLNode phead);
//main.c
#include "SList.h"

// 定义单链表节点结构体
typedef struct SLNode
{
    SLDataType data;     // 数据域
    struct SLNode* pNext;    // 指针域,指向下一个节点
}SLNode, *pSLNode;

// 创建一个新的节点并初始化
pSLNode Built_ListNode(SLDataType value)
{
    pSLNode newnode = (pSLNode)malloc(sizeof(SLNode));    // 分配内存空间
    newnode->data = value;    // 设置数据域的值
    newnode->pNext = NULL;    // 设置指针域为NULL

    return newnode;    // 返回新节点的指针
}

// 在链表尾部插入节点
void SList_Pushback(pSLNode *ppHead, SLDataType value)
{
    pSLNode newnode = Built_ListNode(value);    // 创建新节点
    if(*ppHead == NULL)    // 若链表为空
    {
        *ppHead = newnode;    // 新节点为链表的头节点
    }
    else
    {
        pSLNode ptail = *ppHead;    // 遍历链表找到尾节点
        while (ptail->pNext != NULL)
        {
            ptail = ptail->pNext;
        }
        ptail->pNext = newnode;    // 将新节点连接到尾节点的后面
    }
}

// 删除链表头部节点
void SList_Popfront(pSLNode *pphead)
{
    pSLNode next = (*pphead)->pNext;    // 保存头节点的下一个节点的指针
    free(*pphead);    // 释放头节点的内存空间
    *pphead = next;    // 将头节点的下一个节点作为新的头节点
}

// 删除链表尾部节点
void SList_Popback(pSLNode *pphead)
{
    if(*pphead == NULL)    // 链表为空
    {
        return;
    }
    else if((*pphead)->pNext == NULL)    // 链表只有一个节点
    {
        free(*pphead);    // 释放头节点的内存空间
        *pphead = NULL;    // 头节点指针置空
    }
    else
    {
        pSLNode prev = NULL;    // 用于保存要删除节点的前一个节点的指针
        pSLNode tail = *pphead;    // 遍历链表找到尾节点
        while (tail->pNext != NULL)
        {
            prev = tail;    // 保存前一个节点的指针
            tail = tail->pNext;
        }
        free(tail);    // 释放尾节点的内存空间
        prev->pNext = NULL;    // 将前一个节点的指针域置空,表示删除尾节点
    }
}

// 在链表头部插入节点
void SList_Pushfront(pSLNode *pphead, SLDataType value)
{
    pSLNode newnode = Built_ListNode(value);    // 创建新节点

    newnode->pNext = *pphead;    // 将新节点的下一个节点指向当前头节点
    *pphead = newnode;    // 将新节点设置为头节点
}

// 打印链表的所有节点值
void Print_List(pSLNode phead)
{
    pSLNode cur = phead;    // 从头节点开始遍历链表
    while (cur != NULL)
    {
        printf("%d->", cur->data);    // 打印当前节点的值
        cur = cur->pNext;    // 移动到下一个节点
    }
    printf("NULL\n");    // 链表打印完毕
}

// 在链表中查找指定值的节点
pSLNode SList_find(pSLNode phead, SLDataType value)
{
    pSLNode cur = phead;    // 从头节点开始遍历链表
    while (cur != NULL)
    {
        if(cur->data == value)    // 若当前节点的值等于要查找的值
        {
            return cur;    // 返回当前节点的指针
        }
        cur = cur->pNext;    // 移动到下一个节点
    }
    return NULL;    // 链表中未找到指定值的节点
}

// 在指定位置插入节点
void SList_Insert(pSLNode *pphead, pSLNode pos, SLDataType value)
{
    if(*pphead == pos)    // 若要插入的位置为头节点
    {
        SList_Pushfront(pphead, value);    // 调用头插函数
    }
    else
    {
        pSLNode newnode = Built_ListNode(value);    // 创建新节点
        pSLNode prev = *pphead;    // 用于保存要插入位置的前一个节点的指针
        while (prev->pNext != pos)
        {
            prev = prev->pNext;    // 找到要插入位置的前一个节点
        }
        prev->pNext = newnode;    // 将新节点插入到链表中
        newnode->pNext = pos;
    }
}

// 删除指定位置的节点
void SList_Earse(pSLNode *pphead, pSLNode pos)
{
    if(*pphead == pos)    // 若要删除的位置为头节点
    {
        SList_Popfront(pphead);    // 调用头删函数
    }
    else
    {
        pSLNode prev = *pphead;    // 用于保存要删除位置的前一个节点的指针
        while (prev->pNext != pos)
        {
            prev = prev->pNext;    // 找到要删除位置的前一个节点
        }
        prev->pNext = pos->pNext;    // 将前一个节点的指针域连接到要删除节点的后一个节点
        free(pos);    // 释放要删除节点的内存空间
    }
}
//代码功能测试
int main()
{
    pSLNode plist = NULL;    // 链表的头指针初始化为空
    SList_Pushback(&plist, 1);    // 在链表尾部插入节点
    SList_Pushback(&plist, 2);
    SList_Pushback(&plist, 3);
    SList_Pushback(&plist, 4);
    SList_Pushback(&plist, 5);
    SList_Pushback(&plist, 6);
    SList_Pushfront(&plist, 0);    // 在链表头部插入节点
    Print_List(plist);    // 打印链表的所有节点值
    SList_Popback(&plist);    // 删除链表尾部节点
    Print_List(plist);
    pSLNode pos = SList_find(plist, 1);    // 查找链表中值为1的节点
    if(pos)
    {
        SList_Insert(&plist, pos, 10);    // 在节点pos之前插入新节点
    }
    Print_List(plist);

    pos = SList_find(plist, 0);    // 查找链表中值为0的节点
    if(pos)
    {
        SList_Earse(&plist, pos);    // 删除节点pos
    }
    Print_List(plist);

    return 0;
}

二、易错点分析

1、内存泄漏

// 在链表尾部插入节点
void SList_Pushback(pSLNode *ppHead, SLDataType value)
{
    pSLNode newnode = Built_ListNode(value);    // 创建新节点
    if(*ppHead == NULL)    // 若链表为空
    {
        *ppHead = newnode;    // 新节点为链表的头节点
    }
    else
    {
        pSLNode ptail = *ppHead;    // 遍历链表找到尾节点
        while (ptail->pNext != NULL)
        {
            ptail = ptail->pNext;
        }
        ptail->pNext = newnode;    // 将新节点连接到尾节点的后面
    }
}

问题:

在该代码段中,如果链表为空,但未能将新节点设置为头节点时,导致无法成功将新节点连接到链表尾部,会导致新节点的内存泄漏。

解决方法:

加上链表为空的判断,如果链表为空直接将新的节点作为链表的头节点。

空指针引用:

// 删除链表尾部节点
void SList_Popback(pSLNode *pphead)
{
    if(*pphead == NULL)    // 链表为空
    {
        return;
    }
    else if((*pphead)->pNext == NULL)    // 链表只有一个节点
    {
        free(*pphead);    // 释放头节点的内存空间
        *pphead = NULL;    // 头节点指针置空
    }
    else
    {
        pSLNode prev = NULL;    // 用于保存要删除节点的前一个节点的指针
        pSLNode tail = *pphead;    // 遍历链表找到尾节点
        while (tail->pNext != NULL)
        {
            prev = tail;    // 保存前一个节点的指针
            tail = tail->pNext;
        }
        free(tail);    // 释放尾节点的内存空间
        prev->pNext = NULL;    // 将前一个节点的指针域置空,表示删除尾节点
    }
}

问题

在删除链表尾部节点时,未对空链表进行处理,可能会导致空指针引用错误。如果链表中只有一个节点时,会直接free 导致*pphead的指针没有置空,出现野指针的情况

解决方法

加上特殊情况的判断,如果链表为空直接return函数,如果链表中只有个节点时,直接将头指针置空。

边界条件处理:

// 在指定位置插入节点
void SList_Insert(pSLNode *pphead, pSLNode pos, SLDataType value)
{
    if(*pphead == pos)    // 若要插入的位置为头节点
    {
        SList_Pushfront(pphead, value);    // 调用头插函数
    }
    else
    {
        pSLNode newnode = Built_ListNode(value);    // 创建新节点
        pSLNode prev = *pphead;    // 用于保存要插入位置的前一个节点的指针
        while (prev->pNext != pos)
        {
            prev = prev->pNext;    // 找到要插入位置的前一个节点
        }
        prev->pNext = newnode;    // 将新节点插入到链表中
        newnode->pNext = pos;
    }
}

问题

在插入节点时,如果插入位置为第一个位置时,由于prev->pNext会将第一个位置直接跳过,而导致无法找到第一个位置。

解决方法

在插入节点之前,应对特殊位置的节点进行相应的处理,并最好引入非法位置的判断。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值