一.链表的概念及结构
1.1 链表的概念
概念:链表是一种物理存储结构上非连续、非顺序的存储结构,数据元素的逻辑顺序是通过链表中的指针链接次序实现的。
在这里我们 画个图来理解一下:
1的位置是当前链表的起始位置,它里面除了有段区域放数据外还有一个区域放着的是 2 的地址,而 2 同理,里面存放数据的同时,存放 3 的地址。
1.2 链表节点
我们组成链表的点叫做 链表的节点,将 节点中 存放地址的区域叫做 指针域,将存放数据的区域叫做 数据域。
同时我们也可以得到一个最最简单的 链表节点 的结构:
#include<stdio.h>
#include<stdlib.h>
#include<assert.h>
typedef int SLTDataType;//这样更容易修改数据类型,便于构建存储不同数据类型的链表
typedef struct SListNode
{
SLTDataType data;
struct SListNode* next;
}SLTNode;
此时我们的节点结构为:
1.3 链表结构
链表:由节点构成的线状结构,我们需要操纵节点来完成链表。
在这里我们要完成链表的四种基本操作:增 删 查 改 。
二.链表的实现(以单链表为例)
2.1 SListNode.h 文件(在此文件中声明函数)
大致我们要完成以下几个函数
//构建节点函数
SLTNode* BuyLTNode(SLTDataType x);
//打印单链表函数
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* pos);
此时文件全部内容为:
#pragma once
#include<stdio.h>
#include<stdlib.h>
#include<assert.h>
typedef int SLTDataType;//这样更容易修改数据类型,便于构建存储不同数据类型的链表
typedef struct SListNode
{
SLTDataType data;
struct SListNode* next;
}SLTNode;
//构建节点函数
SLTNode* BuyLTNode(SLTDataType x);
//打印单链表函数
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* pos);
2.2 SListNode.c 文件(在此文件中定义函数)
2.2.1 SLTNode* BuyLTNode(SLTDataType x) 函数
实现内容为:
SLTNode* BuyLTNode(SLTDataType x)
{
SLTNode* newnode = (SLTNode*)malloc(sizeof(SLTNode));
if (newnode == NULL)//判断一下是否创建失败
{
perror("malloc fail");
return NULL;
}
newnode->data = x;
newnode->next = NULL;
return newnode;
}
2.2.2 void SLTPrint(SLTNode* phead) 函数
void SLTPrint(SLTNode* phead)
{
SLTNode* cur = phead;
while (cur != NULL)
{
printf("%d->", cur->data);
cur = cur->next;
}
printf("NULL\n");
}
2.2.3 void SLPushFront(SLTNode** pphead, SLTDataType x) 函数
void SLPushFront(SLTNode** pphead, SLTDataType x)
//传二级指针是因为要在外面定义一个一级指针 要想在函数内部改变一级指针的话就必须传一级指针的地址 也就是二级指针
//如果不想传二级指针的话 返回一个值也行
{
assert(pphead); // 链表为空,pphead也不为空,因为他是头指针plist的地址
//assert(*pphead); // 不能断言,链表为空,也需要能插入
SLTNode* newnode = BuyLTNode(x);
newnode->next = *pphead;
*pphead = newnode;
}
2.2.4 void SLPushBack(SLTNode** pphead, SLTDataType x) 函数
void SLPushBack(SLTNode** pphead, SLTDataType x)
{
assert(pphead); // 链表为空,pphead也不为空,因为他是头指针plist的地址
//assert(*pphead); // 链表为空,可以尾插
SLTNode* newnode = BuyLTNode(x);
//这里需要分两种情况
// 1、空链表 直接替换
// 2、非空链表 遍历找尾 , 在尾的下一个插入
if (*pphead == NULL)
{
*pphead = newnode;
}
else
{
SLTNode* tail = *pphead;
while (tail->next != NULL)
{
tail = tail->next;
}
tail->next = newnode;
}
}
2.2.5 void SLPopFront(SLTNode** pphead) 函数
void SLPopFront(SLTNode** pphead)
{
assert(pphead); // 链表为空,pphead也不为空,因为他是头指针plist的地址
assert(*pphead); // 链表为空,不能头删。(当然你还可以用温柔的检查)
//温柔的检查
/*if (*pphead == NULL)
{
printf("链表为空,不能头删\n");
return;
}*/
SLTNode* del = *pphead;
*pphead = (*pphead)->next;
free(del);
}
2.2.6 void SLPopBack(SLTNode** pphead) 函数
void SLPopBack(SLTNode** pphead)
{
assert(pphead); // 链表为空,pphead也不为空,因为他是头指针plist的地址
assert(*pphead); // 链表为空,不能头删。(当然你还可以用温柔的检查)
// 温柔的检查
/*if (*pphead == NULL)
{
return;
}*/
// 一个节点 直接free
// 多个节点 遍历找尾, free尾
if ((*pphead)->next == NULL)
{
free(*pphead);
*pphead = NULL;
}
else
{
SLTNode* tail = *pphead;
// 找尾
while (tail->next->next)
{
tail = tail->next;
}
free(tail->next);
tail->next = NULL;
}
}
2.2.7 SLTNode* STFind(SLTNode* phead, SLTDataType x)函数
SLTNode* STFind(SLTNode* phead, SLTDataType x)
{
SLTNode* cur = phead;
while (cur)
{
if (cur->data == x)
{
return cur;
}
cur = cur->next;
}
return NULL;
}
2.2.8 void SLInsert(SLTNode** pphead, SLTNode* pos, SLTDataType x) 函数
void SLInsert(SLTNode** pphead, SLTNode* pos, SLTDataType x)
{
assert(pphead);
assert(pos);
//assert(*pphead);
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;
}
}
2.2.9 void SLInsertAfter(SLTNode* pos, SLTDataType x) 函数
void SLInsertAfter(SLTNode* pos, SLTDataType x)
{
assert(pos);
SLTNode* newnode = BuyLTNode(x);
newnode->next = pos->next;
pos->next = newnode;
}
2.2.10 void SLErase(SLTNode** pphead, SLTNode* 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);
}
}
2.2.11 void SLEraseAfter(SLTNode* pos) 函数
void SLEraseAfter(SLTNode* pos)
{
assert(pos);
assert(pos->next);
SLTNode* next = pos->next;
pos->next = next->next;
free(next);
}
此时文件完整内容为:
#include"SlistNode.h"
void SLTPrint(SLTNode* phead)
{
SLTNode* cur = phead;
while (cur != NULL)
{
printf("%d->", cur->data);
cur = cur->next;
}
printf("NULL\n");
}
SLTNode* BuyLTNode(SLTDataType x)
{
SLTNode* newnode = (SLTNode*)malloc(sizeof(SLTNode));
if (newnode == NULL)//判断一下是否创建失败
{
perror("malloc fail");
return NULL;
}
newnode->data = x;
newnode->next = NULL;
return newnode;
}
void SLPushFront(SLTNode** pphead, SLTDataType x)
//传二级指针是因为要在外面定义一个一级指针 要想在函数内部改变一级指针的话就必须传一级指针的地址 也就是二级指针
//如果不想传二级指针的话 返回一个值也行
{
assert(pphead); // 链表为空,pphead也不为空,因为他是头指针plist的地址
//assert(*pphead); // 不能断言,链表为空,也需要能插入
SLTNode* newnode = BuyLTNode(x);
newnode->next = *pphead;
*pphead = newnode;
}
//void SLPushBack(SLTNode* phead, SLTDataType x)
//{
// SLTNode* tail = phead;
// while (tail != NULL)
// {
// tail = tail->next;
// }
//
// SLTNode* newnode = BuyLTNode(x);
// tail = newnode;
//}
void SLPushBack(SLTNode** pphead, SLTDataType x)
{
assert(pphead); // 链表为空,pphead也不为空,因为他是头指针plist的地址
//assert(*pphead); // 链表为空,可以尾插
SLTNode* newnode = BuyLTNode(x);
//这里需要分两种情况
// 1、空链表 直接替换
// 2、非空链表 遍历找尾 , 在尾的下一个替换
if (*pphead == NULL)
{
*pphead = newnode;
}
else
{
SLTNode* tail = *pphead;
while (tail->next != NULL)
{
tail = tail->next;
}
tail->next = newnode;
}
}
void SLPopFront(SLTNode** pphead)
{
assert(pphead); // 链表为空,pphead也不为空,因为他是头指针plist的地址
assert(*pphead); // 链表为空,不能头删。(当然你还可以用温柔的检查)
//温柔的检查
/*if (*pphead == NULL)
{
printf("链表为空,不能头删\n");
return;
}*/
SLTNode* del = *pphead;
*pphead = (*pphead)->next;
free(del);
}
void SLPopBack(SLTNode** pphead)
{
assert(pphead); // 链表为空,pphead也不为空,因为他是头指针plist的地址
assert(*pphead); // 链表为空,不能头删。(当然你还可以用温柔的检查)
// 温柔的检查
/*if (*pphead == NULL)
{
return;
}*/
// 一个节点 直接free
// 多个节点 遍历找尾, free尾
if ((*pphead)->next == NULL)
{
free(*pphead);
*pphead = NULL;
}
else
{
SLTNode* tail = *pphead;
// 找尾
while (tail->next->next)
{
tail = tail->next;
}
free(tail->next);
tail->next = NULL;
}
}
SLTNode* STFind(SLTNode* phead, SLTDataType x)
{
SLTNode* cur = phead;
while (cur)
{
if (cur->data == x)
{
return cur;
}
cur = cur->next;
}
return NULL;
}
// 在pos之前插入
void SLInsert(SLTNode** pphead, SLTNode* pos, SLTDataType x)
{
assert(pphead);
assert(pos);
//assert(*pphead);
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之前插入
void SLInsertAfter(SLTNode* pos, SLTDataType x)
{
assert(pos);
SLTNode* newnode = BuyLTNode(x);
newnode->next = pos->next;
pos->next = newnode;
}
// 删除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);
}
}
void SLEraseAfter(SLTNode* pos)
{
assert(pos);
assert(pos->next);
SLTNode* next = pos->next;
pos->next = next->next;
free(next);
}
三.链表的其它类型
其实链表大抵上有八种类型:
1. 单向或者双向
2.带头或者不带头
3.循环或者非循环
虽然有这么多的链表的结构,但是我们实际中最常用还是两种结构:
1. 无头单向非循环链表:结构简单,一般不会单独用来存数据。实际中更多是作为其他数据结构的子结构,如哈希桶、图的邻接表等等。另外这种结构在笔试面试中出现很多。
2. 带头双向循环链表:结构最复杂,一般用在单独存储数据。实际中使用的链表数据结构,都是带头双向循环链表。另外这个结构虽然结构复杂,但是使用代码实现以后会发现结构会带来很多优势,实现反而简单了,后面我们代码实现了就知道
四.双向链表
typedef int LDateType;
//带头 + 双向 + 循环链表增删查改实现
typedef int LTDataType;
typedef struct ListNode
{
LTDataType _data;
struct ListNode* _next;
struct ListNode* _prev;
}ListNode;
//创建新节点
ListNode* BuyLTNode(LTDataType x);
// 创建返回链表的头结点.
ListNode* ListCreate();
// 双向链表销毁
void ListDestory(ListNode* pHead);
// 双向链表打印
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);
// 双向链表在pos的前面进行插入
void ListInsert(ListNode* pos, LTDataType x);
// 双向链表删除pos位置的节点
void ListErase(ListNode* pos);
//判断空
bool LTEmpty(ListNode* phead);
双向链表实现简单,我们不再多写,请大家自己探索。