提示:文章写完后,目录可以自动生成,如何生成可参考右边的帮助文档
带头双向循环链表
前言
链表中有多种结构,诸如:单链表,双向链表,带头单链表……
本次介绍《关于名字听起来很牛逼但是实现起来简单的一批这个结构》——带头双向循环链表。
一、什么是带头双向循环链表?
顾名思义
- 带头:该链表带有一个傀儡结点,也称哨兵结点,傀儡结点不算作有效数据,只作为处理数据时便捷的一种手段结构。
- 双向:在单链表中,一个单链表结点存储有一个数据域和一个指针域,指针域保存的是下一个结点的地址,而双向则表示一个结点中不仅保存有下一个结点(next)的地址,也保存有上一个结点(prev)的地址。
- 循环:不循环的链表走到底一定会走到空(NULL),带循环就走不到空(NULL)。
图例:
二、具体实现
1.结点的定义
代码如下:
typedef int LTDataType;
typedef struct ListNode
{
LTDataType val;
struct ListNode* next;
struct ListNode* prev;
}ListNode;
- val:数据域
- next:存储下一个结点的地址
- prev:存储上一个结点的地址
2.获取一个值为x的新结点
代码如下:
ListNode* BuyListNode(LTDataType x)
{
ListNode* newnode = (ListNode*)malloc(sizeof(ListNode));
if (!newnode)
{
perror("malloc()");
exit(-1);
}
newnode->val = x;
newnode->next = NULL;
newnode->prev = NULL;
return newnode;
}
用malloc给新结点开辟一块空间,如果malloc开辟空间失败,用perror打印失败信息,然后exit(-1)直接结束程序,如果malloc开辟空间成功,则正常赋值,这里newnode的next和prev不初始化也无所谓,反正后面插入也要修改指向,最后return新开辟的结点。
3.创建返回链表的头结点
代码如下:
ListNode* ListCreate()
{
ListNode* pHead = (ListNode*)malloc(sizeof(ListNode));
if (!pHead)
{
perror("malloc()");
exit(-1);
}
pHead->next = pHead;
pHead->prev = pHead;
return pHead;
}
开辟一个结点,用于当作傀儡结点,傀儡结点不存储有效的数据,因此数据域不需要赋值,因为是循环,所以一开始next和prev都是指向的自己。
4.链表的销毁
代码如下:
void ListDestory(ListNode* pHead)
{
assert(pHead);
ListNode* cur = pHead->next;
while(cur != pHead)
{
ListNode* curNext = cur->next;
free(cur);
cur = curNext;
}
free(pHead);
}
assert断言,按道理来说pHead不可能为空,但当调用者错误的定义,比如:
ListNode* pHead = NULL;
此时如果不加以断言就进行访问的话,势必会造成不必要的麻烦,因此加上断言也大大的提升了程序的安全性,销毁的过程也很简单,但是需要注意,如果直接free掉cur结点是不行的,因为这样你就找不到下一个结点的地址了,因此先把cur->next存放在curNext中,这样再对cur进行free就没问题了,最后再把傀儡结点也free之后,链表的销毁就完成了。
5.链表的打印
代码如下:
void ListPrint(ListNode* pHead)
{
assert(pHead);
ListNode* cur = pHead->next;
while (cur != pHead)
{
printf("%d ", cur->val);
cur = cur->next;
}
printf("\n");
}
打印非常简单,打印当前cur的val值,cur再等于cur->next即可,因为傀儡节点不算作有效数据,而且该链表是循环的,所以最后势必会回到傀儡节点,所以循环继续的条件就是cur != pHead(傀儡结点),当cur等于傀儡结点时,说明链表中的内容已经全部打印完毕了。
6.链表的尾插
代码如下:
void ListPushBack(ListNode* pHead, LTDataType x)
{
assert(pHead);
ListNode* newnode = BuyListNode(x);
pHead->prev->next = newnode;
newnode->prev = pHead->prev;
pHead->prev = newnode;
newnode->next = pHead;
}
简单的改变指向即可,利用该结构的特点,pHead的prev即是尾,所以很便利。
7.链表的尾删
代码如下:
void ListPopBack(ListNode* pHead)
{
assert(pHead);
assert(pHead->next != pHead);
ListNode* tail = pHead->prev;
pHead->prev = pHead->prev->prev;
pHead->prev->next = pHead;
free(tail);
}
8.链表的头插
代码如下:
void ListPushFront(ListNode* pHead, LTDataType x)
{
assert(pHead);
ListNode* newnode = BuyListNode(x);
newnode->next = pHead->next;
pHead->next->prev = newnode;
pHead->next = newnode;
newnode->prev = pHead;
}
9.链表的头删
代码如下:
void ListPopFront(ListNode* pHead)
{
assert(pHead);
assert(pHead->next != pHead);
ListNode* prev = pHead->next;
pHead->next = pHead->next->next;
pHead->next->prev = pHead;
}
10.链表的按内容查找(链表中的第一个val等于x的结点)
代码如下:
ListNode* ListFind(ListNode* pHead, LTDataType x)
{
assert(pHead);
ListNode* cur = pHead->next;
while (cur != pHead)
{
if (cur->val == x)
{
return cur;
}
cur = cur->next;
}
return NULL;
}
找到并返回链表中的第一个val为x的结点,找不到返回空(NULL)。
11.链表的按下标查找(下标从0开始,傀儡结点不算做有效数据)
代码如下:
ListNode* IndexFind(ListNode* pHead, int index)
{
assert(pHead);
assert(pHead->next != pHead);
ListNode* cur = pHead->next;
while (cur != pHead && index > 0)
{
cur = cur->next;
index--;
}
if (cur == pHead)
{
return NULL;
}
return cur;
}
和按内容查找不同,这是按下标查找,需要注意的是傀儡结点并不算作有效数据,因此下标0从傀儡节点的next开始,也需要注意新插入结点时各结点下标的改变。
12.链表的在pos的前面进行插入
代码如下:
void ListInsert(ListNode* pos, LTDataType x)
{
assert(pos);
ListNode* newnode = BuyListNode(x);
pos->prev->next = newnode;
newnode->prev = pos->prev;
newnode->next = pos;
pos->prev = newnode;
}
需要结合Find使用,也就是前面的9、10两点,Find找到的匹配的结点作为参数进行链式访问,在此结点前插入一个新的结点,也是简单的修改指向即可。
13.链表的删除pos位置的节点
代码如下:
void ListErase(ListNode* pos)
{
assert(pos);
ListNode* cur = pos;
pos->prev->next = pos->next;
pos->next->prev = pos->prev;
free(cur);
}
需要结合Find使用,也就是前面的9、10两点,Find找到的匹配的结点作为参数进行链式访问,删除pos结点,也是简单的修改指向即可。
三、完整代码
1.Test.c
#define _CRT_SECURE_NO_WARNINGS 1
#include"DoubleLinkList.h"
void Test1()
{
ListNode* pHead = ListCreate();
//ListPushBack(pHead, 1);
//ListPushBack(pHead, 2);
//ListPushBack(pHead, 3);
//ListPushBack(pHead, 4);
//ListPushBack(pHead, 5);
ListPrint(pHead);
ListPopBack(pHead);
ListPrint(pHead);
ListPopBack(pHead);
ListPrint(pHead);
ListPopBack(pHead);
ListPrint(pHead);
ListPopBack(pHead);
ListPrint(pHead);
ListPopBack(pHead);
ListPrint(pHead);
}
void Test2()
{
ListNode* pHead = ListCreate();
//ListPushFront(pHead, 5);
//ListPushFront(pHead, 4);
//ListPushFront(pHead, 3);
//ListPushFront(pHead, 2);
//ListPushFront(pHead, 1);
ListPrint(pHead);
ListPopFront(pHead);
ListPrint(pHead);
ListPopFront(pHead);
ListPrint(pHead);
ListPopFront(pHead);
ListPrint(pHead);
ListPopFront(pHead);
ListPrint(pHead);
//ListPopFront(pHead);
//ListPrint(pHead);
}
void Test3()
{
ListNode* pHead = ListCreate();
ListPushBack(pHead, 1);
ListPushBack(pHead, 2);
ListPushBack(pHead, 3);
ListPushBack(pHead, 4);
ListPushBack(pHead, 5);
ListPrint(pHead);
ListInsert(IndexFind(pHead, 0), 0);
ListPrint(pHead);
ListInsert(ListFind(pHead, 0), -1);
ListPrint(pHead);
//ListErase(IndexFind(pHead, 0));
//ListPrint(pHead);
//ListErase(IndexFind(pHead, 0));
//ListPrint(pHead);
//ListErase(IndexFind(pHead, 0));
//ListPrint(pHead);
ListErase(ListFind(pHead, -1));
ListPrint(pHead);
ListErase(ListFind(pHead, 0));
ListPrint(pHead);
ListErase(ListFind(pHead, 1));
ListPrint(pHead);
}
int main()
{
//Test1();
//Test2();
Test3();
return 0;
}
2.DoubleLinkList.c
#define _CRT_SECURE_NO_WARNINGS 1
#include"DoubleLinkList.h"
//获取一个值为x的新结点
ListNode* BuyListNode(LTDataType x)
{
ListNode* newnode = (ListNode*)malloc(sizeof(ListNode));
if (!newnode)
{
perror("malloc()");
exit(-1);
}
newnode->val = x;
newnode->next = NULL;
newnode->prev = NULL;
return newnode;
}
// 创建返回链表的头结点.
ListNode* ListCreate()
{
ListNode* pHead = (ListNode*)malloc(sizeof(ListNode));
if (!pHead)
{
perror("malloc()");
exit(-1);
}
pHead->next = pHead;
pHead->prev = pHead;
return pHead;
}
// 双向链表销毁
void ListDestory(ListNode* pHead)
{
assert(pHead);
ListNode* cur = pHead->next;
while(cur != pHead)
{
ListNode* curNext = cur->next;
free(cur);
cur = curNext;
}
free(pHead);
}
// 双向链表打印
void ListPrint(ListNode* pHead)
{
assert(pHead);
ListNode* cur = pHead->next;
while (cur != pHead)
{
printf("%d ", cur->val);
cur = cur->next;
}
printf("\n");
}
// 双向链表尾插
void ListPushBack(ListNode* pHead, LTDataType x)
{
assert(pHead);
ListNode* newnode = BuyListNode(x);
pHead->prev->next = newnode;
newnode->prev = pHead->prev;
pHead->prev = newnode;
newnode->next = pHead;
}
// 双向链表尾删
void ListPopBack(ListNode* pHead)
{
assert(pHead);
assert(pHead->next != pHead);
ListNode* tail = pHead->prev;
pHead->prev = pHead->prev->prev;
pHead->prev->next = pHead;
free(tail);
}
// 双向链表头插
void ListPushFront(ListNode* pHead, LTDataType x)
{
assert(pHead);
ListNode* newnode = BuyListNode(x);
newnode->next = pHead->next;
pHead->next->prev = newnode;
pHead->next = newnode;
newnode->prev = pHead;
}
// 双向链表头删
void ListPopFront(ListNode* pHead)
{
assert(pHead);
assert(pHead->next != pHead);
ListNode* prev = pHead->next;
pHead->next = pHead->next->next;
pHead->next->prev = pHead;
}
// 双向链表按内容查找(链表中的第一个val等于x的结点)
ListNode* ListFind(ListNode* pHead, LTDataType x)
{
assert(pHead);
ListNode* cur = pHead->next;
while (cur != pHead)
{
if (cur->val == x)
{
return cur;
}
cur = cur->next;
}
return NULL;
}
双向链表按下标查找(下标从0开始,傀儡结点不算做有效数据)
ListNode* IndexFind(ListNode* pHead, int index)
{
assert(pHead);
assert(pHead->next != pHead);
ListNode* cur = pHead->next;
while (cur != pHead && index > 0)
{
cur = cur->next;
index--;
}
if (cur == pHead)
{
return NULL;
}
return cur;
}
// 双向链表在pos的前面进行插入
void ListInsert(ListNode* pos, LTDataType x)
{
assert(pos);
ListNode* newnode = BuyListNode(x);
pos->prev->next = newnode;
newnode->prev = pos->prev;
newnode->next = pos;
pos->prev = newnode;
}
// 双向链表删除pos位置的节点
void ListErase(ListNode* pos)
{
assert(pos);
ListNode* cur = pos;
pos->prev->next = pos->next;
pos->next->prev = pos->prev;
free(cur);
}
3.DoubleLinkList.h
#pragma once
#include<stdio.h>
#include<stdlib.h>
#include<assert.h>
// 带头+双向+循环链表增删查改实现
//便于后续链表存储数据的类型的修改
typedef int LTDataType;
//结点
typedef struct ListNode
{
LTDataType val;
struct ListNode* next;
struct ListNode* prev;
}ListNode;
// 创建返回链表的头结点.
ListNode* ListCreate();
// 双向链表销毁
void ListDestory(ListNode* pHead);
// 双向链表打印
void ListPrint(ListNode* pHead);
// 双向链表尾插
void ListPushBack(ListNode* pHead, LTDataType x);
// 双向链表尾删
void ListPopBack(ListNode* pHead);
// 双向链表头插
void ListPushFront(ListNode* pHead, LTDataType x);
// 双向链表头删
void ListPopFront(ListNode* pHead);
// 双向链表按内容查找(链表中的第一个val等于x的结点)
ListNode* ListFind(ListNode* pHead, LTDataType x);
// 双向链表在pos的前面进行插入
void ListInsert(ListNode* pos, LTDataType x);
// 双向链表删除pos位置的节点
void ListErase(ListNode* pos);
//双向链表按下标查找(下标从0开始,傀儡结点不算做有效数据)
ListNode* IndexFind(ListNode* pHead, LTDataType x);
总结
带头双向循环链表是一个很厉害的结构,实现起来也很简单。