数据结构——链表

文章介绍了链表作为顺序表的补充,解决了顺序表头插头删操作效率低下的问题。链表是一种非连续、非顺序的存储结构,其逻辑顺序由指针链接决定。文中详细讲解了链表的概念,包括单向链表、带头与不带头、循环与不循环的分类,并提供了链表节点的定义、动态申请、打印、尾插、头插、尾删、头删等操作的实现代码。此外,还讨论了为何不在特定位置之前插入或删除节点的原因。
摘要由CSDN通过智能技术生成

在对顺序表有一定的了解之后,我们会发现,顺序表的头插头删不是很方便,每头插一次就要将整个表中的每个值向后移动,效率骤减,对此有没有更好的方法呢,那么链表就可以弥补这个问题,下面我们就来看看链表

一、链表的概念

链表是一种物理存储结构上非连续、非顺序的存储结构,数据元素的逻辑顺序是通过链表中的指针链接次序决定的

上图为一个简单的单向链表的图示,值得注意的是

  1. 上图的结构在逻辑上是连续的,但是在物理地址上却可能不是连续的

  1. 一般节点都是从堆上申请出来的

  1. 从堆上申请的空间,可能是连续的,也可能是不连续的

二、链表的分类

  1. 单向或双向链表

  1. 带头和不带头

  1. 循环和不循环

三、链表的实现

typedef int SLTDateType;//为了便于表示结构体的成员
typedef struct SListNode
{
    SLTDateType data;
    struct SListNode* next;
}SListNode;

动态申请一个节点

SListNode* BuySListNode(SLTDateType x)
{
   SListNode*buy=(struct SListNode*)malloc(sizeof(SListNode));
    if(buy==NULL)
    {
        perror("BuySListNode::malloc false");
    }
    buy->val=x;
    buy->next=NULL;
    return buy;
}

// 单链表打印

void SListPrint(SListNode* plist)
{
    if(plist==NULL)
    {
        perror("SListPrint::plist==NULL");
        return;
    }
    SListNode*print=plist;
    while(print!=NULL)
    {
        printf("%d ",print->val);
        print=ptint->next;
    }
    printf("NULL\n");
}

// 单链表尾插

void SListPushBack(SListNode** pplist, SLTDateType x)
{
    //SListNode*newlist=(struct SListNode*)malloc(sizoef(SListNode));
    //newlist->val=x;
    //newlist->next=NULL;
//上述步骤可以用已经写好的申请节点函数
    SListNode*newlist=BuySListNode(x);
    if(*pplist==NULL)
    {
        SListNode*
    }
    else
    {
        while(*pplist->next!=NULL)
        {
            *pplist=*pplist->next;
        }
        *pplist->next=newlist;
    }
    
}

// 单链表的头插

void SListPushFront(SListNode** pplist, SLTDateType x)
{
    //SListNode*newlist=(struct SListNode*)malloc(sizoef(SListNode));
    //newlist->val=x;
    //newlist->next=NULL;
//上述步骤可以用已经写好的申请节点函数
    SListNode*newlist=BuySListNode(x);
    newlist->next=*pplist;
    *pplist=newlist;
}

// 单链表的尾删

void SListPopBack(SListNode** pplist)
{
    if(pplist->next==NULL)
    {
        //只有一个节点
        free(*pplist);
        *pplist=NULL;
    }
    else
    {    
        SListNode*tail=*pplist;
        while(tail->next->next!=NULL)
        {
            tail=tail->next;
        }
        free(tail->next);
        tail->next=NULL;
    }
}

// 单链表头删

void SListPopFront(SListNode** pplist)
{
    if(pplist==NULL)
    {
        perror("SListPopBack::pplist==NULL");
        return;
    }
    SListNode*first=*pplist;
    pplist=first->next;
    free(first);
    first=NULL;
}

// 单链表查找

SListNode* SListFind(SListNode* plist, SLTDateType x)
{
    if(plist==NULL)
    {
        printf("找不到\n");
        return;
    }
    SListNode*cur=plist;
    while(cur!=NULL)
    {
        if(cur->val==x)
        {
            return plist;
        }
        cur=cur->next;;
    }
    printf("找不到\n");
    return NULL;
}

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

void SListInsertAfter(SListNode* pos, SLTDateType x)
{
     assert(pos);
    //SListNode*newlist=(struct SListNode*)malloc(sizoef(SListNode));
    //newlist->val=x;
    //newlist->next=NULL;
//上述步骤可以用已经写好的申请节点函数
    SListNode*newlist=BuySListNode(x);
    SListNode*tmp=pos->next;
    pos->next=newlist;
}

分析思考为什么不在pos位置之前插入?

其实问题还是明显的,若是在pos前的位置插入,我们无法直接得到pos前这个位置,仍需从链表头开始查找。

如图,pos的前一位,若要找到pos的前一位,则必须从头开始找;很不方便

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

void SListEraseAfter(SListNode* pos)
{
    assert(pos);
    if(pos->next==NULL)
    {
        return ;
    }
    
    SListNode*del=pos->next;
    pos->next=del->next;
    free(del);
    del=NULL;
}

// 分析思考为什么不删除pos位置?

原因也很简单,若要删除pos位置的值,我们还得从头遍历找到前一个节点,这样的话才能将pos位前与pos位后的节点连接起来

到这里对链表有了简单认识,建议手敲上述使用代码,看上去很简单,但是画图再理解真的很有用,下次我们针对简单链表出一期练习

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值