更多精彩尽在微信公众号【程序猿声】
数据结构-线性表|顺序表|链表(中)
本节纲要
- 预备知识
- 顺序表(Sequential List)
- 单链表(Singly Linked List )
- 静态链表(Static list )
- 循环链表(circular linked list)
- 双向链表(doubly linked list)
03 单链表(Singly Linked List )
3.1 什么是单链表?
单链表是一种链式存储的结构。它动态的为节点分配存储单元。当有节点插入时,系统动态的为结点分配空间。在结点删除时,应该及时释放相应的存储单元,以防止内存泄露。由于是链式存储,所以操作单链表时,必须知道头结点或者头指针的位置。并且,在查找第i个节点时,必须找到第i-1个节点。
3.2 单链表的存储结构代码描述
对于链式存储,通过上一节的讲解相信大家已经了解得够清楚了。如下图所示:
下面我们来看看单链表存储是如何用代码来实现的。
//单链表的存储结构C语言代码
typedef struct SListNode
{
datatype data; //数据域
struct SListNode * pnext;//指针域
}SLinkList;
由上面的结构我们可以看出,一个节点由存放数据的数据域和存放地址的指针域组成。假如p指向了第i个节点,那么p->data就是该节点存放的数据,而p->pnext自然就是指向下一个节点的指针。如下图所示:
那么接下来我们看看单链表的各个操作具体实现吧。(只讲几个关键步骤)
备注:下面的代码基于这样的一个单链表:
- 有一个头指针phead
- 有一个头结点node
- 头指针指向头结点,头结点位置记为0
3.3 单链表的读取
在拿到头指针以后,单链表的读取也并非一件难事。一开始设置一个计数变量,不断遍历链表,让计数器自增。找到合适的位置将数据读取出来。具体代码实现如下:
#define status bool
#define ERROR false
#define OK true
/*
* 函数功能:获取位置index节点的数据
* 参数说明:phead链表头结点,e用来获取的变量,index索引
*/
status GetSListIndexNode(Node * phead,DType *e, int index)
{
int icount = 0; //计数器
//注:0号位为头结点,头结点不存放任何数据
if (phead->pnext == nullptr || index < 1 || index > GetSListLength()/*此处为链表长度*/)
{
return ERROR; //异常 处理
}
while (phead->pnext != nullptr)
{
icount++;
phead = phead->pnext;
if (icount == index)
{
*e = phead->data;
return OK;
}
}
return ERROR;
}
3.4 单链表的插入
3.4.1 指定位置后插
其实链表的插入和删除都是很简单的操作,初学者只要抓住指针指向的节点,并加以区分开来,就很easy了。如下图:
图中,假如此时p指向了我们要插入的节点的位置。那么,怎样把我们的S节点给插入到p指向的节点之后?在这里我们先不要惊动p以及p后面的节点:
- 我们先让S节点指向p之后的节点(步骤①)
- 之后我们切断p和p后面那个节点的关系(步骤②)
- 最后让p节点的指针域指向s节点(步骤③),搞定
算法描述:
- 声明一个指针p指向链表头结点,向后遍历p=p->next,找到正确的位置。
- 新建一个结点s。
- s->next = p->next ①
- p->next = s ②③
具体代码如下:
#define status bool
#define ERROR false
#define OK true
/*
* 函数功能:指定位置后插
* 参数说明:phead链表头结点,IData插入的数据,index索引
*/
status InsertSListNodeFront(Node * phead, DType IData, int index)
{
if (phead->pnext == nullptr || index < 1 || index > GetSListLength())
{
return ERROR; //异常 处理
}
int iCount = 0; //计数器
Node<DType> * q = nullptr; //备用
while (phead->pnext != nullptr)
{
iCount++;
q = phead;
phead = phead->pnext;
if ( iCount == index )
{
Node<DType> * p = new Node<DType>;
p->data = IData;
p->pnext = phead;
q->pnext = p; //前插
return OK;
}
}
return ERROR;
}
3.4.1 指定位置前插
咳咳,聪明的小伙伴,用脑子想想。指定位置前插 == 指定位置的前一个位置进行后插。懂了吧?直接看具体代码:
/*
* 函数功能:指定位置后插
* 参数说明:phead链表头结点,IData插入的数据,index索引
*/
status InsertSListNodeBack(Node * phead, DType IData, int index)
{
if (phead->pnext == nullptr || index < 1 || index > GetSListLength())
{
return ERROR; //异常 处理
}
int iCount = 0; //计数器
Node<DType> * q = nullptr; //备用
while (phead->pnext != nullptr)
{
iCount++;
q = phead;
phead = phead->pnext;
if (iCount == index)
{
Node<DType> * p = new Node<DType>;
q = phead;
phead = phead->pnext; //后插就是后一个节点的前插咯
p->data = IData;
p->pnext = phead;
q->pnext = p;
return OK;
}
}
return ERROR;
}
3.5 单链表的删除
单链表的删除其实也是很简单。只要比如要删除p指向的节点,只需要让p之前的节点的指针域直接指向p之后的节点,再把p给free就OK了。如下图:
算法描述:
- 声明一个指针p指向链表头结点,向后遍历p=p->next,找到要删除的节点位置。
- q = p->next
- p->next = q->next ①②
- free(q) ③④
具体代码如下:
/*
* 函数功能:指定位置后插
* 参数说明:phead链表头结点,IData获取删除的数据,index索引
*/
//删除指定位置节点(e获取删除元素)
template <typename DType>
status DeleteSListIndexNode(Node * phead, DType *e, int index)
{
int i = 0; //计数器
Node<DType> * q = nullptr;
if (phead->pnext == nullptr || index < 1 || index > GetSListLength())
{
return ERROR; //异常 处理
}
while (phead->pnext != nullptr)
{
i++;
q = phead; //保存备用
phead = phead->pnext;
if (i =