目录
之前两篇介绍了单链表(无头单向非循环链表),接下来我将介绍它的同源兄弟:双链表
前言:
双链表又称带头双向循环链表,它是一种特殊的数据结构,它结合了双向链表和循环链表的特点。在双链表中,每个节点除了拥有指向下一个节点的指针外,还拥有指向上一个节点的指针。此外,列表的头节点和尾节点通过这些指针相互连接,形成一个闭环。这种结构允许从任何一个节点开始,既可以向前遍历,也可以向后遍历,直到回到起点,从而实现高效的双向遍历。
一、双链表基本介绍
1.1操作概念
● 定义节点结构:每个节点包含数据部分和两个指针部分,分别指向前一个节点和后一个节点。
● 创建头节点:头节点是双向循环列表的起始点,它的前驱指针指向自己,后继指针也指向自己,形成一个闭环。
● 插入节点:在特定位置插入新节点时,需要更新新旧节点的前驱和后继指针,确保列表的完整性。
● 删除节点:删除特定节点时,同样需要更新前后节点的指针,避免出现悬空指针。
● 遍历列表:可以通过前驱指针或后继指针从任意节点开始,向前或向后遍历整个列表。
1.2操作类型
常见操作包括初始化、插入、删除、查找和遍历。这些操作通常涉及到对链表节点的指针进行操作,以实现数据的动态管理。
1.3实现图
头结点head被称为哨兵节点,是一个附加的链表节点。该节点作为第一个节点,它的值域不存储任何东西。只是为了操作的方便而引入的。如果一个链表有哨兵节点的话,那么线性表的第一个元素应该是链表的第二个节点
二、双链表的实现
2.1定义结构体
由于是双向带头,所以需要定义两个指针,prev指向上一个节点、next指向下一个节点。
typedef int ListDataType;
typedef struct DList
{
int data;
struct Dlist* prev;
struct Dlist* next;
}ListNode;
2.2双链表的创建新节点
新节点newcode,两个指针都置为空,内含的值data为x
ListNode* BuyNewNode(ListDataType x)
{
ListNode* newcode = (ListNode*)malloc(sizeof(ListNode));
if (newcode == NULL)
{
perror("malloc fail");
return NULL;
}
newcode->data = x;
newcode->next = NULL;
newcode->prev = NULL;
return newcode;
}
2.3初始化
//链表初始化创建哨兵节点
ListNode* ListInit()
{
ListNode* head = BuyNewNode(-1);
head->next = head;
head->prev = head;
return head;
}
2.4遍历打印
//链表打印
void DlistPrint(ListNode* phead)
{
ListNode* cur = phead->next;
printf("<-head->");
while (cur != phead)
{
printf("%d<->", cur->data);
cur = cur->next;
}
printf("\n");
}
2.5插入
头插
在哨兵节点下创建新节点nowcode,进行插入。
//链表头插
void DLlistPushFront(ListNode* phead, ListDataType x)
{
assert(phead);
ListNode* newcode = BuyNewNode(x);
ListNode* cur = phead->next;
phead->next = newcode;
newcode->prev = phead;
newcode->next = cur;
cur->prev = newcode;
}
尾插
在哨兵节点上创建新节点newcode,进行插入。
//链表尾插
void DLlistPushBack(ListNode* phead, ListDataType x)
{
assert(phead);
ListNode* newcode = BuyNewNode(x);
ListNode* tail = phead->prev;
tail->next = newcode;
newcode->prev = tail;
newcode->next = phead;
phead->prev = newcode;
}
指定位置之后插入
在pos位置之后新建节点newnode,进行插入。
//链表pos插入
void DListInset( ListNode* pos,ListDataType x)
{
assert(pos);
ListNode* newcode = BuyNewNode(x);
ListNode* cur = pos->prev;
cur->next = newcode;
newcode->prev = cur;
newcode->next = pos;
pos->prev = newcode;
}
2.6删除
判断链表是否为空
我们利用bool类型判断真假
bool ListEmpty(ListNode* phead)
{
assert(phead);
return phead->prev == phead;
}
头删
把哨兵节点之后的节点del删除。
void DLlistPopFront(ListNode* phead)
{
assert(phead);
assert(!ListEmpty(phead));
ListNode* cur = phead->next;
ListNode* curnext = cur->next;
phead->next = curnext;
curnext->prev = phead;
free(cur);
cur = NULL;
}
尾删
在哨兵节点之前的节点del删除。
//链表尾删
void DLlistPopBack(ListNode* phead)
{
assert(phead);
assert(!ListEmpty(phead));
ListNode* tail = phead->prev;
ListNode* tailprev = tail->prev;
tailprev->next = phead;
phead->prev = tailprev;
free(tail);
tail = NULL;
}
指定位置删除
删除指定节点pos。
//链表pos删除
void DListErase(ListNode* phead, ListNode* pos)
{
assert(!ListEmpty(phead));
assert(pos);
ListNode* posprev = pos->prev;
ListNode* posnext = pos->next;
posprev->next = posnext;
posnext->prev = posprev;
free(pos);
//pos = NULL;
//由于一级指针,NULL改变不了形式参数
}
2.7查找
链表的查找的时候返回写成结构体指针的形式,以便于进行指定位置的插入和删除。
//链表查找
ListNode* DListFind(ListNode* phead, ListDataType pos)
{
assert(phead);
ListNode* cur = phead->next;
while (cur != phead && cur->data != pos)
{
if (cur->data == pos)
{
break;
}
cur = cur->next;
}
return cur;
}
2.8销毁
//链表销毁
void DListDestory(ListNode* phead)
{
assert(phead);
ListNode* cur = phead->next;
while (cur != phead)
{
ListNode* curnext = cur->next;
free(cur);
//在循环第一句自动下一步
cur = curnext;
}
//释放哨兵位头节点
//不需要置空,形参数
free(phead);
}
三、总结
头文件DList.h
#pragma once
#include <stdio.h>
#include <assert.h>
#include <stdlib.h>
#include <stdbool.h>
typedef int ListDataType;
typedef struct DList
{
int data;
struct Dlist* prev;
struct Dlist* next;
}ListNode;
//链表初始化创建哨兵节点
ListNode* ListInit();
//双链表的创建新节点
ListNode* BuyNewNode(ListDataType x);
//打印
void DlistPrint(ListNode* phead);
//尾插
void DLlistPushBack(ListNode* phead, ListDataType x);
//尾删
void DLlistPopBack(ListNode* phead);
//头插
void DLlistPushFront(ListNode* phead, ListDataType x);
//头删
void DLlistPopFront(ListNode* phead);
//查找
ListNode* DListFind(ListNode* phead, ListDataType pos);
//pos插入
void DListInset(ListNode* pos, ListDataType);
//pos删除
void DListErase(ListNode* phead, ListNode* pos);
//销毁
void DListDestory(ListNode* phead);
DList.c
#define _CRT_SECURE_NO_WARNINGS 1
#include "DList.h"
ListNode* BuyNewNode(ListDataType x)
{
ListNode* newcode = (ListNode*)malloc(sizeof(ListNode));
if (newcode == NULL)
{
perror("malloc fail");
return NULL;
}
newcode->data = x;
newcode->next = NULL;
newcode->prev = NULL;
return newcode;
}
//链表初始化创建哨兵节点
ListNode* ListInit()
{
ListNode* head = BuyNewNode(-1);
head->next = head;
head->prev = head;
return head;
}
//打印
void DlistPrint(ListNode* phead)
{
ListNode* cur = phead->next;
printf("<-head->");
while (cur != phead)
{
printf("%d<->", cur->data);
cur = cur->next;
}
printf("\n");
}
//尾插
void DLlistPushBack(ListNode* phead, ListDataType x)
{
assert(phead);
ListNode* newcode = BuyNewNode(x);
ListNode* tail = phead->prev;
tail->next = newcode;
newcode->prev = tail;
newcode->next = phead;
phead->prev = newcode;
}
//判断链表是不是空
bool ListEmpty(ListNode* phead)
{
assert(phead);
return phead->prev == phead;
}
//尾删
void DLlistPopBack(ListNode* phead)
{
assert(phead);
assert(!ListEmpty(phead));
ListNode* tail = phead->prev;
ListNode* tailprev = tail->prev;
tailprev->next = phead;
phead->prev = tailprev;
free(tail);
tail = NULL;
}
//头插
void DLlistPushFront(ListNode* phead, ListDataType x)
{
assert(phead);
ListNode* newcode = BuyNewNode(x);
ListNode* cur = phead->next;
phead->next = newcode;
newcode->prev = phead;
newcode->next = cur;
cur->prev = newcode;
}
//头删
void DLlistPopFront(ListNode* phead)
{
assert(phead);
assert(!ListEmpty(phead));
ListNode* cur = phead->next;
ListNode* curnext = cur->next;
phead->next = curnext;
curnext->prev = phead;
free(cur);
cur = NULL;
}
//查找
ListNode* DListFind(ListNode* phead, ListDataType pos)
{
assert(phead);
ListNode* cur = phead->next;
while (cur != phead && cur->data != pos)
{
if (cur->data == pos)
{
break;
}
cur = cur->next;
}
return cur;
}
//pos插入
void DListInset( ListNode* pos,ListDataType x)
{
assert(pos);
ListNode* newcode = BuyNewNode(x);
ListNode* cur = pos->prev;
cur->next = newcode;
newcode->prev = cur;
newcode->next = pos;
pos->prev = newcode;
}
//pos删除
void DListErase(ListNode* phead, ListNode* pos)
{
assert(!ListEmpty(phead));
assert(pos);
ListNode* posprev = pos->prev;
ListNode* posnext = pos->next;
posprev->next = posnext;
posnext->prev = posprev;
free(pos);
//由于一级指针,NULL改变不了形式参数
}
//销毁
void DListDestory(ListNode* phead)
{
assert(phead);
ListNode* cur = phead->next;
while (cur != phead)
{
ListNode* curnext = cur->next;
free(cur);
//在循环第一句自动下一步
cur = curnext;
}
//释放哨兵位头节点
//不需要值空,形参数
free(phead);
}
测试文件test.c
#define _CRT_SECURE_NO_WARNINGS 1
#include "DList.h"
void testDlist1()
{
ListNode* plist = ListInit();
DLlistPushBack(plist, 1);
DLlistPushBack(plist, 2);
DLlistPushBack(plist, 3);
DLlistPushBack(plist, 4);
ListNode* ret = DListFind(plist, 2);
//ret->data = (ret->data) * 2;
DListInset(ret, 40);
DListErase(plist, ret);
DLlistPopBack(plist);
DlistPrint(plist);
}
void testDlist2()
{
ListNode* plist = ListInit();
DLlistPushFront(plist, 1);
DLlistPushFront(plist, 2);
DLlistPushFront(plist, 3);
DLlistPushFront(plist, 4);
DLlistPushFront(plist, 5);
DLlistPopFront(plist);
DLlistPopFront(plist);
DlistPrint(plist);
}
int main()
{
testDlist1();
//testDlist2();
return 0;
}