一、概念
链表是一种物理存储结构上非连续、非顺序的存储结构,数据元素的逻辑顺序是通过链表中的指针链接次序实现的
结构简单,一般不会单独用来存储数据,实际中更多的是作为其他数据结构的子结构
如哈希桶、图的邻接表
注意:
- 链式结构在逻辑上是连续的,但是在物理上不一定连续
- 现实中的结点一般都是从堆上申请出来的
- 从堆上申请的空间,是按照一定的策略来分配的,两次申请的而空间可能连续,也可能不连续
二、链表的分类
1.单双或者双向
2.带头或者非带头
3.循环或者非循环
三、无头单向非循环链表
无头单向非循环链表:结构简单,一般不会单独用来存数据。实际中更多是作为其他数据结构的子结构,如哈希桶、图的邻接表等等。另外这种结构在笔试面试中出现很多
#pragma once
#include<stdio.h>
#include<stdlib.h>
#include<assert.h>
typedef int SLTDataType;
typedef struct SListNode
{
SLTDataType data;
struct SListNode* next;
}SLTNode;
//打印
void SListPrint(SLTNode* phead);
//尾插
void SListPushBack(SLTNode** pphead, SLTDataType x);
//尾删
void SListPopBack(SLTNode** pphead);
//头插
void SListPushFront(SLTNode** pphead, SLTDataType x);
//头删
void SListPopFront(SLTNode** pphead);
//查找
SLTNode* SListFind(SLTNode* phead, SLTDataType x);
//在pos位置之前插入一个节点
void SListInsert(SLTNode** pphead, SLTNode* pos, SLTDataType x);
//在pos位置之后插入一个节点,与SLisFind配合
void SListInsertAfter(SLTNode* pos, SLTDataType x);
//在pos位置之前删除一个节点
void SListErase(SLTNode** pphead, SLTNode* pos);
//销毁链表
void SListDestory(SLTNode** pphead);
接口实现
开辟新空间
//开辟新空间
SLTNode* BuyListNode(SLTDataType x)
{
SLTNode* newnode = (SLTNode*)malloc(sizeof(SLTNode));
if (newnode == NULL)
{
printf("malloc failed\n");
exit(-1);
}
newnode->data = x;
newnode->next = NULL;
return newnode;
}
打印
//打印
void SListPrint(SLTNode* phead)
{
if (phead != NULL)
{
SLTNode* cur = phead;
while (cur != NULL)
{
printf("%d ", cur->data);
cur = cur->next;
}
printf("NULL\n");
}
else
{
printf("无元素可打印\n");
}
}
尾插
//尾插
void SListPushBack(SLTNode** pphead, SLTDataType x)
{
SLTNode* newnode = BuyListNode(x);
if((*pphead) == NULL)
{
*pphead = newnode;
}
else
{
SLTNode* tail = *pphead;
while (tail->next != NULL)
{
tail = tail->next;
}
tail->next = newnode;
}
}
尾删
//尾删
void SListPopBack(SLTNode** pphead)
{
assert(*pphead);
if ((*pphead)->next == NULL)
{
free(*pphead);
*pphead = NULL;
}
else
{
SLTNode* tail = *pphead;
SLTNode* prev = *pphead;
while (tail->next != NULL)
{
prev = tail;
tail = tail->next;
}
free(tail);
tail = NULL;
prev->next = NULL;
}
}
头插
//头插
void SListPushFront(SLTNode** pphead, SLTDataType x)
{
SLTNode* newnode = BuyListNode(x);
newnode->next = *pphead;
*pphead = newnode;
}
头删
//头删
void SListPopFront(SLTNode** pphead)
{
assert(*pphead);
if ((*pphead)->next == NULL)
{
free(*pphead);
*pphead = NULL;
}
else
{
SLTNode* tmp = ( * pphead)->next;
free(*pphead);
*pphead = tmp;
}
}
查找
//查找
SLTNode* SListFind(SLTNode* phead, SLTDataType x)
{
SLTNode* cur = phead;
if (cur == NULL)
{
return NULL;
}
else
{
while (cur)
{
if (cur->data == x)
return cur;
cur = cur->next;
}
return NULL;
}
}
在pos位置之前插入一个节点
//在pos位置之前插入一个节点
void SListInsert(SLTNode** pphead, SLTNode* pos, SLTDataType x)
{
SLTNode* newnode = BuyListNode(x);
//在第一个元素前面插入一个节点
if (*pphead == pos)
{
newnode->next = *pphead;
*pphead = newnode;
}
else
{
SLTNode* posprev = *pphead;
while (posprev->next != pos)
{
posprev = posprev->next;
}
posprev->next = newnode;
newnode->next = pos;
}
}
在pos位置之后插入一个节点,与SLisFind配合
void SListInsertAfter(SLTNode* pos, SLTDataType x)
{
SLTNode* newnode = BuyListNode(x);
newnode->next = pos->next;
pos->next = newnode;
}
删除pos节点
//删除pos节点
void SListErase(SLTNode** pphead, SLTNode* pos)
{
assert(*pphead);
if (*pphead == pos)
{
SLTNode* tmp = (*pphead)->next;
free(*pphead);
*pphead = tmp;
}
else
{
SLTNode* prev = *pphead;
while (prev->next != pos)
{
prev = prev->next;
}
prev->next = pos->next;
free(pos);
pos = NULL;
}
}
销毁链表
void SListDestory(SLTNode** pphead)
{
assert(*pphead);
SLTNode* cur = *pphead;
while (cur)
{
SLTNode* next = cur->next;
free(cur);
cur = next;
}
*pphead = NULL;
}
四、带头双向循环链表
带头双向循环链表:结构最复杂,一般用在单独存储数据。实际中使用的链表数据结构,都是带头双向循环链表。另外这个结构虽然结构复杂,但是使用代码实现以后会发现结构会带来很多优势,实现反而简单了,后面我们代码实现了就知道了。
#pragma once
#include<stdio.h>
#include<stdlib.h>
#include<assert.h>
typedef int LTDayeType;
typedef struct ListNode
{
LTDayeType data;
struct ListNode* prev;
struct ListNode* next;
}LTNode;
//哨兵位初始化
LTNode* ListInit();
//打印
void ListPrint(LTNode* phead);
//头插
void ListPushFront(LTNode* phead, LTDayeType x);
//头删
void ListPopFront(plist);
//尾插
void ListPushBack(LTNode* phead, LTDayeType x);
//尾删
void ListPopBack(LTNode* phead);
//查找
LTNode* ListFind(LTNode* phead,LTDayeType x);
//在pos位置之前插入
void ListInset(LTNode* pos, LTDayeType x);
//删除pos位置
void ListErase(LTNode* pos);
//销毁链表
void ListDestory(LTNode* phead);
接口实现
哨兵位初始化
//哨兵位初识化
LTNode* ListInit()
{
LTNode* phead = (LTNode*)malloc(sizeof(LTNode));
phead->next = phead;
phead->prev = phead;
return phead;
}
打印
//打印
void ListPrint(LTNode* phead)
{
assert(phead);
LTNode* cur = phead;
while (phead->next != cur)
{
phead = phead->next;
printf("%d ", phead->data);
}
printf("\n");
}
头插
//头插
void ListPushFront(LTNode* phead,LTDayeType x)
{
/*assert(phead);
LTNode* newnode = (LTNode*)malloc(sizeof(LTNode));
newnode->data = x;
newnode->next = phead->next;
phead->next = newnode;
newnode->prev = phead;*/
ListInset(phead->next, x);
}
头删
//头删
void ListPopFront(LTNode* phead)
{
assert(phead);
/*LTNode* pheadnext = phead->next;
if (pheadnext != phead)
{
phead->next = pheadnext->next;
phead->next->prev = phead;
free(pheadnext);
}
else
{
printf("删个屁股?什么都没有了怎么删?");
}*/
ListErase(phead->next);
}
尾插
//尾插
void ListPushBack(LTNode* phead, LTDayeType x)
{
//assert(phead);
//创建一个新空间
//LTNode* tail = phead->prev;
//LTNode* newnode = (LTNode*)malloc(sizeof(LTNode));
//newnode->data = x;
//phead tail newnode
//tail->next = newnode;
//newnode->prev = tail;
//newnode->next = phead;
//phead->prev = newnode;
ListInset(phead, x);
}
尾删
//尾删
void ListPopBack(LTNode* phead)
{
assert(phead);
/*LTNode* tailnext = phead->prev;
if (tailnext != phead)
{
LTNode* tail = tailnext->prev;
tail->next = phead;
phead->prev = tail;
free(tailnext);
}
else
{
printf("删个屁股?什么都没有了怎么删?");
}*/
ListErase(phead->prev);
}
在pos位置之前的插入
//在pos位置之前的插入
void ListInset(LTNode* pos, LTDayeType x)
{
assert(pos);
LTNode* posPrev = pos->prev;
LTNode* newnode = (LTNode*)malloc(sizeof(LTNode));
newnode->data = x;
posPrev->next = newnode;
newnode->prev = posPrev;
newnode->next = pos;
pos->prev = newnode;
}
删除pos位置
void ListErase(LTNode* pos)
{
assert(pos);
if (pos->next != pos)
{
LTNode* posPrev = pos->prev;
LTNode* posNext = pos->next;
posPrev->next = posNext;
posNext->prev = posPrev;
free(pos);
}
else
{
printf("删个屁股?什么都没有了怎么删?");
}
}
查找
//查找
LTNode* ListFind(LTNode* phead,LTDayeType x)
{
assert(phead);
LTNode* cur = phead;
while (cur->next != phead)
{
cur = cur->next;
if (x == cur->data)
{
return cur;
}
else
{
return NULL;
}
}
}
销毁链表
void ListDestory(LTNode* phead)
{
assert(phead);
LTNode* cur = phead->next;
while (cur != phead)
{
LTNode* next = cur->next;
free(cur);
cur = next;
}
free(cur);
}
五、练习题
1.移除元素链表
struct ListNode* removeElements(struct ListNode* head, int val)
{
struct ListNode* prev = NULL;
struct ListNode* cur = head;
while(cur)
{
if(cur->val == val)
{
//1.头删
if(cur == head)
{
head = cur->next;
free(cur);
cur = head;
}
//2.中间删
else
{
prev->next = cur->next;
free(cur);
cur = prev->next;
}
else
{
prev = cur;
cur = cur->next;
}
}
return head;
}
2.反转链表
思路一:
struct ListNode* reverseList(struct ListNode* head)
{
if(head == NULL)
return NULL;
struct ListNode* n1 = NULL;
struct ListNode* n2 = head;
struct ListNode* n3 = head->next;
while(n2)
{
//翻转
n2->next = n1;
//迭代
n1 = n2;
n2 = n3;
if(n3)
n3 = n3->next;
}
return n1;
}
思路二:
struct ListNode* reverseList(struct ListNode* head)
{
struct ListNode* cur = head;
struct ListNode* newhead = NULL;
while(cur)
{
struct ListNode* next = cur->next;
//头插
cur->next = newhead;
newhead = cur;
//迭代
cur = next;
}
return newhead;
}
3.链表的中间结点(双指针快慢走)
struct ListNode* middleNode(struct ListNode* head)
{
struct ListNode* fast = head;
struct ListNode* slow = head;
while(fast && fast->next)
{
fast = fast->next->next;
slow = slow->next;
}
return slow;
}
4.链表中倒数第k个结点(双指针先后走)
struct ListNode* FindKthToTail(struct ListNode* pListHead,int k)
{
struct ListNode* fast = plistHead;
struct ListNode* slow = plistHead;
for(int i = 0;i < k;i++)
{
if(fast == NULL)
return NULL;
fast = fast->next;
}
while(fast)
{
slow = slow->next;
fast = fast->next;
}
return slow;
}
5.合并两个有序列表(一个临界点,分成两堆)
struct ListNode* mergeTwoLists(struct ListNode* l1, struct ListNode* l2)
{
//防止出现其中一个为空的情况
if(l1 == NULL)
return l2;
if(l2 == NULL)
return l1;
struct ListNode* head = NULL;*tail = NULL;
while(l1 && l2)
{
if(l1->val < l2->val0
{
if(head == NULL)
{
head = tail = l1;
}
else
{
tail->next = l1;
tail = l1;
}
l1 = l1->next;
}
else
{
if(head == NULL)
{
head = tail = l2;
}
else
{
tail->next = l2;
tail = l2;
}
l2 = l2->next;
}
}
if(l1 == UNLL)
tail->next = l2;
if(l2 == NULL)
tail->next = l1;
return head;
}
6.链表分割(一个临界点,分成两堆)
ListNode* partition(ListNode* pHead,int x)
{
//开辟一个哨兵位结点,方便尾插
ListNode* lessHead,*lessTail,*greaterHead,*greatTail;
lessHead = lessTail = (struct ListNode*) malloc(sizeof(struct ListNode));
lessTail->next = NULL;
greaterHead = greatTail = (struct ListNode*)malloc(sizeof(struct ListNode));
greatTail->next = NULL;
ListNode* cur = pHead;
//把小于x和大于x的分成两堆
while(cur)
{
if(cur->val < x)
{
lessTail->next = cur;
lessTail = cur;
}
else
{
greatTail->next = cur;
greatTail = cur;
}
cur = cur->next;
}
//防止下一个系欸但与小x有链接
greatTail->next = NULL;
lessTail->next = greaterHead->next;
ListNode* newHead = lessHead->next;
//开辟内存用完要释放
free(lessHead);
free(greaterHead);
return newHead;
}
7.链表的回文结构
bool chkPalindrome(ListNode* A)
{
ListNode* p1 = A;
ListNode* p2 = A;
//双指针
while(p2 && p2->next)
{
p1 = p1->next;
p2 = p2->next->next;
}
//翻转
ListNode* mid = p1;
ListNode* n1,*n2,*n3;
n1 = n2 = n3 = NULL;
n2 = mid;
while(n2)
{
n3 = n2->next;
n2->next = n1;
n1 = n2;
n2 = n3;
}
ListNode* curR = n1;
ListNode* curA = A;
//对比
while(curR && curA)
{
if(curR->val != curA->val)
{
return false;
}
else
{
curR = curR->next;
curA = curA->next;
}
}
return true;
}
8.相交链表
思路一:暴力求解决,一个一个对过去O(N)
struct ListNode *getIntersectionNode(struct ListNode *headA, struct ListNode *headB) {
struct ListNode* b = headB;
struct ListNode* a = headA;
while(a)
{
while(b)
{
if(b == a)
return a;
b = b->next;
}
b = headB;
a = a->next;
}
return NULL;
}
思路2:优化到O(N)
- 尾节点相同就是相交,否则就不相交
- 求交点,长的链表先走,再同时走,第一个相同的就是交点
struct ListNode *getIntersectionNode(struct ListNode *headA, struct ListNode *headB)
{
struct ListNode* a = headA;
struct ListNode* b = headB;
int lena = 0;
while(a->next)
{
lena++;
a = a->next;
}
int lenb = 0;
while(b->next)
{
lenb++;
b = b->next;
}
//判断尾结点是否相同,相同则相交
if(a != b)
return NULL;
struct ListNode* long = headA;
struct ListNode* short = headB;
if(lena < lenb)
{
long = headB;
short = headA;
}
int dis = abs(lena - lenb);
whle(dis--)
{
long = long->next;
}
wihle(long !- short)
{
long = long->next;
short = short->next;
}
return long;
}
9.环形链表1
思路:双指针,一个快(走两步),一个慢(走一步),只要在环中,快指针必定会追上慢指针
bool hasCycle(struct ListNode *head)
{
struct ListNode* fast = head;
struct ListNode* slow = head;
while(fast && fast->next)
{
fast = fast->next->next;
slow = slow->next;
if(fast == slow)
return true;
}
return false;
}
10.环形链表2
2(L+X) = L + N*C + X
L = (N-1)*C + C - X
struct ListNode *detectCycle(struct ListNode *head)
{
struct ListNode* fast = head;
struct ListNode* slow = head;
while(fast && fast->next)
{
fast = fast->next->next;
slow = slow->next;
if(fast == slow)
{
//相遇后
struct ListNode* cur = slow;
///由公式证明得来的
while(cur != head)
{
cur = cur->next;
head = head->next;
}
return cur;
}
}
return NULL;
}
11.复制带随机指针的链表
struct Node* copyRandomList(struct Node* head)
{
//1.插入
struct Node* cur = head;
while(cur)
{
struct Node* copy = (struct Node*)malloc(sizeof(struct Node));
copy->val = cur->val;
copy->next = cur->next;
cur->next = copy;
cur = copy->next;
}
//2.根据原结点,处理copy的random
cur = head;
while(cur)
{
struct Node* copy = cur->next;
if(cur->random == NULL)
copy->random == UNLL;
else
copy->random = cur->random->next;
cur = copy->next;
}
//3.把拷贝节点接下来,链接成新链表,同时回复原链表
struct Node* copyHead = NULL,*copyTail = NULL;
cur = head;
while(cur)
{
struct Node* copy = cur->next;
struct Node* next = copy->next;
if(copyTail == NULL)
{
copyHead = copyTail = copy;
}
else
{
//链接
copyTail->next = copy;
//往下走一步
copyTail = copy;
}
//恢复原链表
cur->next = next;
//往下走一步
cur = next;
}
return copyHead;
}