单链表的应用

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;
}





  • 22
    点赞
  • 101
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值