目录
概念:
双链表(Doubly Linked List)是一种常见的数据结构,它与单链表类似,但每个节点除了前驱指针指向前一个节点外,还有一个后继指针指向后面一个节点,即每个节点拥有两个指针域。与单链表相比,双链表可以在O(1)的时间内实现在任意位置的插入和删除操作,而不需要像单链表一样需要先遍历找到前一个节点。
双链表通常有一个头节点和一个尾节点,它们的前驱和后继指针为空。每个节点包含三个字段:前驱指针、后继指针和数据。因为双链表的每个节点都拥有两个指针,所以节点的空间开销比单链表要大一些。
双链表的优点是插入和删除元素效率高,但缺点是需要占用一定的额外空间。在需要频繁插入和删除元素而对内存空间不是非常敏感的情况下,使用双链表可以提高代码的效率。
结点结构示意图:
结点结构定义:
typedef struct ListNode
{
struct ListNode* next;
struct ListNode* prev;
LTDataType data;
}LTNode;
头结点示意图:
双链表的初始化就是创建一个头结点,头结点的prev指针指向自己,Next指针也指向自己。结点的空间在堆区,在创建结点时向堆区申请一个结点大小的内存。
LTNode* BuyLTNode(LTDataType x)
{
LTNode* node = (LTNode*)malloc(sizeof(LTNode));
if (node == NULL)
{
perror("malloc fail");
exit(-1);
}
node->data = x;
node->next = NULL;
node->prev = NULL;
return node;
}
头结点的初始化:
LTNode* LTInit()
{
LTNode* phead = BuyLTNode(-1);
phead->next = phead;
phead->prev = phead;
return phead;
}
尾插:
我们先创建出双链表,然后在其尾部插入一个新的结点;
功能如图所示
代码如下:
void LTPushBack(LTNode* phead, LTDataType x)
{
assert(phead);
LTNode* newnode = BuyLTNode(x);
LTNode* tail = phead->prev;
newnode->prev = tail;
newnode->next = phead;
phead->prev = newnode;
tail->next = newnode;
}
运行效果:
头插
功能如图所示
代码如下:
void LTPushFront(LTNode* phead, LTDataType x)
{
/*assert(phead);
LTNode* newnode = BuyLTNode(x);
newnode->next = phead->next;
phead->next->prev = newnode;
phead->next = newnode;
newnode->prev = phead;*/
//与顺序无关的写法
/*LTNode* newnode = BuyLTNode(x);
LTNode* first = phead->next ;
phead->next = newnode;
newnode->prev = phead;
newnode->next = first;
first->prev = newnode;*/
LTInsert(phead->next , x);
}
运行效果:
尾删
功能如图所示
代码如下:
void LTPopBack(LTNode* phead)
{
assert(phead);
assert(phead->next !=phead);
LTNode* tail = phead->prev;
LTNode* tailPrev = tail->prev;
phead->prev = tailPrev;
tailPrev->next = phead;
free(tail);
}
运行效果:
头删
代码如下:
void LTPopFront(LTNode* phead)
{
assert(phead);
assert(phead->next!=phead);
LTNode* frist = phead->next;
phead->next = frist->next;
frist->next->prev = phead;
free(frist);
}
运行效果:
查找
对于 双向链表 来说,它是没有指向 NULL 的节点的,它是一个环,停不下来。所以我们要把循环的截止条件设定为 != phead ,这个条件就表示,已经遍历过一遍链表了,走到哨兵位了。
如果找到,返回该节点的地址;如果找不到返回 NULL 。
代码如下:
LTNode* LTFind(LTNode* phead, LTDataType x)
{
assert(phead);
LTNode* cur = phead->next;
while (cur != phead)
{
if (cur->data == x)
return cur;
cur = cur->next;
}
return NULL;
}
在pos位置之前插入新的结点
代码如下:
void LTInsert(LTNode* pos, LTDataType x)
{
assert(pos);
LTNode* newnode = BuyLTNode(x);
newnode->next = pos;
newnode->prev = pos->prev;
pos->prev->next = newnode;
pos->prev= newnode;
}
运行效果:
在pos位置删除
代码如下:
//删除pos
void LTErase(LTNode* pos)
{
assert(pos);
LTNode* Front = pos->prev;
LTNode* Next = pos->next;
Front->next = Next;
Next->prev = Front;
free(pos);
}
运行效果:
销毁
哨兵位和链表的节点全部删除,可以使用循环来删除。所以,销毁 双向链表的思路和查找是差不多的,循环的结束条件为 != phead。在销毁的过程中,每次记住我当前节点的下一个节点,以便迭代。
void LTDestroy(LTNode* phead)
{
assert(phead);
LTNode* cur = phead->next;
while (cur != phead)
{
LTNode* next = cur->next;
free(cur);
cur = next;
}
free(phead);
}
完整代码
List.h
#pragma once
#include <stdio.h>
#include <stdlib.h>
#include <assert.h>
typedef int LTDataType;
typedef struct ListNode
{
LTDataType data;
struct ListNode* next;
struct ListNode* prev;
}LTNode;
// 初始化
LTNode* ListInit();
// 打印
void ListPrint(LTNode* phead);
// 尾插
void ListPushBack(LTNode* phead, LTDataType x);
// 尾删
void ListPopBack(LTNode* phead);
// 头插
void ListPushFront(LTNode* phead, LTDataType x);
// 头删
void ListPopFront(LTNode* phead);
// 查找元素
LTNode* ListFind(LTNode* phead, LTDataType x);
// 双向链表在pos的前面进行插入
void ListInsert(LTNode* pos, LTDataType x);
// 双向链表删除pos位置的节点
void ListErase(LTNode* pos);
// 销毁双向链表
void ListDestroy(LTNode* phead);
List.c
#define _CRT_SECURE_NO_WARNINGS 1
#include "List.h"
// 初始化
LTNode* ListInit()
{
LTNode* phead = (LTNode*)malloc(sizeof(LTNode));
if (phead == NULL)
{
perror("ListInit");
exit(-1);
}
phead->next = phead;
phead->prev = phead;
return phead;
}
LTNode* BuyListNode(LTDataType x)
{
LTNode* newnode = (LTNode*)malloc(sizeof(LTNode));
if (newnode == NULL)
{
perror("ListPushBack");
exit(-1);
}
newnode->data = x;
newnode->next = NULL;
newnode->prev = NULL;
return newnode;
}
void ListPrint(LTNode* phead)
{
assert(phead);
LTNode* cur = phead->next;
while (cur != phead)
{
printf("%d->", cur->data);
cur = cur->next;
}
printf("\n");
}
// 尾插
void ListPushBack(LTNode* phead, LTDataType x)
{
assert(phead);
//LTNode* tail = phead->prev;
//LTNode* newnode = BuyListNode(x);
//tail->next = newnode;
//newnode->prev = tail;
//newnode->next = phead;
//phead->prev = newnode;
ListInsert(phead, x);//复用
}
// 尾删
void ListPopBack(LTNode* phead)
{
assert(phead);
assert(phead->next != phead);
/*LTNode* tail = phead->prev;
LTNode* tailprev = tail->prev;
free(tail);
phead->prev = tailprev;
tailprev->next = phead;*/
ListErase(phead->prev);
}
// 另一种写法
//void ListPopBack(LTNode* phead)
//{
// assert(phead);
// assert(phead->next != phead);// 防止把哨兵位删掉
// LTNode* tail = phead->prev;
//
// phead->prev = tail->prev;
// tail->prev->next = phead;
//
// free(tail);
//}
// 头插
void ListPushFront(LTNode* phead, LTDataType x)
{
assert(phead);
/*LTNode* newnode = BuyListNode(x);
LTNode* next = phead->next;
phead->next = newnode;
newnode->prev = phead;
newnode->next = next;
next->prev = newnode;*/
ListInsert(phead->next, x);
}
// 头删
void ListPopFront(LTNode* phead)
{
assert(phead);
assert(phead->next != phead);
/*LTNode* next = phead->next;
LTNode* nextNext = next->next;
phead->next = nextNext;
nextNext->prev = phead;
free(next);*/
ListErase(phead->next);//复用
}
// 查找
LTNode* ListFind(LTNode* phead, LTDataType x)
{
assert(phead);
LTNode* cur = phead->next;
while (cur != phead)
{
if (cur->data == x)
{
return cur;
}
cur = cur->next;
}
return NULL;
}
// 在pos位置之前插入
void ListInsert(LTNode* pos, LTDataType x)
{
assert(pos);
LTNode* newnode = BuyListNode(x);
LTNode* posPrev = pos->prev;
newnode->prev = posPrev;
posPrev->next = newnode;
newnode->next = pos;
pos->prev = newnode;
}
// 在pos位置删除
void ListErase(LTNode* pos)
{
assert(pos);
LTNode* posPrev = pos->prev;
LTNode* posNext = pos->next;
posPrev->next = posNext;
posNext->prev = posPrev;
free(pos);
pos = NULL;
}
// 销毁
void ListDestroy(LTNode* phead)
{
assert(phead);
LTNode* cur = phead->next;
while (cur != phead)
{
LTNode* next = cur->next;
free(cur);
cur = next;
}
free(phead);
phead = NULL;
}