链表的种类有8种,但我们最常用的为无头单向非循环链表和带头双向循环链表。
带头双向循环链表
当带头双向循环链表只有哨兵位头的时候,双向链表的指向如下图。
head->pre和head->next都是指向自己,这个是有巨大优势的,代码实现会很方便。
1.无头单向非循环链表:结构简单,一般不会单独用来存数据。实际中更多是作为其他数据结
构的子结构,如哈希桶、图的邻接表等等。另外这种结构在笔试面试中出现很多。
2. 带头双向循环链表:结构最复杂,一般用在单独存储数据。实际中使用的链表数据结构,都
是带头双向循环链表。另外这个结构虽然结构复杂,但是使用代码实现以后会发现结构会带
来很多优势,实现反而简单了,后面我们代码实现了就知道了。
在上个博客实现顺序表和单链表的时候我提过这两点,让我们来进入双向循环链表的实现吧!
带头双向循环链表的结点开辟
LTNode* buynode(SedListtype x)
{
LTNode* newnode = (LTNode*)malloc(sizeof(LTNode));
if (newnode == NULL)
{
perror("malloc fail");
return NULL;
}
newnode->data = x;
newnode->pre = NULL;
newnode->next = NULL;
return newnode;
}
这个开辟结点应该没有什么好说的,带头双向循环链表多了个指针pre。
带头双向循环链表的初始化
LTNode* LTInit()
{
LTNode* phead= buynode(-1);
phead->pre = phead;
phead->next = phead;
return phead;
}
哨兵位的结点值是可以随便给的,毕竟只负责站岗,当双向链表为空,phead->pre和phead->next都指向自己,这才是双向循环链表的核心!这里为什么要返回头指针,因为为了与其他增删查改方法实现统一使用一级指针,要不然初始化双向链表需要使用二级指针(因为结构体指针是一级指针,为了改变结构体的内容就要传值并用二级指针接受一级指针(结构体指针)才能改变结构体内容),但为了避免问题复杂化,二级指针变为一级指针最简单方法就是自己返回值。
带头双向循环链表的尾插
void LTPushBack(LTNode* phead, SedListtype x)
{
assert(phead);
LTNode* newnode = buynode(x);
LTNode* tail = phead->pre;
tail->next = newnode;
newnode->pre = tail;
newnode->next = phead;
phead->pre = newnode;
}
不用像单链表一样找尾tail,phead->pre就是tail,而且单链表需要判断链表为空和不为空的两种情况且需要2级指针,不像带头双向循环链表带有哨兵位的头指针使用一级指针就可以,如果链表为空,这段代码也同样适用,即是头也是尾,双向链表改变的是结构体,用结构体指针就够了,不需要二级指针。
带头双向循环链表为空时
带头双向循环链表不为空时
带头双向循环链表的头插
void LTPushFront(LTNode* phead, SedListtype x)
{
assert(phead);
LTNode* newnode = buynode(x);
LTNode* frist = phead->next;
phead->next = newnode;
newnode->pre = phead;
newnode->next = frist;
frist->pre = newnode;
}
错误写法
Void LTPushFront(LTNode* phead, LTDataType x)
assert(phead);
LTNode* newnode = BuyLTNode(x);
phead->next = newnode;
newnode->prev = phead;
newnode->next = phead->next;
phead->next->prev = newnode;
最常见错误之一,会找不到phead->next的原来的地址值
调整一下顺序即可
void LTPushFront(LTNode* phead, LTDataType x)
assert(phead);
LTNode* newnode = BuyLTNode(x);
newnode->next = phead->next;
phead->next->prev = newnode;
phead->next = newnode;
newnode->prev = phead;
先保存phead->next头结点的值,避免找不到或者改变phead的值,然后再按照常规连接即可。
当链表为空,这段代码也一样适用,因为双向链表的核心就是头指针的pre和next都是指向自己,这是带头双向循环链表的优势。
带头双向循环链表的尾删
void LTPopBack(LTNode* phead)
{
assert(phead);
LTNode* tail = phead->pre;
LTNode* pre = tail->pre;
free(tail);
pre->next = phead;
phead->pre = pre;
}
先找尾tail,然后tail->pre就是上一个结点pre的值,再释放掉尾tail,最后常规连接即可。
带头双向循环链表的头删
void LTPopFront(LTNode* phead)
{
assert(phead);
LTNode* frist = phead->next;
LTNode* second = frist->next;
phead->next = second;
second->pre = phead;
free(frist);
}
头删的代码肯定不止一种写法,但我们这种写法是最通俗易懂且简便的,可以让别人一眼就能看出我们的意图所在,先用frist存phead->next的值,再用second存frist->next的值,然后常规连接释放即可。
带头双向循环链表的查找
LTNode* Find(LTNode* phead, SedListtype x)
{
assert(phead);
LTNode* cur = phead->next;
while (cur != phead)
{
if (cur->data == x)
{
return cur;
}
cur = cur->next;
}
return NULL;
}
当cur不等于phead(哨兵位)时就遍历完了链表,这个应该没啥好说的,各位兄弟姐妹也会自己写。但这个查找遍历与顺序表和单链表都一样,查找链表内对应值的内容是可以修改的。
带头双向循环链表的插入(pos之前插入)
void LTInsert(LTNode* pos, SedListtype x)
{
assert(pos);
LTNode* newnode = buynode(x);
LTNode* prepos = pos->pre;
prepos->next = newnode;
newnode->pre = prepos;
newnode->next = pos;
pos->pre = newnode;
}
先用pos->pre找到pos上一个结点的值,再用一个临时变量存储,再常规连接即可。
带头双向循环链表的删除(pos位置删除)
void LTErase(LTNode* pos)
{
assert(pos);
LTNode* pospre = pos->pre;
LTNode* posnext = pos->next;
pospre->next = posnext;
posnext->pre = pospre;
free(pos);
}
利用pos找出pos前面与后面结点的值,用两个临时变量存储,常规连接两个变量即可。
带头双向循环链表的打印
void LTprint(LTNode* phead)
{
assert(phead);
LTNode* cur = phead->next;
printf("guard<==>");哨兵位
while (cur != phead)
{
printf("%d<==>", cur->data);
cur = cur->next;
}
printf("\n");
}
带头双向循环链表的销毁
void LTDestory(LTNode* phead)
{
LTNode* cur = phead->next;
while (cur != phead)
{
LTNode* next = cur->next;
free(cur);
cur = next;
}
free(phead);
}
用next存储结点值,再一个个释放即可。
使用完动态开辟的结点值后要销毁,要不然会内存泄漏,phead可以在test.c文件中再置空,因为释放掉以后,不会再有人使用,但为了安全最好在test.c置空一下即可。
带头双向循环链表的完整代码以及运行结果
SedList.h
#include<stdio.h>
#include<assert.h>
#include<stdlib.h>
typedef int SedListtype;
typedef struct SedList
{
struct SedList* pre;
struct SedList* next;
SedListtype data;
}LTNode;
LTNode* LTInit();//初始化;
void LTprint(LTNode* phead);//打印
void LTDestory(LTNode* phead);//销毁
void LTPushBack(LTNode* phead, SedListtype x);//尾插
void LTPushFront(LTNode* phead, SedListtype x);//头插
void LTPopBack(LTNode* phead);//尾删
void LTPopFront(LTNode* phead);//头删
LTNode* Find(LTNode* phead,SedListtype x);//查找
void LTInsert(LTNode* pos, SedListtype x);//pos之前插入
void LTErase(LTNode* pos);//pos位置删除
SedList.c
#include"sedlist.h"
LTNode* buynode(SedListtype x)
{
LTNode* newnode = (LTNode*)malloc(sizeof(LTNode));
if (newnode == NULL)
{
perror("malloc fail");
return NULL;
}
newnode->data = x;
newnode->pre = NULL;
newnode->next = NULL;
return newnode;
}
LTNode* LTInit()
{
LTNode* phead= buynode(-1);
phead->pre = phead;
phead->next = phead;
return phead;
}
void LTprint(LTNode* phead)
{
assert(phead);
LTNode* cur = phead->next;
printf("guard<==>");//哨兵位
while (cur != phead)
{
printf("%d<==>", cur->data);
cur = cur->next;
}
printf("\n");
}
void LTDestory(LTNode* phead)
{
LTNode* cur = phead->next;
while (cur != phead)
{
LTNode* next = cur->next;
free(cur);
cur = next;
}
free(phead);
}
void LTPushBack(LTNode* phead, SedListtype x)
{
assert(phead);
LTNode* newnode = buynode(x);
LTNode* tail = phead->pre;
tail->next = newnode;
newnode->pre = tail;
newnode->next = phead;
phead->pre = newnode;
}
void LTPushFront(LTNode* phead, SedListtype x)
{
assert(phead);
LTNode* newnode = buynode(x);
LTNode* frist = phead->next;
phead->next = newnode;
newnode->pre = phead;
newnode->next = frist;
frist->pre = newnode;
}
void LTPopBack(LTNode* phead)
{
assert(phead);
LTNode* tail = phead->pre;
LTNode* pre = tail->pre;
free(tail);
pre->next = phead;
phead->pre = pre;
}
void LTPopFront(LTNode* phead)
{
assert(phead);
LTNode* frist = phead->next;
LTNode* second = frist->next;
phead->next = second;
second->pre = phead;
free(frist);
}
LTNode* Find(LTNode* phead, SedListtype x)
{
assert(phead);
LTNode* cur = phead->next;
while (cur != phead)
{
if (cur->data == x)
{
return cur;
}
cur = cur->next;
}
return NULL;
}
void LTInsert(LTNode* pos, SedListtype x)
{
assert(pos);
LTNode* newnode = buynode(x);
LTNode* prepos = pos->pre;
prepos->next = newnode;
newnode->pre = prepos;
newnode->next = pos;
pos->pre = newnode;
}
void LTErase(LTNode* pos)
{
assert(pos);
LTNode* pospre = pos->pre;
LTNode* posnext = pos->next;
pospre->next = posnext;
posnext->pre = pospre;
free(pos);
}
test.c
#include"sedlist.h"
void test1()
{
LTNode* plist = LTInit();
LTPushBack(plist, 1);
LTPushBack(plist, 2);
LTPushBack(plist, 3);
LTprint(plist);
LTDestory(plist);
plist = NULL;
}
void test2()
{
LTNode* plist = LTInit();
LTPushFront(plist, 1);
LTPushFront(plist, 2);
LTPushFront(plist, 3);
LTprint(plist);
LTDestory(plist);
plist = NULL;
}
void test3()
{
LTNode* plist = LTInit();
LTPushBack(plist, 1);
LTPushBack(plist, 2);
LTPushBack(plist, 3);
LTPopBack(plist);
LTPopBack(plist);
LTPushBack(plist, 4);
LTprint(plist);
LTDestory(plist);
plist = NULL;
}
void test4()
{
LTNode* plist = LTInit();
LTPushFront(plist, 1);
LTPushFront(plist, 2);
LTPushFront(plist, 3);
LTPopFront(plist);
LTPopFront(plist);
LTPushFront(plist, 5);
LTprint(plist);
LTDestory(plist);
plist = NULL;
}
void test5()
{
LTNode* plist = LTInit();
LTPushFront(plist, 1);
LTPushFront(plist, 2);
LTPushFront(plist, 3);
LTPushFront(plist, 5);
LTNode* pos = Find(plist, 2);
pos->data = 12;
LTprint(plist);
LTDestory(plist);
plist = NULL;
}
void test6()
{
LTNode* plist = LTInit();
LTPushFront(plist, 1);
LTPushFront(plist, 2);
LTPushFront(plist, 3);
LTPushFront(plist, 5);
LTNode* pos = Find(plist, 2);
if (pos)
{
LTInsert(pos, 24);
}
LTprint(plist);
LTDestory(plist);
plist = NULL;
}
void test7()
{
LTNode* plist = LTInit();
LTPushFront(plist, 1);
LTPushFront(plist, 2);
LTPushFront(plist, 3);
LTPushFront(plist, 5);
LTNode* pos = Find(plist, 3);
if (pos)
{
LTErase(pos);
}
LTprint(plist);
LTDestory(plist);
plist = NULL;
}
int main()
{
test1();
test2();
test3();
test4();
test5();
test6();
test7();
return 0;
}