1链表存储的物理空间是不连续的,存储在逻辑上是连续的,各个结点的地址上是没有关联的,是随机的,东一个西一个。但每个结点有指向下一个结点的地址,最后一个节点指向NULL.
单链表的逻辑结构
2链表在插入和删除数据是非常方便的,只要把结点之间的指向改变即可,不需要像顺序表一样挪动数据
3单链表并不是结构体的嵌套,而是一个节点的结构体中放置下一个结点的结构体指针
4找尾进行尾插的过程中,只有尾结点的next才是NULL
5尾插的本质是上一个结点的next存下一个结点的地址
6两种写法的对比 tail是个局部变量,出了函数作用域就销毁了,而next是结点中的指针变量,tail在变,而next不变。第二种写法没有使节点之间链接起来,导致了最后一个节点的内存泄漏
7链表为空,phead指向的是NULL。
8形参是实参的拷贝,形参的改变不影响实参·
9 plist是全局变量 是个实参,phead时函数内部的局部变量,phead出了函数内部自动销毁,但plist不受其影响,因为形参是实参的拷贝,形参的改变不影响的实参,局部变量的改变不影响全局变量。
10 plist是外部定义的,节点是malloc出来的,都是外部定义的,是在堆上开辟的,不是局部函数定义的。只有通过(间接层)指针的间接作用才能改变。
11因此要改变外面结构体Node,就需要用一级指针Node*,要改变外面结构体Node*,就需要用二级指针Node**。全部变量不需要通过指针就能修改。其他的外面的局部变量不许通过指针这样的中间层才能改变。
12不在当前函数定义的都属于外面的·
13由上可知,头插也需要二级指针,传plist的地址。
14堆上都是通过指针去链接的
15只读不改用不到二级指针
16尾删不需要二级指针?(可能记错了),直接把上一结点的next置为NULL即可,也可以用双指针,tail往下访问结点之前,把自己存在另一个指针pre。双指针情况需要分类讨论,分单结点和多节点,单结点容易出现野指针的问题。头删plist需要置NULL,要不然会出现野指针的问题,已经被释放的空间,还留着其原地址,解引用会导致越界访问。
17 free释放的指针对应的空间,free的不是指针本身。
18头删上来不能之间free,第一个结点的结构体被释放,那没办法链接到第二个节点了。
19缺陷单链表只能往后面的结点走,不能通过结点访问上一个结点。
20 单链表其实只适合头插和头删,其他的时间复杂度都是O(N).
21 空链表能打印也能查找
22后面插入和删除不存在头删的情境,要简单许多。
23栈溢出?没有返回条件的递归才会栈溢出,堆溢出的本质是开空间开不出来了。
24 如果只给pos指针,想改变前面位置的值,就要赋值给pos位置后面的结点,然后交换。
25free(NULL)不会报错,其内部会检查
26哨兵位的头结点,这个结点不存储有效数据,其优势:写链表时不需要二级指针,只需要改变结构体。但是实践中和OJ题中都不带哨兵位。
27头结点的三种状态
代码实现
头文件
#pragma once
#include<stdio.h>
#include<stdlib.h>
#include<assert.h>
typedef int SLNDataTpye;
//单链表 single list
typedef struct SListNode
{
SLNDataTpye val;
struct SListNode* next;//结构体指针变量
}SLNode;
void SLTPrint(SLNode* phead);
SLNode* CreateNote(SLNDataTpye x);
void SLTPushBack(SLNode** phead, SLNDataTpye x);
void SLTPushFront(SLNode** phead, SLNDataTpye x);
void SLTPopBack(SLNode** pphead);
void SLTPopFront(SLNode** pphead);
SLNode* SLTFind(SLNode* plist, SLNDataTpye x);
void SLTInsertionAfter(SLNode* pos, SLNDataTpye x);
void SLTEraseAfter(SLNode* pos);
void SLTDestroy(SLNode** pphead);
void SLTInsert(SLNode** pphead, SLNode* pos, SLNDataTpye x);
void SLTErase(SLNode** pphead, SLNode* pos);
源文件
#include"SList.h"
void SLTPrint(SLNode* phead)
{
SLNode* cur= phead;
if (cur == NULL)
{
return;
}
while (cur !=NULL)
{
printf("%d->", cur->val);
cur = cur->next;
}
printf("NULL\n");
}
SLNode* CreateNote(SLNDataTpye x)
{
SLNode* newnode = (SLNode*)malloc(sizeof(SLNode));
if (newnode == NULL)
{
perror("malloc fail");
(-1);
}
newnode->val = x;
newnode->next = NULL;
return newnode;
}
void SLTPushBack(SLNode** pphead, SLNDataTpye x)
{
SLNode* newnode = CreateNote(x);
//对phead的地址分情况讨论 看是否是NULL
if (*pphead == NULL)
{
*pphead = newnode;
}
else
{
//通过循环找尾
SLNode* tail = *pphead;
while (tail->next != NULL)
{
tail = tail->next;
}
tail->next = newnode;
}
}
void SLTPushFront(SLNode** pphead, SLNDataTpye x)
{
SLNode* newnode = CreateNote(x);
newnode->next=*pphead ;
*pphead = newnode;
}
void SLTPopBack(SLNode**pphead)
{
assert(*pphead);
/*SLNode* tail = *pphead;
//第一种;找尾后,将尾结点空间释放,再将上以结点的next置空。这个方法的前提也是多个结点,而非单个结点
while (tail->next->next != NULL)
{
tail = tail->next;
}
free(tail->next);
tail->next = NULL;*/
//第二种:双指针,一前一后跟着走,前指针负责找尾释放空间,后指针负责next置空。
//分情况讨论一个节点及以上
if ((*pphead)->next == NULL)
{
free(*pphead);
*pphead = NULL;
}
else
{
SLNode* tail = *pphead;
SLNode* prev = NULL;
while (tail->next != NULL)
{
prev = tail;
tail = tail->next;
}
free(tail);
prev->next = NULL;
}
}
void SLTPopFront(SLNode** pphead)
{
assert(*pphead);
SLNode* tail = *pphead;
*pphead = (*pphead)->next;
free(tail);
}
SLNode* SLTFind(SLNode* phead, SLNDataTpye x)
{
assert(phead);
while (phead->val != x)
{
phead = phead->next;
if (phead == NULL)
{
printf("没找到x");
return NULL;
}
}
return phead;
}
void SLTInsertionAfter(SLNode* pos, SLNDataTpye x)
{
assert(pos);
SLNode* newnode = (SLNode*)malloc(sizeof(SLNode));
assert(newnode);
newnode->val = x;
newnode->next = pos->next;
pos->next = newnode;
}
void SLTEraseAfter(SLNode* pos)
{
assert(pos);
SLNode* newnode = pos->next->next;
free(pos->next);
pos->next = newnode;
}
void SLTDestroy(SLNode** pphead)
{
assert(pphead);
SLNode* cur = *pphead;
while (cur)
{
SLNode* newnode = cur->next;
free(cur);
cur = newnode;
}
*pphead = NULL;
}
void SLTInsert(SLNode** pphead, SLNode* pos, SLNDataTpye x)
{
assert(*pphead);
assert(pphead);
assert(pos);
if (pos == *pphead)
{
SLTPushFront(pos,x);
}
SLNode* prev = *pphead;
while (prev->next != pos)
{
prev = prev->next;
}
SLNode* newnode = CreateNote(x);
prev->next = newnode;
newnode->next = pos;
}
void SLTErase(SLNode** pphead, SLNode* pos)
{
assert(*pphead);
assert(pphead);
assert(pos);
if (pos == *pphead)
{
SLTPopFront(pos);
}
else
{
SLNode* prev = *pphead;
while (prev->next != pos)
{
prev = prev->next;
}
SLNode* newnode = pos->next;
prev->next = newnode;
free(pos);
pos = NULL;
}
}