数据结构:链表


提示:以下是本篇文章正文内容,下面案例可供参考

一、什么是链表

在影视作品中,我们可能都见过地下工作者的经典话语:
“上级的姓名、住址,我知道,下级的姓名、住址,我也知道,但是这些都是我们党的秘密,不能告诉你们!”
地下党借助这种单线联络的方式,灵活隐秘地传递着各种重要信息。
在计算机科学领域里,有一种数据结构也恰恰具备这样的特征,这种数据结构就是链表。

链表是什么样的?为什么说它像地下党呢?
让我们来看一下单向链表的结构。
无头单向非循环链表

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

单向链表的每一个节点又包含两部分,一部分是存放数据的变量data,另一部分是指向下一个节点的指针next。

typedef int SLTDateType;
typedef struct SListNode
{
 	SLTDateType data; 
 	struct SListNode* next;
}SListNode;

链表的第一个节点被称为头节点,最后一个节点被称为尾节点,尾节点的next指针指向空。
与数组按照下标来随机寻找元素不同,对于链表的其中一个节点A,我们只能根据节点A的next指针来找到该节点的下一个节点B,
再根据节点B的next指针找到下一个节点C…

这正如地下党的联络方式,一级一级,单线传递。


要想让每个节点都能回溯到它的前置节点,我们可以使用双向链表。

什么是双向链表?

双向链表比单向链表稍微复杂一些,它的每一个节点除了拥有data和next指针,
还拥有指向前置节点的prev指针。
无头双向非循环链表

typedef int LTDataType;
typedef struct ListNode
{
 LTDataType data; 
 struct ListNode* next;
 struct ListNode* prev;
}ListNode;

二、链表的分类

实际中链表的结构非常多样,以下情况组合起来就有8种链表结构
链表分类

虽然有这么多的链表的结构,但是我们实际中最常用的还是这两种结构:

常用链表

  1. 无头单向非循环链表:结构简单,一般不会单独用来存数据。实际中更多是作为其他数据结构的子结构,如哈希桶、图的邻接表等等。另外这种结构在笔试面试中出现很多。
  2. 带头双向循环链表:结构最复杂,一般用在单独存储数据。实际中使用的链表数据结构,都是带头双向循环链表。另外这个结构虽然结构复杂,但是使用代码实现以后会发现结构会带来很多优势,实现反而简单了,后面我们代码实现了就知道了。

三、链表的实现

3.1 无头单向非循环链表的基本操作

1.插入节点

分为三种情况:

  • 尾部插入
  • 头部插入
  • pos位置之后插入

尾部插入,是最简单的情况,把最后一个节点的next指针指向新插入的节点即可。
尾插

头部插入,可以分为两个步骤。

第一步,把新节点的next指针指向原先的头节点。
第二步,把新节点变为链表的头节点。

头插

pos位置之后插入,同样分为两个步骤。

第一步,新节点的next指针,指向pos位置之后的节点。
第二步,pos位置节点的next指针,指向新节点。

在这里插入图片描述

代码

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

    SListNode *newnode=BuySListNode(x);//动态申请一个结点

    if(*pplist==NULL) //当链表没有节点时
    {
        *pplist=newnode;
    }
    else             //有任意节点
    {
        SListNode *cur=*pplist;
        while(cur->next)  //遍历找尾节点,尾的特征是next指针指向空
        {
            cur=cur->next;
        }
        cur->next=newnode;//尾插
    }
}
// 单链表的头插
void SListPushFront(SListNode** pplist, SLTDateType x)
{
    assert(pplist);

    SListNode *newnode=BuySListNode(x);
    
    newnode->next=*pplist;
    *pplist=newnode;
}
// 单链表在pos位置之后插入x
void SListInsertAfter(SListNode* pos, SLTDateType x)
{
    assert(pos);

    SListNode *newnode=BuySListNode(x);

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

2.删除节点

链表的删除操作同样分为三种情况:

  • 尾部删除
  • 头部删除
  • pos位置之后删除

尾部删除,是最简单的情况,把倒数第2个节点的next指针指向空即可。
尾删

头部删除,也很简单,把链表的头节点设为原先头节点的next指针即可。
头删
pos位置之后删除,同样很简单,把pos位置的next指针,指向要删除节点的下一个节点即可。
pos位置之后删除

代码

// 单链表的尾删
void SListPopBack(SListNode** pplist)
{
    assert(pplist);
    assert(*pplist);//考虑链表没有任何节点的情况 不能进行删除


    if((*pplist)->next==NULL)//考虑只有一个节点的情况
    {
        free(*pplist);
        *pplist=NULL;
    }
    else
    {
        SListNode *cur=*pplist;
        while(cur->next->next)//找尾的上一个节点
        {
            cur=cur->next;
        }
        free(cur->next);//释放内存
        cur->next=NULL;//成为新的尾
    }
}
// 单链表头删
void SListPopFront(SListNode** pplist)
{
    assert(pplist);
    assert(*pplist);//考虑链表没有任何节点的情况 不能进行删除

    SListNode *del=*pplist;//保存记录要删的头节点
    *pplist=del->next;//存放新的头节点
    free(del);//释放旧的头节点
}
// 单链表删除pos位置之后的值
void SListEraseAfter(SListNode* pos)
{
    assert(pos);
    assert(pos->next);//考虑pos之后为空指针NULL

    SListNode *del=pos->next;//保存要删除的节点
    pos->next=del->next;//pos的next指针指向 要删除节点的下一节点
    free(del); //释放内存(删除节点)
}

3.查找节点、修改节点

在查找元素时,链表只能从头节点开始向后一个一个节点逐一查找。

例如给出一个链表,需要查找data 为3的节点。

第一步,将查找的指针定位到头节点。
在这里插入图片描述
第二步,判断其data 是否为我们要查找的,如果是则返回当前节点指针,否则根据当前节点的next指针,定位到下一个节点。

在这里插入图片描述
第三步,直到找到目标数据,返回当前节点指针,或直到链表走到NULL也没查找到 ,返回NULL。
在这里插入图片描述

修改节点(查找和修改要配合使用)
根据返回的指针,可以修改节点的data,直接把旧数据替换成新数据。

在这里插入图片描述

代码

// 单链表查找
SListNode* SListFind(SListNode* plist, SLTDateType x)
{
    assert(plist);

    SListNode *cur=plist;
    while(cur) //遍历链表
    {
        if(cur->data==x)
        {
            return cur;
        }
        cur=cur->next;
    }
    return NULL;
}
int main()
{
    SListNode *pos=SListFind(plist,3);//调用查找函数,pos接收返回值
    if(pos!=NULL)//当返回值不为空
    {
        pos->data=0;//对节点进行修改
    }
}

🍎无头+单向+非循环链表增删查改实现

完整代码(含测试)

typedef int SLTDateType;
typedef struct SListNode
{
    SLTDateType data;
    struct SListNode* next;
}SListNode;


// 动态申请一个结点
SListNode* BuySListNode(SLTDateType x)
{
    SListNode *newnode=(SListNode *)malloc(sizeof(SListNode));
    if(newnode==NULL)
    {
        perror("malloc fail");
        return NULL;
    }
    newnode->data=x;
    newnode->next=NULL;
    
    return newnode;
}
// 单链表打印
void SListPrint(SListNode* plist)
{
    SListNode *cur=plist;
    while(cur)
    {
        printf("%d-->",cur->data);
        cur=cur->next;
    }
    printf("NULL\n");
}
// 单链表尾插
void SListPushBack(SListNode** pplist, SLTDateType x)
{
    assert(pplist);

    SListNode *newnode=BuySListNode(x);

    if(*pplist==NULL)
    {
        *pplist=newnode;
    }
    else
    {
        SListNode *cur=*pplist;
        while(cur->next)
        {
            cur=cur->next;
        }
        cur->next=newnode;
    }
}
// 单链表的头插
void SListPushFront(SListNode** pplist, SLTDateType x)
{
    assert(pplist);

    SListNode *newnode=BuySListNode(x);
    
    newnode->next=*pplist;
    *pplist=newnode;
}
// 单链表的尾删
void SListPopBack(SListNode** pplist)
{
    assert(pplist);
    assert(*pplist);

    if((*pplist)->next==NULL)
    {
        free(*pplist);
        *pplist=NULL;
    }
    else
    {
        SListNode *cur=*pplist;
        while(cur->next->next)
        {
            cur=cur->next;
        }
        free(cur->next);
        cur->next=NULL;
    }
}
// 单链表头删
void SListPopFront(SListNode** pplist)
{
    assert(pplist);
    assert(*pplist);

    SListNode *del=*pplist;
    *pplist=del->next;
    free(del);
}
// 单链表查找
SListNode* SListFind(SListNode* plist, SLTDateType x)
{
    assert(plist);

    SListNode *cur=plist;
    while(cur)
    {
        if(cur->data==x)
        {
            return cur;
        }
        cur=cur->next;
    }
    return NULL;
}
// 单链表在pos位置之后插入x
// 分析思考为什么不在pos位置之前插入?
void SListInsertAfter(SListNode* pos, SLTDateType x)
{
    assert(pos);

    SListNode *newnode=BuySListNode(x);

    newnode->next=pos->next;
    pos->next=newnode;
}
// 单链表删除pos位置之后的值
// 分析思考为什么不删除pos位置?
void SListEraseAfter(SListNode* pos)
{
    assert(pos);
    assert(pos->next);

    SListNode *del=pos->next;
    pos->next=del->next;
    free(del);
}
// 单链表的销毁
void SListDestroy(SListNode** pplist)
{
    assert(pplist);

    SListNode *del=*pplist;
    while(del)
    {
        SListNode *next=del->next;
        free(del);
        del=next;
    }
    *pplist=NULL;
}

int main()
{
    SListNode *plist=NULL;
    SListPushFront(&plist,1);
    SListPushFront(&plist,2);
    SListPushFront(&plist,3);
    SListPushFront(&plist,4);
    SListPrint(plist);

    SListPopBack(&plist);
    SListPopFront(&plist);
    SListPopBack(&plist);
    SListPopFront(&plist);
    SListPrint(plist);

    SListPushFront(&plist,1);
    SListPushBack(&plist,2);
    SListPushBack(&plist,3);
    SListPushBack(&plist,4);
    SListPushBack(&plist,5);
    SListPrint(plist);

    SListNode *pos=SListFind(plist,3);
    if(pos!=NULL)
    {
        pos->data=0;
    }
    SListPrint(plist);

    pos=SListFind(plist,0);
    if(pos!=NULL)
    {
        SListInsertAfter(pos,30);
    }
    SListPrint(plist);

    pos=SListFind(plist,2);
    if(pos!=NULL)
    {
        SListEraseAfter(pos);
    }
    SListPrint(plist);

    SListDestroy(&plist);

    return 0;
}

3.2 带头双向循环链表的基本操作

双向带头循环链表结构

在这里插入图片描述

1.插入节点

分为三种情况:

  • 尾部插入
  • 头部插入
  • pos位置之前插入

尾插

头插

pos位置之前插入

代码

// 双向链表尾插
void ListPushBack(ListNode* plist, LTDataType x) 
{
    assert(plist);

	//当链表没有节点或有任意节点时 此代码都适用
    ListNode *tail=plist->prev;
    ListNode *newnode=BuyListNode(x);

    tail->next=newnode;
    newnode->prev=tail;
    newnode->next=plist;
    plist->prev=newnode;
}
// 双向链表头插
void ListPushFront(ListNode* plist, LTDataType x)
{
    assert(plist);

    ListNode *newhead=BuyListNode(x);
    
	//当链表没有节点或有任意节点时 此代码都适用
	//这里没有像上面图所示一样记录头节点
    newhead->next=plist->next;
    plist->next->prev=newhead;

    newhead->prev=plist;
    plist->next=newhead;
}
// 双向链表在pos的前面进行插入
void ListInsert(ListNode* pos, LTDataType x)
{
    assert(pos);

    ListNode *posprev=pos->prev;
    ListNode *newnode=BuyListNode(x);

    newnode->next=pos;
    pos->prev=newnode;

    newnode->prev=posprev;
    posprev->next=newnode;
}

2.删除节点

分为三种情况:

  • 尾部删除
  • 头部删除
  • pos位置删除

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

代码

// 双向链表尾删
void ListPopBack(ListNode* plist)
{
    assert(plist);
    assert(plist->prev!=plist);//考虑链表没有节点

    ListNode *tail=plist->prev;
    ListNode *tailprev=tail->prev;

    free(tail);
    tailprev->next=plist;
    plist->prev=tailprev;
}
// 双向链表头删
void ListPopFront(ListNode* plist)
{
    assert(plist);
    assert(plist->next!=plist);//考虑链表没有节点

    ListNode *first=plist->next;
    ListNode *second=first->next;

    plist->next=second;
    second->prev=plist;
    free(first);
}
// 双向链表删除pos位置的结点
void ListErase(ListNode* pos)
{
    assert(pos);

    ListNode *posprev=pos->prev;
    ListNode *posnext=pos->next;

    posprev->next=posnext;
    posnext->prev=posprev;
    free(pos);
}

此链表查找节点和不带头单向非循环链表 基本一致,除了循环条件不一样,这里就不介绍了。


🍏带头+双向+循环链表增删查改实现

完整代码(含测试)

typedef int LTDataType;
typedef struct ListNode
{
    LTDataType data;
    struct ListNode* next;
    struct ListNode* prev;
}ListNode;
// 动态申请一个结点
ListNode* BuyListNode(SLTDateType x)
{
    ListNode *newnode=(ListNode *)malloc(sizeof(ListNode));
    if(newnode==NULL)
    {
        perror("malloc fail");
        return NULL;
    }
    newnode->data=x;
    newnode->next=NULL;
    newnode->prev=NULL;
    
    return newnode;
}
// 创建返回链表的头结点.
ListNode* ListCreate()
{
    ListNode *head=BuyListNode(-1);
    head->next=head;
    head->prev=head;

    return head;
}
// 双向链表销毁
void ListDestory(ListNode* plist)
{
    assert(plist);

    ListNode *del=plist->next;
    while(del!=plist)
    {
        ListNode *next=del->next;
        free(del);
        del=next;
    }
    free(plist);

}
// 双向链表打印
void ListPrint(ListNode* plist)
{
    assert(plist);

    ListNode *cur=plist->next;
    printf("哨兵位<===>");
    while(cur!=plist)
    {
        printf("%d<===>",cur->data);
        cur=cur->next;
    }
    printf("\n");

}
// 双向链表尾插
void ListPushBack(ListNode* plist, LTDataType x)
{
    assert(plist);

    ListNode *tail=plist->prev;
    ListNode *newnode=BuyListNode(x);

    tail->next=newnode;
    newnode->prev=tail;
    newnode->next=plist;
    plist->prev=newnode;

}
// 双向链表尾删
void ListPopBack(ListNode* plist)
{
    assert(plist);
    assert(plist->prev!=plist);

    ListNode *tail=plist->prev;
    ListNode *tailprev=tail->prev;

    free(tail);
    tailprev->next=plist;
    plist->prev=tailprev;
}
// 双向链表头插
void ListPushFront(ListNode* plist, LTDataType x)
{
    assert(plist);

    ListNode *newhead=BuyListNode(x);

    newhead->next=plist->next;
    plist->next->prev=newhead;

    newhead->prev=plist;
    plist->next=newhead;
}
// 双向链表头删
void ListPopFront(ListNode* plist)
{
    assert(plist);
    assert(plist->next!=plist);

    ListNode *first=plist->next;
    ListNode *second=first->next;

    plist->next=second;
    second->prev=plist;
    free(first);
}
// 双向链表查找
ListNode* ListFind(ListNode* plist, LTDataType x)
{
    assert(plist);

    ListNode *cur=plist->next;
    while(cur!=plist)
    {
        if(cur->data==x)
        {
            return cur;
        }
        cur=cur->next;
    }
    return NULL;
}
// 双向链表在pos的前面进行插入
void ListInsert(ListNode* pos, LTDataType x)
{
    assert(pos);

    ListNode *posprev=pos->prev;
    ListNode *newnode=BuyListNode(x);

    newnode->next=pos;
    pos->prev=newnode;

    newnode->prev=posprev;
    posprev->next=newnode;
}
// 双向链表删除pos位置的结点
void ListErase(ListNode* pos)
{
    assert(pos);

    ListNode *posprev=pos->prev;
    ListNode *posnext=pos->next;

    posprev->next=posnext;
    posnext->prev=posprev;
    free(pos);
}



int main()
{
    ListNode * head=ListCreate();
    ListPushFront(head, 1);
	ListPushBack(head, 2);
	ListPushBack(head, 3);
	ListPushBack(head, 4);
    ListPushFront(head, 0);
    ListPushBack(head, 5);
	ListPrint(head);

    ListNode* pos = ListFind(head, 3);
	if (pos)
	{
		ListInsert(pos, 30);
	}
	ListPrint(head);

    pos = ListFind(head, 30);
	if (pos)
	{
		ListErase(pos);
	}
    ListPrint(head);


	ListPopBack(head);
    ListPopFront(head);
	ListPrint(head);

	ListPopBack(head);
    ListPopFront(head);
	ListPrint(head);

	ListPopBack(head);
    ListPopFront(head);
	ListPrint(head);

    ListDestory(head);
    head=NULL;

    return 0;
}

完。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值