目录
SList.h
#pragma once
#include<stdio.h>
#include<stdlib.h>
#include<assert.h>
typedef int SLNDataType;
//Single List
typedef struct SListNode {
SLNDataType val;
struct SListNode* next;//指向下一个结点的指针
}SLNode;
//打印
void SLPrint(SLNode* phead);
//尾插
void SLPushBack(SLNode** pphead, SLNDataType);
//头插
void SLPushFront(SLNode** pphead, SLNDataType);
//尾删
void SLPopBack(SLNode** pphead);
//头删
void SLPopFront(SLNode** pphead);
//查找
SLNode* SLFind(SLNode* phead, SLNDataType x);
//在pos前插入
void SLInsert(SLNode** pphead,SLNode* pos, SLNDataType x);
//pos可以通过SLFind找到,或者写成int pos
//删除pos位置
void SLErase(SLNode** pphead, SLNode* pos);
//清空单链表
void SLDestory(SLNode** pphead);
//在pos后插入
void SLAfterInsert(SLNode* pos, SLNDataType x);
//不用pphead,因为是在后面操作plist不会发生变动
//删除pos位置
void SLAfterErase(SLNode* pos);
SList.c
#define _CRT_SECURE_NO_WARNINGS
#include"SList.h"
void SLPrint(SLNode* phead) {
SLNode* cur = phead;
while (cur != NULL) {
printf("%d->", cur->val);
cur = cur->next;
}
printf("NULL");
printf("\n");
}
SLNode* CreateNode(SLNDataType x) {
SLNode* newnode = (SLNode*)malloc(sizeof(SLNode));
if (newnode == NULL) {
perror("malloc fail");
exit(-1);
//要是发生错误直接结束程序
//0才是正常终止
}
newnode->val = x;
newnode->next = NULL;
return newnode;
}
但是phead newnode都是临时变量,出了函数就会被销毁
主函数上是SLPushBack(plist, 1);
相当于phead是plist的拷贝,没有被修改的权利
空间还在,但是找不到,也使用不了
//void SLPushBack(SLNode* phead, SLNDataType x) {
// SLNode* newnode = CreateNode(x);
// if (phead == NULL) {
// phead = newnode;
// }
// else {
// SLNode* tail = phead;
// while (tail->next != NULL) {
// tail = tail->next;//循环
// }
// //SLNode* newnode = CreateNode(x);
// tail->next = newnode;
// }
//}
//此处传进来的是SLPushBack(&plist, 1);也就是plist的地址
//用SLNode**接收,pphead就是plist的地址,*pphead就是根据地址找到plist
//此时就可以使用对指针进行修改
void SLPushBack(SLNode** pphead, SLNDataType x) {
assert(pphead);
SLNode* newnode = CreateNode(x);
//这里用的是二级指针
if (*pphead == NULL) {
*pphead = newnode;
}
else {
SLNode* tail = *pphead;
while (tail->next != NULL) {
tail = tail->next;//循环
}
//SLNode* newnode = CreateNode(x);
tail->next = newnode;
//这里用的是一级指针
//改变指向结构体的指针Node*,要用二级指针
// 也就是改变结构体指针的位置
//改变结构体内部的东西Node,比如指针,要用一级指针
// 也就是改变指针的内容
}
}
void SLPushFront(SLNode** pphead, SLNDataType x) {
//此处不用管链表是否为空,并无影响
assert(pphead);
SLNode* newnode = CreateNode(x);
newnode->next = *pphead;
*pphead = newnode;
newnode->val = x;
}
void SLPopBack(SLNode** pphead) {
assert(pphead);
温柔检查
//if (*pphead == NULL)
// return;
//
//暴力检查
assert(*pphead);
//如果删除完链表为空,就要将指针置为空,否则就成为野指针
//free掉一个结点之后,要将上一个节点的next指针置为空
//所以此处可以采用两个指针完成
//一个结点
SLNode* tail = *pphead;
if (tail->next == NULL) {
free(tail);
tail = NULL;
*pphead = NULL;
}
//多个结点
else {
SLNode* prev = NULL;
while (tail->next != NULL) {
prev = tail;
tail = tail->next;
}
free(tail);
tail = NULL;
//其实此处tail可以不置空,因为出了作用域也销毁了
prev->next = NULL;
}
}
void SLPopFront(SLNode** pphead) {
assert(pphead);
assert(*pphead);
//一个结点
if ((*pphead)->next == NULL) {
free(*pphead);
*pphead = NULL;
}
//多个结点
else {
SLNode* tail = *pphead;
*pphead = (*pphead)->next;
free(tail);
tail = NULL;
}
}
SLNode* SLFind(SLNode* phead, SLNDataType x) {
SLNode* cur = phead;
while (cur) {
if (cur->val == x) {
return cur;
}
else {
cur = cur->next;
}
}
printf("未找到\n");
return NULL;
}
void SLInsert(SLNode** pphead, SLNode* pos, SLNDataType x) {
assert(pphead);
//pphead是一个指向plist的指针。万一是头插,则需要改plist,就需要pphead
//需要断言
//assert(*pphead);
//*pphead相当于是plist
//可以不断言,链表为空,相当于空链表的尾插,此时pos也为空
assert((pos== NULL && *pphead == NULL)||(pos && *pphead));
//前者是都为空
//后者是都不为空
//使用assert是为了把以下这种情况检查出来
//SLNode* pos = SLFind(plist, 1000);
//SLInsert(&plist, pos, 100);
if (*pphead == pos) {
//头插
SLPushFront(pphead, x);
}
else {
//找前一个
SLNode* prev = *pphead;
while (prev->next != pos) {
prev = prev->next;
}
//插入
SLNode* newnode = CreateNode(x);
prev->next = newnode;
newnode->next = pos;
}
}
void SLErase(SLNode** pphead, SLNode* pos) {
assert(pphead);
assert(*pphead);
assert(pos);
//找前一个
SLNode* prev = *pphead;
//pos在首结点
if (*pphead == pos) {
//头删
SLPopFront(pphead);
}
else {
//pos不在首结点
while (prev->next != pos) {
prev = prev->next;
}
prev->next = pos->next;
free(pos);
pos = NULL;
}
}
void SLDestory(SLNode** pphead) {
assert(pphead);
SLNode* cur = *pphead;
while (cur) {
SLNode* tmp = (*pphead)->next;
*pphead = tmp;
free(cur);
cur = *pphead;
}
}
void SLAfterInsert(SLNode* pos, SLNDataType x) {
assert(pos);
SLNode* newnode = CreateNode(x);
newnode->next = pos->next;
pos->next = newnode->next;
}
void SLAfterErase(SLNode* pos) {
assert(pos);
assert(pos->next);
SLNode* cur = NULL;
cur = pos->next;
pos->next = pos->next->next;
free(cur);
//free空不会报错
cur = NULL;
}
Text.c
#define _CRT_SECURE_NO_WARNINGS
#include"SList.h"
void Text1() {
SLNode* plist = NULL;
//SLPushBack(plist, 1);
//SLPushBack(plist, 2);
//SLPushBack(plist, 3);
SLPushBack(&plist, 1);
SLPushBack(&plist, 2);
SLPushBack(&plist, 3);
SLPushBack(&plist, 4);
SLPushBack(&plist, 5);
SLPrint(plist);
SLPopBack(&plist);
SLPrint(plist);
SLPopFront(&plist);
SLPrint(plist);
SLFind(plist, 3);
}
void Text2() {
SLNode* plist = NULL;
SLPushBack(&plist, 1);
SLPushBack(&plist, 2);
SLPushBack(&plist, 3);
SLPushBack(&plist, 4);
SLPushBack(&plist, 5);
SLPrint(plist);
SLNode* pos = SLFind(plist, 2);
SLInsert(&plist, pos, 100);
SLPrint(plist);
SLErase(&plist, pos);
SLPrint(plist);
}
void Text3() {
SLNode* plist = NULL;
SLPushBack(&plist, 1);
SLPushBack(&plist, 2);
SLPushBack(&plist, 3);
SLPushBack(&plist, 4);
SLPushBack(&plist, 5);
SLPrint(plist);
SLDestory(&plist);
SLPrint(plist);
}
int main() {
//Text1();
//Text2();
Text3();
return 0;
}
练习:
链表的中间结点
移除链表元素
思路一:
删除结点
思路二:
组成新的单链表,但并不是复制到新的结点,只是使用新的指针
tail用来指向新的单链表的当前节点
最后一个if
语句的作用是在遍历链表的过程中,当最后一个元素是需要移除的val
时,防止tail->next
指向空导致后续访问出错。如果链表的尾部元素是需要删除的val
,那么在删除之后,tail
的next
应该设置为NULL
,表示链表已经结束。这样做是为了保持链表结构的完整性,并在返回新的头节点newhead
时,确保链表的尾部状态被正确更新
链表分割
eg:3 6 1 8 4 6 9 x=5
结果为:3 1 4 6 8 6 9
思路:新建两个链表,一个存放小于x的值,另一个存放大于x的值,然后拼接
要是两个链表其中一个为空,拼接的时候要考虑两个链表谁为空,然后返回不为空的,有点麻烦,所以最好选择带哨兵位(首元结点)的链表,使得都不为空
反转链表
思路一:
改变箭头方向
思路二:
头插
倒数第k个结点
思路:
相对距离:fast先走k步,然后fast和slow同时走,直到fast为空
原理:
slow和fast之间相差k-1步,之间距离就是k
当fast走到尾了,说明slow走到了倒数第k个
相交链表
思路一:
把a链表的所有节点地址和b去比对
思路二:
分别找到尾节点,要是地址相同,就说明相交
如果a、b依次走再判断结点是否为同一个,就有可能完美错过
所以先算出a、b长度,长的先走长度差,然后再a、b同时走
环形链表
也有可能自己指向自己
思路:
快慢指针的追及问题
plus:
思路一:
结论:
一个指针从相遇点开始走,一个指针从头开始走,他们会在环入口点相遇,以此可以求出环入口点
思路二:
合并两个有序链表
不建议把list2插入到list1的合适位置,很难控制,涉及头插尾插以及头指针的变化等等,吃力不讨好
链表的回文结构
思路:
找到中间结点,逆制后半段链表,和前半段比较看是否相同
随机链表的复制
思路一:
找random是原装链表的第i个,然后定义copy链表里的第i个为random,此时合计为O(N^2)
思路二:
要求优化到O(N):
1、复制一个结点在原节点的后面
2、复制结点的random就等于前一个节点的random所在结点(原结点)的后一个结点(复制结点)
3、然后拆下来复制节点,再尾插