目录
链表的分类
链表的结构非常多样,以下情况组合起来就有8种(2x2x2)链表结构:
虽然有这么多的链表的结构,但是我们实际中最常用还是两种结构:单链表和双向带头循环链表。
1.无头单向非循环链表:结构简单,一般不会单独用来存数据。实际中更多是作为其他数据结构的子结构,如哈希桶、图的邻接表等等。另外这种结构在笔试面试中出现很多。
2.带头双向循环链表:结构最复杂,一般用在单独存储数据。实际中使用的链表数据结构,都是带头双向循环链表。另外这个结构虽然结构复杂,但是使用代码实现以后会发现结构会带来很多优势,实现反而简单了,功能和单链表差不多但实现更简单更方便。
本篇就讲的第二种链表。
双向链表
概念与结构
注意:这里的“带头”跟前面我们说的“头结点”是两个概念,实际前面的在单链表阶段称呼不严谨,但是为了同学们更好的理解就直接称为单链表的头结点。
带头链表里的头结点,实际为“哨兵位”,哨兵位结点不存储任何有效元素,只是站在这里“放哨的”。
实现双向链表
创建头文件
创建一个头文件,在里面创建和声明所需要的头文件、函数和单链表结构。如下:
#pragma once
#define _CRT_SECURE_NO_WARNINGS
#pragma once
#include<stdio.h>
#include<stdlib.h>
#include<assert.h>
#include<stdbool.h>
typedef int LTDataType; //定义数据类型方便改代码
//以下是双向链表的基本结构
typedef struct ListNode
{
LTDataType data;
struct ListNode* next;
struct ListNode* prev;
}LTNode;
//以下是对所写函数的声明,所有接下来都会写到
LTNode* BuyNode(LTDataType x);
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);
LTNode* LTFind(LTNode* phead, LTDataType x);
void LTInsert(LTNode* pos, LTDataType x);
void LTErase(LTNode* pos);
创建哨兵位与申请空间
我们知道双向链表是需要一个不存储数据的哨兵位的,创建一个哨兵位也需要申请空间,下面给出空间申请的代码:
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;
}
这里创建哨兵位有个问题,使用一级指针还是二级指针,因为哨兵位不存储数据,我们也不会改变哨兵位的地址,所以使用一级指针。
LTNode* LTInit()
{
LTNode* phead = BuyNode(-1);
return phead;
}
销毁
void LTDesTroy(LTNode* phead)
{
LTNode* pcur = phead->next; //哨兵位不存储数据,所以从next结点开始
while (pcur->next != phead)
{
LTNode* next = pcur->next;
free(pcur);
pcur = next;
}
free(phead);
phead = NULL;
}
打印
链表的打印都大差不差,需要注意一下循环结束的结点。
void LTPrint(LTNode* phead)
{
LTNode* pcur = phead->next;
while (pcur != phead)
{
printf("%d -> ", pcur->data);
pcur = pcur->next;
}
printf("NULL\n");
}
尾插
这里还需要回顾一下上面那张图
这张图很重要,这样才能理解各种插入操作。
void LTPushBack(LTNode* phead, LTDataType x)
{
assert(phead);
LTNode* newnode = BuyNode(x);
newnode->prev = phead->prev;
newnode->next = phead;
phead->prev->next = newnode;
phead->prev = newnode;
}
头插
void LTPushFront(LTNode* phead, LTDataType x)
{
assert(phead);
LTNode* newnode = BuyNode(x);
newnode->prev = phead;
newnode->next = phead->next;
phead->next->prev = newnode;
phead->next = newnode;
}
尾删
这里我使用了一个bool类型,如下:
bool LTEmpty(LTNode* phead)
{
assert(phead);
return (phead->next == phead);
}
返回bool类型的函数是一种返回布尔值(true
或 false
)的函数。这里判断了哨兵位结点是否为空以及下一结点是否为自己。
尾删操作:
void LTPopBack(LTNode* phead)
{
assert(!LTEmpty(phead));
LTNode* del = phead->prev;
del->prev->next = phead;
phead->prev = del->prev;
free(del);
del = NULL;
}
头删
void LTPopFront(LTNode* phead)
{
assert(!LTEmpty(phead));
LTNode* del = phead->next;
del->next->prev = phead;
phead->next = del->next;
free(del);
del = NULL;
}
查找
LTNode* LTFind(LTNode* phead, LTDataType x)
{
assert(phead);
LTNode* pcur = phead->next;
while (pcur != phead)
{
if (pcur->data == x)
return pcur;
pcur = pcur->next;
}
return NULL;
}
在任意位置后插入
void LTInsert(LTNode* pos, LTDataType x) //在pos后插入
{
assert(pos);
LTNode* newnode = BuyNode(x);
newnode->next = pos->next;
newnode->prev = pos;
pos->next->prev = newnode;
pos->next = newnode;
}
删除任意位置
void LTErase(LTNode* pos)
{
assert(pos);
pos->next->prev = pos->prev;
pos->prev->next = pos->next;
free(pos);
pos = NULL;
}
双向链表的整体实现
List.h
#pragma once
#define _CRT_SECURE_NO_WARNINGS
#pragma once
#include<stdio.h>
#include<stdlib.h>
#include<assert.h>
#include<stdbool.h>
typedef int LTDataType;
typedef struct ListNode
{
LTDataType data;
struct ListNode* next;
struct ListNode* prev;
}LTNode;
LTNode* BuyNode(LTDataType x);
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);
LTNode* LTFind(LTNode* phead, LTDataType x);
void LTInsert(LTNode* pos, LTDataType x);
void LTErase(LTNode* pos);
List.c
#define _CRT_SECURE_NO_WARNINGS
#include"List.h"
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;
}
LTNode* LTInit()
{
LTNode* phead = BuyNode(-1);
return phead;
}
void LTDesTroy(LTNode* phead)
{
LTNode* pcur = phead->next;
while (pcur->next != phead)
{
LTNode* next = pcur->next;
free(pcur);
pcur = next;
}
free(phead);
phead = NULL;
}
void LTPrint(LTNode* phead)
{
LTNode* pcur = phead->next;
while (pcur != phead)
{
printf("%d -> ", pcur->data);
pcur = pcur->next;
}
printf("NULL\n");
}
void LTPushBack(LTNode* phead, LTDataType x)
{
assert(phead);
LTNode* newnode = BuyNode(x);
newnode->prev = phead->prev;
newnode->next = phead;
phead->prev->next = newnode;
phead->prev = newnode;
}
void LTPushFront(LTNode* phead, LTDataType x)
{
assert(phead);
LTNode* newnode = BuyNode(x);
newnode->prev = phead;
newnode->next = phead->next;
phead->next->prev = newnode;
phead->next = newnode;
}
bool LTEmpty(LTNode* phead)
{
assert(phead);
return (phead->next == phead);
}
void LTPopBack(LTNode* phead)
{
assert(!LTEmpty(phead));
LTNode* del = phead->prev;
del->prev->next = phead;
phead->prev = del->prev;
free(del);
del = NULL;
}
void LTPopFront(LTNode* phead)
{
assert(!LTEmpty(phead));
LTNode* del = phead->next;
del->next->prev = phead;
phead->next = del->next;
free(del);
del = NULL;
}
LTNode* LTFind(LTNode* phead, LTDataType x)
{
assert(phead);
LTNode* pcur = phead->next;
while (pcur != phead)
{
if (pcur->data == x)
return pcur;
pcur = pcur->next;
}
return NULL;
}
void LTInsert(LTNode* pos, LTDataType x) //在pos后插入
{
assert(pos);
LTNode* newnode = BuyNode(x);
newnode->next = pos->next;
newnode->prev = pos;
pos->next->prev = newnode;
pos->next = newnode;
}
void LTErase(LTNode* pos)
{
assert(pos);
pos->next->prev = pos->prev;
pos->prev->next = pos->next;
free(pos);
pos = NULL;
}