C语言链表学习

何为链表?

链表是一种常见的基础数据结构,结构体指针在这里得到了充分的利用。
链表可以动态的进行存储分配,也就是说,链表是一个功能极为强大的数组,他可以在节点中定义多种数据类型,还可以根据需要随意增添,删除,插入节点。
链表都有一个头指针,一般以head来表示,存放的是一个地址。链表中的节点分为两类,头结点和一般节点,头结点是没有数据域的。链表中每个节点都分为两部分,一个数据域,一个是指针域。
链表就如同车链子一样,head指向第一个元素:第一个元素又指向第二个元素;……,直到最后一个元素,该元素不再指向其它元素,它称为“表尾”,它的地址部分放一个“NULL”(表示“空地址”),链表到此结束。
作为有强大功能的链表,对他的操作有许多,eg:链表的创建,修改,删除,插入,输出,排序,反序,清空链表的元素,求链表的长度等等。

链表的特点

n个节点离散分配
每一个节点之间通过指针相连
每一个节点有一个前驱节点和一个后继节点
首节点没有前驱节点,尾节点没有后继节点
为何要创建链表
malloc在没有连续内存空间的时候分配会失败
解决方案:
不要一次性开辟一块连续的存储空间, 每次少开辟一点
最后利用指针将所有开辟的小的存储空间链接在一起, 组成一个整体

链表的创建

创建链表须知
首节点: 存放第一个有效数据的节点
头节点: 在单链表的第一个结点之前附设一个结点,它没有直接前驱,称之为头结点,头结点的数据域可以不存储任何信息(也可以用来存储链表的节点数),指针域指向第一个节点(首节点)的地址。头结点的作用是使所有链表(包括空表)的头指针非空
头指针: 指向头节点的指针
尾节点: 存放最后一个有效数据的节点
尾指针: 指向尾节点的指针
在这里插入图片描述
建立创建链表需要的结构体

typedef struct link{
int data; //数据域
struct link *next; //指针域
} Link;

数据域的内容自己设置,指针域存放下一个节点的地址。
malloc函数
其函数原型如下:
void *malloc(unsigned int size);
这个函数返回的是个void类型指针,所以在使用时应注意强制类型转换成需要的指针类型。
free函数
其函数原型如下:
void free(void *p);
这个函数是用来释放指针p作指向的内存区。
空链表
只有一个头指针和头结点,并且头结点没有下一个结点且没有数据

Link *createLink();
int main()
{
    Link *head = createLink();
    return 0;
}
Link*createLink()
{
    //创建一个头结点
    Link *head= (Link *)malloc(sizeof(Link));
    //可能分配失败
    if(head == NULL) exit(-1);
    //下一个节点赋值为空
    head -> next = NULL;
    return head;
}

单向不循环链表

链表头插法
1.把新结点的下一个结点指向头结点的下一个结点
2.把头结点的下一个结点指向新结点
链表数据打印

void printList(Link * head)
{
    Link *cur = head;
    while(cur->next != NULL)
    {
        cur = cur -> next;
        printf("%d\n",cur -> data);
    }
}

头插法代码

#include <stdio.h>
#include <stdlib.h>
typedef struct link
{
    int data;
    struct link *next;
} Link;
void printList(Link * head);
Link *createList_TC();
int main()
{
    //创建头结点和头指针
    Link *head = createList_TC();
    //打印数据
    printList(head);
    return 0;
}
/**
 * @brief createList 头插法创建动态链表,先进后出
 * @return 返回头指针
 */
Link *createList_TC()
{
    //创建头指针和头结点
    Link *head = (Link *)malloc(sizeof(Link));
    if(head == NULL) exit(-1);
    head -> next = NULL;
    //用一个变量表示循环条件
    //提示用户输入要保存的数据
    int num = 0;
    printf("输入保存的数据,输入1结束\n");
    scanf("%d",&num);
    while(1 != num)
    {
        Link *link= (Link *)malloc(sizeof(Link));
        //保存数据
        link-> data = num;
        //1.把新结点的下一个结点指向头结点的下一个结点
        link-> next = head -> next;
        //2.把头结点的下一个结点指向新结点
        head -> next = link;
        printf("输入保存的数据,输入1结束\n");
        scanf("%d",&num);
    }
    return head;
}

链表尾插法
1.定义变量记录最后一个结点
2.让新结点成为上一个结点的下一个结点
3.把新结点作为下一个结点的上一个结点
尾插法代码

#include <stdio.h>
#include <stdlib.h>
typedef struct link
{
    int data;
    struct link *next;
} Link;
Link *createList_WC();
void printList(Link *head);
int main()
{
    Link *head = createList_WC();
    printList(head);
    return 0;
}
/**
 * @brief createList 动态链表尾插法,先进先出
 * @return 返回头指针
 */
Link *createList_WC()
{
    //1.创建头结点和头指针
    Link *head = (Link*)malloc(sizeof(Link));
    //分配失败
    if(head == NULL) exit(-1);
    head->next = NULL;

    //定义变量控制循环
    int num = 1;
    printf("输入保存的数据,输入1结束:\n");
    scanf("%d",&num);
    //1.定义变量记录最后一个结点
    Link *cur = head;//一定要放在最外面,否则会引起值覆盖
    while(1 != num)
    {
        Link *link = (Link*)malloc(sizeof(Link));
        link->data = num;
        link->next = NULL;
        //让新结点的上一个结点的下一个结点指向新结点
        cur->next = link;
        //把新结点作为下一个结点的上一个结点
        cur = link;
        printf("输入保存的数据,输入1结束:\n");
        scanf("%d",&num);
    }
    return head;
}

单向循环链表

单向循环链表和普通的链表区别在于尾节点的next指向头节点,使得链表头尾相连,构成循环链表。
一般动态链表创建完成之后再插入节点基本都是使用头插法的方式,没有使用尾插法,这是单向链表的限制,第一没有办法直接取得尾节点的地址(非循环),当然在循环链表中是可以定义一个end节点为尾节点,可以直接取得尾节点地址,这就为非迭代取地址插入链表,直接在链表最后插入节点提供了可能性,当然应为C语言的参数传递问题,在子涵中执行插入时,end节点应为全局变量,从而使得可以变更end节点的地址。

void createEmpty_XH();
void insertLink_WC(int data);
Link *head,*end;//设为全局变量,暂未分配内存空间
int main()
{
    createEmpty_XH();//创建循环空链表
    int n=0;
    for(;n<4;n++)
    {
        insertLink_WC(n);//在链表尾端插入节点和数据
    }
    printList(head);
    return 0;
}
void printList(Link *head)//节点数保存在head.data中
{
    Link *cur = head;
    int n=0;
    printf("保存了%d组数据\n",head -> data);
    for (;n<head->data;n++)//首先判断不是空链表
    {
        cur = cur -> next;
        printf("%d\n",cur -> data);
    }
}
/**
 * @brief createEmpty 创建循环空链表
 * @return 链表的头指针
 */
void createEmpty_XH()
{
    //2.创建一个空节点, 并且赋值给头指针
    head = (Link *)malloc(sizeof(Link));
    if(head == NULL) exit(-1);
    end = (Link *)malloc(sizeof(Link));
    if(end == NULL) exit(-1);

    head->next = end;//首尾相连
    end->next=head;  
    head->data=0;
}
/**
 * @brief insertLink_WC插入节点
 * @param data 插入节点的数据
 * //此时,end要做为全局变量,或者该函数在main中直接实现(不以子函数的形式调用)
 */
void  insertLink_WC(int data)
{
    Link *link = (Link *)malloc(sizeof(Link)); // 创建一个新的节点
    if(link == NULL) exit(-1);
    Link *cur=end->next;
    cur->data++;        //head.data++

    end->data = data;   // 将数据保存到新节点中
    link->next=end->next;     //保存end.next的地址到中间节点
    cur=link;           //保存新节点地址到中间节点
    link=end;           //end的地址赋值给新节点
    end=cur;            //中间节点地址赋值给end节点
    link->next=end;     //新节点之后是end节点
}

链表常用函数封装总(head,end已设置为全局变量)

/**
 * @brief createList_TC 动态链表头插法,先进后出
 * @return 返回头指针
 */
void createList_TC()
{
    //1.创建头指针和头结点
    head = (Link *)malloc(sizeof(Link));
    if(head == NULL) exit(-1);
    head -> next = NULL;
    head->data=0;
    //1.用一个变量表示循环条件
    //2.提示用户输入要保存的数据
    int data = 0;
    printf("输入想要保存的数据,输入1结束\n");
    scanf("%d",&data);
    while(1!= data)
    {
        Link *link= (Link *)malloc(sizeof(Link));
        //保存数据
        link-> data = data;
        //1.把新结点的下一个结点指向头结点的下一个结点
        link-> next = head -> next;
        //2.把头结点的下一个结点指向新结点
        head -> next = link;
        head->data++;
        printf("输入想要保存的数据,输入1结束\n");
        scanf("%d",&data);
    }
}
/**
 * @brief createList_WC 动态链表尾插法,先进先出
 * @return 返回头指针
 */
void createList_WC()
{
    //1.创建头结点和头指针
    head = (Link*)malloc(sizeof(Link));
    //分配失败
    if(head == NULL) exit(-1);
    head->next = NULL;
    head->data=0;
    //定义变量控制循环
    int data = -1;
    printf("请输入你想要保存的数据:\n");
    scanf("%d",&data);
    //1.定义变量记录最后一个结点
    Link *cur = head;//一定要放在最外面,否则会引起值覆盖
    while(-1 != data)
    {
        Link *link = (Link*)malloc(sizeof(Link));
        link->data = data;
        link->next = NULL;
        //2.让新结点的上一个结点的下一个结点指向新结点
        cur->next = link;
        //3.把新结点作为下一个结点的上一个结点
        cur = link;
        head->data++;
        printf("请输入你想要保存的数据:\n");
        scanf("%d",&data);
    }
}
/**
 * @brief createEmpty 创建循环空链表
 * @return 链表的头指针
 */
void createEmpty_XH()
{
    //2.创建一个空节点, 并且赋值给头指针
    head = (Link *)malloc(sizeof(Link));
    if(head == NULL) exit(-1);
    end = (Link *)malloc(sizeof(Link));
    if(end == NULL) exit(-1);

    head->next = end;//首尾相连
    end->next=head;  
    head->data=0;
}
void createEmpty()
{
    //创建一个空节点, 并且赋值给头指针
    head = (Link *)malloc(sizeof(Link));
    if(head == NULL) exit(-1);
    head->next = NULL;
    head->data=0;
}
/**
 * @brief insertLink_TC插入节点
 * @param data 插入节点的数据
 */
void  insertLink_TC(int data)
{
    Link *link = (Link *)malloc(sizeof(Link)); // 1.创建一个新的节点
    if(link == NULL) exit(-1);
    link->data = data;   // 2.将数据保存到新节点中
    // 3.进行插入
    // 3.1让新节点的下一个节点 等于 头节点的下一个节点
    link->next = head->next;
    // 3.2让头结点的下一个节点 等于 新节点
    head->next = link;
    head->data++;
}
/**
 * @brief insertLink_WC插入节点
 * @param data 插入节点的数据
 * //此时,end要做为全局变量,或者该函数在main中直接实现(不以子函数的形式调用)
 */
void  insertLink_WC(int data)
{
    Link *link = (Link *)malloc(sizeof(Link)); // 创建一个新的节点
    if(link == NULL) exit(-1);
    Link *cur=end->next;
    cur->data++;        //head.data++

    end->data = data;   // 将数据保存到新节点中
    link->next=end->next;     //保存end.next的地址到中间节点
    cur=link;           //保存新节点地址到中间节点
    link=end;           //end的地址赋值给新节点
    end=cur;            //中间节点地址赋值给end节点
    link->next=end;     //新节点之后是end节点
}
/**
 * @brief insertLink插入节点
 * @param front 在第几个节点后插入节点
 * @param data  插入节点的数据
 */
int insertLink(int front,int data)
{
    if(front<(head->data+1))
    {
        int count=0;
        Link *link,*cur=head;
        link = (Link *)malloc(sizeof(Link)); //创建一个新的节点
        if(link == NULL) exit(-1);
        link->data = data;   //将数据保存到新节点中
        //进行插入
        for(;count<front;count++) cur=cur->next;
        link->next = cur->next;
        cur->next = link;
        head->data++;
        return 1;
    }
    else return 0;
}
/**
 * @brief insertData插入数据
 * @param front 插入数据的节点,是第几个
 * @param data  插入节点的数据
 */
int insertData(int front,int data)
{
    if(front<(head->data+1))
    {
        Link *cur=head;
        int count=0;
        for(;count<front;count++) cur=cur->next;
        cur->data = data;   //将数据插入到节点中
        return 1;
    }
    else return 0;
}
/**
 * @brief destroyList 销毁链表
 * @param head 链表的头指针
 */
void destroyList(Link *head)
{
    Link *cur = NULL;
    while(head != NULL)
    {
        cur = head->next;
        free(head);
        head = cur;
    }
}
/**
 * @brief findLink 查找指定节点
 * @param key 需要查找的key
 * @return 符合要求的节点, 如果找不到返回NULL
 */
Link *findLink(int key)
{
    Link *cur = head->next; //注意点:头结点不需要查找
    int count=0;
    while(count<head->data)
    {   // 判断当前节点保存的值是否是要查找的值
        if(cur->data == key) return cur;
        else cur = cur->next;
    }
    return NULL;
}
/**
 * @brief deleteLink 删除指定节点
 * @param head 链表的头指针
 * @param link 需要删除的节点
 */
void deleteLink(Link *link)
{
    Link *cur=head;
    //找到需要删除节点的上一个节点
    while(cur->next != link) cur = cur->next;
    cur->next = link->next;
    free(link);    // 3.删除对应节点
}

link->next = NULL和link!=NULL的区别:
while(link->next != NULL) 循环结束时,此时link的位置是尾节点的位置,但如果用于输出函数的判断条件,则尾节点的数据不会输出。
while(link!=NULL) 循环结束时, 此时link指向的位置为尾节点的下一个节点,因为没有申请内存空间,所以是一个未知的区域。

  • 1
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
c语言链表的基本操作包括创建链表、插入元素、删除元素、读取链表和销毁链表。在给出具体操作之前,我先解释一下引用内容中的代码。 引用\[1\]中的代码是作者在学习数据结构时遇到的问题和解决方法。作者通过重新学习C语言和指针相关的知识,成功地理解了指针的概念,并能够使用C语言实现数据结构中的算法。 引用\[2\]中的代码是作者在解决链表内存访问冲突问题时采用的二级指针的方法。作者通过使用二级指针,解决了链表创建后内存访问冲突的问题。 引用\[3\]中的代码是一个主函数,用于测试链表的基本操作功能。其中包括创建链表、读取链表、插入元素、删除元素和销毁链表等操作。 现在我来回答你的问题,c语言链表的基本操作如下: 1. 创建链表:创建一个结点为head的链表。可以通过申请内存空间来创建链表,并将结点的指针赋值给head。 2. 插入元素:在链表中指定的结点前插入元素e。可以通过遍历链表找到指定的结点,然后创建一个新的结点,并将新结点的指针指向指定结点的下一个结点,再将指定结点的指针指向新结点。 3. 删除元素:删除链表中指定的结点。可以通过遍历链表找到指定的结点,然后将指定结点的前一个结点的指针指向指定结点的下一个结点,再释放指定结点的内存空间。 4. 读取链表:读取链表的数据域并打印。可以通过遍历链表,依次读取每个结点的数据域,并将其打印出来。 5. 销毁链表:释放为链表申请的内存空间。可以通过遍历链表,依次释放每个结点的内存空间。 以上就是c语言链表的基本操作。希望对你有帮助! #### 引用[.reference_title] - *1* *2* *3* [C语言实现链表的基本操作(超详细注释)](https://blog.csdn.net/weixin_63069485/article/details/123784826)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v91^control_2,239^v3^insert_chatgpt"}} ] [.reference_item] [ .reference_list ]

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值