目录
一:前言
在上节课学习过单链表后,我们能发现许多单链表结构的缺陷,比如:尾插要遍历整个链表才能实现,时间复杂度为0(N),这节课我们就来认识一种新链表,可以改善这种缺陷,这种链表就是带头循环双向链表。
二:链表的表示
我们先来对比一下两者的优略性:
无头单向非循环链表:结构简单,一般不会用来存放数据,实际中更多是作为其他数据结构的子结构,如哈希桶,图的邻接表等等,另外这种结构在笔试面试中出现很多。
带头双向循环链表:结构复杂,一般用在单独存储数据。实际中使用的链表数据结构,都 是带头双向循环链表。另外这个结构虽然结构复杂,但是使用代码实现以后会发现结构会带 来很多优势,实现反而简单了,后面我们代码实现了就知道了。
另外我们这里说一下我们这里的带哨兵位的头节点:即第一个节点不存储有效值。
我们来看一下带头循环双向链表的逻辑图:
从图中我们可以看出来双向链表不仅可以找到某个节点的next节点,也可以找到某个节点的前驱节点(prev),那么我们在实现的过程中就要与单链表的实现方法有所不同。
三:链表的实现
1:链表的创建(构建结构体,ListInit函数)
我们在用结构体创建此链表时,比起单链表,我们又多出来了一个新的指针成员--prev
typedef int LTDataType; //方便修改链表中存储数据的类型
typedef struct DListNode {
struct DListNode* prev; //指向该节点的前驱节点
struct DListNode* next; //指向该节点的后一个节点
LTDataType data;
}ListNode;
然后我们要创建该链表的头节点,通过ListInit实现
//创建新节点
ListNode* BuyListNode(LTDataType x) {
ListNode* newnode = (ListNode*)malloc(sizeof(ListNode));
newnode->data = x;
newnode->prev = NULL;
newnode->next = NULL;
return newnode;
}
//链表初始化
ListNode* ListInit(ListNode* phead)
{
phead = BuyListNode(0); //给头节点随便赋值,这个0可以是任意数,因为头节点的
data值无意义
phead->next = phead; //双向带头循环链表的phead的next节点和prev节点初始化
设置的都是phead
phead->prev = phead;
return phead;
}
void TestList()
{
ListNode* phead = NULL;
phead = ListInit(phead); //通过ListInit函数来初始化phead节点
}
TestList函数是调用所有我们写过的函数的地方。
2:链表的打印(ListPrint)
和单链表打印时while的临界值不同,我们打印带头双向循环链表中while的结束条件是cur!=phead结点,让我们来看一下为什么
我们最后给出的代码
//链表的打印
void ListPrint(ListNode* phead) {
ListNode* cur = phead->next;
while (cur != phead) {
printf("%d->", cur->data);
cur = cur->next;
}
printf("NULL\n");
}
3:链表的尾插(ListPushBack)
我们先来看逻辑图:
插入前
插入后
按照这样的逻辑图我们很容易写出代码
//链表的尾插
void ListPushBack(ListNode* phead, LTDataType x) {
ListNode* newnode = BuyListNode(x); //给新节点赋值
ListNode* tail = phead->prev; //根据循环链表性质可知,尾节点是头节点的prev
tail->next = newnode;
newnode->prev = tail; //根据双向链表的性质,newnode节点是尾节点的
next,尾节点是newnode节点的prev
newnode->next = phead;
phead->prev = newnode; //newnode成了新的尾节点,要把头节点的prev设
置为newnode,把newnode的next设置为头节点
}
我们可以插入几个数并打印他们试试
void TestList()
{
ListNode* phead = NULL;
phead = ListInit(phead);
ListPushBack(phead, 1);
ListPushBack(phead, 2);
ListPushBack(phead, 3);
ListPushBack(phead, 4);
ListPushBack(phead, 5);
ListPushBack(phead, 4);
ListPrint(phead);
}
运行结果
4:链表的头插(ListPushFront)
先看逻辑图:
插入前
插入后
根据插入前后的逻辑图我们可以写出如下代码
//链表的头插
void ListPushFront(ListNode* phead, LTDataType x)
{
ListNode* newnode = BuyListNode(x);
ListNode* first = phead->next;
newnode->prev = phead;
phead->next = newnode;
newnode->next = first;
first->prev = newnode;
}
//就算在插入前链表中只有一个头节点,依然可以正常插入,可以自行画逻辑图体会
我们可以发现,在链表的尾插里,我们不用像单链表那样还需要找出特殊情况来单独讨论(你可以自己画一些特殊的情况,把代码代入试试会不会出错。),这就是双向带头循环链表相比较于单链表的优势-----容易实现。
5:链表的头删(ListPopFront)
先看逻辑图
删去前
删去后
我们写出的代码如下
//链表的头删
void ListPopFront(ListNode * phead) {
ListNode* first = phead->next;
ListNode* second = first->next;
phead->next = second;
second->prev = phead;
free(first); //最后free掉first,以免造成空间浪费。
}
6:链表的尾删(ListPopBack)
先上逻辑图
删除前
删除后
实现的代码
//链表的尾删
void ListPopBack(ListNode* phead) {
assert(phead);
assert(phead->next != phead);
ListNode* tail = phead->prev;
ListNode* tailprev = tail->prev; //找出尾节点的前一个结点
phead->prev = tailprev;
tailprev->next = phead; //把头节点和找出的倒数第二个结点联系起来
free(tail);
}
7:链表的查找(ListFind)
给定一个值,我们要在链表中找到这个值并返回该结点的地址
我们可以采用遍历的方法,只要找到即可返回结点并结束程序。
代码如下
//链表的查找
ListNode* ListFind(ListNode* phead, LTDataType x) {
assert(phead);
ListNode* cur = phead->next;
while (cur != phead) {
if (cur->data == x) {
return cur;
}
cur = cur->next;
}
return NULL;
}
8:链表指定位置之前插入(ListInsert)
在用find函数找到pos的位置之后,我们用ListInsert函数在pos前面插入一个data值为x的结点
//链表指定位置前插入
void ListInsert(ListNode* phead, ListNode* pos, LTDataType x) {
assert(pos);
ListNode* newnode = BuyListNode(x);
ListNode* prev = pos->prev;
prev->next = newnode;
newnode->prev = prev;
newnode->next = pos;
pos->prev = newnode;
}
具体的运行结果如下
如图是在data值为3的结点前面插入data值为100的新结点
9:指定位置删除(ListErase)
在用find函数找到pos的位置后,我们可以对该结点进行删除,这和之前讲的尾删头删有异曲同工之妙,话不多说直接上代码
//链表指定位置删除
void ListErase(ListNode* phead, ListNode* pos) {
assert(pos);
ListNode* prev = pos->prev;
ListNode* next = pos->next;
prev->next = next;
next->prev = prev;
return;
}
运行结果如下
以上就是带头双向循环链表的基础操作了,最后附上总代码
#define _CRT_SECURE_NO_WARNINGS 1
#include<stdio.h>
#include<stdlib.h>
#include<assert.h>
typedef int LTDataType;
typedef struct DListNode {
struct DListNode* prev;
struct DListNode* next;
LTDataType data;
}ListNode;
//创建新节点
ListNode* BuyListNode(LTDataType x) {
ListNode* newnode = (ListNode*)malloc(sizeof(ListNode));
newnode->data = x;
newnode->prev = NULL;
newnode->next = NULL;
return newnode;
}
//链表初始化
ListNode* ListInit(ListNode* phead)
{
phead = BuyListNode(0);
phead->next = phead;
phead->prev = phead;
return phead;
}
//链表的头插
void ListPushFront(ListNode* phead, LTDataType x)
{
ListNode* newnode = BuyListNode(x);
ListNode* first = phead->next;
newnode->prev = phead;
phead->next = newnode;
newnode->next = first;
first->prev = newnode;
}
//链表的尾插
void ListPushBack(ListNode* phead, LTDataType x) {
ListNode* newnode = BuyListNode(x);
ListNode* tail = phead->prev;
tail->next = newnode;
newnode->prev = tail;
newnode->next = phead;
phead->prev = newnode;
}
//链表的头删
void ListPopFront(ListNode * phead) {
assert(phead);
assert(phead->next != phead);
ListNode* first = phead->next;
ListNode* second = first->next;
phead->next = second;
second->prev = phead;
free(first);
}
//链表的尾删
void ListPopBack(ListNode* phead) {
assert(phead);
assert(phead->next != phead);
ListNode* tail = phead->prev;
ListNode* tailprev = tail->prev;
phead->prev = tailprev;
tailprev->next = phead;
free(tail);
}
//链表的查找
ListNode* ListFind(ListNode* phead, LTDataType x) {
assert(phead);
ListNode* cur = phead->next;
while (cur != phead) {
if (cur->data == x) {
return cur;
}
cur = cur->next;
}
return NULL;
}
//链表指定位置删除
void ListErase(ListNode* phead, ListNode* pos) {
assert(pos);
ListNode* prev = pos->prev;
ListNode* next = pos->next;
prev->next = next;
next->prev = prev;
return;
}
//链表指定位置前插入
void ListInsert(ListNode* phead, ListNode* pos, LTDataType x) {
assert(pos);
ListNode* newnode = BuyListNode(x);
ListNode* prev = pos->prev;
prev->next = newnode;
newnode->prev = prev;
newnode->next = pos;
pos->prev = newnode;
}
//链表的打印
void ListPrint(ListNode* phead) {
ListNode* cur = phead->next;
while (cur != phead) {
printf("%d->", cur->data);
cur = cur->next;
}
printf("NULL\n");
}
void TestList()
{
ListNode* phead = NULL;
phead = ListInit(phead);
ListPushBack(phead, 1);
ListPushBack(phead, 2);
ListPushBack(phead, 3);
ListPushBack(phead, 4);
ListPushBack(phead, 5);
ListPrint(phead);
ListNode* pos=ListFind(phead,3);
if (pos != NULL) {
ListErase(phead, pos);
ListPrint(phead);
}
}
int main()
{
TestList();
return 0;
}