目录
前言
一、无头单向不循环链表的结构
二、各接口函数的实现
(1)插入函数
①头部插入函数(SListPushFront)
②尾部插入函数(SListPushBack)
③任意位置后面插入(SListInsertAfter)
(2)删除函数
①头部删除函数(SLitsPopFront)
②、尾部删除函数(SListPopBack)
③、任意位置后删除(SListEraseAfter)
(3)打印函数、查找函数、销毁函数及新结点的申请函数
①打印函数(SListPrint)
②查找函数(SListFind)
③购买新结点(BuySListNode)
④销毁函数(SListDestroy)
小结
链表是数据结构中不可或缺的一部分,其指的是物理存储单元上非连续、非顺序的存储结构,其链接关系通过指针来实现,形状如链子,故称为链表,这种数据结构在很多地方都有运用。
链表根据其特性有是否带头、是否循环、单向双向三种,分为8种类型,本文介绍的链表为无头单向不循环链表,是链表类型中结构最为简单的。但结构简单并不代表实现起来也是简单的,无头单向不循环链表这种结构常出现在OJ题中,或作为更复杂结构的子结构,如哈希桶等。
下面介绍其结构和各接口函数的实现思路及代码。
链表的基本结构如下图:
分析一个数据结构,一般我们都是通过研究其单一单元来进行分析,如上图所示,链表的单一单元包含了两大部分,分别为存储有效数据的数据域和存储下一地址的指针域,单一单元如下:
理解了链表的基本结构,下面我们来了解一下链表的三大特性:是否带头、是否循环、单向双向。
如图,带哨兵位的头结点,其数据域不存储有效数据;不带哨兵位的头结点,其本质就是一个指针,关于是否带头问题,此处不展开介绍。
②是否循环问题:
若一个链表是循环链表,其链表的tail将指向自身的head,可以无限循环下去;
若一个链表不是循环链表,则其tail将指向NULL。
③单向双向问题:
单向链表:只有一个指针域,指向下一个数据的地址;
双向链表:有两个指针域,一个指针指向上一个数据的地址,另一个指针指向下一个数据的地址。
介绍完三大特性之后,我们来进入本文正题:无头单向不循环链表的结构设计!
代码如下:
typedef struct SListNode//链表单个个体结构
{
SLTDateType data;//数据域
struct SListNode* next;//指针域
}SLTNode;
研究数据结构时一般需要实现初始化Init函数、销毁Destroy函数、插入数据函数(头部插入、尾部插入、任意位置插入)、删除数据函数(头部删除、尾部删除、任意位置删除)、查找函数、打印函数等等。下面对各函数的实现及代码进行简单介绍。
代码实现:
void SListPushFront(SLTNode** pphead, SLTDateType x)
{
SLTNode* newnode = BuySListNode(x);//新结点
newnode->next = *pphead;
*pphead = newnode;
}
实现思路:尾部插入需分情况讨论,当链表没有结点时,将新结点直接当成头结点插入即可;当链表有一个及以上结点时,则需要找到链表的尾结点,将新结点插入到尾结点后面成为新的尾。
代码实现:
void SListPushBack(SLTNode** pphead, SLTDateType x)
{
SLTNode* newnode = BuySListNode(x);//获得新结点
if (*pphead == NULL)//链表没有结点时,将新结点当成头结点
{
*pphead = newnode;
}
else//链表有一个及以上结点时,找到尾结点,在尾结点后插入新结点
{
//找尾
SLTNode* tail = *pphead;
while (tail->next != NULL)//遍历找尾
{
tail = tail->next;
}
//插入结点
tail->next = newnode;
}
}
实现思路:该函数根据提供的参数,在pos指针后插入数据,实现较简单,只需注意链接的先后顺序即可。
代码实现:
void SListInsertAfter(SLTNode* pos, SLTDateType x)
{
assert(pos);
SLTNode* newnode = BuySListNode(x);//新结点
newnode->next = pos->next;//新结点的next指向pos的next
pos->next = newnode;//pos的next指向新结点
}
实现思路:
如插入函数需要申请新结点一般,删除函数都需要考虑的是是否有结点可以删除,所以删除函数分两种情况,第一种情况是有结点删除,第二种情况是没有结点删除。
代码实现:
void SListPopFront(SLTNode** pphead)
{
assert(*pphead != NULL);//断言;没有结点删除直接退出
SLTNode* next = (*pphead)->next;//保存头结点的下一个结点地址,防止释放头结点后无法找到
free(*pphead);//释放头结点
*pphead = next;//将下一个结点设置为头结点
}
实现思路:同头部删除类似,也需分情况解决,但尾部删除不仅需要考虑没有结点的情况,也需要考虑只有一个结点的情况和两个及两个以上结点的情况。
代码实现:
void SListPopBack(SLTNode** pphead)
{
assert(*pphead != NULL);//断言;没有结点删除直接退出
SLTNode* tail = *pphead;//用于找到尾
if ((*pphead)->next == NULL)//只有一个结点
{
free(*pphead);
*pphead = NULL;
}
else//多个结点
{
while (tail->next->next != NULL)//找尾的前一个结点
{
tail = tail->next;
}
free(tail->next);//释放尾结点
tail->next = NULL;//让尾结点的前一个结点指向空,成为新的尾
}
}
实现思路:在指定地址后面删除结点,改变链接关系即可。
代码实现:
void SListEraseAfter(SLTNode* pos)
{
SLTNode* next = pos->next;//保存将要删除的结点,避免修改pos的指向后无法找到此结点
pos->next = next->next;//让pos指向将要删除的结点的下一个结点
free(next);//释放结点
next = NULL;
}
由于实现思路简单,只需控制指针遍历链表,打印数据域的数据即可,直接上代码。
void SListPrint(SLTNode* phead)
{
SLTNode* cur = phead;//一般不改变phead,所以用cur来控制遍历
while (cur)//cur为空时循环结束
{
printf("%d->", cur->data);//打印
cur = cur->next;//迭代
}
printf("NULL\n");
}
与打印函数类似,整体思路都是遍历链表,若找到数据与需查找的数据一致,则返回该结点的地址,否则返回NULL。
SLTNode* SListFind(SLTNode* phead, SLTDateType x)
{
SLTNode* cur = phead;//控制遍历
while (cur)
{
if (cur->data == x)//判断是否是查找的数据
{
return cur;
}
else
{
cur = cur->next;
}
}
return NULL;
}
SLTNode* BuySListNode(SLTDateType x)
{
SLTNode* newnode = (SLTNode*)malloc(sizeof(SLTNode));//申请结点
if (newnode == NULL)//判断申请是否成功
{
printf("malloc fail\n");
exit(-1);
}
newnode->data = x;//设置数据
newnode->next = NULL;
return newnode;
}
利用双指针迭代遍历,从头到尾释放每一个结点。
void SListDestroy(SLTNode** pphead)
{
SLTNode* next = (*pphead)->next;//保存下一个结点
while (next)
{
free(*pphead);//释放头结点
*pphead = next;//保存的结点成为新的头
next = next->next;//保存下一个结点
}
free(*pphead);//最后释放指针
*pphead = NULL;
}
本文介绍的链表结构虽然存在许多缺陷,如无法随机访问,已知一个结点无法快速得到其上一个结点的地址等等,这些都是无头单向不循环链表不实用的原因,但是其却是许多面试题中常考察的结构,其还是大部分高级结构的子结构,所以了解并熟悉它十分重要。
本次数据结构的介绍到此就告一段落了,希望能对大家有所帮助。
附整体代码。
SList.h
#pragma once
#include
#include
#include
typedef int SLTDateType;//链表存储数据的类型
typedef struct SListNode//链表单个个体结构
{
SLTDateType data;//数据域
struct SListNode* next;//指针域
}SLTNode;
//打印链表
void SListPrint(SLTNode* phead);
//销毁链表
void SListDestroy(SLTNode** pphead);
// 尾插 头插 尾删 头删
void SListPushBack(SLTNode** pphead,SLTDateType x);
void SListPushFront(SLTNode** pphead, SLTDateType x);
void SListPopBack(SLTNode** pphead);
void SListPopFront(SLTNode** pphead);
//查找
SLTNode* SListFind(SLTNode* phead,SLTDateType x);
//在pos位置之后插入
void SListInsertAfter(SLTNode* pos, SLTDateType x);
//在pos位置之后删除
void SListEraseAfter(SLTNode* pos);
//购买新结点
SLTNode* BuySListNode(SLTDateType x);
SList.c
#include"SList.h"
SLTNode* BuySListNode(SLTDateType x)
{
SLTNode* newnode = (SLTNode*)malloc(sizeof(SLTNode));//申请结点
if (newnode == NULL)//判断申请是否成功
{
printf("malloc fail\n");
exit(-1);
}
newnode->data = x;//设置数据
newnode->next = NULL;
return newnode;
}
void SListPrint(SLTNode* phead)
{
SLTNode* cur = phead;//一般不改变phead,所以用cur来控制遍历
while (cur)//cur为空时循环结束
{
printf("%d->", cur->data);//打印
cur = cur->next;//迭代
}
printf("NULL\n");
}
void SListPushBack(SLTNode** pphead, SLTDateType x)
{
SLTNode* newnode = BuySListNode(x);//获得新结点
if (*pphead == NULL)//链表没有结点时,将新结点当成头结点
{
*pphead = newnode;
}
else//链表有一个及以上结点时,找到尾结点,在尾结点后插入新结点
{
//找尾
SLTNode* tail = *pphead;
while (tail->next != NULL)//遍历找尾
{
tail = tail->next;
}
tail->next = newnode;
}
}
void SListPushFront(SLTNode** pphead, SLTDateType x)
{
SLTNode* newnode = BuySListNode(x);//新结点
newnode->next = *pphead;
*pphead = newnode;
}
void SListPopBack(SLTNode** pphead)
{
assert(*pphead != NULL);//断言;没有结点删除直接退出
SLTNode* tail = *pphead;//用于找到尾
if ((*pphead)->next == NULL)//只有一个结点
{
free(*pphead);
*pphead = NULL;
}
else//多个结点
{
while (tail->next->next != NULL)//找尾的前一个结点
{
tail = tail->next;
}
free(tail->next);//释放尾结点
tail->next = NULL;//让尾结点的前一个结点指向空,成为新的尾
}
}
void SListPopFront(SLTNode** pphead)
{
assert(*pphead != NULL);//断言;没有结点删除直接退出
SLTNode* next = (*pphead)->next;//保存头结点的下一个结点地址,防止释放头结点后无法找到
free(*pphead);//释放头结点
*pphead = next;//将下一个结点设置为头结点
}
SLTNode* SListFind(SLTNode* phead, SLTDateType x)
{
SLTNode* cur = phead;//控制遍历
while (cur)
{
if (cur->data == x)//判断是否是查找的数据
{
return cur;
}
else
{
cur = cur->next;
}
}
return NULL;
}
void SListInsertAfter(SLTNode* pos, SLTDateType x)
{
assert(pos);
SLTNode* newnode = BuySListNode(x);//新结点
newnode->next = pos->next;//新结点的next指向pos的next
pos->next = newnode;//pos的next指向新结点
}
void SListEraseAfter(SLTNode* pos)
{
SLTNode* next = pos->next;//保存将要删除的结点,避免修改pos的指向后无法找到此结点
pos->next = next->next;//让pos指向将要删除的结点的下一个结点
free(next);//释放结点
next = NULL;
}
void SListDestroy(SLTNode** pphead)
{
SLTNode* next = (*pphead)->next;//保存下一个结点
while (next)
{
free(*pphead);//释放头结点
*pphead = next;//保存的结点成为新的头
next = next->next;//保存下一个结点
}
free(*pphead);//最后释放指针
*pphead = NULL;
}