目录
双向带头循环链表结构:
创建结构体,用于存放数据,上一个节点,和下一个节点。
typedef --- 关键字,类型重命名
typedef int LTDataType;
typedef struct ListNode
{
LTDataType _data;
struct ListNode* _next;
struct ListNode* _prev;
}ListNode;
一:初始化链表
// 创建返回链表的头结点.
ListNode* ListCreate();
初始化时,需要创建哨兵位,即开辟新的节点,由于后续头插,尾插也要开辟新的节点,可以将其分装成为一个函数 --- ListNode* OpenNode(LTDataType x);
//开辟新的节点
ListNode* OpenNode(LTDataType x);开辟新的节点,需要创建结构体指针去接收,使用 malloc 开辟空间,由于 malloc 又可能失败,需要对新开辟的节点(newnode)进行判空判断,若 newnode 为 NULL --- 空间开辟失败,否则,空间开辟成功。将x放入新节点(newnode)中。
//开辟新的节点 ListNode* OpenNode(LTDataType x) { ListNode* newnode = (ListNode*)malloc(sizeof(ListNode)); //开辟的节点有可能开辟失败 if (newnode == NULL) { perror("malloc fail"); return NULL;//返回类型为 ListNode* } //开辟成功,使用 newnode->_data = x; newnode->_next = NULL; return newnode; }
链表初始时,只有一个哨兵位,即判断条件为:pHead->_next = pHead; pHead->_prev = pHead
代码为:
// 创建返回链表的头结点.
ListNode* ListCreate()
{
ListNode* pHead = OpenNode(-1);
pHead->_next = pHead;
pHead->_prev = pHead;
return pHead;
}
二:打印链表
// 双向链表打印
void ListPrint(ListNode* pHead);
打印链表时,可以先将哨兵位打印出来,后续继续打印链表。
打印哨兵位:printf("sentry<==>");
代码为:
// 双向链表打印
void ListPrint(ListNode* pHead)
{
//打印哨兵位
printf("sentry<==>");
ListNode* cur = pHead->_next;
//循环终止的条件是 cur != pHead
while (cur != pHead)
{
printf("%d<==>", cur->_data);
cur = cur->_next;
}
printf("\n");//换行
}
三:尾插
// 双向链表尾插
void ListPushBack(ListNode* pHead, LTDataType x);
尾插时,需要开辟一个新的节点,使用函数 --- ListNode* OpenNode(LTDataType x);
先找链表的尾,并记录下来(cur), 链表的尾为 pHead->_prev; 然后将链表链接起来。
代码为:
// 双向链表尾插
void ListPushBack(ListNode* pHead, LTDataType x)
{
assert(pHead);
//尾插 --- 插入数据
assert(pHead);
ListNode* newnode = OpenNode(x);
//找尾 , pHead->_prev 即为原来的尾
ListNode* cur = pHead->_prev;
cur->_next = newnode;
newnode->_prev = cur;
newnode->_next = pHead;
pHead->_prev = newnode;
}
进行测试,尾插入 1 2 3 4 , 效果为:
四:头插
// 双向链表头插
void ListPushFront(ListNode* pHead, LTDataType x);
头插时,需要插入数据,使用函数 --- ListNode* OpenNode(LTDataType x);
头插入时,插入的位置为哨兵位后面的位置,需要记录起始位置(ListNode* cur = pHead->_next;)
然后进行连接。
代码为:
// 双向链表头插
void ListPushFront(ListNode* pHead, LTDataType x)
{
assert(pHead);
//头插 --- 插入数据
ListNode* newnode = OpenNode(x);
//记录原起始位置(pHead 后面的那个节点)
ListNode* cur = pHead->_next;
//进行链接
pHead->_next = newnode;
newnode->_prev = pHead;
newnode->_next = cur;
cur->_prev = newnode;
}
头插入 1 2 3 4 ,效果为:
五:尾删
// 双向链表尾删
void ListPopBack(ListNode* pHead);
尾删时,需要判断链表是否只有哨兵位,若链表只有哨兵位的话,则无法进行尾删。
尾删时,先找链表的尾(ListNode* tail = pHead->_prev;),
及其前一个节点(ListNode* cur = tail->_prev;)
然后进行链接 --- 将 cur 与哨兵位连接起来。
最后释放 原尾节点(tail)。
代码为:
//判断链表中是否只有哨兵位,真为 true 假为 false
bool LTEmpty(ListNode* pHead)
{
assert(pHead);
return pHead->_next == pHead;// 若相等为真,若不相等为假
}
// 双向链表尾删
void ListPopBack(ListNode* pHead)
{
assert(pHead);
//若链表中只有 哨兵位,无法进行尾删
assert(!LTEmpty(pHead));//仅有哨兵位时,无法尾删
ListNode* tail = pHead->_prev;
ListNode* cur = tail->_prev;
//进行链接
pHead->_prev = cur;
cur->_next = pHead;
//释放尾节点
free(tail);
}
尾插入 1 2 3 4 ,打印结果,然后 尾删一次的效果为:
尾插入 1 2 3 4 ,打印结果,然后 尾删五次(即影响到了哨兵位)的效果为:
观察ListNode.c 文件中的 line 111 行:
可知,当链表只有哨兵位时,仍在进行尾删,所以报错(链表中只有哨兵位时,不能进行尾删)
六:头删
// 双向链表头删
void ListPopFront(ListNode* pHead);
同尾删类似,当链表中只有哨兵位时,无法进行头删,应使用 --- assert(!LTEmpty(pHead)); 进行判断。
头删时,应该找到哨兵位后两个节点(ListNode* prve = pHead->_next;)
(ListNode* cur = prve->_next;) ,并将其保存起来,然后将哨兵位与哨兵位后第二个节点链接起来,释放掉哨兵位后第一个节点。
代码为:
//判断链表中是否只有哨兵位,真为 true 假为 false
bool LTEmpty(ListNode* pHead)
{
assert(pHead);
return pHead->_next == pHead;// 若相等为真,若不相等为假
}
// 双向链表头删
void ListPopFront(ListNode* pHead)
{
assert(pHead);
//若链表中只有 哨兵位,无法进行头删
assert(!LTEmpty(pHead));
ListNode* prve = pHead->_next;
ListNode* cur = prve->_next;
//进行链接
pHead->_next = cur;
cur->_prev = pHead;
//释放头节点
free(prve);
}
头插入 1 2 3 4 ,打印,然后进行一次头删,效果为:
头插入 1 2 3 4 ,打印,然后进行5次头删,效果为:
观察 ListNode.c 的 第 129 行:
可知,当链表只有哨兵位时,仍在进行头删,所以报错(链表中只有哨兵位时,不能进行头删)
七:查找
// 双向链表查找
ListNode* ListFind(ListNode* pHead, LTDataType x);//传入起始位置(非链表的),和要查找的内容
代码为:
// 双向链表查找
ListNode* ListFind(ListNode* pHead, LTDataType x)
{
assert(pHead);
//找尾 --- cur != pHead , 当 cur == pHead 时,链表开始绕圈圈
ListNode* cur = pHead->_next;//从 cur = pHead->_next 开始
while (cur != pHead)
{
if (cur->_data == x)
{
return cur;
}
cur = cur->_next;//进行循环的条件
}
return NULL;
}
八:在pos的前面进行插入
// 双向链表在pos的前面进行插入
void ListInsert(ListNode* pos, LTDataType x);
查找过程返回的节点即为 pos 。
判断 pos 的值 --- 防止输入错误的 pos 。断言 assert(pos);
需要开辟一个新的节点,用到 函数 --- ListNode* OpenNode(LTDataType x);
链表的长度未知,只能使用 pos 的前后节点,想要在 pos 的前面插入新的节点,需要记录 pos 前一个节点(ListNode* prve = pos->_prev;) ,然后进行链接。
代码为:
//开辟新的节点
ListNode* OpenNode(LTDataType x)
{
ListNode* newnode = (ListNode*)malloc(sizeof(ListNode));
//开辟的节点有可能开辟失败
if (newnode == NULL)
{
perror("malloc fail");
return NULL;//返回类型为 ListNode*
}
//开辟成功,使用
newnode->_data = x;
newnode->_next = NULL;
return newnode;
}
// 双向链表在pos的前面进行插入
void ListInsert(ListNode* pos, LTDataType x)
{
assert(pos);
//插入数据
ListNode* newnode = OpenNode(x);
ListNode* prve = pos->_prev;
//进行链接
prve->_next = newnode;
newnode->_prev = prve;
newnode->_next = pos;
pos->_prev = newnode;
}
头插入 1 2 3 4 ,打印结果,进行一次头删,打印结果,查找数据为2的节点,若该节点存在。则在该节点前边插入30,打印结果。
代码如下:
void test2()
{
ListNode* plist = ListCreate();
//头插
ListPushFront(plist, 1);
ListPushFront(plist, 2);
ListPushFront(plist, 3);
ListPushFront(plist, 4);
//打印
ListPrint(plist);
//头删
ListPopFront(plist);
//打印
ListPrint(plist);
//查找
ListNode* pos = ListFind(plist, 2);
if (pos)
{
ListInsert(pos, 30);
}
//打印
ListPrint(plist);
}
int main()
{
test2();
return 0;
}
运行效果为:
九:删除pos位置的节点
// 双向链表删除pos位置的节点
void ListErase(ListNode* pos);
查找过程返回的节点即为 pos 。
判断 pos 的值 --- 防止输入错误的 pos 。断言 assert(pos);
需要记录 pos 的前一个节点(ListNode* cur = pos->_prev;)
和后一个节点(ListNode* tail = pos->_next;)
然后将这两个节点链接在一起,释放掉 pos 节点。
代码为:
// 双向链表删除pos位置的节点
void ListErase(ListNode* pos)
{
assert(pos);
ListNode* cur = pos->_prev;
ListNode* tail = pos->_next;
//链接
cur->_next = tail;
tail->_prev = cur;
free(pos);
}
头插入 1 2 3 4 ,打印结果,进行一次头删,打印结果,查找数据为2的节点,若该节点存在。则在该节点前边插入30,打印结果,最后删除 pos 位置的数值,打印结果。(在pos的前面进行插入 的示例上加上一个删除 pos 位置的值。
十:链表的销毁
// 双向链表销毁
void ListDestory(ListNode* pHead);
链表销毁时,应该一个节点一个节点的销毁,先销毁链表中的节点,最后销毁哨兵位。
// 双向链表销毁
void ListDestory(ListNode* pHead)
{
assert(pHead);
ListNode* cur = pHead;
while (cur != pHead)
{
ListNode* next = cur->_next;
//一节点一个节点的释放
free(cur);
cur = next;
}
//释放哨兵位
free(pHead);
}