前言
我们前面学习了单链表(点击这里跳转到单链表博客),那么应该发现了一个问题,就是我每次尾插和尾删都需要先把链表遍历一遍,这样是不是过于麻烦了,这时候我们就可以使用双向链表。
1. 链表的分类
- 带头和不带头
首先带头就是在链表的头节点前面再添加一个节点,这个节点的next指针指向的是头节点并且不存储任何的数据,就像一个哨兵一样(所以我们也把这个节点成为哨兵位)。
不带头就是我的链表的第一个节点就会存储数据。
- 单向和双向
单向就是我只有一个指针变量,只用来存放我下一个节点的地址。
双向就是我在单向的基础上,再加一个指针变量,这个指针变量是用来存放前一个节点的地址。
- 循环和不循环
是否循环是看你最后一个节点的next的指向;如果指向NULL
,就为不循环;如果指向的是我的头节点或者链表的其他节点,就为循环链表。
一个链表都是包含这三个元素的,这样下来,前前后后能组成八种链表,其中单链表和双链表是两个极端。
单链表的全名是不带头单向不循环链表。
双链表的全名是带头双向循环链表。
2. 双链表的结构
typedef int LTDATATYPE;
typedef struct ListNode
{
LTDATATYPE data;
struct ListNode* next; //指向下一个节点
struct ListNode* prev; //指向前一个节点 prev(previous的缩写)
}LTNode;
3. 双链表接口的实现
头文件(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; //指向前一个 prev(previous的缩写)
}LTNode;
//初始化
LTNode* LTInit();
void LTDesTroy(LTNode* phead);
void LTPrint(LTNode* phead);
//插入数据之前,链表必须初始化到只有一个头结点的情况
//不改变哨兵位的地址,因此传一级即可
//尾插
void LTPushBack(LTNode* phead, LTDATATYPE x);
//头插
void LTPushFront(LTNode* phead, LTDATATYPE x);
//尾删
void LTPopBack(LTNode* phead);
//头删
void LTPopFront(LTNode* phead);
//在pos位置之后插入数据
void LTInsert(LTNode* pos, LTDATATYPE x);
//删除pos节点
void LTErase(LTNode* pos);
LTNode* LTFind(LTNode* phead, LTDATATYPE x);
3.1 创建新节点
LTNode* BuyNode(LTDATATYPE x)
{
LTNode* Node = (LTNode*)malloc(sizeof(LTNode));
if(Node == NULL)
{
perror("malloc fail!");
exit(1);
}
Node->data = x;
Node->next = Node->prev = Node;
return Node;
}
由于这段函数只用来服务该文件内的函数,所以不需要再头节点声明;如果后面需要再其他文件内使用,则再声明到头文件即可。
3.2 双链表的初始化
由于我们这个双链表是带头的,所以我们可以使用函数(创建新节点)来解决
//初始化
LTNode* LTInit(