本文介绍一种带有哨兵位的双向循环链表。
目录
哨兵位的双向循环链表
结构
如图所示,此链表有两个节点,一个prev指向前一个节点,一个next指向下一个节点。
代码:
typedef int LNDataType;//方便改动数据类型
typedef struct DListNode
{
LNDataType data;
struct DListNode* prev;//前一个节点
struct DListNode* next;//后一个节点
}DLTN;
初始化
此链表带有哨兵位的 头节点,初始化时,就是将哨兵位创建出来,应是这样的结构:
让它自己指向自己。
代码:
//初始化
DLTN* InitList()
{
DLTN* head = BuyNode(-1);//BuyNode()是创建节点的函数
head->next = head;
head->prev = head;
return head;
}
创建新节点BuyNode()
代码:
DLTN* BuyNode(LNDataType x)
{
DLTN* node = (DLTN*)malloc(sizeof(DLTN));
if (node == NULL)
{
perror("BuyNode malloc:");
return NULL;
}
node->data = x;
node->next = NULL;
node->prev = NULL;
return node;
}
尾插
在链表尾部插入新节点,首先要找到尾部位置,此链表的优势就体现出来了,头节点的prev就是指向链表的尾部的,所以我们这样做:
代码:
void DLPushBack(DLTN* phead, LNDataType x)
{
//找尾
DLTN* tail = phead->prev;
//创建新节点
DLTN* newnode = BuyNode(x);
//连接
tail->next = newnode;
newnode->prev = tail;
newnode->next = phead;
phead->prev = newnode;
}
头插
在链表头部插入新节点,注意:此头部不是哨兵位节点,而是指有效链表的第一个节点。
和尾插一样的思路,先找头部位置:哨兵位的next指向头部位置。然后创建新节点,插入。
代码:
void DLPushFront(DLTN* phead, LNDataType x)
{
//找头
DLTN* head = phead->next;
//创建新节点
DLTN* newnode = BuyNode(x);
//连接
newnode->next = head;
head->prev = newnode;
phead->next = newnode;
newnode->prev = phead;
}
尾删
删除尾部节点
代码:
void DLTPopBack(DLTN* phead)
{
assert(phead);
assert(!DLTEmpty(phead));
DLTN* tail = phead->prev;//记录尾部节点
DLTN* tailprev = tail->prev;//记录尾部的前一个节点
//连接尾部的前一个节点,使其成为新的尾部
tailprev->next = phead;
phead->prev = tailprev;
//释放旧尾部节点
free(tail);
}
注意:有效链表全部删除后,就只剩哨兵位,此时不可再删除。DLTEmpty()是一个判空函数,当有效链表为空时,会返回true,否则返回false。
代码:
bool DLTEmpty(DLTN* phead)
{
assert(phead);
return phead == phead->next;
}
头删
删除头部节点
代码:
void DLTPopFront(DLTN* phead)
{
assert(phead);
assert(!DLTEmpty(phead));
DLTN* head = phead->next;//找头
DLTN* headnext = head->next;//找:头部位置的下一个节点
//连接,形成新的头
phead->next = headnext;
headnext->prev = phead;
//释放旧的头
free(head);
}
头删尾删大同小异,可互相参考。
查找
代码:
DLTN* DLTFind(DLTN* phead, LNDataType x)
{
assert(phead);
DLTN* cur = phead->next;
//根据提供的数据查找,遍历一遍
//注意:此链表是循环的,遍历时从头开始,回到头结束,就视为一遍。
while (cur != phead)
{
if (cur->data == x)
{
return cur;
}
cur = cur->next;
}
//找不到返回空指针
return NULL;
}
插入
这里的插入是在指定位置的前面插入新的节点。
代码:
//插入
void DLTInsert(DLTN* pos, LNDataType x)
{
assert(pos);
DLTN* posprev = pos->prev;//记录指定位置的前一个节点
DLTN* newnode = BuyNode(x);
//插入新节点
posprev->next = newnode;
newnode->prev = posprev;
pos->prev = newnode;
newnode->next = pos;
}
删除
这里的删除是删除指定位置的节点。
代码:
//删除
void DLTErase(DLTN* pos)
{
assert(pos);
DLTN* posprev = pos->prev;//记录当前位置的前一个节点
DLTN* posnext = pos->next;//记录当前位置的下一个节点
//连接前一个节点和下一个节点
posnext->prev = posprev;
posprev->next = posnext;
//释放当前节点
free(pos);
}
销毁
遍历一遍链表,进行销毁
代码:
//销毁
void LTDestory(DLTN* phead)
{
assert(phead);
DLTN* cur = phead->next;
while (cur != phead)
{
DLTN* curnext = cur->next;
free(cur);
cur = curnext;
}
free(phead);
}
完整代码
分三个文件:
list.h
存放函数的声明,结构体的定义,头文件的引用等
#pragma once
#include<stdio.h>
#include<stdlib.h>
#include<assert.h>
#include<stdbool.h>
typedef int LNDataType;
typedef struct DListNode
{
LNDataType data;
struct DListNode* prev;
struct DListNode* next;
}DLTN;
//初始化
DLTN* InitList();
//打印
void DLTPrint(DLTN* phead);
void DLPushBack(DLTN* phead, LNDataType x);
void DLPushFront(DLTN* phead, LNDataType x);
void DLTPopFront(DLTN* phead);
void DLTPopBack(DLTN* phead);
//查找
DLTN* DLTFind(DLTN* phead, LNDataType x);
//插入
void DLTInsert(DLTN* pos, LNDataType x);
//删除
void DLTErase(DLTN* pos);
//销毁
void LTDestory(DLTN* phead);
list.c
具体实现链表的功能。
#include"list.h"
DLTN* BuyNode(LNDataType x)
{
DLTN* node = (DLTN*)malloc(sizeof(DLTN));
if (node == NULL)
{
perror("BuyNode malloc:");
return NULL;
}
node->data = x;
node->next = NULL;
node->prev = NULL;
return node;
}
bool DLTEmpty(DLTN* phead)
{
assert(phead);
return phead == phead->next;
}
//初始化
DLTN* InitList()
{
DLTN* head = BuyNode(-1);
head->next = head;
head->prev = head;
return head;
}
//打印
void DLTPrint(DLTN* phead)
{
assert(phead);
DLTN* cur = phead->next;
printf("哨兵位<==>");
while (cur != phead)
{
printf("%d<==>", cur->data);
cur = cur->next;
}
printf("\n");
}
void DLPushBack(DLTN* phead, LNDataType x)
{
//找尾
DLTN* tail = phead->prev;
//创建新节点
DLTN* newnode = BuyNode(x);
//连接
tail->next = newnode;
newnode->prev = tail;
newnode->next = phead;
phead->prev = newnode;
//DLTInsert(phead, x);
}
void DLPushFront(DLTN* phead, LNDataType x)
{
//找头
DLTN* head = phead->next;
//创建新节点
DLTN* newnode = BuyNode(x);
//连接
newnode->next = head;
head->prev = newnode;
phead->next = newnode;
newnode->prev = phead;
//DLTInsert(phead->next, x);
}
void DLTPopBack(DLTN* phead)
{
assert(phead);
assert(!DLTEmpty(phead));
DLTN* tail = phead->prev;
DLTN* tailprev = tail->prev;
tailprev->next = phead;
phead->prev = tailprev;
free(tail);
//DLTErase(phead->prev);
}
void DLTPopFront(DLTN* phead)
{
assert(phead);
assert(!DLTEmpty(phead));
DLTN* head = phead->next;
DLTN* headnext = head->next;
free(head);
phead->next = headnext;
headnext->prev = phead;
//DLTErase(phead->next);
}
//查找
DLTN* DLTFind(DLTN* phead, LNDataType x)
{
assert(phead);
DLTN* cur = phead->next;
while (cur != phead)
{
if (cur->data == x)
{
return cur;
}
cur = cur->next;
}
return NULL;
}
//插入
void DLTInsert(DLTN* pos, LNDataType x)
{
assert(pos);
DLTN* posprev = pos->prev;
DLTN* newnode = BuyNode(x);
posprev->next = newnode;
newnode->prev = posprev;
pos->prev = newnode;
newnode->next = pos;
}
//删除
void DLTErase(DLTN* pos)
{
assert(pos);
DLTN* posprev = pos->prev;
DLTN* posnext = pos->next;
posnext->prev = posprev;
posprev->next = posnext;
free(pos);
}
//销毁
void LTDestory(DLTN* phead)
{
assert(phead);
DLTN* cur = phead->next;
while (cur != phead)
{
DLTN* curnext = cur->next;
free(cur);
cur = curnext;
}
free(phead);
}
test.c
用来测试链表,可自行编写。