前面的文章有介绍过顺序表与单向链表
前言 : 这一部份我们介绍的双向链表主要是说带头双向循环的结构 !
一、双向链表的构造
在不考虑有头无头、循环不循环的情况下,双向链表和单向链表在结构上的差别就是双向链表的结构多一个指针,指向的是前一个节点,这也让我们在许多操作上可以更加高效
结构比较
二、双向链表基本功能接口
2.1 定义结构体
// 定义结构体
typedef int LTDataType;
typedef struct ListNode {
struct ListNode* prev;
struct ListNode* next;
LTDataType data;
}LTNode;
2.2 创建节点
// 创建节点
LTNode* BuyLTNode(LTDataType x) {
LTNode* node = (LTNode*)malloc(sizeof(LTNode));
if (node == NULL) {
perror("malloc fail");
return NULL;
exit(-1);
}
node->next = NULL;
node->prev = NULL;
node->data = x;
return node;
}
2.3 初始化
// 初始化
LTNode* LTInit() {
// 这一部份是创建哨兵位头节点
LTNode* phead = BuyLTNode(-1);
phead->next = phead;
phead->prev = phead;
return phead;
}
哨兵位头节点的作用
- 哨兵节点不存储实际数据,仅作为链表存在的标识,简化插入/删除操作的边界条件处理
- 空链表时,哨兵节点的 next 和 prev 均指向自身,形成循环结构
这里我们说哨兵位节点不存储实际数据。在创建节点传入的 -1 只是为了创建哨兵位节点,实际上不会使用到。
要记得,只要头节点做完初始化后,phead就不可能为空,因此后面的断言都是在这个观念上进行断言。
2.4 销毁
//销毁
void ListDestory(LTNode* phead)
{
assert(phead);
LTNode* cur = phead->next;
while (cur != phead)
{
LTNode* next = cur->next;
ListErase(cur);
cur = next;
}
free(phead);
}
2.5 打印
// 打印
void LTPrint(LTNode* phead) {
assert(phead);
LTNode* cur = phead;
printf("%d->", cur->data);
cur = cur->next;
while (cur != phead) {
printf("%d->", cur->data);
cur = cur->next;
}
printf("\n");
}
2.6 判断是否为空
需要注意的是,如果是只剩下哨兵位节点,就不能继续进行删的动作
// 判断是否为空 若只有哨兵位的头结点就不能继续删
bool LTEmpty(LTNode* phead) {
assert(phead);
return phead->next == phead;
}
三、双向链表增加功能接口
3.1 尾插
// 尾插
void LTPushBack(LTNode* phead, LTDataType x) {
assert(phead); //phead一定不为空
LTNode* newnode = BuyLTNode(x);
LTNode* tail = phead->prev;
tail->next = newnode;
newnode->prev = tail;
newnode->next = phead;
phead->prev = newnode;
// 也可以直接使用后面的pos位置前插入
// LTInsert(phead, x);
}
3.2 头插
需要注意的是,头插是在哨兵位节点的下一个节点插入,不是在哨兵位头节点前面插入
// 头插
void LTPushfFront(LTNode* phead, LTDataType x) {
assert(phead);
LTNode* newnode = BuyLTNode(x);
newnode->next = phead->next;
phead->next->prev = newnode;
phead->next = newnode;
newnode->prev = phead;
//LTInsert(phead, x); // 一样可以直接替换成pos位置前插入
}
3.3 在pos位置前插入
// pos位置前插入
void LTInsert(LTNode* pos, LTDataType x) {
assert(pos);
LTNode* prev = pos->prev;
LTNode* newnode = BuyLTNode(x);
prev->next = newnode;
newnode->prev = prev;
newnode->next = pos;
pos->prev = newnode;
}
四、 双向链表删除功能接口
4.1 尾删
// 尾删
void LTPopBack(LTNode* phead) {
assert(phead);
// 如果剩下哨兵位头节点,就不能继续删
assert(!LTEmpty(phead));
LTNode* tail = phead->prev;
LTNode* tailprev = tail->prev;
tailprev->next = phead;
phead->prev = tailprev;
free(tail);
tail = NULL;
}
4.2 头删
// 头删
void LTPopFront(LTNode* phead) {
assert(phead);
LTNode* cur = phead->next;
LTNode* next = cur->next;
phead->next = next;
next->prev = phead;
free(cur);
cur = NULL;
}
4.3 在pos位置处删除
//删除pos位置节点
void ListErase(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->next;
while (cur != phead) {
if (cur->data == x) {
return cur;
}
cur = cur->next;
}
return NULL;
}
六、 顺序表与链表的比较
七、完整代码
7.1 List.h
#define _CRT_SECURE_NO_WARNINGS 1
#include <stdio.h>
#include <stdlib.h>
#include <assert.h>
#include <stdbool.h>
// 定义结构体
typedef int LTDataType;
typedef struct ListNode {
struct ListNode* prev;
struct ListNode* next;
LTDataType data;
}LTNode;
// 创建节点
LTNode* BuyLTNode(LTDataType x);
//初始化
LTNode* LTInit();
// 销毁
void destroy();
// 打印
void LTPrint(LTNode* phead);
// 判断是否链表为空
bool LTEmpty();
//尾插
void LTPushBack(LTNode* phead, LTDataType x);
// 尾删
void LTPopBack(LTNode* phead);
// 头插
void LTPushfFront(LTNode* phead, LTDataType x);
// 头删
void LTPopFront(LTNode* phead);
// pos前插入
void LTInsert(LTNode* pos, LTDataType x);
// 在pos位置删除节点
void ListErase(LTNode* pos);
// 查找
LTNode* LTFind(LTNode* phead, LTDataType x);
7.2 List.c
#define _CRT_SECURE_NO_WARNINGS 1
#include "List.h"
// 创建节点
LTNode* BuyLTNode(LTDataType x) {
LTNode* node = (LTNode*)malloc(sizeof(LTNode));
if (node == NULL) {
perror("malloc fail");
return NULL;
exit(-1);
}
node->next = NULL;
node->prev = NULL;
node->data = x;
return node;
}
// 初始化
LTNode* LTInit() {
LTNode* phead = BuyLTNode(-1);
phead->next = phead;
phead->prev = phead;
return phead;
}
//销毁
void ListDestory(LTNode* phead)
{
assert(phead);
LTNode* cur = phead->next;
while (cur != phead)
{
LTNode* next = cur->next;
ListErase(cur);
cur = next;
}
free(phead);
}
// 打印
void LTPrint(LTNode* phead) {
assert(phead);
LTNode* cur = phead;
printf("%d->", cur->data);
cur = cur->next;
while (cur != phead) {
printf("%d->", cur->data);
cur = cur->next;
}
printf("\n");
}
// 判断是否为空 若只有哨兵位的头结点就不能继续删
bool LTEmpty(LTNode* phead) {
assert(phead);
return phead->next == phead;
}
// 尾插
void LTPushBack(LTNode* phead, LTDataType x) {
assert(phead); //phead一定不为空
//LTNode* newnode = BuyLTNode(x);
//LTNode* tail = phead->prev;
//tail->next = newnode;
//newnode->prev = tail;
//newnode->next = phead;
//phead->prev = newnode;
LTInsert(phead, x);
}
// 尾删
void LTPopBack(LTNode* phead) {
assert(phead);
assert(!LTEmpty(phead));
LTNode* tail = phead->prev;
LTNode* tailprev = tail->prev;
tailprev->next = phead;
phead->prev = tailprev;
free(tail);
tail = NULL;
}
// 头插
void LTPushfFront(LTNode* phead, LTDataType x) {
assert(phead);
//LTNode* newnode = BuyLTNode(x);
//newnode->next = phead->next;
//phead->next->prev = newnode;
//phead->next = newnode;
//newnode->prev = phead;
LTInsert(phead, x);
}
// 头删
void LTPopFront(LTNode* phead) {
assert(phead);
LTNode* cur = phead->next;
LTNode* next = cur->next;
phead->next = next;
next->prev = phead;
free(cur);
cur = NULL;
}
// pos位置前插入
void LTInsert(LTNode* pos, LTDataType x) {
assert(pos);
LTNode* prev = pos->prev;
LTNode* newnode = BuyLTNode(x);
prev->next = newnode;
newnode->prev = prev;
newnode->next = pos;
pos->prev = newnode;
}
//删除pos位置节点
void ListErase(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->next;
while (cur != phead) {
if (cur->data == x) {
return cur;
}
cur = cur->next;
}
return NULL;
}