链表的分类可以分为三种:
带头和不带头 (有无头节点)
单向和双向 (节点中有一个还是两个节点)
循环和不循环 (头尾节点是否会指向彼此)
比起单向链表,双向链表结构更复杂,这次我们准备讲的是带头双向循环,虽然看起来复杂,但是用起来特别的舒服,哈哈,
首先来实现一下双向链表,带头双向链表的的每个节点都有两个指针,一个prev指针指向前一个节点,一个next和单链表一样指向下一个节点,
1.双向链表节点类型
它的类型如下:
typedef int LDataType;
typedef struct ListNode
{
LDataType val;
struct ListNode* prev;//指向前一个节点
struct ListNode* next;//指向后一个节点
}ListNode;
2. 准备工作
接下来是做一些准备工作,比如打印函数,初始化函数,销毁函数,
2.1 初始化函数
先从初始化函数开始吧!
初始化函数主要是创建头节点,开辟一块空间,该节点prev和next指针都指向自己,
如图:
需要注意的是该函数返回结构体变量,然后就是把头节点的值初始化为0,
其代码如下:
//初始化
ListNode* LNInit()
{
ListNode* phead = (ListNode*)malloc(sizeof(ListNode));
if (phead == NULL)
{
perror("malloc fail\n");
exit(-1);
}
phead->val = 0;
phead->next = phead;
phead->prev = phead;
return phead;
}
2.2 打印函数
打印函数和单链表不同的是,单链表定义一个变量一直往下找到空就行,但是带头双向循环链表无法这样做,但我们可以从头开始找,下一个节点不为头节点就打印,一直往下走,再次走到头节点就停止打印,其代码如下:
//打印
void LNPrint(ListNode* phead)
{
ListNode* cur = phead->next;
while (cur != phead)
{
printf("%d<=>", cur->val);
cur = cur->next;
}
printf("\n");
}
2.3 销毁函数
销毁函数和打印函数的循环停止条件一样,其它的内容和单线链表的差不多,
其代码如下:
//销毁
void LNDestroy(ListNode* phead)
{
assert(phead);
ListNode* cur = phead->next;
while (cur != phead)
{
ListNode* next = cur->next;
free(cur);
cur = next;
}
}
2.4 创建节点函数
由于插入函数需要频繁地创建一个节点,所以就将它封装成一个函数,能够随时使用,同样这个函数也是要返回结构体指针的,代码如下:
//创建节点
ListNode* CreateLN(LDataType x)
{
ListNode* newnode = (ListNode*)malloc(sizeof(ListNode));
if (newnode == NULL)
{
perror("malloc fail\n");
exit(-1);
}
newnode->val = x;
newnode->prev = NULL;
newnode->next = NULL;
return newnode;
}
3.头尾插入和删除函数
3.1 尾插函数
接下来实现一下尾插函数,这回就能体验到双向带头循环链表的不一样了,如图:
先找到尾节点,然后在链接,但是这个双向循环链表可以直接找到尾节点,找到尾节点后再将他们的前驱指针和后驱指针连接起来就行,写起来比单链表简单易懂。代码如下:
void LNPushBack(ListNode* phead, LDataType x)
{
assert(phead);
ListNode* newnode = CreateLN(x);
ListNode* tail = phead->prev;
//链接
tail->next = newnode;
newnode->prev = tail;
newnode->next = phead;
phead->prev = newnode;
//LNInsert(phead, x);
}
这里也是照常,用一个测试函数来看看我们的函数是否有问题,测试函数的代码如下:
void test1()
{
ListNode* phead = LNInit();
LNPushBack(phead, 1);
LNPushBack(phead, 2);
LNPushBack(phead, 3);
LNPushBack(phead, 4);
LNPrint(phead);
/*LNPushFront(phead, 10);
LNPushFront(phead, 20);
LNPushFront(phead, 30);
LNPushFront(phead, 40);
LNPrint(phead);*/
LNDestroy(phead);
phead = NULL;
}
结果符合预期,但是销毁函数我们并不能直接看出是否有问题,得调式看一看,如图
可以看到,cur的每个节点都被改成了随机值,
3.2 头插函数
头插函数因为头节点的原因变得更简单了不少,把newnode和phead链接起来,再把newnode和Next链接起来,然后我们就实现了
void LNPushFront(ListNode* phead, LDataType x)
{
assert(phead);
ListNode* newnode = CreateLN(x);
ListNode* Next = phead->next;
phead->next = newnode;
newnode->prev = phead;
newnode->next = Next;
Next->prev = newnode;
//LNInsert(phead->next, x);
}
3.3 尾删函数
尾删同样是找到尾节点的前一个节点tailPrev,然后将其和phead头节点链接起来,再把tail尾节点的空间给释放掉,最后指向NULL,就可以了,
其代码如下:
void LNPopBack(ListNode* phead)
{
assert(phead);
assert(phead->next != phead);
ListNode* tail = phead->prev;
ListNode* tailNext = tail->prev;
tailNext->next = phead;
phead->prev = tailNext;
free(tail);
tail = NULL;
//LNErase(phead->prev);
}
3.4 头删函数
头删函数就是将phead的下一个节点next删除,然后把next的下一个节点nextNext和phead链接,
原来是这样的:
头删除之后是这样的:
4.插入和删除函数
4.1 查找函数
查找函数的参数类型是:
ListNode* LNFind(ListNode* phead , LDataType x);
也是返回一个结构体指针,查找函数和打印差不多,都是遍历一遍,然后再判断,其代码如下:
ListNode* LNFind(ListNode* phead,LDataType x)
{
assert(phead);
ListNode* cur = phead->next;
while (cur != phead)
{
if (cur->val == x)
{
return cur;
}
cur = cur->next;
}
return NULL;
}
4.2 插入函数
插入函数的参数:
void LNInsert(ListNode* pos, LDataType x);
插入函数根据传入的结构体指针,在指针指向的结构体前面插入一个结构体,再将他们之间的关系连接起来,
其代码如下:
void LNInsert(ListNode* pos, LDataType x)
{
assert(pos);
ListNode* posPrev = pos->prev;
ListNode* newnode = CreateLN(x);
posPrev->next = newnode;
newnode->prev = posPrev;
newnode->next = pos;
pos->prev = newnode;
}
4.3 删除函数
删除函数的参数是:
void LNErase(ListNode* pos);
删除函数是删除pos位置的节点,将pos位置前后节点链接起来,
代码如下:
void LNErase(ListNode* pos)
{
assert(pos);
assert(pos->next != pos);//这里有疑问
ListNode* posPrev = pos->prev;
ListNode* posNext = pos->next;
posPrev->next = posNext;
posNext->prev = posPrev;
free(pos);
pos = NULL;
}
最后说一下,函数赋用,头删和尾删其实就是把不同的pos传给了删除函数,头插和尾插函数也是可以用插入函数,传不同的位置,
如果用LNErase函数的话,头删就传phead->next,尾删就传phead->prev
如果用LNInsert函数的话,头插就传phead->next,尾插就传phead
5. 代码
5.1 List.h
#pragma once
#include <stdio.h>
#include <stdlib.h>
#include <assert.h>
typedef int LDataType;
typedef struct ListNode
{
LDataType val;
struct ListNode* prev;
struct ListNode* next;
}ListNode;
void LNPrint(ListNode* phead);
ListNode* LNInit();//要返回指针,无形参数
void LNPushBack(ListNode* phead, LDataType x);
void LNPushFront(ListNode* phead, LDataType x);
ListNode* CreateLN(LDataType x);
void LNPopFront(ListNode* phead);
void LNPopBack(ListNode* phead);
ListNode* LNFind(ListNode* phead, LDataType x);
void LNInsert(ListNode* pos, LDataType x);
void LNErase(ListNode* pos);
void LNDestroy(ListNode* phead);
5.2 List.c
#define _CRT_SECURE_NO_WARNINGS 1
#include "List.h"
//初始化
ListNode* LNInit()
{
ListNode* phead = (ListNode*)malloc(sizeof(ListNode));
if (phead == NULL)
{
perror("malloc fail\n");
exit(-1);
}
phead->val = 0;
phead->next = phead;
phead->prev = phead;
return phead;
}
void LNPrint(ListNode* phead)
{
ListNode* cur = phead->next;
while (cur != phead)
{
printf("%d<=>", cur->val);
cur = cur->next;
}
printf("\n");
}
//创建节点
ListNode* CreateLN(LDataType x)
{
ListNode* newnode = (ListNode*)malloc(sizeof(ListNode));
if (newnode == NULL)
{
perror("malloc fail\n");
exit(-1);
}
newnode->val = x;
newnode->prev = NULL;
newnode->next = NULL;
return newnode;
}
void LNPushBack(ListNode* phead, LDataType x)
{
assert(phead);
ListNode* newnode = CreateLN(x);
ListNode* tail = phead->prev;
//链接
tail->next = newnode;
newnode->prev = tail;
newnode->next = phead;
phead->prev = newnode;
//LNInsert(phead, x);
}
void LNPushFront(ListNode* phead, LDataType x)
{
assert(phead);
ListNode* newnode = CreateLN(x);
ListNode* Next = phead->next;
phead->next = newnode;
newnode->prev = phead;
newnode->next = Next;
Next->prev = newnode;
//LNInsert(phead->next, x);
}
void LNPopBack(ListNode* phead)
{
assert(phead);
assert(phead->next != phead);
ListNode* tail = phead->prev;
ListNode* tailNext = tail->prev;
tailNext->next = phead;
phead->prev = tailNext;
free(tail);
tail = NULL;
//LNErase(phead->prev);
}
void LNPopFront(ListNode* phead)
{
/*assert(phead);
assert(phead->next != phead);
ListNode* next = phead->next;
ListNode* nextNext = next->next;
phead->next = nextNext;
nextNext->prev = phead;
free(next);
next = NULL;*/
LNErase(phead->next);
}
ListNode* LNFind(ListNode* phead,LDataType x)
{
assert(phead);
ListNode* cur = phead->next;
while (cur != phead)
{
if (cur->val == x)
{
return cur;
}
cur = cur->next;
}
return NULL;
}
void LNInsert(ListNode* pos, LDataType x)
{
assert(pos);
ListNode* posPrev = pos->prev;
ListNode* newnode = CreateLN(x);
posPrev->next = newnode;
newnode->prev = posPrev;
newnode->next = pos;
pos->prev = newnode;
}
void LNErase(ListNode* pos)
{
assert(pos);
assert(pos->next != pos);//这里有疑问
ListNode* posPrev = pos->prev;
ListNode* posNext = pos->next;
posPrev->next = posNext;
posNext->prev = posPrev;
free(pos);
pos = NULL;
}
//销毁
void LNDestroy(ListNode* phead)
{
assert(phead);
ListNode* cur = phead->next;
while (cur != phead)
{
ListNode* next = cur->next;
free(cur);
cur = next;
}
}
5.3 Test.c
#define _CRT_SECURE_NO_WARNINGS 1
#include "List.h"
void test1()
{
ListNode* phead = LNInit();
LNPushBack(phead, 1);
LNPushBack(phead, 2);
LNPushBack(phead, 3);
LNPushBack(phead, 4);
LNPrint(phead);
/*LNPushFront(phead, 10);
LNPushFront(phead, 20);
LNPushFront(phead, 30);
LNPushFront(phead, 40);
LNPrint(phead);*/
LNDestroy(phead);
phead = NULL;
}
void test2()
{
ListNode* phead = LNInit();
LNPushBack(phead, 1);
LNPushBack(phead, 2);
LNPushBack(phead, 3);
LNPushBack(phead, 4);
LNPushBack(phead, 5);
LNPushBack(phead, 6);
LNPrint(phead);
LNPopBack(phead);
LNPopBack(phead);
LNPrint(phead);
LNPopFront(phead);
LNPopFront(phead);
LNPrint(phead);
LNDestroy(phead);
}
void test3()
{
ListNode* phead = LNInit();
LNPushBack(phead, 1);
LNPushBack(phead, 2);
LNPushBack(phead, 3);
LNPushBack(phead, 4);
LNPushBack(phead, 5);
LNPushBack(phead, 6);
LNPrint(phead);
ListNode* pos = LNFind(phead,4);
LNInsert(pos, 40);
LNPrint(phead);
LNErase(pos);
LNPrint(phead);
LNDestroy(phead);
}
int main()
{
test1();
//test2();
//test3();
return 0;
}
6. 写在最后
今天,我想写一些不一样的,之前总是在鼓励自己,要向前,要赢,可坚持真的很难,总是会迷茫,效率不高,我最后能成功吗,我想只要坚持下去,就能成功,就像看自己开始的时候,现在是不是更厉害了一些,不论路上有什么妖魔鬼怪,我都会一步一步逐渐上去!!!