链表
- 1. 链表的概念及结构
- 2. 链表面试题
1. 链表的概念及结构
1.1 链表的概念及结构
// 1、无头+单向+非循环链表增删查改实现
typedef int SLTDateType;
typedef struct SListNode
{
SLTDateType data;
struct SListNode* next;
}SListNode;
虽然有这么多的链表的结构,但是我们实际中最常用还是两种结构
1.2 链表的实现(无头+单向+非循环链表增删查改实现)
// 1、无头+单向+非循环链表增删查改实现
typedef int SLTDateType;
typedef struct SListNode
{
SLTDateType data;
struct SListNode* next;
}SListNode;
1. 动态申请一个节点
// 动态申请一个节点
SListNode* BuySListNode(SLTDateType x)
{
SListNode* node = (SListNode*)malloc(sizeof(SListNode));
node->data = x;
node->next = NULL;
return node;
}
2. 单链表打印
void SListPrint(SListNode* plist)
{
SListNode* cur = plist;
while (cur)
//while (cur != NULL)
{
printf("%d->", cur->data);
cur = cur->next;
}
printf("NULL\n");
}
3. 单链表尾插
1. 找尾用 tail -> next
void SListPushBack(SListNode** pplist, SLTDateType x)
{
SListNode* newnode = BuySListNode(x);
if (*pplist == NULL)
{
*pplist = newnode;
}
else
{
SListNode* tail = *pplist;
while (tail->next != NULL)
{
tail = tail->next;
}
tail->next = newnode;
}
}
4. 单链表尾删 1、没有节点 ,一个节点 2、多个节点
void SListPopBack(SListNode** pplist)
{
SListNode* prev = NULL;
SListNode* tail = *pplist;
// 1.空、只有一个节点
// 2.两个及以上的节点
if (tail == NULL || tail->next == NULL)
{
free(tail);
*pplist = NULL;
}
else
{
while (tail->next)
{
prev = tail;
tail = tail->next;
}
free(tail);
tail = NULL;
prev->next = NULL;
}
}
5. 单链表头插
void SListPushFront(SListNode** pplist, SLTDateType x)
{
assert(pplist);
// 1.空
// 2.非空
SListNode* newnode = BuySListNode(x);
if (*pplist == NULL)
{
*pplist = newnode;
}
else
{
newnode->next = *pplist;
*pplist = newnode;
}
}
6. 单链表头删 1、没有节点 2.一个节点 3、多个节点
先建立一个头节点,用于保留第二个结点的位置
void SListPopFront(SListNode** pplist)
{
// 1.空
// 2.一个
// 3.两个及以上
SListNode* first = *pplist;
if (first == NULL)
{
return;
}
else if (first->next == NULL)
{
free(first);
*pplist = NULL;
}
else
{
SListNode* next = first->next;
free(first);
*pplist = next;
}
}
7. 单链表查找
SListNode* SListFind(SListNode* plist, SLTDateType x)
{
SListNode* cur = plist;
while (cur)
{
if (cur->data == x)
return cur;
cur = cur->next;
}
return NULL;
}
8. 单链表插入
可以利用7的 查找后 再插入
void SListInsertAfter(SLTNode* pos, SLTDataType x)
{
assert(pos);
SLTNode* newnode = BuySLTNode(x);
newnode->next = pos->next;
pos->next = newnode;
}
在pos之前插入 需要遍历
void SListInsertBefore(SLTNode** pplist, SLTNode* pos, SLTDataType x)
{
assert(pos);
SLTNode* newnode = BuySLTNode(x);
if (pos == *pplist) // 头插
{
newnode->next = pos;
*pplist = newnode;
}
else
{
SLTNode* prev = NULL;
SLTNode* cur = *pplist;
while (cur != pos)
{
prev = cur;
cur = cur->next;
}
prev->next = newnode;
newnode->next = pos;
}
}
8. 删除pos之后的位置
void SListEraseAfter(SListNode* pos)
{
assert(pos);
// pos next nextnext
SListNode* next = pos->next;
if (next != NULL)
{
SListNode* nextnext = next->next;
free(next);
pos->next = nextnext;
}
}
1.3 总结
1.3.1 传参是传地址(包括指针地址)还是本身取决于,是否要改变传入的值
如果用返回值,那么就传本身就可以 具体看我的博客PTA里的链表例题
原题链接
链表传入的是实参还是形参,取决于是否需要改变自身,而不用考虑next,
1.3.2 assert用于判断指针是否为 空
***1.3.3 在一个单链表中,在n位置前插入一个x,其实可以在后面插入,然后交换值
2. 链表面试题
1. 删除 val 节点(一直递归直到NULL返回值结束递归,从而回头处理)
原题链接
1.1 递归思想
1.先直接递归到了最后
2.然后让该结点 等于 下一个递归的返回值
2. 反转一个单链表(通过递归下一个,返回自身,来从后往前处理)
原题链接
1.1 迭代思想
一个next 接下来要处理的指针
一个cur 是指向当前的
一个prev 是指向前一个的
1.2 递归思想(通过递归下一个,返回自身,来从后往前处理)
1.2.1 还是先通过递归 找到最后(最后几个看题意)
找到倒数第二个,利用 next 的 next 等于 自身,自身等于NULL
1.2.2 通过递归下一个,返回自身,来从后往前处理
3. 链表的中间结点
原题链接
数组
两次遍历
快慢指针
4. 链表中倒数第k个结点
1. 快慢指针
5. 合并两个有序链表(条件递归)
原题链接
递归思想
1. 比较两个结点大小,让小的 = 递归
并且此时,接下来返回的值,应该是l1(因为l1小,返回值用于返回上次的递归)
返回值,如果l1是空 那么直接返回剩余的 l2
6. 链表分割
原题链接
建立两个链表,然后链接
7. 判断链表是否对称
原题链接
先用快慢指针找中点
8. *****相交链表
原题链接
哈希表
快慢指针
9. *****判断环形链表
原题链接
哈希表
快慢指针
*****10. 环形链表 II 判断是否为环,且返回环点
原题链接
哈希表
快慢指针
11. 复制链表(带随机指针)
原题链接
12. 对链表进行插入排序
原题链接
13. 删除链表中重复的结点
原题链接