目录
1.链表的概念及结构
概念:链表是一种物理存储结构上非连续、非顺序的存储结构,但是逻辑顺序是连续的存储结构。数据元素的逻辑顺序是通过链表中的指针链接次序实现的。
链表的结构跟火车车厢相似,只需要将火车里的某节车厢去掉/加上,不会影响其他车厢,每节车厢都是独立存在的。
与顺序表不同的是,链表里的每节“车厢”都是独立申请下来的空间,称之为“节点/结点”。节点的组成主要有两个部分:当前节点要保存的数据和下一个节点的地址(指针变量)。
图中指针变量plist保存的是第一个节点的地址,我们称plist此时“指向”第一个节点,如果我们希望plist“指向”第二个节点时,只需要修改plist保存的内容为0x0012FFA0.
链表中每个节点都是独立申请的(需要插入数据时才去申请一块节点的空间),我们需要通过指针变量来保存下一个节点位置才能从当前节点找到下一个节点。
假设当前保存的节点为整形,则链表的节点结构如下:
当我们想要保存⼀个整型数据时,实际是向操作系统申请了⼀块内存,这个内存不仅要保存整型数据,也需要保存下⼀个节点的地址(当下⼀个节点为空时保存的地址为空)。
当我们想要从第⼀个节点⾛到最后⼀个节点时,只需要在前⼀个节点拿上下⼀个节点的地址(下⼀个节点的钥匙)就可以了。
补充说明:
1.链式机构在逻辑上是连续的,在物理结构上不一定连续。
2.节点一般是从堆上申请的。
3.从堆上申请来的空间,是按照一定策略分配出来的,每次申请的空间可能连续,可能不连续。
2.链表的分类
链表的结构非常多样,以下情况组合起来就有8种(2*2*2)链表结构:
虽然有这么多的链表的结构,但是我们实际中最常用的还是两种结构:单链表和双向带头链表。
1.无头单向非循环链表(单链表):结构简单,⼀般不会单独⽤来存数据。实际中更多是作为其他数据结构的⼦结构,如哈希桶、图的邻接表等等。
2.带头双向循环链表:结构最复杂,⼀般⽤在单独存储数据。实际中使⽤的链表数据结构,都是带头双向循环链表。另外这个结构虽然结构复杂,但是使⽤代码实现以后会发现结构会带来很多优势,实现反⽽简单了。
3.实现单链表
3.1头文件SList.h
头文件主要是包含整个项目所用到的其他头文件以及声明函数。
#pragma once
#include <stdio.h>
#include <stdlib.h>
typedef int SLTDataType;
typedef struct SListNode //链表节点结构
{
SLTDataType val;
struct SListNode* next;
}SLTNode;
//创建节点
SLTNode* BuyNode(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 x);
//在指定位置之前插入数据
void SLTInsert(SLTNode** pphead, SLTNode* pos, SLTDataType x);
//删除pos节点
void SLTErase(SLTNode** pphead, SLTNode* pos);
//在指定位置之后插入数据
void SLTInsertAfter(SLTNode* pos, SLTDataType x);
//删除pos之后的节点
void SLTEraseAfter(SLTNode* pos);
//销毁链表
void SListDesTroy(SLTNode** pphead);
3.2相关函数实现文件SList.c
3.2.1创建节点和打印链表
3.2.1.1创建节点
函数实现:
SLTNode* BuyNode(SLTDataType x)
{
SLTNode* newNode = (SLTNode*)malloc(sizeof(SLTNode)); //申请一个节点的大小
if (newNode == NULL)
{
perror("malloc fail!\n");
exit(1);
}
newNode->val = x; //将数据放入新申请的节点中
newNode->next = NULL; //将新节点的next指针初始化指向NULL
return newNode;
}
3.2.1.2打印链表函数
函数实现:
void SLTPrint(SLTNode* phead)
{
SLTNode* cur = phead;
while (cur)
{
printf("%d->", cur->val);
cur = cur->next;
}
printf("NULL\n");
}
测试代码:
#include "SList.h"
void test()
{
SLTNode* node1 = BuyNode(1);
SLTNode* node2 = BuyNode(2);
SLTNode* node3 = BuyNode(3);
SLTNode* node4 = BuyNode(4);
SLTNode* node5 = BuyNode(5);
SLTNode* node6 = BuyNode(6);
node1->next = node2;
node2->next = node3;
node3->next = node4;
node4->next = node5;
node5->next = node6;
SLTNode* s = node1;
SLTPrint(s);
}
int main()
{
test();
return 0;
}
3.2.2头部插入删除/尾部插入删除
3.2.2.1尾插
函数实现:
void SLTPushBack(SLTNode** pphead, SLTDataType x)
{
assert(pphead); //下面要对pphead解引用,所以pphead不能为空
//*pphead等同与指向头节点的指针,如果这个为空则
//相当于为空链表,所以不用对*pphead进行断言
//创建一个节点
SLTNode* newnode = BuyNode(x);
//空链表和非空链表
if (*pphead == NULL)
{
*pphead = newnode;
}
else
{
//找尾
SLTNode* ptail = *pphead;
while (ptail->next)
{
ptail = ptail->next;
}
//在尾部进行插入
ptail->next = newnode;
}
}
测试代码:
#include "SList.h"
void test()
{
SLTNode* plist = NULL;
SLTPushBack(&plist, 1);
SLTPushBack(&plist, 2);
SLTPushBack(&plist, 3);
SLTPushBack(&plist, 4);
SLTPrint(plist);
}
int main()
{
test();
return 0;
}
3.2.2.2头插
函数实现:
void SLTPushFront(SLTNode** pphead, SLTDataType x)
{
assert(pphead);
//创建一个节点
SLTNode* newnode = BuyNode(x);
//先将newnode->next指向之前的头节点,然后在将newnode作为新的头节点
newnode->next = *pphead;
*pphead = newnode;
}
测试代码:
#include "SList.h"
void test()
{
SLTNode* plist = NULL;
SLTPushBack(&plist, 1); //尾插
SLTPushBack(&plist, 2);
SLTPushBack(&plist, 3);
SLTPushBack(&plist, 4);
SLTPushFront(&plist, 5); //头插
SLTPrint(plist);
}
int main()
{
test();
return 0;
}
3.2.2.3尾删
函数实现:
void SLTPopBack(SLTNode** pphead)
{
assert(pphead);
//链表不能为空链表
assert(*pphead);
//只有一个节点的情况和多个节点的情况
if ((*pphead)->next == NULL) //只有一个节点
{
free(*pphead);
*pphead = NULL;
}
else //多个节点
{
//找尾节点和尾节点前面的一个节点
SLTNode* ptail = *pphead;
SLTNode* prev = ptail; //prev表示尾节点的前面一个节点
while (ptail->next)
{
prev = ptail; //将现在的尾节点给到prev
ptail = ptail->next; //将尾节点的next变成新的尾节点
}
//删除尾节点
free(ptail);
prev->next = NULL;
}
}
测试代码:
#include "SList.h"
void test()
{
SLTNode* plist = NULL;
SLTPushBack(&plist, 1); //尾插
SLTPushBack(&plist, 2);
SLTPushBack(&plist, 3);
SLTPushBack(&plist, 4);
//SLTPushFront(&plist, 5); //头插
SLTPopBack(&plist); //尾删
SLTPopBack(&plist);
SLTPrint(plist);
}
int main()
{
test();
return 0;
}
3.2.2.4头删
函数实现:
void SLTPopFront(SLTNode** pphead)
{
assert(pphead && *pphead);
SLTNode* del = *pphead; //先创建一个新的指针存储之前的头节点
*pphead = (*pphead)->next; //将头节点的下一个节点作为新的头节点
free(del); //删除之前的头节点
del = NULL;
}
测试代码:
#include "SList.h"
void test()
{
SLTNode* plist = NULL;
SLTPushBack(&plist, 1); //尾插
SLTPushBack(&plist, 2);
SLTPushBack(&plist, 3);
SLTPushBack(&plist, 4);
//SLTPushFront(&plist, 5); //头插
//SLTPopBack(&plist); //尾删
//SLTPopBack(&plist);
SLTPopFront(&plist);
SLTPopFront(&plist);
SLTPrint(plist);
}
int main()
{
test();
return 0;
}
3.2.3查找
函数实现:
SLTNode* SLTFind(SLTNode* phead, SLTDataType x)
{
SLTNode* cur = phead;
while (cur)
{
if (cur->val == x)
{
printf("找到了!\n");
return cur;
}
cur = cur->next;
}
printf("没有找到!\n");
return NULL;
}
测试代码:
#include "SList.h"
void test()
{
SLTNode* plist = NULL;
SLTPushBack(&plist, 1); //尾插
SLTPushBack(&plist, 2);
SLTPushBack(&plist, 3);
SLTPushBack(&plist, 4);
//SLTPushFront(&plist, 5); //头插
//SLTPopBack(&plist); //尾删
//SLTPopBack(&plist);
//SLTPopFront(&plist); //头删
//SLTPopFront(&plist);
SLTPrint(plist);
SLTFind(plist, 5);
}
int main()
{
test();
return 0;
}
将上述查找的数字“5”换为“2”.
3.2.4对指定位置做增删操作
3.2.4.1在指定位置之前插入数据
函数实现:
//在指定位置之前插入数据
void SLTInsert(SLTNode** pphead, SLTNode* pos, SLTDataType x)
{
assert(pphead && *pphead);
assert(pos);
SLTNode* newnode = BuyNode(x);
if (pos == *pphead) //pos在第一个位置--头插入
{
SLTPushFront(pphead, x); //头插
}
else //pos在中间和尾部
{
SLTNode* pcur = *pphead;
SLTNode* prev = *pphead;
//找到pos节点和pos前面的一个节点
while (pcur != pos)
{
prev = pcur;
pcur = pcur->next;
}
newnode->next = pcur; //将新节点的next指向pos
prev->next = newnode; //将pos前一个节点的next指向新节点
}
}
测试代码:
#include "SList.h"
void test()
{
SLTNode* plist = NULL;
SLTPushBack(&plist, 1); //尾插
SLTPushBack(&plist, 2);
SLTPushBack(&plist, 3);
SLTPushBack(&plist, 4);
//SLTPushFront(&plist, 5); //头插
//SLTPopBack(&plist); //尾删
//SLTPopBack(&plist);
//SLTPopFront(&plist); //头删
//SLTPopFront(&plist);
SLTNode* pos = SLTFind(plist, 2);
SLTInsert(&plist, pos, 6); //在指定位置之前插入数据
SLTPrint(plist);
}
int main()
{
test();
return 0;
}
3.2.4.2 删除pos节点
函数实现:
//删除pos节点
void SLTErase(SLTNode** pphead, SLTNode* pos)
{
assert(pphead && *pphead);
assert(pos);
if (*pphead == pos)
{
//删除第一个节点--头删
SLTPopFront(pphead);
}
else
{
//多个节点
SLTNode* pcur = *pphead;
SLTNode* prev = *pphead;
while (pcur != pos)
{
prev = pcur;
pcur = pcur->next;
}
prev->next = pcur->next;
free(pcur);
pcur = NULL;
}
}
测试代码:
#include "SList.h"
void test()
{
SLTNode* plist = NULL;
SLTPushBack(&plist, 1); //尾插
SLTPushBack(&plist, 2);
SLTPushBack(&plist, 3);
SLTPushBack(&plist, 4);
//SLTPushFront(&plist, 5); //头插
//SLTPopBack(&plist); //尾删
//SLTPopBack(&plist);
//SLTPopFront(&plist); //头删
//SLTPopFront(&plist);
SLTNode* pos = SLTFind(plist, 2);
//SLTInsert(&plist, pos, 6); //在指定位置之前插入数据
SLTErase(&plist, pos); //删除pos节点
SLTPrint(plist);
}
int main()
{
test();
return 0;
}
3.2.4.3在指定位置之后插入数据
函数实现:
//在指定位置之后插入数据
void SLTInsertAfter(SLTNode* pos, SLTDataType x)
{
assert(pos);
SLTNode* newnode = BuyNode(x);
newnode->next = pos->next;
pos->next = newnode;
}
测试代码:
#include "SList.h"
void test()
{
SLTNode* plist = NULL;
SLTPushBack(&plist, 1); //尾插
SLTPushBack(&plist, 2);
SLTPushBack(&plist, 3);
SLTPushBack(&plist, 4);
//SLTPushFront(&plist, 5); //头插
//SLTPopBack(&plist); //尾删
//SLTPopBack(&plist);
//SLTPopFront(&plist); //头删
//SLTPopFront(&plist);
SLTNode* pos = SLTFind(plist, 1);
//SLTInsert(&plist, pos, 6); //在指定位置之前插入数据
//SLTErase(&plist, pos); //删除pos节点
SLTInsertAfter(pos, 7); //在指定位置之后插入数据
SLTPrint(plist);
}
int main()
{
test();
return 0;
}
3.2.4.4删除pos之后的节点
函数实现:
//删除pos之后的节点
void SLTEraseAfter(SLTNode* pos)
{
assert(pos);
SLTNode* del = pos->next; //先用del存储pos->next指向的节点
pos->next = pos->next->next; //将pos->next指向pos下一个节点的下一个节点
free(del);
del = NULL;
}
测试代码:
#include "SList.h"
void test()
{
SLTNode* plist = NULL;
SLTPushBack(&plist, 1); //尾插
SLTPushBack(&plist, 2);
SLTPushBack(&plist, 3);
SLTPushBack(&plist, 4);
//SLTPushFront(&plist, 5); //头插
//SLTPopBack(&plist); //尾删
//SLTPopBack(&plist);
//SLTPopFront(&plist); //头删
//SLTPopFront(&plist);
SLTNode* pos = SLTFind(plist, 2);
//SLTInsert(&plist, pos, 6); //在指定位置之前插入数据
//SLTErase(&plist, pos); //删除pos节点
//SLTInsertAfter(pos, 7); //在指定位置之后插入数据
SLTEraseAfter(pos);
SLTPrint(plist);
}
int main()
{
test();
return 0;
}
3.2.5销毁链表
函数实现:
//销毁链表
void SListDesTroy(SLTNode** pphead)
{
assert(pphead && *pphead);
SLTNode* pcur = *pphead;
while (pcur)
{
SLTNode* next = pcur->next;
free(pcur);
pcur = next;
}
*pphead = NULL;
}
测试代码:
#include "SList.h"
void test()
{
SLTNode* plist = NULL;
SLTPushBack(&plist, 1); //尾插
SLTPushBack(&plist, 2);
SLTPushBack(&plist, 3);
SLTPushBack(&plist, 4);
//SLTPushFront(&plist, 5); //头插
//SLTPopBack(&plist); //尾删
//SLTPopBack(&plist);
//SLTPopFront(&plist); //头删
//SLTPopFront(&plist);
//SLTNode* pos = SLTFind(plist, 2);
//SLTInsert(&plist, pos, 6); //在指定位置之前插入数据
//SLTErase(&plist, pos); //删除pos节点
//SLTInsertAfter(pos, 7); //在指定位置之后插入数据
/*SLTEraseAfter(pos);*/
SLTPrint(plist);
SListDesTroy(&plist);
SLTPrint(plist);
}
int main()
{
test();
return 0;
}
3.3参考代码
//SList.h
#pragma once
#include <stdio.h>
#include <stdlib.h>
#include <assert.h>
typedef int SLTDataType;
typedef struct SListNode
{
SLTDataType val;
struct SListNode* next;
}SLTNode;
//创建节点
SLTNode* BuyNode(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 x);
//在指定位置之前插入数据
void SLTInsert(SLTNode** pphead, SLTNode* pos, SLTDataType x);
//删除pos节点
void SLTErase(SLTNode** pphead, SLTNode* pos);
//在指定位置之后插入数据
void SLTInsertAfter(SLTNode* pos, SLTDataType x);
//删除pos之后的节点
void SLTEraseAfter(SLTNode* pos);
//销毁链表
void SListDesTroy(SLTNode** pphead);
//SList.c
#include "SList.h"
SLTNode* BuyNode(SLTDataType x)
{
SLTNode* newNode = (SLTNode*)malloc(sizeof(SLTNode));
if (newNode == NULL)
{
perror("malloc fail!\n");
exit(1);
}
newNode->val = x;
newNode->next = NULL;
return newNode;
}
void SLTPrint(SLTNode* phead)
{
SLTNode* pcur = phead;
while (pcur)
{
printf("%d->", pcur->val);
pcur = pcur->next;
}
printf("NULL\n");
}
//头部插入删除/尾部插入删除
void SLTPushBack(SLTNode** pphead, SLTDataType x)
{
assert(pphead); //下面要对pphead解引用,所以pphead不能为空
//*pphead等同与指向头节点的指针,如果这个为空则
//相当于为空链表,所以不用对*pphead进行断言
//创建一个节点
SLTNode* newnode = BuyNode(x);
//空链表和非空链表
if (*pphead == NULL)
{
*pphead = newnode;
}
else
{
//找尾
SLTNode* ptail = *pphead;
while (ptail->next)
{
ptail = ptail->next;
}
//在尾部进行插入
ptail->next = newnode;
}
}
void SLTPushFront(SLTNode** pphead, SLTDataType x)
{
assert(pphead);
//创建一个节点
SLTNode* newnode = BuyNode(x);
//先将newnode->next指向之前的头节点,然后在将newnode作为新的头节点
newnode->next = *pphead;
*pphead = newnode;
}
void SLTPopBack(SLTNode** pphead)
{
assert(pphead);
//链表不能为空链表
assert(*pphead);
//只有一个节点的情况和多个节点的情况
if ((*pphead)->next == NULL) //只有一个节点
{
free(*pphead);
*pphead = NULL;
}
else //多个节点
{
//找尾节点和尾节点前面的一个节点
SLTNode* ptail = *pphead;
SLTNode* prev = ptail; //prev表示尾节点的前面一个节点
while (ptail->next)
{
prev = ptail; //将现在的尾节点给到prev
ptail = ptail->next; //将尾节点的next变成新的尾节点
}
//删除尾节点
free(ptail);
prev->next = NULL;
}
}
void SLTPopFront(SLTNode** pphead)
{
assert(pphead && *pphead);
SLTNode* del = *pphead;
*pphead = (*pphead)->next;
free(del);
del = NULL;
}
//查找
SLTNode* SLTFind(SLTNode* phead, SLTDataType x)
{
SLTNode* pcur = phead;
while (pcur)
{
if (pcur->val == x)
{
//printf("找到了!\n");
return pcur;
}
pcur = pcur->next;
}
//printf("没有找到!\n");
return NULL;
}
//在指定位置之前插入数据
void SLTInsert(SLTNode** pphead, SLTNode* pos, SLTDataType x)
{
assert(pphead && *pphead);
assert(pos);
SLTNode* newnode = BuyNode(x);
if (pos == *pphead) //pos在第一个位置--头插入
{
SLTPushFront(pphead, x);
}
else //pos在中间和尾部
{
SLTNode* pcur = *pphead;
SLTNode* prev = *pphead;
while (pcur != pos)
{
prev = pcur;
pcur = pcur->next;
}
newnode->next = pcur;
prev->next = newnode;
}
}
//删除pos节点
void SLTErase(SLTNode** pphead, SLTNode* pos)
{
assert(pphead && *pphead);
assert(pos);
if (*pphead == pos)
{
//删除第一个节点--头删
SLTPopFront(pphead);
}
else
{
//多个节点
SLTNode* pcur = *pphead;
SLTNode* prev = *pphead;
while (pcur != pos)
{
prev = pcur;
pcur = pcur->next;
}
prev->next = pcur->next;
free(pcur);
pcur = NULL;
}
}
//在指定位置之后插入数据
void SLTInsertAfter(SLTNode* pos, SLTDataType x)
{
assert(pos);
SLTNode* newnode = BuyNode(x);
newnode->next = pos->next;
pos->next = newnode;
}
//删除pos之后的节点
void SLTEraseAfter(SLTNode* pos)
{
assert(pos && pos->next); //pos后面的节点必须存在
SLTNode* del = pos->next; //先用del存储pos->next指向的节点
pos->next = pos->next->next; //将pos->next指向pos下一个节点的下一个节点
free(del);
del = NULL;
}
//销毁链表
void SListDesTroy(SLTNode** pphead)
{
assert(pphead && *pphead);
SLTNode* pcur = *pphead;
while (pcur)
{
SLTNode* next = pcur->next;
free(pcur);
pcur = next;
}
*pphead = NULL;
}
//test.c
#include "SList.h"
void test()
{
SLTNode* plist = NULL;
SLTPushBack(&plist, 1); //尾插
SLTPushBack(&plist, 2);
SLTPushBack(&plist, 3);
SLTPushBack(&plist, 4);
//SLTPushFront(&plist, 5); //头插
//SLTPopBack(&plist); //尾删
//SLTPopBack(&plist);
//SLTPopFront(&plist); //头删
//SLTPopFront(&plist);
//SLTNode* pos = SLTFind(plist, 2);
//SLTInsert(&plist, pos, 6); //在指定位置之前插入数据
//SLTErase(&plist, pos); //删除pos节点
//SLTInsertAfter(pos, 7); //在指定位置之后插入数据
/*SLTEraseAfter(pos);*/
SLTPrint(plist);
SListDesTroy(&plist);
SLTPrint(plist);
}
int main()
{
test();
return 0;
}