单链表的应用
1.单链表的简单介绍
单链表是一种链式存取的数据结构,用一组任意地址空间(地址空间即存储单元)来存放线性表的数据元素。单链表中的数据是以节点的形式来表示,而节点是用结构体来描述,每个节点都是由元素和指针构成,即该结构体中包含两个成员变量:存放元素的成员变量和存放下一个节点地址的成员变量。单链表的节点结构为如下所示:
2.顺序表的简单介绍
用顺序存储方法存储的线性表称为顺序表,顺序表是用一组连续的地址空间(地址空间即存储单元)存放线性表的数据元素。顺序表的特点为:逻辑相邻的两个节点其物理存储位置也是相邻的。顺序表的存储方式如下所示:
3.顺序表与链表的区别
顺序表的特点为:逻辑相邻的两节点其物理地址也是相邻的;链表的特点为:逻辑相邻的两节点其物理地址不相邻。顺序表的存储方式是:节点元素连续存放在存储单元;链表的存储方式是:节点元素随机存放在存储单元。
4.链表的应用
了解了什么是链表之后,需考虑如何定义一个结构体?我们需要定义一个结构体用于表示节点信息,故还结构体需要存放节点元素和下一节点地址,即:
typedef struct LinkNode
{
char data;
struct LinkNode* next;
}LinkNode;
那么我如果需要的不是char类型的节点元素呢?那么就得修改每次操作的节点元素的数据类型,这使得代码不具有可拓展性,那么就想到了宏定义一个节点元素的数据类型,从而只要修改该宏即可修改该节点元素的数据类型,具体如下:
#define LinkListType char
typedef struct LinkNode
{
LinkListType data;
struct LinkNode* next;
}LinkNode;
根据已有的思路,下面具体写头文件linklist.h的代码:
//头文件只被编译一次
#pragma once
//自定义元素的类型,方便用户去修改元素的数据类型
typedef char LinkListType;
//定义一个结构体,用于元素的存放和查找下一个元素所在的位置
typedef struct LinkNode
{
LinkListType data;
struct LinkNode* next;
}LinkNode; //其中LinkNode是对所定义的结构体的重命名,LinkList*是定义指向该结构
体的指针类型
//宏定义一个标识符,用于测试函数时打印其对应的函数名,方便了代码的编写
#define HEADER printf("============%s===========\n",__FUNCTION__);
4.1 链表的初始化
#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>
#include"linklist.h"
//思路:初始链表中的节点个数为0,故只需要一个头指针指向一个NULL,则表明该链表为空链表
void LinkListInit(LinkNode** phead)
{
//判断非法输入
if(phead==NULL)
return;
*phead=NULL;
}
注:为了方便表述,以下的代码不再引用头文件,所需头文件与上述相同。
4.2 链表的打印
//思路:第一个参数接收链表的头指针,第二个参数接收字符串的地址,用于打印时显示关于打印的具体信息
void LinkListPrintChar(LinkNode* head,const char* msg){
printf("[%s]\n",msg);
LinkNode* cur=head;
for(cur=head;cur!=NULL;cur=cur->next)
{
printf("[%p]|[%c] ",cur,cur->data);
}
printf("\n");
}
4.3 链表的尾插
//思路:创建一个新节点并修改链表中最后一个节点的next值,当为空链表时,则需要修改头指针的内容
//创建一个节点,用函数封装起来从而提高代码的可维护性
LinkNode* CreateNode(LinkListType value)
{
LinkNode* new_node=(LinkNode*)malloc(sizeof(LinkNode));
new_node->data=value;
new_node->next=NULL;
return new_node;
}
void LinkListPushBack(LinkNode** phead,LinkListType value){
//判断非法输入
if(phead==NULL)
return;
//链表为空时,是通过创建新节点并修改头指针的指向
if(*phead==NULL)
{
LinkNode* new_node=CreateNode(value);
*phead=new_node;
return;
}
//链表非空时,通过创建新节点并遍历找到最后一个节点,将其的next进行修改
LinkNode* cur=*phead;
while(cur->next!=NULL)
{
cur=cur->next;
}
cur->next=CreateNode(value);
}
4.4 链表的尾删
//销毁一个节点,用函数封装起来从而提高代码的可维护性
void DestroyNode(LinkNode* node)
{
free(node);
}
//思路:有三种情况处理:1.链表为空 2.链表只有一个节点 3.链表至少有两个节点
void LinkListPopBack(LinkNode** phead)
{
//判断非法输入
if(phead==NULL)
return;
//判断链表是否为空
if(*phead==NULL)
return;
//链表为一个节点时,销毁该节点的空间并修改头指针的指向
if((*phead)->next==NULL)
{
DestroyNode(*phead);
*phead=NULL;
}
//链表至少为两个节点时,遍历链表需要两个指针:存放倒数第二个节点的地址和需要释放的最后一个节点的地址
LinkNode* cur=*phead; //存放要释放的最后一个节点的地址
LinkNode* pre=NULL; //存放倒数第二个节点的地址
while(cur->next!=NULL)
{
pre=cur;
cur=cur->next;
}
//当循环结束后,cur就指向最后一个节点,pre就指向倒数第二个节点
pre->next=NULL;
DestroyNode(cur);
}
4.5 链表的头插
//思路:创建一个新节点,并修改头结点的指向和新节点的next值
void LinkListPushFront(LinkNode** phead,LinkListType value)
{
//判断非法输入
if(phead==NULL)
return;
//空链表与非空链表一致处理
LinkNode* new_node=CreateNode(value);
new_node->next=*phead;
*phead=new_node;
}
4.6 链表的头删
//思路:分情况处理:1.链表为空 2.链表只有一个节点 3.链表至少有两个节点
void LinkListPopFront(LinkNode** phead)
{
//判断非法输入
if(phead==NULL)
return;
//判断链表是否为空
if(*phead==NULL)
return;
//链表只有一个节点时,直接释放第一个节点的空间并修改头指针的指向
if((*phead)->next==NULL)
{
DestroyNode(*phead);
*phead=NULL;
return;
}
//链表至少有两个节点时,将头指针指向第二个节点位置,释放第一个节点空间
LinkNode* freenode=*phead;
DestroyNode(freenode);
*phead=(*phead)->next;
//也可以将2.3情况一并处理
}
4.7 链表任意位置的插入
4.7.1任意位置的插入(pos后插)
//思路:创建新节点并修改新节点的next值和pos的next值
void LinkListInsert(LinkNode* pos,LinkListType value)
{
//判断非法输入,pos表示一个节点的地址,若pos==NULL则表示不存在这样的节点
if(pos==NULL)
return;
LinkNode* new_node=CreateNode(value);
new_node->next=pos->next;
pos->next=new_node;
}
4.7.2任意位置的插入(pos前插)
//思路:去遍历链表找到pos的前一个节点,在用LinkListInsert()函数进行插入
void LinkListInsertBefore(LinkNode** phead,LinkNode* pos,LinkListType value)
{
//判断非法输入
if(phead==NULL||pos==NULL)
return;
//链表中只有一个节点时
if(*phead==pos)
{
//要插入的位置为第一个节点,用头插法
LinkListPushFront(phead,value);
return;
}
//链表中至少有两个节点
LinkNode* cur=*phead;
for(cur=*phead;cur!=NULL;cur=cur->next)
{
if(cur->next==pos)
break;
}
//循环结束之后,需要知道是找到pos了还是没找到
if(cur==NULL)
//没找到
return;
//找到pos的前一个节点后使用LinkListInsert()函数
LinkListInsert(cur,value);
}
4.7.3不允许遍历单链表,在pos位置前插入,使得时间复杂度变为O(1)
//思路:先插在pos后面,再修改两个节点的值
void LinkListInsertBeforeEx(LinkNode* pos,LinkListType value)
{
//判断非法输入,当pos==NULL时表示不存在这样的节点
if(pos==NULL)
return;
LinkNode* new_node=CreateNode(pos->data);
new_node->next=pos->next;
pos->next=new_node;
pos->data=value;
//以上四条语句的功能可以用以下两条语句代替
//LinkListInsert(pos,pos->data);
//pos->data=value;
}
4.8 查找元素在链表中的位置
//思路:通过遍历查找,返回所查找到的节点地址
LinkNode* LinkListFind(LinkNode* head,LinkListType to_find)
{
//判断链表是否为空
if(head==NULL)
return NULL;
LinkNode* cur=head;
while(cur!=NULL)
{
if(cur->data==to_find)
return cur;
cur=cur->next;
}
return NULL;
}
4.9 删除指定位置的元素
//思路:通过遍历销毁pos位置的节点,并修改pos位置的前一个节点的next值
void LinkListErase(LinkNode** phead,LinkNode* pos)
{
//判断非法输入
if(phead==NULL||pos==NULL)
return;
//空链表时
if(*phead==NULL)
return;
//删除第一个节点时
if((*phead)==pos)
{
//当只有一个节点时
if((*phead)->next==NULL)
{
*phead=NULL;
DestroyNode(pos);
return;
}
else
{
*phead=pos->next;
DestroyNode(pos);
}
}
//链表节点至少为2时,遍历链表,并判断是否找到pos
LinkNode* cur=*phead;
for(cur=*phead;cur!=NULL;cur=cur->next)
{
if(cur->next==pos)
break;
}
//循环结束,判断是否找到pos
if(cur==NULL) //未找到pos
return;
cur->next=pos->next;
DestroyNode(pos);
}
4.10 删除指定值的元素(如有重复的元素只删除一个即可)
//思路:通过LinkListFind()找到对应元素的地址,再使用对任意位置做删除的函数LinkListErase()即可
void LinkListRemove(LinkNode** phead,LinkListType to_delete)
{
//判断非法输入
if(phead==NULL)
return;
LinkNode* to_find=LinkListFind(*phead,to_delete);
LinkListErase(phead,to_find);
}
4.11 指定值的所有元素都删除
//思路1:通过遍历链表以及利用LinkListRemove()删除所有指定的元素
void LinkListRemoveAll(LinkNode** phead,LinkListType remove)
{
//判断非法输入
if(phead==NULL)
return;
LinkNode* cur=*phead;
for(cur=*phead;cur!=NULL;cur=cur->next)
{
if(cur->data==remove)
LinkListRemove(phead,remove);
}
}
//思路2:通过遍历链表以及利用LinkListFind()和LinkListErase()删除所有指定的元素
void LinkListRemoveAllEx(LinkNode** phead,LinkListType remove)
{
if(phead==NULL)
//非法输入
return;
while(1)
{
LinkNode* pos=LinkListFind(*phead,remove);
if(pos==NULL)
return;
LinkListErase(phead,pos);
}
}
//思路3:时间复杂度变为O(n)
void LinkListRemoveAllExp(LinkNode** phead,LinkListType remove)
{
if(phead==NULL)
//非法输入
return;
//空链表
if(*phead==NULL)
return;
LinkNode* cur=*phead;
//非空链表
while(cur!=NULL)
{
//当删除的元素为第一个元素时
if((*phead)->data==remove)
{
LinkNode* delete=*phead;
*phead=delete->next;
cur=*phead;
DestroyNode(delete);
}
//当删除的元素不是第一个元素,第二个元素存在时
else if(cur->next!=NULL)
{
//第二个元素存在时,判断第二个元素是否是想要删除的元素
if(cur->next->data==remove)
{
LinkNode* delete=cur->next;
cur->next=delete->next;
DestroyNode(delete);
}
else
cur=cur->next;
}
//删除后,第二个元素不存在
else
return;
}
}
4.12 判定链表为空
//思路:返回类型为int,链表为空返回1,链表非空返回0
int LinkListEmpty(LinkNode* head)
{
if(head==NULL)
return 1;
return 0;
}
//方法二思路:返回类型为int,链表为空返回1,链表非空返回0
int LinkListEmpty(LinkNode* head)
{
return head==NULL?1:0;
}
4.13 求链表的元素个数
//思路:遍历链表去统计元素个数
size_t LinkListSize(LinkNode* head)
{
if(head==NULL)
return 0;
size_t size=0;
LinkNode* cur=head;
while(cur!=NULL)
{
size++;
cur=cur->next;
}
return size;
}
4.14 逆序打印单链表
//思路1:分三种情况:1. 链表为空 2.链表只有一个元素 3.链表至少有两个元素
void LinkListReservePrintChar(LinkNode* head)
{
//判断链表是否为空
if(head==NULL)
return;
//判断链表是否只有一个元素
if(head->next==NULL)
{
printf("[%p][%c] \n",head,head->data);
return;
}
//链表至少有两个元素时
LinkNode* end=NULL;
LinkNode* cur=head->next;
while(cur!=head)
{
for(cur=head;cur!=NULL;cur=cur->next)
{
if((cur->next)==end)
{
printf("[%p][%c] ",cur,cur->data);
end=cur;
break;
}
}
}
//printf("[%p][%c]\n",head,head->data);
printf("\n");
}
//思路2:上述的方式是非递归的方式,下面将利用递归的思想逆序打印链表元素
void LinkListReversePrintCharEx(LinkNode* head)
{
LinkNode* cur=head;
if(cur==NULL)
return;
LinkListReversePrintCharEx(cur->next);
printf("%c ",cur->data);
}
4.15 测试上述函数是否正确
/*测试代码块,用于测试代码是否正确*///1.测试初始化链表是否正确
void Test_LinkListInit()
{
HEADER;
LinkNode* head;
LinkListInit(&head);
LinkListPrintChar(head,"对空链表打印");
}
//2.测试尾插法是否正确
void Test_LinkListPushBack()
{
HEADER;
LinkNode* head;
LinkListInit(&head);
LinkListPushBack(&head,'a');
LinkListPushBack(&head,'b');
LinkListPushBack(&head,'c');
LinkListPrintChar(head,"尾插节点'a','b','c'");
}
//3.测试尾删法是否正确
void Test_LinkListPopBack()
{
HEADER;
LinkNode* head;
LinkListInit(&head);
LinkListPushBack(&head,'a');
LinkListPushBack(&head,'b');
LinkListPushBack(&head,'c');
LinkListPopBack(&head);
LinkListPrintChar(head,"链表节点'a' 'b' 'c'中尾删一个节点");
}
//4.测试头插法是否正确
void Test_LinkListPushFront()
{
HEADER;
LinkNode* head;
LinkListInit(&head);
LinkListPushFront(&head,'a');
LinkListPushFront(&head,'b');
LinkListPushFront(&head,'c');
LinkListPrintChar(head,"头插节点'a' 'b' 'c'");
}
//5.测试头删法
void Test_LinkListPopFront()
{
HEADER;
LinkNode* head;
LinkListInit(&head);
LinkListPopFront(&head);
LinkListPrintChar(head,"头删法删除空链表");
LinkListPushFront(&head,'a');
LinkListPushFront(&head,'b');
LinkListPushFront(&head,'c');
LinkListPopFront(&head);
LinkListPrintChar(head,"头删法删除链表节点'c' 'b' 'a'的一个节点");
}
//6.测试任意位置的插入(pos后插)是否正确
void Test_LinkListInsert()
{
HEADER;
LinkNode* head;
LinkListInit(&head);
LinkListPushFront(&head,'a');
LinkListPushFront(&head,'b');
LinkListPushFront(&head,'c');
LinkListInsert(head,'x');
LinkListPrintChar(head,"在链表'c' 'b' 'a'的'c'节点后插入节点'x'");
}
//7.测试任意位置的插入(pos前插)是否正确
void Test_LinkListInsertBefore()
{
HEADER;
LinkNode* head;
LinkListInit(&head);
LinkListPushFront(&head,'a');
LinkListPushFront(&head,'b');
LinkListPushFront(&head,'c');
LinkListInsertBefore(&head,head,'x');
LinkListPrintChar(head,"在链表'c' 'b' 'a'的'c'节点前插入节点'x'");
}
//7.1.测试不允许遍历链表,在pos前插入的时间复杂度为O(1)是否正确
void Test_LinkListInsertBeforeEx()
{
HEADER;
LinkNode* head;
LinkListInit(&head);
LinkListPushFront(&head,'a');
LinkListPushFront(&head,'b');
LinkListPushFront(&head,'c');
LinkListInsertBeforeEx(head->next,'x');
LinkListPrintChar(head,"使用时间复杂度为O(1)在链表'c' 'b' 'a'的'b'节点前插入节点'x'");
}
//8.测试查找元素在链表中的位置是否正确
void Test_LinkListFind()
{
HEADER;
LinkNode* head;
LinkListInit(&head);
LinkListPushFront(&head,'a');
LinkListPushFront(&head,'b');
LinkListPushFront(&head,'c');
LinkListPrintChar(head,"查找链表中没有的节点'x'");
LinkNode* find=LinkListFind(head,'x');
printf("&a=%p\n",find);
LinkListPrintChar(head,"查找链表中的节点'a'");
find=LinkListFind(head,'a');
printf("&a=%p\n",find);
}
//9.测试指定位置的删除是否正确
void Test_LinkListErase()
{
HEADER;
LinkNode* head;
LinkListInit(&head);
LinkListErase(&head,head);
LinkListPrintChar(head,"对空链表进行删除");
LinkListPushBack(&head,'a');
LinkListErase(&head,head);
LinkListPrintChar(head,"删除链表中的节点'a'");
LinkListPushBack(&head,'a');
LinkListPushBack(&head,'b');
LinkListPushBack(&head,'c');
LinkListErase(&head,head);
LinkListPrintChar(head,"删除链表中的节点'a'");
}
//10.测试指定元素的删除是否正确
void Test_LinkListRemove()
{
HEADER;
LinkNode* head;
LinkListInit(&head);
LinkListPushBack(&head,'a');
LinkListPushBack(&head,'b');
LinkListPushBack(&head,'c');
LinkListRemove(&head,'a');
LinkListPrintChar(head,"[指定元素的删除]删除链表中的节点'a'");
}
//11.测试删除所有指定元素是否正确
void Test_LinkListRemoveAll()
{
HEADER;
LinkNode* head;
LinkListInit(&head);
LinkListPushBack(&head,'a');
LinkListPushBack(&head,'x');
LinkListPushBack(&head,'a');
LinkListPushBack(&head,'b');
LinkListPushBack(&head,'c');
LinkListPushBack(&head,'a');
LinkListRemoveAll(&head,'a');
LinkListPrintChar(head,"[删除所有指定元素]删除链表中的节点'a'");
}
void Test_LinkListRemoveAllEx()
{
HEADER;
LinkNode* head;
LinkListInit(&head);
LinkListPushBack(&head,'a');
LinkListPushBack(&head,'x');
LinkListPushBack(&head,'a');
LinkListPushBack(&head,'b');
LinkListPushBack(&head,'c');
LinkListPushBack(&head,'a');
LinkListRemoveAllEx(&head,'a');
LinkListPrintChar(head,"[删除所有指定元素]删除链表中的节点'a'");
}
void Test_LinkListRemoveAllExp()
{
HEADER;
LinkNode* head;
LinkListInit(&head);
LinkListPushBack(&head,'a');
LinkListPushBack(&head,'x');
LinkListPushBack(&head,'a');
LinkListPushBack(&head,'b');
LinkListPushBack(&head,'c');
LinkListPushBack(&head,'a');
LinkListRemoveAllExp(&head,'a');
LinkListPrintChar(head,"[删除所有指定元素]删除链表中的节点'a'");
}
//12.测试判定链表为空是否正确
void Test_LinkListEmpty()
{
HEADER;
LinkNode* head;
LinkListInit(&head);
int ret=LinkListEmpty(head);
if(ret==1)
printf("链表为空\n");
else if(ret==0)
printf("链表非空\n");
LinkListPushBack(&head,'a');
LinkListPushBack(&head,'x');
LinkListPushBack(&head,'a');
ret=LinkListEmpty(head);
if(ret==1)
printf("链表为空\n");
else if(ret==0)
printf("链表非空\n");
}
//13.测试求链表元素个数是否正确
void Test_LinkListSize()
{
HEADER;
LinkNode* head;
LinkListInit(&head);
size_t ret=LinkListSize(head);
printf("expect 0,autual %lu\n",ret);
LinkListPushBack(&head,'a');
LinkListPushBack(&head,'x');
LinkListPushBack(&head,'a');
LinkListPushBack(&head,'b');
LinkListPushBack(&head,'c');
ret=LinkListSize(head);
printf("expect 5,autual %lu\n",ret);
}
//14.测试逆序打印链表是否正确
void Test_LinkListReversePrintChar()
{
HEADER;
LinkNode* head;
LinkListInit(&head);
LinkListReservePrintChar(head);
LinkListPushBack(&head,'a');
LinkListPushBack(&head,'b');
LinkListPushBack(&head,'c');
LinkListPrintChar(head,"正向打印");
printf("[逆序打印]\n");
LinkListReservePrintChar(head);
}
void Test_LinkListReversePrintCharEx()
{
HEADER;
LinkNode* head;
LinkListInit(&head);
LinkListReservePrintCharEx(head);
LinkListPushBack(&head,'a');
LinkListPushBack(&head,'b');
LinkListPushBack(&head,'c');
LinkListPrintChar(head,"正向打印");
printf("[逆序打印]\n");
LinkListReservePrintCharEx(head);
}
/*主函数*/
int main()
{
Test_LinkListInit();
Test_LinkListPushBack();
Test_LinkListPopBack();
Test_LinkListPushFront();
Test_LinkListPopFront();
Test_LinkListInsert();
Test_LinkListInsertBefore();
Test_LinkListInsertBeforeEx();
Test_LinkListFind();
Test_LinkListErase();
Test_LinkListRemove();
Test_LinkListRemoveAll();
Test_LinkListRemoveAllEx();
Test_LinkListEmpty();
Test_LinkListSize();
Test_LinkListReversePrintChar();
Test_LinkListReversePrintCharEx();
return 0;}