双向带头循环链表及相关OJ
在开始双链表学习前,如果对单链表有疑问或者是已经忘记了的,也可以到这篇博客去回忆一下💗💗💗
顺序表与单链表
💗1.双向带头循环链表
💞1.1 功能实现
💛1.1.1 初始化
初始化这个功能就是旨在开辟这样一个结构,比如我们这里初始化就是开辟一个不存储有效值的哨兵位。由于带头双向循环链表的特殊性质,我们这里要想实现空的双头链表,也就是将prev和next、都指向自己
由于在后面的操作中,比如插入的时候,会多次新建结点,所以我们将这个功能封装BuyLTNode函数,通过复用函数,加强了程序的可读性减少了代码的冗余。
LTNode* BuyLTNode(LTDataType x)
{
LTNode* node = (LTNode*)malloc(sizeof(LTNode));
if (node == NULL)
{
perror("malloc failed");
exit(-1);
}
node->data = x;
node->next = NULL;
node->prev = NULL;
return node;
}
LTNode* LTInit()
{
LTNode* phead = BuyLTNode(-1);
phead->next = phead;
phead->prev = phead;
return phead;
}
💛1.1.2 打印
打印只需要遍历结点然后打印就行,我就不赘述了。
void LTPrint(LTNode* phead)
{
assert(phead);
LTNode* cur = phead->next;
while (cur != phead)
{
printf("%d ", cur->data);
cur = cur->next;
}
printf("\n");
}
💛1.1.3 尾删
我们知道在单链表中我们是通过对链表进行遍历找到尾结点以及尾结点的前一个结点来实现尾删的时间复杂度为O(N)。由于双向带头循环列表特殊性质,我们可以轻松访问到尾结点,也就是tail = phead->prev ,时间复杂度变为了O(1),其他操作与单链表的相似。另外,由于带了头,我们也不需要考虑链表为空的时候头插进去就是将新节点赋给头节点,也避免了二级指针的使用。这样看来这双向带头循环链表真的是一个非常完美的结构呢!!!
void LTPopBack(LTNode* phead)
{
assert(phead);
assert(phead->next != phead);//空
LTNode* del = phead->prev;
LTNode* delprev = del->prev;
delprev->next = phead;
phead->prev = delprev;
free(del);
}
💛1.1.4 头删
void LTPopFront(LTNode* phead)
{
assert(phead);
assert(phead->next != phead);
LTNode* del = phead->next;
LTNode* delnext = del->next;
phead->next = del->next;
delnext->prev = phead;
free(del);
}
💛1.1.5 尾插
还是那句话数据结构就是要多画图!!!
void LTPushBack(LTNode* phead, LTDataType x)
{
assert(phead);
LTNode* tail = phead->prev;
LTNode* newnode = BuyLTNode(x);
tail->next = newnode;
newnode->prev = tail;
newnode->next = phead;
phead->prev = newnode;
}
💛1.1.6 头插
这里呢我没有像前面一样保存头节点,这个时候就要注意在链接节点时的顺序了。要先链接newnode后面的两根在链接前面两根,不然就找不到newnode后面的节点了。
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;
}
💛1.1.7 插入
链接的时候一定要注意不重不漏,连接节点一定是偶数次所以也可以检测自己有没有少连或者多连。
//在pos的位置插入x
void LTInsert(LTNode* pos, LTDataType x)
{
assert(pos);
LTNode* newnode = BuyLTNode(x);
LTNode* prev = pos->prev;
prev->next = newnode;
newnode->prev = prev;
newnode->next = pos;
pos->prev = newnode;
}
💛1.1.8 删除
像这一个删除我们保存了pos节点的前后节点就不用考虑连接节点时的顺序了!!!
//在pos的位置删除
void LTErase(LTNode* pos)
{
assert(pos);
LTNode* prev = pos->prev;
LTNode* next = pos->next;
prev->next = next;
next->prev = prev;
free(pos);
}
💛1.1.9 查找
还是遍历结点
LTNode* LTFind(LTNode* phead, LTDataType x)
{
assert(phead);
LTNode* cur = phead->next;
while (cur != phead)
{
if (cur->data == x)
{
return cur;
}
else
{
return NULL;
}
cur = cur->next;
}
}
💛1.1.10 判空
和我们最开始初始化的时候研究的一样
bool LTEmpty(LTNode* phead)
{
assert(phead);
return phead->next == phead;
}
💛1.1.11 长度
求长度(有效数据个数)很简单啊,直接遍历链表就行了,但是在实现之前,我们需要考虑循环的结束条件是什么?
当cur从head开始,当cur走到最后,也就是head = cur->next时,遍历完链表。
size_t LTSize(LTNode* phead)
{
assert(phead);
size_t size = 0;
LTNode* cur = phead->next;
while (cur != phead)
{
++size;
cur = cur->next;
}
return size;
}
在研究完所有的接口函数之后,我们会敏锐地发现。头删尾删不就是删除的特殊情况吗?同理头插尾插不也是插入的特殊情况吗?所以我们完全可以复用删除和插入的代码来实现头删尾删头插尾插。最后的完整代码如下。
💞1.2 全部代码
List.h
#pragma once
#include<stdio.h>
#include<stdlib.h>
#include<assert.h>
typedef int LTDataType;
typedef struct ListNode
{
struct ListNode* next;
struct ListNode* prev;
LTDataType data;
}LTNode;
LTNode* LTInit();
void LTPrint(LTNode* phead);
void LTPushBack(LTNode* phead, LTDataType x);
void LTPushFront(LTNode* phead, LTDataType x);
void LTPopBack(LTNode* phead);
void LTPopFront(LTNode* phead);
LTNode* LTFind(LTNode* phead, LTDataType x);
void LTInsert(LTNode* pos, LTDataType x);
void LTErase(LTNode* pos);
bool LTEmpty(LTNode* phead);
size_t LTSize(LTNode* phead);
void LTDestroy(LTNode* phead);
List.c
#include"List.h"
LTNode* BuyLTNode(LTDataType x)
{
LTNode* node = (LTNode*)malloc(sizeof(LTNode));
if (node == NULL)
{
perror("malloc failed");
exit(-1);
}
node->data = x;
node->next = NULL;
node->prev = NULL;
return node;
}
LTNode* LTInit()
{
LTNode* phead = BuyLTNode(-1);
phead->next = phead;
phead->prev = phead;
return phead;
}
void LTPrint(LTNode* phead)
{
assert(phead);
LTNode* cur = phead->next;
while (cur != phead)
{
printf("%d ", cur->data);
cur = cur->next;
}
printf("\n");
}
void LTPushBack(LTNode* phead, LTDataType x)
{
assert(phead);
/*LTNode* tail = phead->prev;
LTNode* newnode = BuyLTNode(x);
tail->next = newnode;
newnode->prev = tail;
newnode->next = phead;
phead->prev = newnode;*/
LTInsert(phead->prev, x);
}
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;*/
LTInsert(phead->next, x);
}
void LTPopBack(LTNode* phead)
{
assert(phead);
assert(phead->next != phead);//空
/*LTNode* del = phead->prev;
LTNode* delprev = del->prev;
delprev->next = phead;
phead->prev = delprev;
free(del);*/
LTErase(phead->prev);
}
void LTPopFront(LTNode* phead)
{
assert(phead);
assert(phead->next != phead);
/*LTNode* del = phead->next;
LTNode* delnext = del->next;
phead->next = del->next;
delnext->prev = phead;
free(del);*/
LTErase(phead->next);
}
LTNode* LTFind(LTNode* phead, LTDataType x)
{
assert(phead);
LTNode* cur = phead->next;
while (cur != phead)
{
if (cur->data == x)
{
return cur;
}
else
{
return NULL;
}
cur = cur->next;
}
}
//在pos的位置插入x
void LTInsert(LTNode* pos, LTDataType x)
{
assert(pos);
LTNode* newnode = BuyLTNode(x);
LTNode* prev = pos->prev;
prev->next = newnode;
newnode->prev = prev;
newnode->next = pos;
pos->prev = newnode;
}
//在pos的位置删除
void LTErase(LTNode* pos)
{
assert(pos);
LTNode* prev = pos->prev;
LTNode* next = pos->next;
prev->next = next;
next->prev = prev;
free(pos);
}
bool LTEmpty(LTNode* phead)
{
assert(phead);
return phead->next == phead;
}
size_t LTSize(LTNode* phead)
{
assert(phead);
size_t size = 0;
LTNode* cur = phead->next;
while (cur != phead)
{
++size;
cur = cur->next;
}
return size;
}
void LTDestroy(LTNode* phead)
{
LTNode* cur = phead->next;
while (cur != phead)
{
LTNode* next = cur->next;
free(cur);
cur = next;
}
}
test.c
#include"List.h"
void TestList1()
{
LTNode* phead = LTInit();
LTPushBack(phead, 1);
LTPushBack(phead, 2);
LTPushBack(phead, 3);
LTPushBack(phead, 4);
LTPushBack(phead, 5);
LTPrint(phead);
LTPopBack(phead);
LTPrint(phead);
LTPopBack(phead);
LTPrint(phead);
LTPopBack(phead);
LTPrint(phead);
LTPopBack(phead);
LTPrint(phead);
LTPopBack(phead);
LTPrint(phead);
//LTPopBack(phead);
}
void TestList2()
{
LTNode* phead = LTInit();
LTPushFront(phead, 1);
LTPushFront(phead, 2);
LTPushFront(phead, 3);
LTPushFront(phead, 4);
LTPushFront(phead, 5);
LTPrint(phead);
LTPopFront(phead);
LTPrint(phead);
LTPopFront(phead);
LTPrint(phead);
LTPopFront(phead);
LTPrint(phead);
LTPopFront(phead);
LTPrint(phead);
LTPopFront(phead);
LTPrint(phead);
}
void TestList3()
{
LTNode* phead = LTInit();
LTPushFront(phead, 1);
LTPushFront(phead, 2);
LTPushFront(phead, 3);
LTPushFront(phead, 4);
LTPushFront(phead, 5);
LTPrint(phead);
LTNode* pos = LTFind(phead, 3);
if (pos)
{
pos->data *= 10;
}
LTPrint(phead);
LTDestroy(phead);
phead = NULL;
}
int main()
{
TestList1();
TestList2();
TestList3();
return 0;
}
💞1.3 OJ
#define _CRT_SECURE_NO_WARNINGS
#include<stdio.h>
#include<stdlib.h>
#include<assert.h>
struct ListNode
{
int val;
struct ListNode* next;
};
void ListPrint(struct ListNode* plist)
{
struct ListNode* cur = plist;
while (cur)
{
if (cur->next == NULL)
{
printf("%d", cur->val);
}
else
{
printf("%d->", cur->val);
}
cur = cur->next;
}
printf("\n");
}
ListNode* CreateList(int* a, int n)
{
ListNode* phead = NULL, * ptail = NULL;
int x = 0;
for (int i = 0; i < n; ++i)
{
ListNode* newnode = (struct ListNode*)malloc(sizeof(struct ListNode));
if (newnode == NULL)
{
perror("malloc failed");
exit(-1);
}
newnode->val = a[i];
if (phead == NULL)
{
ptail = phead = newnode;
}
else
{
ptail->next = newnode;
ptail = newnode;
}
}
ptail->next = NULL;
return phead;
}
bool hasCycle(struct ListNode* head)
{
struct ListNode* slow = head, * fast = head;
while (fast && fast->next)
{
slow = slow->next;
fast = fast->next->next;
if (fast == slow)
{
return true;
}
}
return false;
}
int main()
{
int a[] = { 1,5,2,7 };
struct ListNode* plist = CreateList(a, 4);
int ret = hasCycle(plist);
printf("%d\n", ret);
return 0;
}
2、相交链表
#define _CRT_SECURE_NO_WARNINGS
#include<stdio.h>
#include<stdlib.h>
#include<assert.h>
struct ListNode
{
int val;
struct ListNode* next;
};
void ListPrint(struct ListNode* plist)
{
struct ListNode* cur = plist;
while (cur)
{
if (cur->next == NULL)
{
printf("%d", cur->val);
}
else
{
printf("%d->", cur->val);
}
cur = cur->next;
}
printf("NULL\n");
}
ListNode* CreateList(int* a, int n)
{
ListNode* phead = NULL, * ptail = NULL;
int x = 0;
for (int i = 0; i < n; ++i)
{
ListNode* newnode = (struct ListNode*)malloc(sizeof(struct ListNode));
if (newnode == NULL)
{
perror("malloc failed");
exit(-1);
}
newnode->val = a[i];
if (phead == NULL)
{
ptail = phead = newnode;
}
else
{
ptail->next = newnode;
ptail = newnode;
}
}
ptail->next = NULL;
return phead;
}
struct ListNode* getIntersectionNode(struct ListNode* headA, struct ListNode* headB)
{
struct ListNode* curA = headA, * curB = headB;
int lenA = 0, lenB = 0;
//找尾结点
while (curA->next)
{
++lenA;
curA = curA->next;
}
while (curB->next)
{
++lenB;
curB = curB->next;
}
//尾结点都不相等的话,就不相交
if (curA != curB)
{
return NULL;
}
int gap = abs(lenA - lenB);
struct ListNode* longList = headA, * shortList = headB;
if (lenA < lenB)
{
longList = headB;
shortList = headA;
}
//使两个链表一样长
while (gap--)
{
longList = longList->next;
}
//同时动
while (longList != shortList)
{
longList = longList->next;
shortList = shortList->next;
}
return longList;
}
int main()
{
int a1[] = { 1,5,2,7 };
int a2[] = { 1,2,3,5 };
struct ListNode* list1 = CreateList(a1, 4);
struct ListNode* list2 = CreateList(a2, 4);
struct ListNode* ret = getIntersectionNode(list1, list2);
ListPrint(ret);
return 0;
}
刷题心得:同一个长度的链表判断是否相交就是看尾结点的next是否相等,如果长度不相同的话可以通过先让长链表缩短至和短链表一样长之后,在用上面的方法比较。
#define _CRT_SECURE_NO_WARNINGS
#include<stdio.h>
#include<stdlib.h>
#include<assert.h>
struct ListNode
{
int val;
struct ListNode* next;
};
void ListPrint(struct ListNode* plist)
{
struct ListNode* cur = plist;
while (cur)
{
if (cur->next == NULL)
{
printf("%d", cur->val);
}
else
{
printf("%d->", cur->val);
}
cur = cur->next;
}
printf("NULL\n");
}
ListNode* CreateList(int* a, int n)
{
ListNode* phead = NULL, * ptail = NULL;
int x = 0;
for (int i = 0; i < n; ++i)
{
ListNode* newnode = (struct ListNode*)malloc(sizeof(struct ListNode));
if (newnode == NULL)
{
perror("malloc failed");
exit(-1);
}
newnode->val = a[i];
if (phead == NULL)
{
ptail = phead = newnode;
}
else
{
ptail->next = newnode;
ptail = newnode;
}
}
ptail->next = NULL;
return phead;
}
//方法一:结合数理知识,先设置两个快慢指针从头开始走,然后两
// 个指针分别从环形链表的头和相遇的位置一起走,再次相遇的位
// 置就是入环节点处
//ListNode* detectCycle(struct ListNode* head)
//{
// ListNode* fast = head, * slow = head;
// while (fast && fast->next)
// {
// fast = fast->next->next;
// slow = slow->next;
// //fast slow相遇
// if (fast == slow)
// {
// ListNode* meet = slow;
// ListNode* cur = head;
// while (cur != meet)
// {
// cur = cur->next;
// meet = meet->next;
// }
// return meet;
// }
// }
// return NULL;
//}
//方法二:强行将环形链表在相遇处切断,问题就转换为了求两个单链表相交节点问题
struct ListNode* GetIntersectionNode(struct ListNode* ListA, struct ListNode* ListB)
{
struct ListNode* curA = ListA, * curB = ListB;
int lenA = 0, lenB = 0;
while (curA->next)
{
++lenA;
curA = curA->next;
}
while (curB->next)
{
++lenB;
curB = curB->next;
}
//如果尾结点不相交就没有交点
if (curA == curB)
{
return NULL;
}
int gap = abs(lenA - lenB);
struct ListNode* longList = ListA;
struct ListNode* shortList = ListB;
if (lenA < lenB)
{
longList = ListB;
shortList = ListA;
}
while (gap--)
{
longList = longList->next;
}
while (longList != shortList)
{
longList = longList->next;
shortList = shortList;
}
return longList;
}
struct ListNode* detectCycle(struct ListNode* head)
{
struct ListNode* fast = head, * slow = head;
struct ListNode* meet = NULL;
while (fast && fast->next)
{
fast = fast->next->next;
slow = slow->next;
if (fast == slow)
{
meet = slow;
}
}
if (meet == NULL)
{
return NULL;
}
struct ListNode* newhead = meet->next;
return GetIntersectionNode(newhead, head);
}
int main()
{
int a[] = { 1,5,2,7 };
struct ListNode* plist = CreateList(a, 4);
ListNode* ret = detectCycle(plist);
ListPrint(ret);
return 0;
}
复制带随机指针的链表
解题思路:
/**
* Definition for a Node.
* struct Node {
* int val;
* struct Node *next;
* struct Node *random;
* };
*/
struct Node* copyRandomList(struct Node* head)
{
struct Node* cur = head;
while(cur)
{
struct Node* next = cur->next;
struct Node* copy = (struct Node*)malloc(sizeof(struct Node));
copy->val = cur->val;
//插入链接
cur->next = copy;
copy->next = next;
cur = next;
}
//2、置random
cur = head;
while(cur)
{
struct Node* copy = cur->next;
if(cur->random==NULL)
{
copy->random = NULL;
}
else
{
copy->random = cur->random->next;
}
cur = cur->next->next;
}
//3、解下来链接在一起
cur = head;
struct Node* copyHead = NULL,*copyTail = NULL;
while(cur)
{
struct Node* copy = cur->next;
struct Node* next = copy->next;
cur->next = next;
//尾插
if(copyTail == NULL)
{
copyTail = copyHead = copy;
}
else
{
copyTail ->next = copy;
copyTail = copyTail ->next;
}
cur = next;
}
return copyHead;
}
刷题心得:这一个题是链表中比较难的题了。这个栓裤腰带的解法也很妙,需要好好理解!!!
💞1.4 总结
双向带头循环链表和无哨兵位的单链表比起来,由于可以往前访问前一个结点,提高了尾插尾删的效率,同时由于有哨兵位的存在,不需要再改变结构体的指针,只用改变结构体内部的元素,就避免了二次指针的使用,也避免了删除插入时对链表是否为空的讨论!!!看起来是个怪物但其实却非常友好呢!!!