C语言实现不带头节点的单链表

在上两篇文章中,我们介绍了基于静态数组和动态数组的顺序表,顺序表频繁查询而插入删除动作少的情况下,顺序表也适用于进行尾插的时候,因为相对于链表而言,顺序表在进行尾插时,顺序表不需要通过遍历来找到最后一个插入点,比较而言,顺序表尾插效率高。
但是,在进行头插和中插时,顺序表需要将插入元素位置之后的元素整体后移才能插入数据,这样做在有大量插入删除场景下即为麻烦且效率低,因此,提出了链表的思想。而链表在头插或者中插时,只需要创建一个新节点,然后将节点链入所插入位置即可。

链表概述

链表是一种常见的数据结构。数组可以存放数据,但是数组存放数据时必须提前指定数组包含元素个数,即数组长度。但是数组保存数据的缺点在于如果要保存元素大于数组大小时,则不能将所有内容保存入数组,而当要保存元素远小于数组大小时,又造成了空间的浪费。而链表其存储元素个数不受限定,当进行添加元素时,存储个数随之增加。
链表是一种链式存储的线性表,用一组地址任意的存储单元存放线性表的数据元素,称存储单元为一个节点。

typedef int DataType;

typedef int DataType;

typedef struct LinkNode
{
    DataType _data;//当前节点保存数据
    struct LinkNode* _pNext;//指向链表中下一结点的指针

}Node;

如下图,为链表结构示意图:
这里写图片描述
在链表中有一个头指针变量,图中head表示的就是头指针,这个指针变量保存一个地址。从图中的箭头可以看到该地址为一个变量的地址,也就是说头指针指向一个变量。这个变量称为元素,在链表中每一个元素包括两个部分:数据部分和指针部分。数据部分用来存放元素所包含的数据,而指针部分用来指向下一个元素。最后一个元素指针指向NULL,表示指向的地址为空。
从图可以看到,head头结点指向第一个元素,第一个元素中的指针又指向第二个元素,第二个元素的指针又指向第三个元素的地址,第三个元素的指针指向第四个元素,第四个元素的指针就指向为空。

链表的分类
  • 单链表
  • 双向链表
  • 双向循环链表

需要注意的是,上述三种链表都有两种形式,分别为带头结点和不带头结点。

这里写图片描述

C语言实现不带头节点的单链表

具体代码实现如下:

#pragma once 

#include <stdio.h>
#include <assert.h>
#include <malloc.h>

/***************************************/

//链表的定义
typedef int DataType;

typedef struct LinkNode
{
    DataType _data;//当前节点保存数据
    struct LinkNode* _pNext;//指向链表中下一结点的指针

}Node;

/***************************************/

//链表的初始化
void LinkListInit(Node** pHead);

//创建新结点
Node* LinkListCreatNode(DataType data);

//销毁结点
void LinkListIDestroyNode(Node** pHead);

//遍历打印链表
void LinkListPrint(Node** pHead);

//尾插元素
void LinkListPushBack(Node** pHead, DataType data);

//尾删元素
void LinkListPopBack(Node** pHead);

//头插元素
void LinkListPushFront(Node** pHead, DataType data);

//头删元素
void LinkListPopFront(Node** pHead);

//查找元素
size_t LinkListFind(Node** pHead, DataType data);

//任意位置的插入
void LinkListInsert(Node** pHead, DataType data, Node* pos);

//任意位置的删除
void LinkListErase(Node** pHead, Node* pos);

//求单链表长度
size_t LinkListSize(Node** pHead);

//销毁一个单链表
void LinkListDestroy(Node** pHead);

//判空
size_t LinkListEmpty(Node** pHead);


/***************************************/
//链表的初始化
void LinkListInit(Node** pHead)
{
    //考虑指针为空
    assert(pHead);
    *pHead = NULL;
}

//创建新结点
Node* LinkListCreatNode(DataType data)
{
    Node* pNewNode = (Node*)malloc(sizeof(Node));
    assert(pNewNode);

    pNewNode->_data = data;
    pNewNode->_pNext = NULL;
    return pNewNode;
}

//销毁结点
void LinkListIDestroyNode(Node** pHead)
{
    //释放该结点,并将头指针指向NULL,防止出现野指针
    free(pHead);
    *pHead = NULL;
}

//遍历打印链表
void LinkListPrint(Node** pHead)
{
    Node* pCur = *pHead;
    for (pCur; pCur != NULL; pCur = pCur->_pNext)
    {
        printf("%d--->", pCur->_data);
    }
    printf("NULL\n");
}

//尾插元素
void LinkListPushBack(Node** pHead, DataType data)
{
    Node* NewNode = LinkListCreatNode(data);
    assert(pHead);
    //如果链表为空,则直接让头指针指向新申请的节点即可
    if (*pHead == NULL)
    {
        *pHead = NewNode;
        return;
    }
    //否则从头开始遍历链表,直到当前节点的指针域指向NULL,然后让当前节
    //点的指针域指向新申请的节点即可
    Node* pTail = *pHead;
    while (pTail->_pNext)
    {
        pTail = pTail->_pNext;
    }
    pTail->_pNext = NewNode;
}

//尾删元素
void LinkListPopBack(Node** pHead)
{
    assert(pHead);
    if (*pHead == NULL)
    {
        printf("链表为空,无法删除!\n");
        return;
    }
    //如果当前只有一个结点,则释放该结点,
    //并将头指针指向NULL,防止出现野指针--相当于销毁结点
    if ((*pHead)->_pNext == NULL)
    {
        free(pHead);
        *pHead = NULL;
    }
    //定义两个指针,依次向后走
    //pTail不为空时,将其前一个结点指针pPreTail赋给pTail
    //当pTail为空时,释放pTail,pPreTail指向NULL
    Node* pTail = *pHead;
    Node* pPreTail = NULL;
    while (pTail->_pNext)
    {
        pPreTail = pTail;
        pTail = pTail->_pNext;
    }
    free(pTail);
    pPreTail->_pNext = NULL;

}

//头插元素
void LinkListPushFront(Node** pHead, DataType data)
{
    assert(pHead);
    Node* NewNode = LinkListCreatNode(data);
    NewNode->_pNext= (*pHead);
    *pHead = NewNode;
}

//头删元素
void LinkListPopFront(Node** pHead)
{
    Node* pDel = NULL;
    assert(pHead);
    //考虑链表为空情况
    if ((*pHead) == NULL)
    {
        printf("链表为空!无法删除!\n");
        return;
    }
    pDel = *pHead;
    *pHead = pDel->_pNext;
    free(pDel);
}

//查找元素
size_t LinkListFind(Node** pHead, DataType data)
{
    assert(pHead);
    //考虑链表为空的情况
    if (*pHead == NULL)
    {
        printf("链表为空!无法查找!\n");
        return -1;
    }
    Node* pFind = *pHead;
    while (pFind && pFind->_data != data)
        pFind = pFind->_pNext;
    if (pFind != NULL)
    {
        printf("找到了数据%d\n", pFind->_data);
        return 1;
    }   
    else
        printf("没有找到数据%d\n",data);
    return -1;

}

//任意位置的插入
void LinkListInsert(Node** pHead, Node* pos, DataType data)
{
    assert(pHead);
    //考虑pos位置的合法性
    assert(pos);
    //考虑空链表情况
    Node* pNewNode = LinkListCreatNode(data);
    if (*pHead == NULL)
    {
        *pHead = pNewNode;
        return;
    }
    if (pos == NULL)
    {
        LinkListPushBack(pHead, data);
        return;
    }
    if (pos == *pHead)
    {
        LinkListPushFront(pHead, data);
        return;
    }
    Node* pCur = *pHead;
    while (pCur->_pNext != NULL)
    {
        if (pCur->_pNext == pos)
        {
            pNewNode->_pNext = pos;
            pCur->_pNext = pNewNode;
        }
        pCur = pCur->_pNext;
    }
    return;
}

//任意位置的删除
void LinkListErase(Node** pHead, Node* pos)
{
    assert(pHead);
    assert(pos);
    //考虑链表为空
    if ((*pHead) == NULL)
    {
        printf("链表为空!无法删除!\n");
        return;
    }
    if (pos == (*pHead))
    {
        LinkListPopFront(pHead);
        return;
    }
    Node* pE = *pHead;
    while ( pE != NULL)
    {
        if (pE->_pNext != NULL)
        {
            pE->_pNext = pos->_pNext;
            LinkListIDestroyNode(pos);
            return;
        }
        pE = pE->_pNext;    
    }
    return;
}

//求单链表长度
size_t LinkListSize(Node** pHead)
{
    assert(pHead);
    Node* pSize = *pHead;
    size_t count = 0;
    while (pSize != NULL)
    {
        pSize = pSize->_pNext;
        count++;
    }
    printf("链表长度为%d\n",count);
    return count;
}

//销毁一个单链表
void LinkListDestroy(Node** pHead)
{
    assert(pHead);
    Node* pDpre = NULL;
    Node* pD = *pHead;
    //定义两个指针同时向链尾走,pD不为空时,用pDpre标记pD
    //pD不断向后移同时释放pDpre,直至将整个链表结点全部释放
    while (pD)
    {
        pDpre = pD;
        pD = pD->_pNext;
        free(pDpre);
    }
    //切记将头指针指向空,不然将变成野指针
    *pHead = NULL;
}

//判空
size_t LinkListEmpty(Node** pHead)
{
    assert(pHead);
    return ((*pHead) == NULL);
}


/***************************************/
//测试部分
void LinkListTest()
{
    Node* p;
    LinkListInit(&p);

    LinkListPushBack(&p, 1);
    LinkListPushBack(&p, 2);
    LinkListPushBack(&p, 3);
    LinkListPushBack(&p, 4);
    LinkListPushBack(&p, 5);
    LinkListPrint(&p);

    LinkListPopBack(&p);
    LinkListPrint(&p);

    LinkListPushFront(&p, 5);
    LinkListPushFront(&p, 6);
    LinkListPrint(&p);

    LinkListPopFront(&p); 
    LinkListPopFront(&p);
    LinkListPrint(&p);

    LinkListFind(&p, 2);
    LinkListFind(&p, 5);

    LinkListSize(&p);

    LinkListInsert(&p,p,8);
    LinkListPrint(&p);


    //LinkListErase(&p,2);
    //LinkListPrint(&p);


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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值