目录
1 单链表
1.1 链表优点
-
按需申请空间,需要就申请,不需要就释放
-
头部或中间插入数据,不需要挪动数据
-
不存在空间浪费
1.2 缺点
-
每次存放一个数据,到要存一个指针去链接后面的数据节点
-
不支持随机访问(用下标直接访问某
i
个)
1.3 分析
*cur->next
指向下一个数据,然后cur->data
指向下一个数据的数值
该示例中结构体 存储1个数值+下一个节点的地址
当下一个节点的地址为NULL
,则结束
1.3.1 尾插数据不同情况的分析
123
节点后插入一个新节点4
,4
后指向空指针
void SLtPushBack(SLT* phead, SLTDataType x)
{
// 找尾节点
SLT* tail = phead;
while (tail->next != NULL)
{
tail = tail->next;
}
SLT* newcode = (SLT*)malloc(sizeof(SLT));
newcode->data = x;
newcode->next = NULL;
tail->next = newcode;
}
注意:malloc
里sizeof
后应为SLT
,是整个结构体类型大小
如果这样写代码,则会 报错
因为 tail
本就是NULL
空,->
操作是解引用,对空的tail
解引用非法访问
如果这么写:
void SLtPushBack(SLT* phead, SLTDataType x)
{
// 找尾节点
SLT* newcode = (SLT*)malloc(sizeof(SLT));
newcode->data = x;
newcode->next = NULL;
if(phead==NULL)
{
phead=newnode;
}
else
{
SLT* tail = phead;
while (tail->next != NULL)
{
tail = tail->next;
}
tail->next = newcode;
}
}
改变了形参phead
,未改变实参plist
实参是int
,要改变a
,要传int*
,解引用找到a
改变
实参是int*
,要传int**
,解引用找到int
同理:
void SLtPushBack(SLT** pphead, SLTDataType x)
{
// 找尾节点
SLT* newnode = (SLT*)malloc(sizeof(SLT));
newnode->data = x;
newnode->next = NULL;
if (*pphead == NULL)
{
*pphead = newnode; // 解引用
}
else
{
SLT* tail = *pphead;
while (tail->next != NULL)
{
tail = tail->next;
}
tail->next = newnode;
}
}
1.4 实现
1.4.1 创建节点
SLT* CreatListNode(SLTDataType x)
{
SLT* newnode = (SLT*)malloc(sizeof(SLT));
newnode->data = x;
newnode->next = NULL;
return newnode;
}
1.4.2 尾插
void SLtPushBack(SLT** pphead, SLTDataType x)
{
// 找尾节点
/*SLT* newnode = (SLT*)malloc(sizeof(SLT));
newnode->data = x;
newnode->next = NULL; */ // 直接封装成一个函数,如上
SLT* newnode = CreatListNode(x);
if (*pphead == NULL)
{
*pphead = newnode; // 解引用
}
else
{
SLT* tail = *pphead;
while (tail->next != NULL)
{
tail = tail->next;
}
tail->next = newnode;
}
}
1.4.3 头插
void SLtPushFront(SLT** pphead, SLTDataType x)
{
SLT* newnode = CreatListNode(x); // 创建新节点,值x已经传入newnode
newnode->next = *pphead; // 将原来头的地址放入新节点的next
*pphead = newnode; // 将新节点作为新的头
}
1.4.4 尾删
思路:
-
如果直接这样写
void SLtPopBack(SLT** pphead)
{
SLT* tail = *pphead;
while (tail->next != NULL)
{
tail = tail->next;
}
free(tail);
tail = NULL;
}
则tail
指向的是最后一个结点,被释放后,其前面节点的next
仍然指向他,并可访问到,会造成非法访问(前面的结点将成为野指针-指向一块被释放的空间)
-
如果这样写
void SLtPopBack(SLT** pphead)
{
SLT* tail = *pphead;
SLT* prev = NULL;
while (tail->next != NULL)
{
prev = tail;
tail = tail->next;
}
free(tail);
tail = NULL;
prev->next = NULL;
}
则删除时可以正常进行
但是,如果删到最后一个
4个数据删4次,程序直接崩了
跟上面类似的情况
因为此处prev
的下一个已经为空,->
为解引用操作,又造成非法访问
因此
3. 分三种情况
-
直接为空
-
有一个结点,第二个为空
-
大于等于3结点
void SLtPopBack(SLT** pphead)
{
//1 .直接为空
assert(*pphead != NULL);
//2 .只有一个结点(第二个为空)
if ((*pphead)->next == NULL)
{
free(*pphead);
*pphead = NULL;
}
//3 .结点 >= 3
else
{
SLT* tail = *pphead;
SLT* prev = NULL;
while (tail->next != NULL)
{
prev = tail;
tail = tail->next;
}
free(tail);
tail = NULL;
prev->next = NULL;
}
}
同时,最后情况3可以这样写
SLT* tail = *pphead;
while (tail->next->next)
{
tail = tail->next;
}
free(tail->next);
tail->next = NULL;
1.4.5 头删
不能上来就free
掉plist
,因为会找不到下一个
所以先保存头,并指向下一个地址
再free
掉原来的头,再把原来的头指向刚保存的
void SLtPopFront(SLT** pphead)
{
assert(*pphead != NULL);
SLT* head = (*pphead)->next;
free(*pphead);
*pphead = head;
}
并要考虑到,若链表中无任何节点,(pphead)->next
,又对下一个地址解引用,又非法访问
所以要考虑是否有节点,加入assert / if
1.4.6 查找
SLT* SListFind(SLT* phead, SLTDataType x)
{
SLT* cur = phead;
while (cur)
{
if (cur->data == x)
{
return cur;
}
else
{
cur = cur->next;
}
}
return NULL;
}
找到之后,可以修改
pos = SListFind(plist, 33);
if (pos)
{
pos->data = 333;
}
如图,找到33后改为333
1.4.7 在pos之前插入x
分2种情况:
-
pos就在头部
此时,相当于头插,而且不能直接用插入,因为newnode->next
永远不是pos(newnode)
-
pos在其他任何地方
void SListInsert(SLT** pphead, SLT* pos, SLTDateType x)
{
SLT* newnode = CreatListNode(x);// 创建节点x
if (*pphead == pos)//如果pos等于头
{
//newnode->next = *pphead; // 直接把新节点的next存入原来的头
//*pphead = newnode; // 把新节点的地址放入作为新的头
SLtPushFront(pphead, x); // 头插
}
else
{
SLT* posPrev = *pphead;
while (posPrev->next != pos)
{
posPrev = posPrev->next;
}
posPrev->next = newnode;
newnode->next = pos;
}
}
1.4.8 在pos之后插入x
创建节点,然后pos->next
指向新节点的头,新节点的next
指向原来pos
的next
void SListInsertAfter(SLT* pos, SLTDateType x)
{
SLT* newnode = CreatListNode(x);
newnode->next = pos->next;
pos->next = newnode;
}
1.4.9 删除pos后的一个节点
void SListEraseAfter(SLT* pos)
{
assert(pos->next != NULL);
SLT* posPrev = pos;
posPrev->next = posPrev->next->next;
//free(pos->next);
}
最后可以free
,也可以不free
,因为pos
传过来的是形参,这里free
不会影响外面的实参
1.4.10 删除pos处节点
时间复杂度O(N)
思路:
分2种情况
-
pos
不在头
-
pos
就是头
void SListErase(SLT**pphead,SLT* pos)
{
if (pos == *pphead)
{
// 1.正常写法
/**pphead = pos->next;
free(pos);
pos == NULL;*/
// 2.调用头删
SLtPopFront(pphead);
}
else
{
SLT* posPrev = *pphead;
while((posPrev)->next != pos)
{
posPrev = posPrev->next;
}
posPrev->next = pos->next;
free(pos);
}
}
同时pos
是头的话,也有2种写法,可以直接调用头删函数,也可以正常删
因为其时间复杂度是O(N)
,所以一般不考虑删除pos
处的节点,一般是删除后面的
1.4.11 销毁链表
void SListDestory(SLT** pphead)
{
SLT* cur = *pphead;
while (cur)
{
SLT* next = cur->next;
free(cur);
cur = next;
}
*pphead == NULL;
}