前面我们已经知道,链表一共有8种结构,最常用的只有两种。
无头单向非循环链表我们已经学过---> 无头单向非循环链表
那么本节我们就来介绍 带头双向循环链表。
#define _CRT_SECURE_NO_WARNINGS 1
#include"List.h"
void test1(LTNode* phead)//头插
{
LTPushFront(phead, 6);
LTPushFront(phead, 1);
LTPushFront(phead, 4);
LTPushFront(phead, 2);
LTPushFront(phead, 3);
LTPushFront(phead, 5);
LTPrint(phead);
}
void test2(LTNode* phead)//头删
{
LTPopFront(phead);
LTPopFront(phead);
LTPopFront(phead);
LTPrint(phead);
}
void test3(LTNode* phead)//尾插
{
LTPushBack(phead, 12);
LTPushBack(phead, 33);
LTPrint(phead);
}
void test4(LTNode* phead)//尾删
{
LTPopBack(phead);
LTPopBack(phead);
LTPopBack(phead);
LTPopBack(phead);
LTPrint(phead);
}
void test5(LTNode* phead)//查询
{
LTNode* node = LTFind(phead, 4);
if (node == NULL)
{
printf("没找到");
}
else
{
printf("%d\n", node->val);
}
}
void test6(LTNode* phead)//插入pos位置的前一个元素
{
LTNode* pos = LTFind(phead, 4);
LTInsert(pos, 10000);
LTPrint(phead);
LTInsert(phead, 6666); //尾插
LTPrint(phead);
}
//删除pos位置的元素
void test7(LTNode* phead)
{
LTNode* pos = LTFind(phead, 4);
if (pos)//查询成功才进入
{
LTErase(pos);
pos = NULL;
}
LTPrint(phead);
LTDestroy(phead);//空间释放/防止内存泄露
phead = NULL;
}
void test8()
{
LTNode* plist = LTInit();
LTPushBack(plist, 1);
LTPushBack(plist, 2);
LTPushBack(plist, 3);
LTPushBack(plist, 5);
LTPushBack(plist, 4);
LTPrint(plist);
LTNode* pos = LTFind(plist, 3);
if (pos)
{
LTErase(pos);
pos = NULL;
}
LTPrint(plist);
LTDestroy(plist);
plist = NULL;
}
int main()
{
LTNode* phead = LTInit();//已经被初始化了
test1(phead);//头插
test2(phead);//头删
test3(phead);//尾插
test4(phead);//尾删
test5(phead);//查询某个数字
test6(phead);//插入pos位置的前一个/后一个元素
test7(phead);//删除pos位置的元素
test8(phead);//删除pos位置的元素
return 0;
}
#pragma once
#include
#include
#include
typedef int LTDateType;
//声明节点
typedef struct DListNode
{
LTDateType val;
struct DListNode* prev;
struct DListNode* next;
}LTNode;
LTNode* LTInit();
void LTPushFront(LTNode*phead, LTDateType x);
void LTPopFront(LTNode* phead);
void LTPushBack(LTNode* phead,LTDateType x);
void LTPopBack(LTNode* phead);
void LTPrint(LTNode* phead);
LTNode* LTFind(LTNode* phead);
void LTInsert(LTNode* pos, LTDateType x);
void LTErase(LTNode* pos);
void LTDestroy(LTNode* phead);
#pragma once
#include
#include
#include
typedef int LTDataType;
//定义节点
typedef struct DListNode
{
LTDataType val;
struct DListNode* prev;
struct DListNode* next;
}LTNode;
LTNode* CreateLTNode(LTDataType x);//创造节点
//初始化
LTNode* LTInit();//不用二级指针/用返回值改变实参
void LTPushFront(LTNode* phead, LTDataType x);//头插
void LTPopFront(LTNode* phead);//头删
void LTPushBack(LTNode* phead, LTDataType x);//尾插
void LTPopBack(LTNode* phead);//尾删
void LTPrint(LTNode* phead);//打印
LTNode* LTFind(LTNode* phead);//查询
void LTInsert(LTNode* pos, LTDataType x);
void LTErase(LTNode* pos);//删除pos位置的元素
void LTDestroy(LTNode* phead);//销毁释放
//初始化
LTNode* LTInit()
{
LTNode* phead = CreateLTNode(-1);
phead->prev = phead;
phead->next = phead;
return phead;
}
//创造节点
LTNode* CreateLTNode(LTDataType x)
{
LTNode* newnode = (LTNode*)malloc(sizeof(LTNode));
if (newnode == NULL)
{
perror("malloc");
exit(-1);//程序停止
}
newnode->val = x;
newnode->prev = NULL;
newnode->next = NULL;
return newnode;
}
//打印
void LTPrint(LTNode* phead)
{
assert(phead);
printf("哨兵位<=>");
LTNode* cur = phead->next;
while (cur != phead)
{
printf("%d<=>", cur->val);
cur = cur->next;
}
printf("\n");
}
//头插
void LTPushFront(LTNode* phead, LTDataType x)
{
assert(phead);
LTNode* newnode = CreateLTNode(x);
newnode->next = phead->next;
phead->next->prev = newnode;
phead->next = newnode;
newnode->prev = phead;
}
这里有另一种办法,可以不用考虑先后顺序,那就是再定义一个指针,指向head->next。这样我们就可以不用考虑先后顺序啦。
void LTPushFront(LTNode* phead, LTDataType x)
{
assert(phead);
LTNode* newnode = CreateLTNode(x);
LTNode* first = phead->next;
phead->next = newnode;
newnode->prev = phead;
newnode->next = first;
first->prev = newnode;
}
void LTPopFront(LTNode* phead)
{
assert(phead);
// 空
assert(phead->next != phead);
LTNode* first = phead->next;
LTNode* second = first->next;
phead->next = second;
second->prev = phead;
free(first);
first = NULL;
}
如果只有一个哨兵位,链表为空:释放空间实质上是把使用权还给操作系统,非法访问。所以在free的那一步会出问题。
//尾插
void LTPushBack(LTNode* phead, LTDataType x)
{
assert(phead);
LTNode* tail = phead->prev;
LTNode* newnode = CreateLTNode(x);
// phead tail newnode
tail->next = newnode;
newnode->prev = tail;
newnode->next = phead;
phead->prev = newnode;
}
void LTPopBack(LTNode* phead)
{
assert(phead);
//
assert(phead->next != phead);
LTNode* tail = phead->prev;
LTNode* tailPrev = tail->prev;
free(tail);
tailPrev->next = phead;
phead->prev = tailPrev;
}
LTNode* LTFind(LTNode* phead, LTDataType x)
{
assert(phead);
LTNode* cur = phead->next;
while (cur != phead)
{
if (cur->val == x)
{
return cur;
}
cur = cur->next;
}
return NULL;
}
// 在pos前面的插入
void LTInsert(LTNode* pos, LTDataType x)
{
assert(pos);
LTNode* posPrev = pos->prev;
LTNode* newnode = CreateLTNode(x);
// posprev newnode pos
posPrev->next = newnode;
newnode->prev = posPrev;
newnode->next = pos;
pos->prev = newnode;
}
这里注意,pos也可以是phead,因为如果要在phead前面插入一个值,就会变成尾插。链表是循环的!!所以不需要在这里断言。
有了这个 LTInsert以后,我们可以知道, 前面的头插尾插都可以直接调用LTInsert函数。
头插:LTInsert(phead->next, x);
尾插:LTInsert(phead, x);
// 删除pos位置
void LTErase(LTNode* pos)
{
assert(pos);
LTNode* posNext = pos->next;
LTNode* posPrev = pos->prev;
posPrev->next = posNext;
posNext->prev = posPrev;
free(pos);
//pos = NULL;
}
有了 LTErase,我们也可以知道, 前面的头删尾删都可以直接调用 LTErase函数。
头删:LTErase(phead->next);
尾删:LTErase(phead->prev);
void LTDestroy(LTNode* phead)
{
assert(phead);
LTNode* cur = phead->next;
while (cur != phead)
{
LTNode* next = cur->next;
free(cur);
cur = next;
}
free(phead);
//phead = NULL;
//这里置空没有意义。因为外面是有实参的,外面的实参传给这个形参,
//形参的改变并不能影响什么
}
#define _CRT_SECURE_NO_WARNINGS 1
#include"List.h"
#include
//初始化
LTNode* LTInit()
{
LTNode* phead = CreateLTNode(-1);
phead->prev = phead;
phead->next = phead;
return phead;
}
//创造节点
LTNode* CreateLTNode(LTDataType x)
{
LTNode* newnode = (LTNode*)malloc(sizeof(LTNode));
if (newnode == NULL)
{
perror("malloc");
exit(-1);//程序停止
}
newnode->val = x;
newnode->prev = NULL;
newnode->next = NULL;
return newnode;
}
//打印
void LTPrint(LTNode* phead)
{
assert(phead);
printf("哨兵位<=>");
LTNode* cur = phead->next;
while (cur != phead)
{
printf("%d<=>", cur->val);
cur = cur->next;
}
printf("\n");
}
//头插
void LTPushFront(LTNode* phead, LTDataType x)
{
assert(phead);
LTNode* newnode = CreateLTNode(x);
LTNode* first = phead->next;
phead->next = newnode;
newnode->prev = phead;
newnode->next = first;
first->prev = newnode;
}
//头删
void LTPopFront(LTNode* phead)
{
assert(phead);
// 空
assert(phead->next != phead);
LTNode* first = phead->next;
LTNode* second = first->next;
phead->next = second;
second->prev = phead;
free(first);
first = NULL;
}
//尾插
void LTPushBack(LTNode* phead, LTDataType x)
{
assert(phead);
LTNode* tail = phead->prev;
LTNode* newnode = CreateLTNode(x);
// phead tail newnode
tail->next = newnode;
newnode->prev = tail;
newnode->next = phead;
phead->prev = newnode;
}
void LTPopBack(LTNode* phead)
{
assert(phead);
//
assert(phead->next != phead);
LTNode* tail = phead->prev;
LTNode* tailPrev = tail->prev;
free(tail);
tailPrev->next = phead;
phead->prev = tailPrev;
}
//查询
LTNode* LTFind(LTNode* phead, LTDataType x)
{
assert(phead);
LTNode* cur = phead->next;
while (cur != phead)
{
if (cur->val == x)
{
return cur;
}
cur = cur->next;
}
return NULL;
}
// 在pos前面的插入
void LTInsert(LTNode* pos, LTDataType x)
{
assert(pos);
LTNode* posPrev = pos->prev;
LTNode* newnode = CreateLTNode(x);
// posprev newnode pos
posPrev->next = newnode;
newnode->prev = posPrev;
newnode->next = pos;
pos->prev = newnode;
}
// 删除pos位置
void LTErase(LTNode* pos)
{
assert(pos);
LTNode* posNext = pos->next;
LTNode* posPrev = pos->prev;
posPrev->next = posNext;
posNext->prev = posPrev;
free(pos);
//pos = NULL;
}
void LTDestroy(LTNode* phead)
{
assert(phead);
LTNode* cur = phead->next;
while (cur != phead)
{
LTNode* next = cur->next;
free(cur);
cur = next;
}
free(phead);
//phead = NULL;
//这里置空没有意义。因为外面是有实参的,外面的实参传给这个形参,
//形参的改变并不能影响什么
}
这样就可以实现一个带头双向循环链表了!