1. 带头双向循环链表的定义
带头双向循环链表是链表的一种,它的每个数据结点中都有两个指针,分别指向直接后继和直接前驱。所以,从双向链表中的任意一个结点开始,都可以很方便地访问它的前驱结点和后继结点,这也是对比单链表所具有的优点。
先定义结构体
typedef struct DSListNode
{
//前驱和后继
struct DSListNode* prev;
struct DSListNode* next;
//数据域
DSLtDataType data;
}DSLTNode;
2. 带头双向循环链表的创建
双向循环链表(仅列举一个结点d1)
prev:the previous 上一个
next:the next 下一个
(其他函数放在文末)
- 作用
带头就是这种链表比起其他链表带有一个额外的头结点phead(哨兵位);循环就是在头和尾之间有直接的联系,不用遍历链表找尾;双向就是它的前驱和后继可以直接找到前后结点,它的结构可以完美解决顺序表的缺点,这种结构的好处是在尾插尾删和pos位置删除插入的时候都不用像单链表那样过多地去考虑找尾或头和尾丢失的情况,提高程序效率。
- [ 1 ]
例如尾插操作,在单链表的时候我们需要找到尾,这就需要遍历数组,时间复杂度为O(n),而使用带头双向循环结构时,可以认为尾插是在哨兵位前头插
这样时间复杂度就变成了O(1)
//不用循环找尾,phead->prev直接指向尾
DSLTNode* tail = phead->prev
3. 带头双向循环链表的增删改查
(1)头插头删
- [ 1 ] DSLTPushFront:头插,即在phead->next的位置插入一个结点
//-----------------头插
void DSLTPushFront(DSLTNode* phead, DSLtDataType x)
{
assert(phead);
DSLTNode* newnode = GetNewNode(x);//获取新节点
DSLTNode* cur = phead;
DSLTNode* next = phead->next;//保存原来的第二个结点防止丢失
cur->next = newnode;//先链接新节点和哨兵位
newnode->prev = cur;
newnode->next = next;//链接新节点和原来的第二个结点
next->prev = newnode;
}
- [ 2 ]DSLTPopFront:头删 ,即在phead->next的位置删除一个结点
bool DSLTEmpty(DSLTNode* phead)
{
assert(phead);
return phead->next == phead;//只剩哨兵位
}
//----------------头删
void DSLTPopFront(DSLTNode* phead)
{
assert(phead);
assert(!DSLTEmpty(phead));//暴力检查,只剩哨兵位直接结束
DSLTNode* first = phead->next;
DSLTNode* scend = first->next;
//断开phead->next的链接
phead->next = scend;
scend->prev = phead;
free(first);
first = NULL;
}
(2)尾插尾删
//---------------------尾插
void DSLTPushBack(DSLTNode* phead, DSLtDataType x)
{
assert(phead);
DSLTNode* tail = phead->prev;//找尾
DSLTNode* newnode = GetNewNode(x);
//链接新结点
tail->next = newnode;
newnode->prev = tail;
//头尾链接
newnode->next = phead;
phead->prev = newnode;
}
- 我们可以看到在尾插的过程中并不需要像单链表那样遍历去找尾,而是直接利用循环的特性:tail = phead->prev
尾删也是一样的原理:
//--------------------尾删
void DSLTPopBack(DSLTNode* phead)
{
assert(phead);
assert(!DSLTEmpty(phead));
DSLTNode* tail = phead->prev;
DSLTNode* prev = tail->prev;
prev->next = phead;
phead->prev = prev;
free(tail);
tail = NULL;
}
(3)pos位置的前插与删除
- pos位置的前插:如果使用单链表pos前插,我们需要遍历链表找到pos前的结点,时间复杂度增加;而循环结构只需要pos->prev即可找到pos前的结点。
void DSLTInsert(DSLTNode* pos, DSLtDataType x)
{
assert(pos);
DSLTNode* prev = pos->prev;
DSLTNode* newnode = GetNewNode(x);
newnode->next = pos;
pos->prev = newnode;
prev->next = newnode;
newnode->prev = prev;
}
pos位置删除:
void DSLTEarse(DSLTNode* pos)
{
assert(pos);
DSLTNode* next = pos->next;
DSLTNode* prev = pos->prev;
prev->next = next;
next->prev = prev;
free(pos);
pos = NULL;
}
- 当删除到只剩下哨兵位,是不会继续删除的,因为pos的位置不会置于哨兵位上,因为find函数(文末有代码)实现的时候就是从phead->next开始,不会遍历哨兵位,最终哨兵位自己指向自己
4.插入与删除改良
- [ 1 ]
对于普通前插,我们可以用Pos位置的前插来实现:把phead->next传给pos前插函数,相当于在原链表的哨兵位和第二个结点之间插入数据
void DSLTPushFront(DSLTNode* phead, DSLtDataType x)
{
assert(phead);
DSLTInsert(phead->next, x);
}
前删同理:
void DSLTPopFront(DSLTNode* phead)
{
assert(phead);
DSLTEarse(phead->next);
}
- [ 2 ]
后插可以传phead过去,这样就相当于在phead前插
void DSLTPushBack(DSLTNode* phead, DSLtDataType x)
{
assert(phead);
DSLTInsert(phead, x);
}
而后删可以相当于哨兵位前的pos位置删除,把phead->prev传给pos位置删除函数
void DSLTPopBack(DSLTNode* phead)
{
assert(phead);
assert(!DSLTEmpty(phead));
DSLTEarse(phead->prev);
}
剩下的是总的代码还有find函数的传参和调试,仅供参考:
头文件DSLT.h:
#pragma once
#include<stdio.h>
#include<assert.h>
#include <stdlib.h>
#include <stdbool.h>
typedef int DSLtDataType;
typedef struct DSListNode
{
struct DSListNode* next;
struct DSListNode* prev;
DSLtDataType data;
}DSLTNode;
//不使用二级指针
DSLTNode* DSLTInit();
void DSLTPrint(DSLTNode* phead);
DSLTNode* GetNewNode(DSLtDataType x);
bool DSLTEmpty(DSLTNode* phead);
DSLTNode* DSLTFind(DSLTNode* phead, DSLtDataType x);
size_t ListSize(DSLTNode* phead);
void DSLTDestroy(DSLTNode* phead);
void DSLTPushFront(DSLTNode* phead,DSLtDataType x);
void DSLTPopFront(DSLTNode* phead);
void DSLTPushBack(DSLTNode* phead, DSLtDataType x);
void DSLTPopBack(DSLTNode* phead);
void DSLTInsert(DSLTNode* pos, DSLtDataType x);
void DSLTEarse(DSLTNode* pos);
测试函数DSList.c
#define _CRT_SECURE_NO_WARNINGS
#include "DSLT.h"
DSLTNode* DSLTInit()
{
DSLTNode* guard = (DSLTNode*)malloc(sizeof(DSLTNode));
if (!guard)
{
perror("malloc fail");
exit(-1);
}
guard->next = guard;
guard->prev = guard;
return guard;
//哨兵位不存放值
}
void DSLTPrint(DSLTNode* phead)
{
assert(phead);
printf("phead<=>");
DSLTNode* cur = phead->next;
while (cur != phead)
{
printf("%d<=>", cur->data);
cur = cur->next;
}
printf("\n");
}
DSLTNode* GetNewNode(DSLtDataType x)
{
DSLTNode* newnode= (DSLTNode*)malloc(sizeof(DSLTNode));
if (!newnode)
{
perror("malloc fail");
exit(-1);
}
newnode->next = NULL;
newnode->prev = NULL;
newnode->data = x;
return newnode;
}
bool DSLTEmpty(DSLTNode* phead)
{
assert(phead);
return phead->next == phead;
}
DSLTNode* DSLTFind(DSLTNode* phead, DSLtDataType x)
{
assert(phead);
DSLTNode* cur = phead->next;
while (cur != phead)
{
if (cur->data == x)
{
return cur;
}
cur = cur->next;
}
return NULL;
}
size_t ListSize(DSLTNode* phead)
{
assert(phead);
size_t n = 0;
DSLTNode* cur = phead->next;
while (cur != phead)
{
++n;
cur = cur->next;
}
return n;
}
void DSLTDestroy(DSLTNode* phead)
{
assert(phead);
DSLTNode* cur = phead->next;
while (cur != phead)
{
DSLTNode* next = cur->next;
free(cur);
cur = next;
}
free(phead);
phead = NULL;
}
void DSLTPushFront(DSLTNode* phead, DSLtDataType x)
{
assert(phead);
/*DSLTNode* newnode = GetNewNode(x);
DSLTNode* cur = phead;
DSLTNode* next = phead->next;
cur->next = newnode;
newnode->prev = cur;
newnode->next = next;
next->prev = newnode;*/
DSLTInsert(phead->next, x);
}
void DSLTPopFront(DSLTNode* phead)
{
assert(phead);
/*assert(!DSLTEmpty(phead));
DSLTNode* first = phead->next;
DSLTNode* scend = first->next;
phead->next = scend;
scend->prev = phead;
free(first);
first = NULL;*/
DSLTEarse(phead->next);
}
void DSLTPushBack(DSLTNode* phead, DSLtDataType x)
{
assert(phead);
/*DSLTNode* tail = phead->prev;
DSLTNode* newnode = GetNewNode(x);
tail->next = newnode;
newnode->prev = tail;
newnode->next = phead;
phead->prev = newnode;*/
DSLTInsert(phead, x);//相当于在phead前插
}
void DSLTPopBack(DSLTNode* phead)
{
assert(phead);
assert(!DSLTEmpty(phead));
/*DSLTNode* tail = phead->prev;
DSLTNode* prev = tail->prev;
prev->next = phead;
phead->prev = prev;
free(tail);
tail = NULL;*/
DSLTEarse(phead->prev);
}
//pos之前插入
void DSLTInsert(DSLTNode* pos, DSLtDataType x)
{
assert(pos);
DSLTNode* prev = pos->prev;
DSLTNode* newnode = GetNewNode(x);
newnode->next = pos;
pos->prev = newnode;
prev->next = newnode;
newnode->prev = prev;
}
//pos位置删除
void DSLTEarse(DSLTNode* pos)
{
assert(pos);
DSLTNode* next = pos->next;
DSLTNode* prev = pos->prev;
prev->next = next;
next->prev = prev;
free(pos);
pos = NULL;
}
测试函数test.c:
#define _CRT_SECURE_NO_WARNINGS
#include "DSLT.h"
void DSLTTest1()
{
DSLTNode* plist = DSLTInit();
DSLTPushFront(plist, 1);
DSLTPushFront(plist, 2);
DSLTPushFront(plist, 3);
DSLTPushFront(plist, 4);
DSLTPushFront(plist, 5);
DSLTPrint(plist);
DSLTPopFront(plist);
DSLTPrint(plist);
DSLTPopFront(plist);
DSLTPrint(plist);
DSLTDestroy(plist);
}
void DSLTTest2()
{
DSLTNode* plist = DSLTInit();
DSLTPushBack(plist, 1);
DSLTPushBack(plist, 2);
DSLTPushBack(plist, 3);
DSLTPushBack(plist, 4);
DSLTPushBack(plist, 5);
DSLTPrint(plist);
DSLTPopBack(plist);
DSLTPopBack(plist);
DSLTPrint(plist);
DSLTDestroy(plist);
}
void DSLTTest3()
{
DSLTNode* plist = DSLTInit();
DSLTPushBack(plist, 1);
DSLTPushBack(plist, 2);
DSLTPushBack(plist, 3);
DSLTPushBack(plist, 4);
DSLTPushBack(plist, 5);
DSLTPrint(plist);
DSLTNode* tmp = DSLTFind(plist,1);
if (tmp == NULL)
{
printf("没找到!\n");
}
else
{
DSLTInsert(tmp, 100);
DSLTEarse(tmp);
}
DSLTPrint(plist);
DSLTDestroy(plist);
}
int main()
{
//DSLTTest1();//头插头删
DSLTTest2();//尾插尾删
//DSLTTest3();//pos位置
return 0;
}
码字不易,不足之处希望可以指出