文章目录
前言:
书接上文,顺序表的问题及思考:
- 中间或头部的插入删除,时间复杂度为O(N)
- 增容需要申请新空间,拷贝数据,释放旧空间。会有不小的消耗。
- 增容一般是呈2倍的增长,势必会有一定的空间浪费。例如当前容量为100,满了以后增容到200,我们再继续插入了5个数据,后面没有数据插入了,那么就浪费了95个数据空间。
为了更好的解决上述问题,本文我们来学习链表:
一.链表的概念及结构
1.何为链表
链表是一种物理存储结构上非连续、非顺序的存储结构,数据元素的逻辑顺序是通过链表中的指针链接次序实现的
如图为单链表的两种结构:
①物理结构
②逻辑结构
注意:
- 链表的链式结构在逻辑上是连续的,但在物理上不一定连续。
- 现实中的节点一般都是从堆上申请出来的。
- 从堆上申请的节点,是按照一定的策略来分配的,两次申请的空间可能连续,也可能不连续
2.链表常见的几种形式
实际中链表的结构非常多样,各种情况组合起来就有8种链表结构:
①单向或者双向
②带头或者不带头
③循环或者非循环
虽然有这么多的链表的结构,但是我们实际中最常用还是两种结构:
-
无头单向非循环链表
结构简单,一般不会单独用来存数据。实际中更多是作为其他数据结构的子结构,如哈希桶、图的邻接表等等。另外这种结构在笔试面试中出现很多。
-
带头双向循环链表
结构最复杂,一般用在单独存储数据。实际中使用的链表数据结构,都是带头双向循环链表。另外这个结构虽然结构复杂,但是使用代码实现以后会发现结构会带来很多优势,实现反而简单了,后面我们代码实现了就知道了。
二.无头单向非循环链表的实现
函数接口:
typedef int SLTDataType;
//定义节点
typedef struct SListNode
{
SLTDataType data; //存放数据
struct SListNade* next; //指向下一个节点
}SLTnode;
//申请节点
SLTnode* BuySLTNode(SLTDataType x);
//打印
void SLTPrint(SLTnode* phead);
//尾插
void SLTPushBack(SLTnode** pphead, SLTDataType x);
//头插
void SLTPushFront(SLTnode** pphead, SLTDataType x);
//尾删
void SLTPopBack(SLTnode** pphead);
//头删
void SLTPopFront(SLTnode** pphead);
//查找
SLTnode* SLTFind(SLTnode* phead, SLTDataType);
//在pos之前插入
void SLTInsert(SLTnode** pphead, SLTnode* pos, SLTDataType x);
//删除pos之前的节点
voide SLTErase(SLTnode** pphead, SLTnode* pos);
//在pos之后插入
void SLTInsertAftter(SLTnode* pos, SLTDataType x);
//删除pos之后的节点
void SLTEraseAfter(SLTnode* pos);
1.定义节点
//节点
typedef struct SListNode
{
SLTDataType data; //存放数据
struct SListNade* next; //指向下一个节点
}SLTnode;
- 链式结构由一个个的节点链接而成,节点中存放当前的数据data和指向下一个节点的指针。
- 链表正是有节点串联而成,所以只需记住排在最前面的头节点的位置,就能访问链表中的任意一个节点
注:这里的头节点指的是链表中第一个节点,它本身也会存储数据,而后面讲的带头双向循环链表中的头节点仅是链表起始的标志,并不会存储有效数据
2.创建一个新节点
后面我们要在单链表中进行插入和删除,此时的插入删除是针对节点进行的,这个结点是包括SLTDateType数据以及SLTDateType*的指针,因此,为了方便和减少代码的重复度,我们另写一个函数用来专门创建新结点。
SLTnode* BuySLTNode(SLTDataType x)
{
SLTnode* newnode = (SLTnode*)malloc(sizeof(SLTnode));
if (newnode == NULL)
{
perror("malloc");
return;
}
newnode->data = x;
newnode->next = NULL;
return newnode;
}
3.单链表打印
void SLTPrint(SLTnode* phead)
{
SLTnode* cur = phead; //cur指向phead存放的地址
while (cur != NULL) //不为空
{
printf("%d->", cur->data); //输出当前节点存的值
cur=cur->next; //该节点指向下一个节点
}
printf("NULL\n");
}
cur=cur->next; 这句话的含义是:
4.单链表尾插
//尾插
void SLTPushBack(SLTnode** pphead, SLTDataType x)
{
assert(pphead);
SLTnode* newnode = BuySLTNode(x);
if (*pphead == NULL) //空链表
{
*pphead = newnode;
}
else
{
//找到尾部
SLTnode* tail = *pphead;
while (tail->next != NULL)
{
tail = tail->next;
}
tail->next = newnode;
}
}
注意:
- 从代码中可以看出,我们使用了二级指针,这是因为我们要在原链表上做出修改,所以要传原链表头节点的地址,也就是一级指针的地址,要用二级指针来接收。
- 然后是找尾过程:
5.单链表尾删
//尾删
void SLTPopBack(SLTnode** pphead)
{
assert(pphead);
//1.检查是否为空
assert(*pphead);
//2.只有一个节点
if ((*pphead)->next == NULL)
{
free(*pphead);
*pphead = NULL;
}
else
{
SLTnode* tail = *pphead;
while (tail->next->next != NULL)
{
tail = tail->next;
}
free(tail->next);
tail->next = NULL;
}
}
注意:
- 链表为空时,没东西可删,要特判一下
- 链表只有一个节点时,删完就空了,也特别处理一下
- tail->next->next != NULL这句循环条件的含义是:
6.单链表头插
//头插
void SLTPushFront(SLTnode** pphead, SLTDataType x)
{
assert(pphead);
SLTnode* newnode = BuySLTNode(x);
newnode->next = *pphead;
*pphead = newnode;
}
如下图所示:
7.单链表头删
//头删
void SLTPopFront(SLTnode** pphead)
{
assert(pphead);
//1.检查是否为空
assert(*pphead);
SLTnode* first = *pphead;
*pphead = first->next;
free(first);
first = NULL;
}
先检查是否为空,如果不为空,则:
直接覆盖掉第一个节点以达到头删的效果。
8.单链表查找
//查找
SLTnode* SLTFind(SLTnode* phead, SLTDataType)
{
SLTnode* cur = phead;
while (cur)
{
if (cur->data == x) //找到了
{
return cur;
}
cur = cur->next;
}
return NULL; //没找到
}
9.在pos之前插入
//在pos之前插入
void SLTInsert(SLTnode** pphead, SLTnode* pos, SLTDataType x)
{
assert(pos); //pos这个节点存在
assert(pphead); //正确传值,不能为空
if (pos == *pphead) //pos在第一个节点处
{
SLTPushFront(pphead, x); //复用头插函数
}
else
{
//找到pos的前一个位置
SLTnode* prev = *pphead;
while (prev->next != pos)
{
prev = prev->next;
}
SLTnode* newnode = BuySLTNode(x);
prev->next = newnode;
newnode->next = pos;
}
}
10.在pos之前删除
//删除pos之前的节点
voide SLTErase(SLTnode** pphead, SLTnode* pos)
{
assert(pphead);
assert(pos);
if (*pphead == pos)
{
SLTPopFront(pphead);
}
else
{
//找到pos的前一个位置
SLTnode* prev = *pphead;
while (prev->next != pos)
{
prev = prev->next;
}
prev->next = pos->next; //覆盖掉pos
free(pos);
}
}
11.在pos之后插入
//在pos之后插入
void SLTInsertAftter(SLTnode* pos, SLTDataType x)
{
assert(pos);
SLTnode* newnode = BuySLTNode(x);
newnode->next = pos->next;
pos->next = newnode;
}
12.在pos之后删除
//删除pos之后的节点
void SLTEraseAfter(SLTnode* pos)
{
assert(pos);
assert(pos->next); //保证pos之后有节点
SLTnode* del = pos->next;
pos->next = del->next; //覆盖以删除
free(del);
del = NULL;
}
13.assert( )断言的使用
在上面代码中我们会发现,为什么有的代码需要断言指针,有的就不需要,有的要断言一级指针,有的要断言二级指针,也有的什么都不用断言,这是为什么呢?
- 首先,assert函数在调试的时候非常好用,一旦()内为假,会立刻报错,而且会帮我们提示报错在哪个文件和在哪行代码。
- 一级指针也就是phead,当链表为空的时候,phead就是为NULL,此时需要根据函数要实现的实际功能来确定是否断言。比如:打印函数SLTPrint(),当传来的指针为空时,说明链表中没有元素,但是没有元素也是可以打印的,如果断言就会报错。
- 二级指针永远指向phead,phead的地址是永远存在的,那么pphead就一定不可能为空,所以有**pphead的地方就需要断言pphead,这样可以及时发现在传参时传成空指针的错误。
三.带头双向循环链表的实现
1.函数接口:
typedef int LTDataType;
typedef struct ListNode
{
LTDataType data;
struct ListNode* next;
struct ListNode* prev;
}listnode;
//创建节点
listnode* Buylistnode(LTDataType x);
//双链表初始化
listnode* init();
//双向链表的打印
void listprint(listnode* phead);
//双向链表的尾插
void listpushback(listnode* phead, LTDataType x);
//双向链表的尾删
void listpopback(listnode* phead);
//双向链表的头插
void listpushfront(listnode* phead, LTDataType x);
//双向链表的头删
void listpopfront(listnode* phead);
//双向链表查找
listnode* listfind(listnode* phead, LTDataType x);
//销毁双向链表
void listdestroy(ListNode* phead);
//双向链表pos位置前面插入
void listinsert(listnode* pos, LTDataType x);
//双向链表删除pos处的节点
void listera
故名思意,带头双向循环链表新增加了哨兵位的头节点,并且链表首尾是循环链接的,严格来说并没有实际的头,所以我们将哨兵位的头节点当作头,方便函数接口的实现。
2.节点结构的定义
既然是双向循环链表,那就得要求每个结点既保存下一个结点的地址,又要保存上一个结点的地址。结构中,prev指向前一个结点,next指向后一个结点。
typedef int LTDataType;
typedef struct ListNode
{
LTDataType data;
struct ListNode* prev;//指向前一个结点
struct ListNode* next;//指向后一个结点
}LTNode;
四.源码
1.单链表
(1)SList.h
#pragma once
#include <stdio.h>
#include <stdlib.h>
#include <assert.h>
typedef int SLTDataType;
//定义节点
typedef struct SListNode
{
SLTDataType data; //存放数据
struct SListNade* next; //指向下一个节点
}SLTnode;
//申请节点
SLTnode* BuySLTNode(SLTDataType x);
//打印
void SLTPrint(SLTnode* phead);
//尾插
void SLTPushBack(SLTnode** pphead, SLTDataType x);
//头插
void SLTPushFront(SLTnode** pphead, SLTDataType x);
//尾删
void SLTPopBack(SLTnode** pphead);
//头删
void SLTPopFront(SLTnode** pphead);
//查找
SLTnode* SLTFind(SLTnode* phead, SLTDataType);
//在pos之前插入
void SLTInsert(SLTnode** pphead, SLTnode* pos, SLTDataType x);
//删除pos之前的节点
voide SLTErase(SLTnode** pphead, SLTnode* pos);
//在pos之后插入
void SLTInsertAftter(SLTnode* pos, SLTDataType x);
//删除pos之后的节点
void SLTEraseAfter(SLTnode* pos);
(2)SList.c
#define _CRT_SECURE_NO_WARNINGS 1
#include "SList.h"
//申请一个新节点
SLTnode* BuySLTNode(SLTDataType x)
{
SLTnode* newnode = (SLTnode*)malloc(sizeof(SLTnode));
if (newnode == NULL)
{
perror("malloc");
return;
}
newnode->data = x;
newnode->next = NULL;
return newnode;
}
//打印
void SLTPrint(SLTnode* phead)
{
SLTnode* cur = phead; //临时指针
while (cur != NULL) //不为空
{
printf("%d->", cur->data); //输出当前节点存的值
cur=cur->next; //该节点指向下一个节点
}
printf("NULL\n");
}
//尾插
void SLTPushBack(SLTnode** pphead, SLTDataType x)
{
assert(pphead);
SLTnode* newnode = BuySLTNode(x);
if (*pphead == NULL) //空链表
{
*pphead = newnode;
}
else
{
//找到尾部
SLTnode* tail = *pphead;
while (tail->next != NULL)
{
tail = tail->next;
}
tail->next = newnode;
}
}
//尾删
void SLTPopBack(SLTnode** pphead)
{
assert(pphead);
//1.检查是否为空
assert(*pphead);
//2.只有一个节点
if ((*pphead)->next == NULL)
{
free(*pphead);
*pphead = NULL;
}
else
{
SLTnode* tail = *pphead;
while (tail->next->next != NULL)
{
tail = tail->next;
}
free(tail->next);
tail->next = NULL;
}
}
//头插
void SLTPushFront(SLTnode** pphead, SLTDataType x)
{
assert(pphead);
SLTnode* newnode = BuySLTNode(x);
newnode->next = *pphead;
*pphead = newnode;
}
//头删
void SLTPopFront(SLTnode** pphead)
{
assert(pphead);
//1.检查是否为空
assert(*pphead);
SLTnode* first = *pphead;
*pphead = first->next;
free(first);
first = NULL;
}
//查找
SLTnode* SLTFind(SLTnode* phead, SLTDataType)
{
SLTnode* cur = phead;
while (cur)
{
if (cur->data == x) //找到了
{
return cur;
}
cur = cur->next;
}
return NULL; //没找到
}
//在pos之前插入
void SLTInsert(SLTnode** pphead, SLTnode* pos, SLTDataType x)
{
assert(pos); //pos这个节点存在
assert(pphead); //正确传值,不能为空
if (pos == *pphead) //pos在第一个节点处
{
SLTPushFront(pphead, x); //复用头插函数
}
else
{
//找到pos的前一个位置
SLTnode* prev = *pphead;
while (prev->next != pos)
{
prev = prev->next;
}
SLTnode* newnode = BuySLTNode(x);
prev->next = newnode;
newnode->next = pos;
}
}
//删除pos之前的节点
voide SLTErase(SLTnode** pphead, SLTnode* pos)
{
assert(pphead);
assert(pos);
if (*pphead == pos)
{
SLTPopFront(pphead);
}
else
{
//找到pos的前一个位置
SLTnode* prev = *pphead;
while (prev->next != pos)
{
prev = prev->next;
}
prev->next = pos->next; //覆盖掉pos
free(pos);
}
}
//在pos之后插入
void SLTInsertAftter(SLTnode* pos, SLTDataType x)
{
assert(pos);
SLTnode* newnode = BuySLTNode(x);
newnode->next = pos->next;
pos->next = newnode;
}
//删除pos之后的节点
void SLTEraseAfter(SLTnode* pos)
{
assert(pos);
assert(pos->next); //保证pos之后有节点
SLTnode* del = pos->next;
pos->next = del->next; //覆盖以删除
free(del);
del = NULL;
}
2.双链表
(1)list.h
#pragma once
#include <stdbool.h>
#include <stdio.h>
#include <assert.h>
typedef int LTDataType;
typedef struct ListNode
{
LTDataType data;
struct ListNode* next;
struct ListNode* prev;
}listnode;
//创建节点
listnode* Buylistnode(LTDataType x);
//双链表初始化
listnode* init();
//双向链表的打印
void listprint(listnode* phead);
//双向链表的尾插
void listpushback(listnode* phead, LTDataType x);
//双向链表的尾删
void listpopback(listnode* phead);
//双向链表的头插
void listpushfront(listnode* phead, LTDataType x);
//双向链表的头删
void listpopfront(listnode* phead);
//双向链表查找
listnode* listfind(listnode* phead, LTDataType x);
//销毁双向链表
void listdestroy(ListNode* phead);
//双向链表pos位置前面插入
void listinsert(listnode* pos, LTDataType x);
//双向链表删除pos处的节点
void listera
(2)list.c
#define _CRT_SECURE_NO_WARNINGS 1
#include "list.h"
//创建节点
listnode* Buylistnode(LTDataType x)
{
listnode* node = (listnode*)malloc(sizeof(listnode));
if ((node == NULL))
{
perror("malloc");
return NULL;
}
node->next = NULL;
node->prev = NULL;
node->data = x;
return node;
}
//双链表初始化
listnode* init()
{
listnode* phead = Buylistnode(-1);
//自己指向自己
phead->next = phead;
phead->prev = phead;
return phead;
}
//双向链表的打印
void listprint(listnode* phead)
{
//phead不能为空,至少有哨兵卫的头节点
assert(phead);
printf("<=head=>");
//从哨兵卫头节点下一个节点开始打印
listnode* cur = phead->next;
while (cur != phead)
{
printf("%d<=>",cur->next);
cur = cur->next;
}
printf("\n");
}
//双向链表的尾插
void listpushback(listnode* phead, LTDataType x)
{
assert(phead);
头节点指向的上一个节点就是尾节点
//listnode* newnode = Buylistnode(x);
//listnode* tail = phead->prev;
//tail->next = newnode;
//newnode->prev = tail;
//newnode->next = phead;
//phead->prev = newnode;
listinsert(phead, x);
}
//判断是否只有一个头节点
bool ltempty(listnode* phead)
{
assert(phead);
return phead->next == phead;
}
//双向链表的尾删
void listpopback(listnode* phead)
{
/*assert(phead);
assert(!ltempty(phead));
listnode* tail = phead->prev;
listnode* tailprev = tail->prev;
tailprev->next = phead;
phead->prev = tailprev;
free(tail);
tail = NULL;*/
listerase(phead->prev);
}
//双向链表的头插
void listpushfront(listnode* phead, LTDataType x)
{
assert(phead);
/*listnode* newnode = Buylistnode(x);
listnode* first = phead->next;
phead->next = newnode;
newnode->prev = phead;
newnode->next = first;
first->prev = newnode;*/
listinsert(phead->next, x);
}
//双向链表的头删
void listpopfront(listnode* phead)
{
/*assert(phead);
assert(!ltempty(phead));
listnode* first = phead->next;
listnode* firstnext = first->next;
firstnext->prev = phead;
phead->next = firstnext;
free(first);
first = NULL;*/
listerase(phead->next);
}
//双向链表查找
listnode* listfind(listnode* phead, LTDataType x)
{
assert(phead);
listnode* cur = phead->next;
while (cur != phead)
{
if (cur->data == x)
{
return cur;
}
cur = cur->next;
}
return NULL;
}
//双向链表pos位置前面插入
void listinsert(listnode* pos, LTDataType x)
{
assert(pos);
listnode* prev = pos->prev;
listnode* newnode = Buylistnode(x);
prev->next = newnode;
newnode->prev = prev;
newnode->next = pos;
pos->prev = newnode;
}
//双向链表删除pos处的节点
void listerase(listnode* pos)
{
assert(pos);
listnode* prev = pos->prev;
listnode* next = pos->next;
prev->next = next;
next->prev = prev;
free(pos);
pos = NULL;
}
//销毁双向链表
void listdestroy(ListNode* phead)
{
assert(phead);
ListNode* cur = phead->next;
while (cur!=phead)
{
listnode* next = cur->next;
free(cur);
cur = next;
}
free(phead);
phead = NULL;
}
本篇到此结束,码文不易,还请多多支持哦!!