前言
本次博客讲结合图例讲解单向不带头非循环链表
此后会讲解一些题目
1单链表的实现
1.1什么是单链表
我们先看数组,即顺序表的是什么样的,再看链表
1.2单链表的特点
实际中要实现的链表的结构非常多样,以下情况组合起来就有8种链表结构:
1. 单向、双向
2. 带头、不带头
3. 循环、非循环
我们今天可以讲解最复杂的情况单向不带头非循环链表
1. 无头单向非循环链表:结构简单,一般不会单独用来存数据。实际中更多是作为其他数据结 构的子结构,如哈希桶、图的邻接表等等。另外这种结构在笔试面试中出现很多。
2. 带头双向循环链表:结构最复杂,一般用在单独存储数据。实际中使用的链表数据结构,都 是带头双向循环链表。另外这个结构虽然结构复杂,但是使用代码实现以后会发现结构会带 来很多优势,实现反而简单了,后面我们代码实现了就知道了
链表的本质就是牺牲空间节省时间,它的访问速度快
1.2链表的实现
头文件部分
#pragma once
#define _SECURE_NO_WARNINGS
#include<stdio.h>
#include<stdlib.h>
#include<assert.h>
typedef int datatype;
struct slnode{
datatype data;
struct slnode* next;
};
void slinit(struct slnode**pphead);
void slprint(struct slnode* phead);
void slPushBack(struct slnode** pphead,datatype put);
void slPushfront(struct slnode** pphead, datatype put);
void slPopfront(struct slnode** pphead);
void slPopback(struct slnode** pphead);
struct slnode* slFind(struct slnode* phead,datatype n);
void slInsertfront(struct slnode**pphead, datatype find,datatype i);
void slErase(struct slnode** pphead, datatype find);
大家简略看看就好,后面还会再提到,主要关注链表的定义
接下来讲解如何实现该链表
初始化链表 slinit()
由于 没有哨兵位指针,所以它的初始化只需要让头指针为空即可
void slnodeinit(struct slnode **pphead)
{
*pphead=NULL;
}
扩展空间 struct slnode* buyslnode(datatype put)
如果要尾插或者头插一个链表,我们必须要动态开辟空间,才能增添一个结点
思路
首先他它有开辟一个空间,就必须要有一个指针记住该空间的地址
所以让该函数返回一个指针,注意在开辟成功后,必须要
让开辟的空间的next成员指向空指针,不然它就是一个野指针
struct slnode* buyslnode(datatype put)
{
struct slnode* newnode = (struct slnode*)malloc(sizeof(struct slnode));
if (newnode == NULL)
{
return NULL;
}
newnode->data = put;//插入元素的大小
newnode->next = NULL;//赋值w为空指针
return newnode;
}
头插void slPushfront(struct slnode** pphead, datatype put)
思路
如果一开始没有一个结点,也就是 phead此时为NULL
要让 *phead=buyslnode(put);
否则就是普通的头插,此时画图让大家理解头插
但此时 不管头结点是不是NULL指针都可以直接通过以下代码实现头插
void slPushfront(struct slnode** pphead, datatype put)
{
struct slnode* newnode = buyslnode(put);
newnode->next = *pphead;
*pphead = newnode;
}
尾插void slPushBack(struct slnode** pphead,datatype put)
思路
仍然考虑phead为NULL的情况,此时只要让newnode=*pphead即可
但是它是需要找到尾巴也就是最后一个非0结点
怎么找,看图
void slPushBack(struct slnode** pphead,datatype put)
{
struct slnode* newnode = buyslnode(put);
if (*pphead == NULL)
{
*pphead = newnode;
}
else
{
struct slnode* cur = *pphead;
while (cur->next)
{
cur = cur->next;
}
cur->next = newnode;
}
}
打印 void slprint(struct slnode* phead)
其实只需要遍历一遍即可
直接看代码
但是要注意这里是cur!=NULL才算遍历完
void slprint(struct slnode* phead)
{
struct slnode* cur = phead;
while (cur)
{
printf("%d->", cur->data);
cur = cur->next;
}
printf("NULL\n");
}
到这里应该要测试一遍了
看图
这里分别尾插1 2 3 4 5 最后头插一个 0 再把他们打印出来
尾删 void slPopback(struct slnode** pphead)
分析
首先要有结点才能删除,所以头结点可以通过assert判断
如果只有一个结点让头指针置空
如果有两个以上的结点那么就找到尾巴并且找到尾巴前一个结点让它置空,并删除尾结点
看图吧
看代码
void slPopback(struct slnode** pphead)
{
assert(*pphead);
struct slnode* cur = *pphead;
struct slnode* prev = *pphead;
if (cur->next == NULL)
{
free(*pphead);
pphead = NULL;
}
else
{
while (cur->next)
{
prev = cur;
cur = cur->next;
}
free(cur);
prev->next = NULL;
}
}
头删 void slPopfront(struct slnode** pphead)
分析
首先还是要有结点可删才可以删,所以还是要assert
其次还是这样,如果只有一个结点那就直接free掉
如果有两个以上的结点,还要找到下一个结点
看图
这里头删只要一个变量就可
void slPopfront(struct slnode** pphead)
{
assert(*pphead);
if ((*pphead)->next == NULL)
{
free(*pphead);
*pphead = NULL;
}
else
{
struct slnode*tem = (*pphead)->next;
free(*pphead);
*pphead = tem;
}
}
定位 struct slnode* slFind(struct slnode* phead, datatype n)
分析
遍历一遍 判断即可
struct slnode* slFind(struct slnode* phead, datatype n)
{
assert(phead);
while (phead)
{
if (phead->data== n)
{
return phead;
}
else
phead = phead->next;
}
printf("没有该数据\n");
return NULL;
}
在pos前插入 void slInsertfront(struct slnode**pphead, datatype find, datatype i)
分析
先找到该位置,如果没有找到位置,结束
找到该位置后
如果该位置是第一个位置就是头插
还得找到这个位置的前一个位置把他们相连
看图
void slInsertfront(struct slnode**pphead, datatype find, datatype i)
{
struct slnode* pfind=slFind(*pphead, find);
if (pfind == NULL)
return;
else
{
if (pfind == *pphead)
slPushfront(pphead, i);
else
{
struct slnode* cur = *pphead;
while (cur->next != pfind)
{
cur = cur->next;
}
cur->next = buyslnode(i);
cur->next->next = pfind;
}
}
}
删除该位置的数据 void slErase(struct slnode** pphead, datatype find)
分析
但凡要删除数据就必须要有数据,所以还是要assert一下头指针
如果是pos位置在头指针位置即为头删 如果是在最后一个结点即为尾删
如果在中间,就是必须要找到前一个和后一个结点
看图
看代码
void slErase(struct slnode** pphead, datatype find)
{
struct slnode* pfind = slFind(*pphead, find);
if (pfind == *pphead)
{
*pphead = pfind->next;
free(pfind);
}
else if(pfind->next==NULL)
{
slPopback(pphead);
}
else
{
struct slnode* cur = *pphead;
while (cur->next != pfind)
{
cur = cur->next;
}
cur->next = pfind->next;
free(pfind);
}
}
至此基本所有的功能都实现了
我们可以测试一下
看看首先 尾插 1 2 3 4 5然后头插一个0
此时链表为 0 1 2 3 4 5
然后再头删 尾删 链表为 1 2 3 4
再在2的位置前插入10,删除4这个结点
最终结果为 1 2 10 3 ok对上了
总结
到这里单链表的实现就完成了,还是多练
祝大家开心