目录
前言
链表的概念及结构
链表的分类
单链表的实现
接口实现
1.结构体
2.创建一个新结点
3.打印链表数据
4.尾插数据
5.尾删数据
6.头插数据
7.头删数据
8.任意位置删除
9.查找位置
10.pos之前插入
11.pos之后插入
12.释放内存
完整源码
总结
前言
在我们学习了顺序表之后,我们发现了顺序表有很多的不足之处,例如顺序表在空间不足之后需要扩容,扩容会对空间造成浪费,并且当需要头部或者中部操作数据时,顺序表必须进行挪动数据,挪动数据会造成浪费。
链表的概念及结构
概念:链表是一种物理存储结构上非连续、非顺序的存储结构,数据元素的逻辑顺序是通过链表中的指针链接次序实现的 。
链表就像一辆火车一样,一节车厢接着一节车厢,这每一节车厢都可以被看做是一个结点,每一个结点链接在一起就构成了链表。
链表中在每一个结点都是一个结构体,结构体中分为数据域和指着域,就是一个结点同时存储数据和下一个结点的地址,这样我们才能找到下一个结点。
虽然我们在逻辑上看到一个结点是接着一个结点的,但是在物理上他们不一定是连续的,只是在结点中存储下一个结点的地址,所以才看上去是连续的。
链表的分类
根据链表结构的不同,我们可以将链表分为八类,分别从以下三个方面进行区分。
1.带头或不带头
2.循环或不循环
3.双向或单向
我们这篇文章主要来探究不带头单向不循环链表,也就是我们最常见到的单链表,还有就是结构复杂但是效率更高的带头循环双向链表。
1. 无头单向非循环链表:结构简单,一般不会单独用来存数据。实际中更多是作为其他数据结构的子结构,如哈希桶、图的邻接表等等。另外这种结构在笔试面试中出现很多。2. 带头双向循环链表:结构最复杂,一般用在单独存储数据。实际中使用的链表数据结构,都是带头双向循环链表。另外这个结构虽然结构复杂,但是使用代码实现以后会发现结构会带来很多优势,实现反而简单了,后面我们代码实现了就知道了。
单链表的实现
接口实现
1.结构体
定义结构体来存储当前结点数据以及下一个结点的地址。
typedef int ElementType;
typedef struct SLTNode
{
ElementType date;
struct SLTNode* next;
}SLTNode;
2.创建一个新结点
使用malloc创建一个新结点,但是切记最后必须free掉。
SLTNode* BuyNewNode(ElementType x)
{
SLTNode* newNode = (SLTNode*)malloc(sizeof(SLTNode));
if (newNode == NULL)
{
perror("malloc");
exit(-1);
}
newNode->date = x;
newNode->next = NULL;
return newNode;
}
3.打印链表数据
直接顺序打印,直到遍历到空为止。
void SListPrint(SLTNode* phead)
{
SLTNode* cur = phead;
while (cur)
{
printf("%d->", cur->date);
cur = cur->next;
}
printf("NULL\n");
}
4.尾插数据
当链表为空时,直接令头结点等于新结点就好,当链表不为空时,我们要通过遍历来找尾,当cur的next为空时找到尾,插入到尾的后边。
void SListPushBack(SLTNode** pphead, ElementType x)
{
assert(pphead);
SLTNode* newNode = BuyNewNode(x);
if (*pphead == NULL)
{
*pphead = newNode;
}
else
{
SLTNode* cur = *pphead;
while (cur->next!=NULL)
{
cur = cur->next;
}
cur->next = newNode;
}
}
5.尾删数据
当只有一个结点时,我们将这个结点释放掉就好了,如果有多个结点,还是先来找尾,但是切记要保存上一个结点的地址,我们free掉尾结点后,还要将尾的上一个结点的next置为空,否则就会造成野指针。
void SListPopBack(SLTNode** pphead)
{
assert(*pphead);
if ((*pphead)->next== NULL)
{
free(*pphead);
}
else
{
SLTNode* cur = *pphead, * prev = NULL;
while (cur->next)
{
prev = cur;
cur = cur->next;
}
free(cur);
prev->next = NULL;
}
}
6.头插数据
只需将新结点的next指向原本的头结点,再将头结点变为新结点。
void SListPushFront(SLTNode** pphead, ElementType x)
{
SLTNode* newNode = BuyNewNode(x);
newNode->next = (*pphead);
*pphead = newNode;
}
7.头删数据
保存头结点的next,free头结点,将新的头指向保存的next。
void SListPopFront(SLTNode** pphead)
{
assert(*pphead);
SLTNode* next = (*pphead)->next;
free(*pphead);
*pphead = next;
}
8.任意位置删除
当pos就是头结点时,只需头删就好了,如果pos不是头,就逐一向后遍历,要记录pos位置的前一个结点,方便删除pos位置结点。
void SListDel(SLTNode** pphead,SLTNode* pos)
{
assert(pphead && (*pphead));
assert(pos);
if ((*pphead)==pos)
{
SListPopFront(pphead);
}
else
{
SLTNode* cur = *pphead;
while (cur->next != pos)
{
cur = cur->next;
}
cur->next = pos->next;
free(pos);
}
}
9.查找位置
使用数据来查找,逐一向后遍历,如果相等,返回该结点。
SLTNode* SListFind(SLTNode** pphead,ElementType x)
{
assert(pphead && *pphead);
SLTNode* cur = *pphead;
while (cur->date != x)
{
cur = cur->next;
}
return cur;
}
10.pos之前插入
在pos位置之前插入必须要保存上一个结点prev,当查找到pos位置时,只需要将新结点的next指向pos,prev的next指向新结点。
void SListInsert(SLTNode** pphead, SLTNode* pos, ElementType x)
{
assert(pphead && pos);
SLTNode* newNode = BuyNewNode(x);
if ((*pphead)== pos)
{
newNode->next = (*pphead);
*pphead = newNode;
}
else
{
SLTNode* cur = *pphead, * prve = NULL;
while (cur->next != pos)
{
cur = cur->next;
}
newNode->next = pos;
cur->next = newNode;
}
}
11.pos之后插入
直接插入,但是要记得先连接后边再连接前边。
void SListInsertAfter(SLTNode** pphead, SLTNode* pos, ElementType x)
{
assert(pphead && pos);
SLTNode* newNode = BuyNewNode(x);
newNode->next = pos->next;
pos->next = newNode;
}
12.释放内存
void SListDestory(SLTNode** pphead)
{
assert(*pphead);
SLTNode* cur = *pphead;
while (cur)
{
SLTNode* next = cur->next;
free(cur);
cur = next;
}
*pphead = NULL;
printf("内存释放完毕\n");
}
完整源码
slist.h
#define _CRT_SECURE_NO_WARNINGS 1
#include<stdio.h>
#include<assert.h>
typedef int ElementType;
typedef struct SLTNode
{
ElementType date;
struct SLTNode* next;
}SLTNode;
//创建新节点
SLTNode* BuyNewNode(ElementType x);
//打印链表
void SListPrint(SLTNode* phead);
//尾插
void SListPushBack(SLTNode** pphead, ElementType x);
//尾删
void SListPopBack(SLTNode** pphead);
//头删
void SListPopFront(SLTNode** pphead);
//头插
void SListPushFront(SLTNode** pphead, ElementType x);
//pos位置删除
void SListDel(SLTNode** pphead,SLTNode* pos);
//查找位置
SLTNode* SListFind(SLTNode** pphead, ElementType x);
//之前插入数据
void SListInsert(SLTNode** pphead, SLTNode* pos, ElementType x);
//之后插入数据
void SListInsertAfter(SLTNode** pphead, SLTNode* pos, ElementType x);
//释放内存
void SListDestory(SLTNode** pphead);
slist.c
#define _CRT_SECURE_NO_WARNINGS 1
#include"slist.h"
SLTNode* BuyNewNode(ElementType x)
{
SLTNode* newNode = (SLTNode*)malloc(sizeof(SLTNode));
if (newNode == NULL)
{
perror("malloc");
exit(-1);
}
newNode->date = x;
newNode->next = NULL;
return newNode;
}
void SListPrint(SLTNode* phead)
{
SLTNode* cur = phead;
while (cur)
{
printf("%d->", cur->date);
cur = cur->next;
}
printf("NULL\n");
}
//尾插
void SListPushBack(SLTNode** pphead, ElementType x)
{
assert(pphead);
SLTNode* newNode = BuyNewNode(x);
if (*pphead == NULL)
{
*pphead = newNode;
}
else
{
SLTNode* cur = *pphead;
while (cur->next!=NULL)
{
cur = cur->next;
}
cur->next = newNode;
}
}
//尾删
void SListPopBack(SLTNode** pphead)
{
assert(*pphead);
if ((*pphead)->next== NULL)
{
free(*pphead);
}
else
{
SLTNode* cur = *pphead, * prev = NULL;
while (cur->next)
{
prev = cur;
cur = cur->next;
}
free(cur);
prev->next = NULL;
}
}
//头插
void SListPushFront(SLTNode** pphead, ElementType x)
{
SLTNode* newNode = BuyNewNode(x);
newNode->next = (*pphead);
*pphead = newNode;
}
//头删
void SListPopFront(SLTNode** pphead)
{
assert(*pphead);
SLTNode* next = (*pphead)->next;
free(*pphead);
*pphead = next;
}
//在任意位置删除
void SListDel(SLTNode** pphead,SLTNode* pos)
{
assert(pphead && (*pphead));
assert(pos);
if ((*pphead)==pos)
{
SListPopFront(pphead);
}
else
{
SLTNode* cur = *pphead;
while (cur->next != pos)
{
cur = cur->next;
}
cur->next = pos->next;
free(pos);
}
}
//查找位置
SLTNode* SListFind(SLTNode** pphead,ElementType x)
{
assert(pphead && *pphead);
SLTNode* cur = *pphead;
while (cur->date != x)
{
cur = cur->next;
}
return cur;
}
//pos之前插入
void SListInsert(SLTNode** pphead, SLTNode* pos, ElementType x)
{
assert(pphead && pos);
SLTNode* newNode = BuyNewNode(x);
if ((*pphead)== pos)
{
newNode->next = (*pphead);
*pphead = newNode;
}
else
{
SLTNode* cur = *pphead, * prve = NULL;
while (cur->next != pos)
{
cur = cur->next;
}
newNode->next = pos;
cur->next = newNode;
}
}
//pos之后插入
void SListInsertAfter(SLTNode** pphead, SLTNode* pos, ElementType x)
{
assert(pphead && pos);
SLTNode* newNode = BuyNewNode(x);
newNode->next = pos->next;
pos->next = newNode;
}
//释放内存
void SListDestory(SLTNode** pphead)
{
assert(*pphead);
SLTNode* cur = *pphead;
while (cur)
{
SLTNode* next = cur->next;
free(cur);
cur = next;
}
*pphead = NULL;
printf("内存释放完毕\n");
}
总结
今天主要学习了单链表的原理以及实现,希望可以帮到大家。