1.什么是链表
1.1链表的概念及结构
链表是一种
物理存储结构上非连续
、非顺序的存储结构,数据元素的
逻辑顺序
是通过链表
中的
指针链接
次序实现的 。每一个节点分为数据域data和指针域next,最后一个节点的指针域为NULL,其结构如下图所示:
1.2链表的分类
实际中链表的结构非常多样,单向或者双向、带头或者不带头、循环或者非循环都会出现,但是在日常使用和刷题过程中,主要考察的是以下两种链表结构:
1.
无头单向非循环链表:
结构简单
,一般不会单独用来存数据。实际中更多是作为
其他数据结
构的子结构
,如哈希桶、图的邻接表等等。另外这种结构在
笔试面试
中出现很多。
2.
带头双向循环链表:
结构最复杂
,一般用在单独存储数据。实际中使用的链表数据结构,都
是带头双向循环链表。另外这个结构虽然结构复杂,但是使用代码实现以后会发现反而简单,它也是使用最为方便,最接近完美的链表类型。
本次重点介绍无头单向非循环链表的各个接口的实现。
2.无头单向非循环链表各个接口的实现
2.1链表的定义
//定义链表
typedef struct slistnode
{
int data;//数据域
struct slistnode *next;//指针域
}SLTNode;
2.2链表的打印
//链表的打印
void SListprint(SLTNode* phead)
{
SLTNode* cur = phead;
while (cur)//指针不为空,说明链表没到最后一位
{
printf("%d ", cur->data);
cur = cur->next;
}
}
2.3创建一个新的节点
//创建一个新的节点
SLTNode* BuySListNode(SLDatatype x)
{
SLTNode* tail = (SLTNode*)malloc(sizeof(SLTNode));
tail->data = x;
tail->next = NULL;
return tail;
}
2.4尾插
//尾插
//先建立一个新节点,然后遍历找到原链表中的尾节点;
void SListPushback(SLTNode** pphead, SLDatatype x)
{
SLTNode* newnode = BuySListNode(x);//先建立一个新节点
if(*pphead==NULL)//如果节点为空,则直接将新的节点放置进去
*pphead = newnode;
else//不为空,遍历找到原链表中的尾节点后插入
{
SLTNode* tail = *pphead;
while (tail->next != NULL)
{
tail = tail->next;
}
tail->next = newnode;
}
}
2.5头插
//头插
void SListPushfront(SLTNode** pphead, SLDatatype x)
{
//先建立一个新节点
SLTNode* newnode = BuySListNode(x);
newnode->next = *pphead;//新节点的下一个节点为原链表的第一个节点
*pphead = newnode;//令新插入的节点为新链表的第一个节点
}
2.6尾删
void SListPopback(SLTNode** pphead)
{
//如果此时链表为空
if (*pphead == NULL)
{
return;
}
//此时只有一个节点
else if ((*pphead)->next==NULL)
{
free(*pphead);
*pphead = NULL;
}
else
{
//方法一
SLTNode* tail = *pphead;
SLTNode* prev = NULL;
while (tail->next != NULL)
{
prev = tail;
tail = tail->next;
}
free(tail);
tail = NULL;
prev->next = NULL;
//方法二
/*SLTNode* tail = *pphead;
while (tail->next->next)
{
tail = tail->next;
}
free(tail->next);
tail->next = NULL;*/
}
}
2.7头删
//头删
void SListPopfront(SLTNode** pphead)
{
SLTNode* tail = (*pphead)->next;
free(*pphead);
*pphead = tail;//将tail视为接下来的新链表的第一个节点,继续执行头删
}
2.8查找
//查找
SLTNode* SListFind(SLTNode* phead, SLDatatype x)
{
SLTNode* cur = phead;
while (cur)
{
if(cur->data==x)
return cur;
cur = cur->next;
}
return NULL;
}
2.9在pos值之前插入一个数
//在pos值之前插入一个数
void SListInsert(SLTNode** pphead, SLTNode* pos, SLDatatype x)
{
if (pos == *pphead)
{
SListPushfront(pphead, x);//如果此时链表为空,则直接用头插
}
else
{
SLTNode* newnode = BuySListNode(x);
SLTNode* prev = *pphead;
while (prev->next!=pos)
{
prev = prev->next;//找到pos前的那个节点
}
prev->next = newnode;
newnode->next = pos;
}
}
2.10在pos值之后插入一个数
//在pos值之后插入一个数
void SListInsertAfter(SLTNode** pphead, SLTNode* pos, SLDatatype x)
{
if (*pphead == NULL)
{
SListPushfront(pphead, x);//如果此时链表为空,则直接用头插
}
else
{
SLTNode* newnode = BuySListNode(x);
SLTNode* next = pos->next;
//顺序无所谓
pos->next = newnode;
newnode->next = next;
}
}
2.11删除链表中指定的某一个数
//删除链表中指定的某一个数
SLTNode* RemoveElements(SLTNode* head, int data)
{
SLTNode* prev = NULL;
SLTNode* tail = head;
while (tail)
{
if (tail->data == data)//如果此时的值正好是需要删除的数
{
if (prev == NULL)//如果第一个节点就是要删除的数字
{
head = tail->next;
}
else//需要删除的数在链表中,不是在开头
{
prev->next = tail->next;
}
}
else//如果此时的值不是需要删除的数
{
prev = tail;
}
tail = tail->next;
}
return head;
}
2.12链表销毁
//链表销毁
void SListDestroy(SLTNode** pphead)
{
assert(pphead);
SLTNode* cur = *pphead;
while (cur)
{
SLTNode* next = cur->next;
free(cur);
cur = next;
}
*pphead = NULL;
}
//遍历销毁,销毁前保留下一个节点的位置,然后将该节点销毁,再将cur给到下一个节点,再次销毁cur;
3.总结
单链表适合头插或者头删,不适合插入或者删除中间某一个值,因为需要从头开始遍历寻找,时间复杂度较高,而双向带头循环链表,是链表所有结构的最优解,后续会有介绍,同时也会更新链表相关的高频面试题。