目录
一.为什么要使用链表存储数据?
二.链表的分类
单向或者双向链表:
带头或者不带头:
循环或者非循环:
三.链表的实现
3.1无头单向非循环链表的实现:
3.1.1单向无头非循环链表的声明
3.1.2动态申请一个节点
3.1.3单链表打印
3.1.4单链表尾插
3.1.5单链表的头插
3.1.6单链表的尾删
3.1.7单链表头删
3.1.8单链表查找
3.1.9单链表在pos位置之前插入x
3.1.10单链表在pos位置之后插入x
3.1.11单链表删除pos之后的值
3.1.12单链表删除pos位置的值
3.1.13销毁单链表
头文件:
测试文件:
3.2带头双向循环链表的实现
3.2.1带头双向循环链表的声明
3.2.2动态申请一个节点
3.2.3哨兵位初始化(创建链表的头结点)
3.2.4带头双向循环链表打印
3.2.5双向链表尾插
3.2.6双向链表头插
3.2.7双线链表尾删
3.2.8双线链表头删
3.2.9双向链表查找
3.2.10双向链表在pos的前面进行插入
3.2.11双向链表删除pos位置的结点
3.2.12双向链表销毁
头文件:
测试文件:
四.链表总结
一.为什么要使用链表存储数据?
内存空间是所有程序的公共资源,在一个复杂的系统运行环境下,空闲的内存空间可能散落在内存各处。我们知道,存储数组的内存空间必须是连续的,而当数组非常大时,内存可能无法提供如此大的连续空间。此时链表的灵活性优势就体现出来了。
让我们来看看链表的结构:
- 可以得出:链表是一种物理存储结构上非连续、非顺序的存储结构,数据元素的逻辑顺序是通过链表中的指针链接次序实现的 。
- 上述图中的链表知识链表中的其中一种
二.链表的分类
单向或者双向链表:
带头或者不带头:
循环或者非循环:
虽然链表的种类很多,但我们主要使用的还是无头单向非循环链表(OJ题中最常见的链表)和带头循环双向链表(实践应用)。
三.链表的实现
3.1无头单向非循环链表的实现:
对于项目我们需要区分测试文件和接口文件,这样做有利于培养良好的代码能力。
3.1.1单向无头非循环链表的声明
#pragma once
#include<stdio.h>
#include<stdlib.h>
#include<assert.h>typedef int SLNDataType;// 声明一个 Single Link List Node (单向无头链表)typedef struct SLLN
{//节点值SLNDataType val;//指向下一个节点的指针struct SLLN* next;}SLNode;
3.1.2动态申请一个节点
// 动态申请一个节点
SLNode* CreateNode(SLNDataType x)
{SLNode* newnode = (SLNode*)calloc(1, sizeof(SLNode));if (newnode == NULL){perror("calloc");//直接终止程序exit(-1);}newnode->val = x;newnode->next = NULL;return newnode;
}
3.1.3单链表打印
// 单链表打印
void SLNodePrint(SLNode* plist)
{SLNode* cur = plist;if (cur != NULL){while (cur){printf("%d->", cur->val);cur = cur->next;}printf("NULL");printf("\n");}else{printf("链表为空无需打印\n");}
}
3.1.4单链表尾插
// 单链表尾插
void SLNodePushBack(SLNode** pplist, SLNDataType x)
{//先创造一个新节点SLNode* newnode = CreateNode(x);SLNode* tail = *pplist;//将plist赋值给tail//找尾if (*pplist == NULL){*pplist = newnode;}else{while (tail->next != NULL){tail = tail->next;}tail->next = newnode;}
}
3.1.5单链表的头插
// 单链表的头插
void SLNodePushFront(SLNode** pplist, SLNDataType x)
{//先创建一个新节点SLNode* newnode = CreateNode(x);newnode->next = *pplist;*pplist = newnode;
}
3.1.6单链表的尾删
// 单链表的尾删
void SLNodePopBack(SLNode** pplist)
{//如果链表为空则不能删除,报错assert(*pplist);//找尾SLNode* tail = *pplist;//tail移动时我们还需要有一个前驱指针 prev 跟在tail后面SLNode* prev = NULL;//单链表只有一个节点情况下尾删if (tail->next == NULL){free(*pplist);*pplist = NULL;}//单链表有多个节点情况下尾删else{while (tail->next != NULL){prev = tail;tail = tail->next;}free(tail);tail = NULL;prev->next = NULL;}}
3.1.7单链表头删
// 单链表头删
void SLNodePopFront(SLNode** pplist)
{//当链表为空时不能删除,报错assert(*pplist);SLNode* cur = *pplist;SLNode* newplist = (*pplist)->next;free(cur);cur = NULL;*pplist = newplist;}
3.1.8单链表查找
// 单链表查找(配合在pos位置插入或者删除使用)
SLNode* SLNodeFind(SLNode* plist, SLNDataType x)
{while (plist != NULL){if (plist->val == x){return plist;}else{plist = plist->next;}}return NULL;
}
3.1.9单链表在pos位置之前插入x
//单链表在pos位置之前插入x
void SLNodeInsertBefore(SLNode** pplist, SLNode* pos, SLNDataType x)//此处传入二级指针pplist是为了在头插时改变plist的值,传址调用
{//此处需要对哪个指针进行断言检查呢?assert(pos && *pplist);//防止人为乱传空SLNode* cur = *pplist;SLNode* prev = NULL;//前驱指针prev保存cur前一个节点的地址if (pos == *pplist){//在头节点位置前插入x实质上就是头插,我们调用之前写的头插函数即可SLNodePushFront(pplist, x);}else{SLNode* newnode = CreateNode(x);while (cur != pos){prev = cur;cur = cur->next;}prev->next = newnode;newnode->next = cur;}
}
3.1.10单链表在pos位置之后插入x
// 单链表在pos位置之后插入x
void SLNodeInsertAfter(SLNode* pos, SLNDataType x)
{//链表为空无法在pos位置之后插入xassert(pos);SLNode* newnode = CreateNode(x);if (newnode == NULL){perror(calloc);return;}SLNode* next = pos->next;pos->next = newnode;newnode->next = next;}
3.1.11单链表删除pos之后的值
//单链表删除pos之后的值
void SLNodeEraseAfter(SLNode* pos)
{//链表为空无法删除assert(pos);//当链表只剩下一个节点或者pos后面无节点时也无法删除assert(pos->next);SLNode* del = pos->next;pos->next = pos->next->next;free(del);del == NULL;
}
3.1.12单链表删除pos位置的值
//单链表删除pos位置的值
void SLNodeErasepos(SLNode** pplist, SLNode* pos)
{//链表为空不能删除assert(*pplist);SLNode* cur = *pplist;SLNode* prev = NULL;//前驱指针prev保存cur前一个节点的地址if (pos != *pplist){while (cur != pos){prev = cur;cur = cur->next;}SLNode* next = cur->next;free(cur);cur = NULL;//链接两个节点prev->next = next;}else{SLNode* next = cur->next;free(cur);cur = NULL;//更新头节点*pplist = next;}
}
3.1.13销毁单链表
//销毁链表
void SLNodeDestory(SLNode** pplist)
{assert(pplist);assert(*pplist);SLNode* cur = *pplist;while (cur){SLNode* next = cur->next;free(cur);cur = next;}*pplist = NULL;
}
以上代码为单链表所实现的所有功能(接口)
头文件:
#pragma once
#include<stdio.h>
#include<stdlib.h>
#include<assert.h>typedef int SLNDataType;// 声明一个 Single Link List Node (单向无头链表)typedef struct SLLN
{//节点值SLNDataType val;//指向下一个节点的指针struct SLLN* next;}SLNode;// 动态申请一个节点
SLNode* CreateNode(SLNDataType x);// 单链表打印
void SLNodePrint(SLNode* plist);// 单链表尾插
void SLNodePushBack(SLNode** pplist, SLNDataType x);// 单链表的头插
void SLNodePushFront(SLNode** pplist, SLNDataType x);// 单链表的尾删
void SLNodePopBack(SLNode** pplist);// 单链表头删
void SLNodePopFront(SLNode** pplist);// 单链表查找
SLNode* SLNodeFind(SLNode* plist, SLNDataType x);//单链表在pos位置之前插入x
void SLNodeInsertBefore(SLNode** pplist, SLNode* pos, SLNDataType x);//单链表在pos位置之后插入x
void SLNodeInsertAfter(SLNode* pos, SLNDataType x);//单链表删除pos位置之后的值
void SLNodeEraseAfter(SLNode* pos);//单链表删除pos位置的值
void SLNodeErasepos(SLNode** pplist, SLNode* pos);//销毁单链表
void SLNodeDestory(SLNode** pplist);
测试文件:
#define _CRT_SECURE_NO_WARNINGS 1
#include"Single_linked_lists.h"//单链表尾插测试
void text1()
{SLNode* plist = NULL;SLNodePushBack(&plist, 1);SLNodePushBack(&plist, 2);SLNodePushBack(&plist, 3);SLNodePushBack(&plist, 4);SLNodePrint(plist);
}
//单向链表头插测试
void text2()
{SLNode* plist = NULL;SLNodePushFront(&plist, 1);SLNodePushFront(&plist, 2);SLNodePushFront(&plist, 3);SLNodePushFront(&plist, 4);SLNodePrint(plist);}
//单向链表尾删测试
void text3()
{//SLNode* plist = NULL;//SLNodePushBack(&plist, 1);//SLNodePushBack(&plist, 2);//SLNodePushBack(&plist, 3);//SLNodePushBack(&plist, 4);多节点尾删测试//SLNodePopBack(&plist);SLNode* plist = NULL;SLNodePushBack(&plist, 1);//单节点尾删测试SLNodePopBack(&plist);SLNodePrint(plist);
}
//单向链表头删测试
void text4()
{SLNode* plist = NULL;SLNodePushBack(&plist, 1);SLNodePushBack(&plist, 2);SLNodePushBack(&plist, 3);SLNodePushBack(&plist, 4);//头删测试SLNodePopFront(&plist);SLNodePrint(plist);
}//单向链表查找 val 测试
void text5()
{SLNode* plist = NULL;SLNodePushBack(&plist, 1);SLNodePushBack(&plist, 2);SLNodePushBack(&plist, 3);SLNodePushBack(&plist, 4);//查找val测试SLNode* pos = SLNodeFind(plist, 4);printf("%p\n", pos);
}// 单链表在pos位置之后插入 x 测试
void text6()
{SLNode* plist = NULL;SLNodePushBack(&plist, 1);SLNodePushBack(&plist, 2);SLNodePushBack(&plist, 3);SLNodePushBack(&plist, 4);//pos之后插入xSLNode* pos = SLNodeFind(plist, 3);SLNodeInsertAfter(pos, 0);SLNodePrint(plist);
}//单链表删除pos之后的值测试
void text7()
{SLNode* plist = NULL;SLNodePushBack(&plist, 1);SLNodePushBack(&plist, 2);SLNodePushBack(&plist, 3);SLNodePushBack(&plist, 4);//单链表删除pos之后的值测试SLNode* pos = SLNodeFind(plist, 3);SLNodeEraseAfter(pos);SLNodePrint(plist);}
//单链表在pos前一个位置插入 x 测试
void text8()
{SLNode* plist = NULL;SLNodePushBack(&plist, 1);SLNodePushBack(&plist, 2);SLNodePushBack(&plist, 3);SLNodePushBack(&plist, 4);SLNodePrint(plist);SLNode* posbefore = SLNodeFind(plist, 3);//单链表在pos前一个位置插入 x 测试SLNodeInsertBefore(&plist, posbefore, 0);SLNodePrint(plist);}
//单链表删除pos位置的值
void text9()
{SLNode* plist = NULL;SLNodePushBack(&plist, 1);SLNodePushBack(&plist, 2);SLNodePushBack(&plist, 3);SLNodePushBack(&plist, 4);SLNodePrint(plist);//单链表删除pos位置的值SLNode* pos1 = SLNodeFind(plist, 1);SLNode* pos2 = SLNodeFind(plist, 3);SLNodeErasepos(&plist, pos1);SLNodeErasepos(&plist, pos2);SLNodePrint(plist);}
int main()
{//text1();//单链表尾插测试//text2();//单向链表头插测试//text3();//单向链表尾删测试//text4();//单向链表头删测试//text5();//单向链表查找 val 测试//text6();// 单链表在pos位置之后插入 x 测试//text7();//单链表删除pos之后的值测试//text8();//单链表在pos前一个位置插入 x 测试//text9();//单链表删除pos位置的值测试return 0;
}
3.2带头双向循环链表的实现
3.2.1带头双向循环链表的声明
#pragma once
#include<stdio.h>
#include<stdlib.h>
#include<assert.h>typedef int DataType;typedef struct ListNode
{DataType val;struct LTNode* next;struct LTNode* prev;
}LTNode;
3.2.2动态申请一个节点
//Create one newnode
LTNode* Createnode(DataType x)
{LTNode* newnode = (LTNode*)malloc(sizeof(LTNode));if (newnode == NULL){perror("malloc");exit(-1);}newnode->val = x;newnode->next = NULL;newnode->prev = NULL;return newnode;
}
3.2.3哨兵位初始化(创建链表的头结点)
//哨兵位初始化(创建链表的头结点)
LTNode* LTInit()
{LTNode* plist = Createnode(-1);plist->next = plist;plist->prev = plist;return plist;
}
3.2.4带头双向循环链表打印
//带头双向循环链表打印
void LTPrint(LTNode* plist)
{assert(plist);LTNode* cur = plist->next;printf("哨兵位<=>");while (cur != plist){printf("%d<=>", cur->val);cur = cur->next;}printf("\n");
}
3.2.5双向链表尾插
// 双向链表尾插
void LTNodePushBack(LTNode* plist, DataType x)
{LTNode* newnode = Createnode(x);LTNode* tail = plist->prev;tail->next = newnode;newnode->prev = tail;plist->prev = newnode;newnode->next = plist;}
3.2.6双向链表头插
// 双向链表头插
void LTNodePushFront(LTNode* plist, DataType x)
{assert(plist);LTNode* first = plist->next;LTNode* newnode = Createnode(x);newnode->next = first;first->prev = newnode;plist->next = newnode;newnode->prev = plist;}
3.2.7双线链表尾删
// 双向链表尾删
void LTNodePopBack(LTNode* plist)
{//防止链表不存在assert(plist);//防止链表为空assert(plist->next);LTNode* tail = plist->prev;plist->prev = tail->prev;LTNode* tailprev = tail->prev;tailprev->next = plist;
}
3.2.8双线链表头删
// 双向链表头删
void LTNodePopFront(LTNode* plist)
{//防止链表不存在assert(plist);//防止链表为空assert(plist->next);LTNode* first = plist->next;LTNode* second = first->next;plist->next = second;second->prev = plist;free(first);first = NULL;}
3.2.9双向链表查找
// 双向链表查找
LTNode* LTNodeFind(LTNode* plist, DataType x)
{//防止链表不存在assert(plist);//防止链表为空assert(plist->next);LTNode* cur = plist->next;while (cur != plist){if (cur->val == x){return cur;}cur = cur->next;}return NULL;
}
3.2.10双向链表在pos的前面进行插入
// 双向链表在pos的前面进行插入
void LTNodeInsert(LTNode* pos, DataType x)
{assert(pos);LTNode* newnode = Createnode(x);LTNode* posprev = pos->prev;posprev->next = newnode;newnode->next = pos;pos->prev = newnode;newnode->prev = posprev;
}
3.2.11双向链表删除pos位置的结点
// 双向链表删除pos位置的结点
void LTNodeErase(LTNode* pos)
{//防止传空assert(pos);LTNode* posnext = pos->next;LTNode* posprev = pos->prev;posprev->next = posnext;posnext->prev = posprev;free(pos);
}
3.2.12双向链表销毁
// 双向链表销毁
void LTNodeDestory(LTNode* plist)
{//防止链表不存在assert(plist);LTNode* cur = plist->next;while (cur != plist){LTNode* next = cur->next;free(cur);cur = next;}free(plist);plist = NULL;
}
以上代码为单链表所实现的所有功能(接口)
头文件:
#pragma once
#include<stdio.h>
#include<stdlib.h>
#include<assert.h>typedef int DataType;typedef struct ListNode
{DataType val;struct LTNode* next;struct LTNode* prev;
}LTNode;//生成新节点
LTNode* Createnode(DataType x);//哨兵位初始化
LTNode* LTInit();//带头双向循环链表打印
void LTPrint(LTNode* plist);// 双向链表销毁
void LTNodeDestory(LTNode* plist);// 双向链表尾插
void LTNodePushBack(LTNode* plist, DataType x);// 双向链表尾删
void LTNodePopBack(LTNode* plist);// 双向链表头插
void LTNodePushFront(LTNode* plist, DataType x);// 双向链表头删
void LTNodePopFront(LTNode* plist);// 双向链表查找
LTNode* LTNodeFind(LTNode* plist, DataType x);// 双向链表在pos的前面进行插入
void LTNodeInsert(LTNode* pos, DataType x);// 双向链表删除pos位置的结点
void LTNodeErase(LTNode* pos);
测试文件:
#define _CRT_SECURE_NO_WARNINGS 1
#include"list.h"
// 初步测试
void test1()
{// 创建一个哨兵位头节点LTNode* plist = LTInit();LTPrint(plist);
}// 尾插尾删测试
void test2()
{LTNode* plist = LTInit();LTNodePushBack(plist, 1);LTNodePushBack(plist, 2);LTNodePushBack(plist, 3);LTNodePushBack(plist, 4);LTNodePushBack(plist, 5);LTPrint(plist);LTNodePopBack(plist);LTPrint(plist);
}
// 头插头删测试
void test3()
{LTNode* plist = LTInit();LTNodePushFront(plist, 1);LTNodePushFront(plist, 2);LTNodePushFront(plist, 3);LTNodePushFront(plist, 4);LTNodePushFront(plist, 5);LTPrint(plist);LTNodePopFront(plist);LTPrint(plist);
}
// 链表查找测试
void test4()
{LTNode* plist = LTInit();LTNodePushBack(plist, 1);LTNodePushBack(plist, 2);LTNodePushBack(plist, 3);LTNodePushBack(plist, 4);LTNodePushBack(plist, 5);LTPrint(plist);LTNode* pos = LTNodeFind(plist, 3);printf("%d\n", pos->val);}
// 双向链表在pos的前面进行插入测试
void test5()
{LTNode* plist = LTInit();LTNodePushBack(plist, 1);LTNodePushBack(plist, 2);LTNodePushBack(plist, 3);LTNodePushBack(plist, 4);LTNodePushBack(plist, 5);LTPrint(plist);LTNode* pos = LTNodeFind(plist, 3);LTNodeInsert(pos, 100);LTNodeInsert(pos, 200);LTPrint(plist);}
// 双向链表删除pos位置的结点测试
void test6()
{LTNode* plist = LTInit();LTNodePushBack(plist, 1);LTNodePushBack(plist, 2);LTNodePushBack(plist, 3);LTNodePushBack(plist, 4);LTNodePushBack(plist, 5);LTPrint(plist);LTNode* pos = LTNodeFind(plist, 3);LTNodeErase(pos);LTPrint(plist);
}
// 链表销毁测试
void test7()
{LTNode* plist = LTInit();LTNodePushBack(plist, 1);LTNodePushBack(plist, 2);LTNodePushBack(plist, 3);LTNodePushBack(plist, 4);LTNodePushBack(plist, 5);LTNodeDestory(plist);plist = NULL;LTPrint(plist);
}
int main()
{// 初步测试test1();// 尾插尾删测试test2();// 头插头删测试test3();// 链表查找测试test4();// 双向链表在pos的前面进行插入测试test5();// 双向链表删除pos位置的结点测试test6();// 链表销毁测试//test7();return 0;
}
四.链表总结
这是一个我个人做的思维导图,对于学习链表的一些总结,希望对你有所帮助: