C语言-链表

举报
DS小龙哥 发表于 2021/12/03 14:45:03 2021/12/03
【摘要】 从效率上来讲,数组的空间是连续的,查询、读取数据数组占优势;链表的优势在于节点可以动态增加、动态删除,删除支持任意位置的节点删除。

1. 链表结构介绍

在前面章节已经学习了数组的使用,数组的空间是连续空间,数组的大小恒定的,在很多动态数据存储的应用场景下,使用不方便;而这篇文章介绍的链表结构,支持动态增加节点,释放节点,比较适合存储动态数据的应用场景,而且链表的空间是存储在堆上面的,可以动态分配,释放。从效率上来讲,数组的空间是连续的,查询、读取数据数组占优势;链表的优势在于节点可以动态增加、动态删除,删除支持任意位置的节点删除。

特点:

  1. 数组的空间是连续的,可以直接通过[]下标访问。

  2. 链表的节点是不连续的,需要通过每个节点的指针,来找到上一个节点或者下一个节点的地址。

    链表的每个节点就是一个结构体变量,节点里有一个或者两个指针,可以保存上一个节点和下一个节点的地址,方便遍历链表,删除、插入节点时定位位置。

image-20211203094458762

2. 案例: 单向链表的创建与使用

下面例子采用函数封装的形式编写,每个功能都使用子函数实现。

image-20211203095248628

image-20211203095310341

实现的功能如下:

  1. 初始化链表头
  2. 插入节点的函数(链表任意位置插入,链表尾插入)
  3. 删除节点的函数(链表任意位置删除、链表尾删除)
  4. 遍历链表,输出链表里的所有信息
#include <stdio.h>
#include <stdlib.h>

//定义链表节点的结构体
struct app
{
    int a;
    struct app *next; //能保存结构体的地址
};

struct app *list_head=NULL;  //链表的头指针

void list_print(struct app *head);
struct app *list_HeadInit(struct app *head);
void list_add(int a,struct app *head);
void list_del(int a,struct app *head);

int main()
{
    //1. 初始化链表头
    list_head=list_HeadInit(list_head);
    //2. 在链表尾插入数据
    list_add(10,list_head);
    list_add(11,list_head);
    list_add(12,list_head);
    list_add(13,list_head);
    //3. 删除节点
    list_del(11,list_head);
    //4. 输出链接节点里的数据
    list_print(list_head);
    return 0;
}

/*
函数功能: 初始化链表头--给链表头申请空间
*/
struct app *list_HeadInit(struct app *head)
{
    if(head==NULL) //没有空间
    {
        //1. 申请链表头空间
        head=malloc(sizeof(struct app));
        //2. 初始化链表头
        head->next=NULL;
    }
    return head;
}

/*
函数功能: 在链表尾插入数据
int a  插入的数据值
struct app *head  链表头
*/
void list_add(int a,struct app *head)
{
    struct app *new_p=NULL;
    struct app *next_p=head;
    struct app *tmp_p; //保存上一个节点的地址
    //1.申请空间并给空间成员赋值
    new_p=malloc(sizeof(struct app));
    new_p->a=a;
    new_p->next=NULL;

    //2. 找到链表尾
    while(next_p!=NULL)
    {
        tmp_p=next_p;
        next_p=next_p->next; //指针指向下一个节点
    }

    //3. 插入新节点--链接结尾
    tmp_p->next=new_p;
}

/*
函数功能: 遍历输出链接里的所有数据
*/
void list_print(struct app *head)
{
    struct app *next_p=head;
    int cnt=0;
    if(head!=NULL)
    {
        while(next_p->next!=NULL)
        {
            next_p=next_p->next;
            printf("链表节点[%d]:a=%d\n",cnt++,next_p->a);
        }
    }
}

/*
函数功能:链表的删除--按照指定的数据删除
*/
void list_del(int a,struct app *head)
{
    struct app *next_p=head;
    struct app *tmp_p; //保存上一个节点的地址
    //1. 找到要删除的链表
    if(next_p!=NULL)
    {
        while(next_p->next!=NULL)
        {
            tmp_p=next_p; //保存上一个节点的地址
            next_p=next_p->next; //获取有效节点的地址
            if(next_p->a==a) //判断是否需要删除
            {
                tmp_p->next=next_p->next;
                free(next_p);
            }
        }
    }
}

3. 案例: 单向循环链表

代码直接在上面的案例2例子上改造的,区别就是尾结点指向了头结点而不是NULL。

#include <stdio.h>
#include <stdlib.h>

//定义链表节点的结构体
struct app
{
    int a;
    struct app *next; //能保存结构体的地址
};

struct app *list_head=NULL;  //链表的头指针

void list_print(struct app *head);
struct app *list_HeadInit(struct app *head);
void list_add(int a,struct app *head);
void list_del(int a,struct app *head);

int main()
{
    //1. 初始化链表头
    list_head=list_HeadInit(list_head);
    //2. 在链表尾插入数据
    list_add(10,list_head);
    list_add(11,list_head);
    list_add(12,list_head);
    list_add(13,list_head);
    //3. 删除节点
    list_del(11,list_head);
    //4. 输出链接节点里的数据
    list_print(list_head);
    return 0;
}

/*
函数功能: 初始化链表头--给链表头申请空间
*/
struct app *list_HeadInit(struct app *head)
{
    if(head==NULL) //没有空间
    {
        //1. 申请链表头空间
        head=malloc(sizeof(struct app));
        //2. 初始化链表头
        head->next=head;
    }
    return head;
}

/*
函数功能: 在链表尾插入数据
int a  插入的数据值
struct app *head  链表头
*/
void list_add(int a,struct app *head)
{
    struct app *new_p=NULL;
    struct app *next_p=head;
    struct app *tmp_p; //保存上一个节点的地址
    //1.申请空间并给空间成员赋值
    new_p=malloc(sizeof(struct app));
    new_p->a=a;
    new_p->next=head;

    //2. 找到链表尾
    if(head!=NULL)
    {
        if(next_p->next==head)  //表示第一次插入节点
        {
            next_p->next=new_p;
            //head ----<节点1>---head
        }
        else
        {
            while(next_p->next!=head)
            {
                next_p=next_p->next; //指针指向下一个节点
            }
            //3. 插入新节点--链接结尾
            next_p->next=new_p;
        }   
    } 
}

/*
函数功能: 遍历输出链接里的所有数据
*/
void list_print(struct app *head)
{
    struct app *next_p=head;
    int cnt=0;
    if(head!=NULL)
    {
        printf("头地址: %#x\n",next_p); //头
        printf("第一个节点地址:%#x\n",next_p->next); //下一个节点地址
        printf("第二个节点地址:%#x\n",next_p->next->next); //下下一个节点地址
        printf("第三个节点地址:%#x\n",next_p->next->next->next);
        printf("第四个节点地址:%#x\n",next_p->next->next->next->next);
    
        while(next_p->next!=head)
        {
            next_p=next_p->next;
            printf("链表节点[%d]:a=%d\n",cnt++,next_p->a);
        }
    }
}

/*
函数功能:链表的删除--按照指定的数据删除
*/
void list_del(int a,struct app *head)
{
    struct app *next_p=head;
    struct app *tmp_p; //保存上一个节点的地址
    //1. 找到要删除的链表
    if(next_p!=NULL)
    {
        while(next_p->next!=head)
        {
            tmp_p=next_p; //保存上一个节点的地址
            next_p=next_p->next; //获取有效节点的地址
            if(next_p->a==a) //判断是否需要删除
            {
                tmp_p->next=next_p->next;
                free(next_p);
            }
        }
    }
}

4. 案例: 创建双向链表循环,实现插入、删除、遍历

双向链表在每个节点里新增加了一个指针,用于保存上一个节点的地址,现在的节点里一个用两个指针,一个保存上一个节点的地址,一个保存下一个节点的地址。

#include <stdio.h>
#include <stdlib.h>

//定义链表节点的结构体
struct app
{
    int a;
    struct app *next; //下一个节点地址
    struct app *prev; //前一个节点地址
};

//全局变量声明区域
struct app *list_head=NULL;  //链表的头指针

//函数声明
struct app *List_HeadInit(struct app *head);
void list_add(int a,struct app *head);
void list_print(struct app *head);
void list_del(int a,struct app *head);

int main()
{
    /*1. 初始化链表*/
    list_head=List_HeadInit(list_head);
    /*2. 添加链表节点*/
    list_add(10,list_head);
    list_add(11,list_head);
    list_add(12,list_head);
    list_add(13,list_head);
    list_add(14,list_head);
    /*3.删除指定节点*/
    list_del(12,list_head);
    /*4. 遍历输出所有节点信息*/
    list_print(list_head);
    return 0;
}

/*
函数功能: 创建链表头
*/
struct app *List_HeadInit(struct app *head)
{
    if(head==NULL)
    {
        head=malloc(sizeof(struct app));
        head->a=0;
        head->next=head;
        head->prev=head;
    }
    return head;
}

/*
函数功能: 添加数据-链表尾添加数据
*/
void list_add(int a,struct app *head)
{
    struct app *next_p=head;
    struct app *new_p=NULL;
    /*1. 申请新的节点*/
    new_p=malloc(sizeof(struct app));
    new_p->a=a;
    new_p->next=head;
    /*2. 完成新节点的添加*/
    //遍历链表
    while(next_p->next!=head)
    {
        next_p=next_p->next;
    }
    //添加新节点
    new_p->prev=next_p;
    next_p->next=new_p;
    //修改链表头的上一个节点地址
    head->prev=new_p;
}

/*
函数功能: 输出链表里的所有数据
*/
void list_print(struct app *head)
{
    struct app *next_p=head;
    int cnt=0;
    /*1. 顺向遍历*/
    while(next_p->next!=head)
    {
        next_p=next_p->next;
        printf("节点[%d]:%d\n",cnt++,next_p->a);
    }
    /*2. 反向遍历*/
    next_p=head;
    while(next_p->prev!=head)
    {
        next_p=next_p->prev;
        printf("节点[%d]:%d\n",cnt--,next_p->a);
    }
}

/*
函数功能: 删除链表里的指定节点
*/
void list_del(int a,struct app *head)
{
    struct app *next_p=head;
    struct app *tmp_p=NULL;
    while(next_p->next!=head)
    {
        tmp_p=next_p; //保存上一个节点的地址
        next_p=next_p->next;
        if(next_p->a==a)
        {
            //方式1
            tmp_p->next=tmp_p->next->next;
            tmp_p->next->prev=tmp_p;

            //方式2
            //tmp_p->next=next_p->next;
            //next_p->next->prev=tmp_p;
            
            //printf("%d\n",tmp_p->a); //11
            //printf("%d\n",tmp_p->next->a); //13
            //printf("%d\n",next_p->next->prev->a); //11
            free(next_p);
            break;
        }
    }
}
【版权声明】本文为华为云社区用户原创内容,转载时必须标注文章的来源(华为云社区)、文章链接、文章作者等基本信息, 否则作者和本社区有权追究责任。如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@huaweicloud.com
  • 点赞
  • 收藏
  • 关注作者

评论(0

0/1000
抱歉,系统识别当前为高风险访问,暂不支持该操作

全部回复

上滑加载中

设置昵称

在此一键设置昵称,即可参与社区互动!

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。