目录
1.链表的概念及结构
链表是一种物理存储结构上非连续、非顺序的存储结构,数据元素的逻辑顺序时通过链表中的指针链接次序实现的。
从上图可以看出:
1.链式结构在逻辑上时连续的,但在物理上不一定连续
2.显示中的节点一般都是从堆上申请出来的
3.从堆上申请的空间,是按照一定的策略来分配的,两次申请的空间可能连续,也可能不连续
2.实现单链表
2.1 SList.h
#pragma once
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <assert.h>
typedef int SLTDataType;
typedef struct SListNode
{
SLTDataType data;
struct SListNode* next;
}SListNode;
//单链表打印
void SListPrint(SListNode* phead);
//单链表销毁
void SListDestory(SListNode** pphead);
//单链表尾插
void SListPushBack(SListNode** pphead, SLTDataType x);
//单链表头插
void SListPushFront(SListNode** pphead, SLTDataType x);
//单链表尾删
void SListPopBack(SListNode** pphead);
//单链表头删
void SListPopFront(SListNode** pphead);
//找到x数据元素的位置
SListNode* SListFind(SListNode* phead, SLTDataType x);
//在pos位置之前插入x
void SListInsertBefore(SListNode** pphead, SListNode* pos, SLTDataType x);
//在pos位置之后插入x
void SListInsertAfter(SListNode* pos, SLTDataType x);
//删除pos位置的数据
void SListErase(SListNode** pphead, SListNode* pos);
2.2 SList.c
2.2.1 单链表打印
//单链表打印
void SListPrint(SListNode* phead)
{
//创建新指针cur,并用phead赋值
SListNode* cur = phead;
//当cur为NULL时循环结束
while (cur)
{
printf("%d->", cur->data);
//将cur下一个结点赋值给cur
cur = cur->next;
}
printf("NULL\n");
}
Text.c进行测试
#include "SList.h"
int main()
{
//创建头节点
SListNode* slist;
//创建结点
SListNode* n1 = (SListNode*)malloc(sizeof(SListNode));
SListNode* n2 = (SListNode*)malloc(sizeof(SListNode));
SListNode* n3 = (SListNode*)malloc(sizeof(SListNode));
n1->data = 1;
n2->data = 2;
n3->data = 3;
n1->next = n2;
n2->next = n3;
n3->next = NULL;
//将n1赋值给头节点
slist = n1;
SListPrint(slist);
return 0;
}
结果:
2.2.2单链表尾插
由于头插、尾插等都需要建立新结点,所以将结点功能作为独立函数,方便后续调用,避免函数之间的冗余。
//创建新节点
SListNode* BuySListNode(SLDataType x)
{
SListNode* newnode = (SListNode*)malloc(sizeof(SListNode));
if (newnode == NULL)
{
printf("malloc fail\n");
exit(-1);
}
else
{
newnode->data = x;
newnode->next = NULL;
}
}
1.若phead指向的头节点为空,则要将创建的新节点赋值给phead
2.若phead指向的头节点不为空,则要找尾(tail),并将新节点赋值给tail->next
//单链表尾插
void SListPushBack(SListNode* phead, SLDataType x)
{
SListNode* newnode = BuySListNode(x);
if (phead == NULL)
{
phead = newnode;
}
else
{
//找尾
SListNode* tail = phead;
while (tail->next)
{
tail = tail->next;
}
tail->next = newnode;
}
}
既然尾插函数写完了,那么让我们来进行测试。
Test.c
我们进行了4次尾插,原本期待的结果是 0->1->2->3->NULL,但是运行出来的结果却是NULL。
那么是什么错误导致结果与预想有出入呢?
这个错误涉及到函数的传值传址。
我们将头结点slist传给SListNode* phead 实际上只是传了值。phead作为形参改变,不影响到实参。当链表为空时,我们将新结点赋值给phead影响不了原来的slist,那么后续操作也就都无效了。
解决方法:进行传址,将slist的地址传给SListNode** pphead,对pphead进行操作。
//单链表尾插 correct
void SListPushBack(SListNode** pphead, SLDataType x)
{
SListNode* newnode = BuySListNode(x);
if (*pphead == NULL)
{
*pphead = newnode;
}
else
{
//找尾
SListNode* tail = *pphead;
while (tail->next)
{
tail = tail->next;
}
tail->next = newnode;
}
}
2.2.3 单链表头插
//单链表头插
void SListPushFront(SListNode** pphead, SLDataType x)
{
SListNode* newnode = BuySListNode(x);
newnode->next = *pphead;
*pphead = newnode;
}
2.2.4单链表尾删
尾删分为三种情况:
1.当链表为空的时候,直接返回 return
2.当链表只有一个节点的时候,直接释放并置空头节点
3.当链表有两个以上节点的时候,用tail遍历链表,当tail->next->next ==NULL时,遍历结束并将tail->next释放置空。
//单链表尾删
void SListPopBack(SListNode** pphead)
{
if (*pphead == NULL)
{
return;
}
else if ((*pphead)->next == NULL)
{
free(*pphead);
*pphead = NULL;
}
else
{
SListNode* tail = *pphead;
while (tail->next->next)
{
tail = tail->next;
}
free(tail->next);
tail->next = NULL;
}
}
2.2.5单链表头删
单链表头删分两种情况:
1.当单链表为空时,直接return
2.当单链表不为空时,将头指针置空,并将头指针next赋值给头指针
//单链表头删
void SListPopFront(SListNode** pphead)
{
if (*pphead == NULL)
{
return;
}
else
{
SListNode* next = (*pphead)->next;
free(*pphead);
*pphead = next;
}
}
2.2.6单链表查找
//单链表查找
SListNode* SListFind(SListNode* phead, SLDataType x)
{
SListNode* cur = phead;
while (cur)
{
if (cur->data == x)
{
return cur;
}
cur = cur->next;
}
return NULL;
}
2.2.7单链表在pos位置之前插入
分为两种情况:
1.当pos位置为头节点时,使用头插函数。
2.当pos为头节点之外的其它位置时,用prev->next遍历链表找到pos,创建新节点插入到prev和prev->next之间。
//在pos位置之前插入x
void SListInsertBefore(SListNode** pphead, SListNode* pos, SLDataType x)
{
assert(pphead);
assert(pos);
if (pos == *pphead)
{
SListNodeFront(pphead, x);
}
else
{
SListNode* prev = *pphead;
while (prev->next != pos)
{
prev = prev->next;
}
SListNode* newnode = BuySListNode(x);
prev->next = newnode;
newnode->next = pos;
}
}
2.2.8单链表在pos位置之后插入
//在pos位置之后插入x
void SListInsertAfter(SListNode* pos, SLDataType x)
{
assert(pos);
SListNode* newnode = BuySListNode(x);
newnode->next = pos->next;
pos->next = newnode;
}
2.2.9单链表删除pos位置
分为两种情况:
1.当pos为头节点时,调用头删。
2.当pos不为头节点时,用prev->next指向pos->next,释放并置空pos
//删除pos位置的数据
void SListErase(SListNode** pphead, SListNode* pos)
{
assert(pphead);
assert(pos);
if (*pphead == pos)
{
SListNodePopFront(pphead);
}
else
{
SListNode* prev = *pphead;
while (prev->next != pos)
{
prev = prev->next;
}
prev->next = pos->next;
free(pos);
pos = NULL;
}
}
小结:单链表结构适合头插,尾插或任意插入不适合。
如果要使用链表单独存储数据,后续学习的双向链表更加合适