[C语言实现]带你手撕带头循环双链表


目录

什么是双链表?

带头结点的优势:

双链表的实现:


什么是循环双链表?

众所周知,顺序表的插入和删除有时候需要大量移动数据,并且每次开辟空间都可能会浪费大量内存和CPU资源,于是我们有了链表,我们之前已经实现了单链表,我们可以发现单链表的一个劣势——每次查找都必须从头结点开始,并且这是一个不可逆的过程,你不可以通过下一个结点找到前一个,只能通过前一个结点找到下一个。

我们将链表的结点空间不再一分为二,而是一分为三,分别是:存储数据的空间,存储下一个结点地址的空间,存储上一个结点地址的空间。这样我们就可以通过一个结点找到它的上一个结点和下一个结点了。

最后我们再将首结点的prev指向尾结点,尾结点的next指向首结点,就变成了循环双链表。

我们在此基础上,再为这个结构加入头结点。

带头结点的优势:

头结点就像是一个哨兵,一直站在第一个位置,它的data域不存储任何值,next域指向第一个有效的链表节点,prev域指向链表的尾结点。

我们为什么需要头结点?

1. 头结点可以让插入操作变得更加方便,不需要分有无结点的情况讨论,因为永远存在一个头结点

2. 头结点可以让我们函数传参的时候不需要传二级指针,因为此时不需要改变head本身。

此外还有其他优点,在之后的代码实现中我们会慢慢体会到头结点的优势。


双链表的代码实现+详细注释:

头文件:

//List.h
#include <stdio.h>
#include <stdlib.h>
#include <assert.h>

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

LTNode* ListNodeInit(LTNode* phead);//初始化
void LTPushBack(LTNode* phead, LTDataType x);//尾插
void LTPushFront(LTNode* phead, LTDataType x);//头插
void LTPopFront(LTNode* phead);//头删
void LTPopBack(LTNode* phead);//尾删
void LTPrint(LTNode* phead);//打印 
void LTInsert(LTNode* pos, LTDataType x);//pos前一个位置的插入
void LTErase(LTNode* pos);//pos当前位置的删除
LTNode* LTFind(LTNode* phead, LTDataType x);//查找
LTNode* LTDestroy(LTNode* phead);//销毁

函数实现:

//List.c
#include "List.h"
LTNode* ListNodeInit(LTNode* phead)
{
    //创建头结点
    LTNode* head = (LTNode*)malloc(sizeof(LTNode));
    //防止内存开辟失败
    assert(head);
    //初始化头结点
    head->prev = head->next = head;
    //返回头结点
    return head;
}
void LTPushBack(LTNode* phead, LTDataType x)
{
    //断言 防止用户传入空指针
    assert(phead);
    //尾插
    LTNode* newnode = (LTNode*)malloc(sizeof(LTNode));
    assert(newnode);
    newnode->data = x;
    //找到尾结点
    LTNode* tail = phead->prev;
    //开始插入
    newnode->next = phead;
    phead->prev = newnode;
    tail->next = newnode;
    newnode->prev = tail;
}
void LTPushFront(LTNode* phead, LTDataType x)
{
    //断言
    assert(phead);
    //头插
    LTNode* newnode = (LTNode*)malloc(sizeof(LTNode));
    assert(newnode);
    newnode->data = x;
    //找到第一个有效节点
    LTNode* Next = phead->next;
    //开始插入
    Next->prev = newnode;
    newnode->next = Next;
    phead->next = newonde;
    newnode->prev = phead;
}
void LTPopBack(LTNode* phead)
{
    assert(phead);
    //找尾
    LTNode* tail = phead->prev;
    //判空
    if(tail == phead);
        return;
    //找到尾结点的前一个结点(倒数第二个)
    LTNode* prevTail = tail->prev;
    //删除尾结点
    prevTail->next = phead;
    phead->prev = prevTail;
    free(tail);     
}
void LTPopFront(LTNode* phead)
{
    //同上
    assert(phead);
    LTNode* del = phead->next;
    if(del == phead)
        return;
    LTNode* next = del->next;
    next->prev = phead;
    phead->next = next;
    free(del);
}
//以下两个接口需要配合查找使用 所以删除接口不需要判断是否为头结点
void LTInsert(LTNode* pos, LTDataType x)
{
    assert(pos);
    LTNode* newnode = (LTNode*)malloc(sizeof(LTNode));
    assert(newnode);
    newnode->data = x;
    LTNode* prev = pos->prev;
    //插入
    pos->prev = newnode;
    newnode->next = pos;
    newnode->prev = prev;
    prev->next = newnode;
}
void LTErase(LTNode* pos)
{  
    assert(pos);
    LTNode* prev = pos->prev;
    LTNode* next = pos->next;
    prev->next = next;
    next->prev = prev;
    free(pos);
}
LTNode* LTFind(LTNode* phead, LTDataType x)
{
    assert(phead);
    LTNode* cur = phead;
    while(cur != phead)
    {
        if(cur->data == x)
            return cur;
        cur = cur->next;
    }
    return NULL;
}
void LTPrint(LTNode* phead)
{
    assert(phead);
    LTNode* cur = phead->next;
    printf("head");
    while(cur != phead)
    {
        printf("<-%d->", cur->data);
        cur = cur->next;
    }
    printf("head\n");
}
void ListDestroy(LTNode* phead)
{
	assert(phead);
	LTNode* cur = 	phead->next;
	while(cur != phead)
	{
		LTNode* next = cur->next;
		ListErase(cur);
		cur = next;
	}
	free(phead);
}
  • 15
    点赞
  • 12
    收藏
    觉得还不错? 一键收藏
  • 11
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值