目录
什么是双链表?
带头结点的优势:
双链表的实现:
什么是循环双链表?
众所周知,顺序表的插入和删除有时候需要大量移动数据,并且每次开辟空间都可能会浪费大量内存和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);
}