单链表操作
- 1. 链表的概念
- 2. 链表的分类
- 2.1.单向或者双向
- 2.2 带头或者不带头
- 2.3 循环或者非循环
- 2.4 常用的链表
- 3. 单链表的实现
- 3.1 单链表的打印
- 3.2 单链表的头插
- 3.3 单链表的尾插
- 3.4 单链表的头删
- 3.5 单链表的尾删
- 3.6 单链表的查询
- 3.7 在pos前插入数据
- 3.8 在pos后插入数据
- 3.9 删除pos位置数据
- 3.10 删除pos位置后的数据
1. 链表的概念
概念:链表是一种物理存储结构上非连续、非顺序的存储结构,数据元素的逻辑顺序是通过链表中的指针链接次序实现的 。
2. 链表的分类
实际中链表的结构非常多样,以下情况组合起来就有8种链表结构:
2.1.单向或者双向
2.2 带头或者不带头
2.3 循环或者非循环
2.4 常用的链表
虽然有这么多的链表的结构,但是我们实际中最常用还是两种结构:
- 无头单向非循环链表:结构简单,一般不会单独用来存数据。实际中更多是作为其他数据结构的子结构,如哈希桶、图的邻接表等等。另外这种结构在笔试面试中出现很多。
- 带头双向循环链表:结构最复杂,一般用在单独存储数据。实际中使用的链表数据结构,都是带头双向循环链表。另外这个结构虽然结构复杂,但是使用代码实现以后会发现结构会带来很多优势,实现反而简单了,后面我们代码实现了就知道了。
3. 单链表的实现
以上有这么多种类的链表,我们这里来实现单链表的操作:
#include<stdio.h>
#include<assert.h>
#include<stdlib.h>
typedef int SLTDataType;
//定义一个结构体
typedef struct SListNode
{
SLTDataType data;//数据域
struct SListNode* next;//指针域
}SLTNode;
//单链表的打印
void SLTPrint(SLTNode* phead);
//单链表的头插
void SLPushFront(SLTNode** pphead,SLTDataType x);
//单链表的尾插
void SLPushBack(SLTNode** pphead, SLTDataType x);
//单链表的头删
void SLPopFront(SLTNode** pphead);
//单链表的尾删
void SLPopBack(SLTNode** pphead);
//单链表的查询
SLTNode* STFind(SLTNode* phead,SLTDataType x);
//在pos前插入数据
void SLInsert(SLTNode** pphead, SLTNode* pos, SLTDataType x);
//在pos后插入数据
void SLInsertAfter(SLTNode* pos, SLTDataType x);
//删除pos位置数据
void SLErase(SLTNode** pphead, SLTNode* pos);
//删除pos位置后的数据
void SLEraseAfter(SLTNode** pphead, SLTNode* pos);
3.1 单链表的打印
void SLTPrint(SLTNode* phead)
{
SLTNode* cur = phead;
while (cur != NULL)
{
printf("%d->",cur->data);
cur = cur->next;
}
printf("NULL\n");
}
cur指针指向头结点,令cur->next走起来并打印data,直到cur->next指向空指针。
3.2 单链表的头插
SLTNode* BuyLTNode(SLTDataType x)
{
SLTNode* newnode = (SLTNode*)malloc(sizeof(SLTNode));
if (newnode == NULL)
{
perror("malloc");
return NULL;
}
newnode->data = x;
newnode->next = NULL;
return newnode;
}
void SLPushFront(SLTNode** pphead, SLTDataType x)
{
assert(pphead);
SLTNode* newnode = BuyLTNode(x);
newnode->next = *pphead;
*pphead = newnode;
}
因为插入时我们要先创建一个新的结点,为了减少代码的冗余,我们直接写了一个函数*BuyLTNode()*来创建新的结点。这里传参传的是二级指针,因为我们要改变的是头指针(本来头指针指向空,我们现在尾插了一个新结点,让头指针指向新结点),改变指针,我们就传指针的地址,所以用二级指针来接受。
3.3 单链表的尾插
void SLPushBack(SLTNode** pphead, SLTDataType x)
{
SLTNode* newnode = BuyLTNode(x);
//空链表
if (*pphead == NULL)
{
*pphead = newnode;
}
//非空链表
else
{
SLTNode* tail = *pphead;
while (tail->next != NULL)
{
tail = tail->next;
}
tail->next = newnode;
}
}
头插时应当判断链表是否为空。
3.4 单链表的头删
void SLPopFront(SLTNode** pphead)
{
assert(pphead);
//没有节点
assert(*pphead);
/*if ((*pphead)->next == NULL)
{
free(*pphead);
*pphead = NULL;
}
else
{*/
SLTNode* del = *pphead;
*pphead = del->next;
free(del);
//}
}
头删如果没有结点,我们直接报出错误。
3.5 单链表的尾删
void SLPopBack(SLTNode** pphead)
{
//没有节点
assert(*pphead);
/*if (*pphead == NULL)
{
return;
}*/
//一个节点
if ((*pphead)->next == NULL)
{
free(*pphead);
*pphead = NULL;
}
else
{
/*SLTNode* prev = NULL;
SLTNode* tail = *pphead;
while (tail->next)
{
prev = tail;
tail = tail->next;
}
free(tail);
prev->next = NULL;*/
SLTNode* tail = *pphead;
while (tail->next->next)
{
tail = tail->next;
}
free(tail->next);
tail->next = NULL;
}
}
尾删也一样,没有结点直接报错,有一个直接将头结点置为空。
3.6 单链表的查询
SLTNode* STFind(SLTNode* phead, SLTDataType x)
{
SLTNode* cur = phead;
while (cur)
{
if (cur->data == x)
{
return cur;
}
cur = cur->next;
}
return NULL;
}
查询没什么好说的,从头开始一个一个遍历,找到返回该指针,找不到返回空。
3.7 在pos前插入数据
void SLInsert(SLTNode** pphead, SLTNode* pos, SLTDataType x)
{
assert(pphead);
assert(pos);
if (*pphead == pos)
{
SLPushFront(pphead, x);
}
else
{
SLTNode* prev = *pphead;
while (prev->next != pos)
{
prev = prev->next;
}
SLTNode* newnode = BuyLTNode(x);
prev->next = newnode;
newnode->next = pos;
}
}
在pos前插入数据需要判断一下是否头指针指向的结构体是你需要插入的,如果是,直接头插。
3.8 在pos后插入数据
void SLInsertAfter(SLTNode* pos, SLTDataType x)
{
assert(pos);
SLTNode* newnode = BuyLTNode(x);
newnode->next = pos->next;
pos->next = newnode;
}
在pos前插入数据就没必要需要判断一下是否头指针指向的结构体是你需要插入的,即使是,也直接插入就行了。
3.9 删除pos位置数据
void SLErase(SLTNode** pphead, SLTNode* pos)
{
assert(pphead);
assert(pos);
if (pos == *pphead)
{
SLPopFront(pphead);
}
else
{
SLTNode* prev = *pphead;
while (prev->next != pos)
{
prev = prev->next;
}
prev->next = pos->next;
free(pos);
}
}
删除pos位置数据需要判断头是否是你要删除的,如果是,直接头删。
3.10 删除pos位置后的数据
void SLEraseAfter(SLTNode** pphead, SLTNode* pos)
{
assert(pos);
assert(pos->next);
SLTNode* after=pos->next;
pos->next = pos->next->next;
free(after);
}
删除pos位置后的数据也就不需要判断头是否是你要删除的了。
代码参考: https://gitee.com/yujin518/test_c/tree/master/test_3_13