数据结构_链表

数据结构_链表

链表的定义

链表是物理存储单元上非连续的、非顺序的存储结构,数据元素的逻辑顺序是通
过链表的指针地址实现,每个元素包含两个结点,一个是存储元素的数据域 (内
存空间),另一个是指向下一个结点地址的指针域。根据指针的指向,链表能形
成不同的结构,例如单链表,双向链表,循环链表等。

链表的分类

单向链表

在这里插入图片描述
创建一个节点

struct node
{
    void *data;//数据域
    struct node *next;//指针域:指向下一个节点地址
};
typedef struct node node_t;

链表头结点不存放任何数据:
原因:有使用data == NULL判读它为头结点
单向链表尾结点的next指向NULL
原因:有使用next == NULL 判读它这个结点为尾结点

链表的创建

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

/******************************
 * 函数名:node_t* node_creat();
 * 功能:链表的创建
 * 参数:无
 * 返回值:
 *      成功:返回链表的首地址
 *      失败:返回NULL
 * ****************************/
node_t* node_creat()
{
    node_t *head_temp = (node_t *)malloc(sizeof(node_t));
    printf("%lu\n",sizeof(head_temp));
    if(head_temp == NULL)
    {
        printf("%s:%d:链表头节点空间申请失败\n",__func__,__LINE__);
        return NULL;
    }
    head_temp->data = NULL; //用于判断该节点为头节点
    head_temp->next = NULL; //用于判断该节点为尾节点
 
    free(head_temp);
    return head_temp;

}

误区解决:
指针在64位机中占8个字节,他要能存储本位所有的内存地址 指针是存储地址单元的变量
链表和数组都是存放在堆中;
链表中的每一个结点都是同一种结构类型。
在这里插入图片描述

链表的插入

  • 尾插
/******************************
 * 函数名:int node_inset_end(node_t* head_ptr,const void *data_ptr,int size);
 * 功能:链表的插入:尾插
 * 参数:
 *      head_ptr:链表的表头首地址
 *      data_ptr:要插入的数据的首地址
 *      size:要插入的数据的大小
 * 返回值:
 *      成功:0
 *      失败:返回node_inset_error错误码
 * ****************************/
int node_inset_end(node_t* head_ptr,const void *data_ptr,int size)
{
    if(head_ptr == NULL)
    {
        return node_inset_error1;   //链表的表头首地址传入错误,head_ptr == NULL
    }

    if(data_ptr == NULL)
    {
        return node_inset_error2;   //要插入的数据的首地址传入错误,data_ptr == NULL
    }

    if(size <= 0)
    {
        return node_inset_error3;  //要插入的数据的大小传入错误,size <= 0
    }

    node_t *node_new = (node_t*)malloc(sizeof(node_t));
    if(node_new == NULL)
    {
        return node_inset_error4;   //新节点空间申请失败
    }

    node_new->data = malloc(size);
    if(node_new->data == NULL)
    {
        free(node_new); //把前面申请的节点空间释放
        return node_inset_error5;   //新节点数据域空间申请失败
    }

    memcpy(node_new->data,data_ptr,size);   //内存拷贝

    //找到链表尾部节点
    node_t *temp_node =head_ptr;    //先存储节点
    while(temp_node->next != NULL)
    {
        temp_node = temp_node->next;    //指向自己的下一节点
    }

    temp_node->next = node_new; //将新节点链接到尾部节点
    node_new->next = NULL;      //新节点承担尾部节点
    return node_inset_error0;
}

链表的遍历

/******************************
 * 函数名:node_show(node_t* head_ptr,void(*show)(const void*));
 * 功能:链表的遍历
 * 参数:
 *      head_ptr:链表的表头首地址
 *      show:数据显示回调函数首地址
 * 返回值:无
 * ****************************/
void node_show(node_t* head_ptr,void(*show)(const void*))
{
    if(head_ptr == NULL || show == NULL)
    {
        return ;
    }

    node_t *temp_node = head_ptr;
    //遍历节点
    while(temp_node != NULL)
    {
        if(temp_node->data != NULL) //判断数据域有数据
        {
            show(temp_node->data);  //调用显示函数
        }

        temp_node = temp_node->next;    //指向自己的下一节点        
    }
}

链表的释放

/******************************
 * 函数名:void node_free(node_t** head_ptr);
 * 功能:链表的释放
 * 参数:
 *      head_ptr:链表的表头首地址的指针的地址
 * 返回值:无
 * ****************************/
void node_free(node_t** head_ptr)
{
    if(head_ptr == NULL)
    {
        return ;
    }

    if(*head_ptr == NULL)
    {
        return ;
    }

    node_t *temp_node = *head_ptr;
    node_t *node_next = NULL;

    int node_count = 0;  //统计释放了多少节点
    int data_count = 0;  //统计放释放多少数据
    while(temp_node != NULL)
    {
        node_next = temp_node->next;    //存储当前节点的下一节点
        if(temp_node->data != NULL)     //判断当前节点书否有数据域
        {
            data_count++;
            free(temp_node->data);      //有数据域就释放数据域空间
        }
        free(temp_node);    //释放节点空间
        node_count++;
        temp_node = node_next;
    }

    printf("成功释放:%d的节点\t%d的数据域\n",node_count,data_count);

    *head_ptr = NULL;   //更改外部实参链表指针的指向呢
}

链表的查找

/******************************
 * 函数名:node_t *node_find(node_t* head_ptr,const void *data_find,int (*cmp)(const void *,const void *));
 * 功能:链表的查找
 * 参数:
 *      head_ptr:链表的表头首地址的指针的地址
 *      data_find:要查找的数据首地址
 *      cmp:用户自定义的比较函数首地址
 * 返回值:
 *      成功:查找的到的节点首地址
 *      失败:NULL
 * ****************************/ 
node_t *node_find(node_t* head_ptr,const void *data_find,int (*cmp)(const void *,const void *))
{
    if(head_ptr == NULL || data_find == NULL || cmp == NULL)
    {
        return NULL;
    }

    node_t *temp_node = head_ptr;
    while(temp_node != NULL)
    {
        if(temp_node->data != NULL)
        {
            if(cmp(temp_node->data,data_find) == 0)
            {
                return temp_node;
            }
        }

        temp_node = temp_node->next;
    }

    return NULL;
}

链表的更新

/******************************
 * 函数名void node_updata(node_t *node_ptr,const void *data_updata,void (*updata)(void *,const void*));
 * 功能:链表的更新
 * 参数:
 *      node_ptr:要修改的节点首地址
 *      data_updata:修改成为什么数据的首地址
 *      updata:用户自定义修改函数的首地址
 * 返回值:无
 * ****************************/
void node_updata(node_t *node_ptr,const void *data_updata,void (*updata)(void *,const void*))
{
    if(node_ptr == NULL || data_updata == NULL  || updata == NULL)
    {
        return ;
    }
    updata(node_ptr->data,data_updata); //调用用户传入的自定义修改函数
}

链表的删除

/******************************
 * 函数名void node_delete(node_t* head_ptr,node_t *node_ptr);
 * 功能:链表节点的删除
 * 参数:
 *      node_ptr:链表的表头首地址的指针的地址
 *      node_ptr:要删除的节点首地址
 * 返回值:无
 * ****************************/
void node_delete(node_t* head_ptr,node_t *node_ptr)
{
    if(head_ptr == NULL || node_ptr == NULL)
    {
        return ;
    }

    if(head_ptr == node_ptr)    //不能删除头节点,头节点不存数据
    {
        return ;
    }

    node_t *temp_node = head_ptr;
    while(temp_node->next != node_ptr)
    {
        temp_node = temp_node->next;
    }

    temp_node->next = node_ptr->next;

    free(node_ptr->data);   //释放数据域
    free(node_ptr);         //释放节点域
}

链表的排序

void node_sort(node_t* head_ptr,int (*cmp)(const void *,const void *))
{
    if(head_ptr == NULL || cmp == NULL || head_ptr->data != NULL)
    {
        return ;
    }

    //作外层循环
    node_t *temp_i = head_ptr->next;    //不想使头节点的数据域发生更改
    node_t *temp_j = NULL;      //内层循环
    node_t *temp_end = NULL;    //作遍历末尾判断

    void *temp = NULL;  //做零时存储数据域地址,便于两个数据域地址交换
    while(temp_i->next != temp_end)
    {
        temp_j = temp_i;    //一直使用有效表头地址保存
        while(temp_j->next != temp_end)
        {
            if(cmp(temp_j->data,temp_j->next->data) > 0)
            {
                temp = temp_j->data;
                temp_j->data = temp_j->next->data;
                temp_j->next->data = temp;
            }

            temp_j = temp_j->next;  //往后移动
            if(temp_j->next == temp_end)    //判断当前节点的下一节点是否是比较好的末尾节点
            {
                temp_end = temp_j;  //如果是,则将该节点作为新的比较好的末尾节点
                break;
            }
        }

    }
}
  1. 先定义一个指针去指向我们创建的链表,由于物理地址不连续,且可以任意的删减,所以使用的堆空间。 malloc申请、free释放
  2. 先定义节点,为其节点的数据域指针分配指定的存储类型的空间,再做内存拷贝
    malloc申请 free 释放 mencpy 内存拷贝
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值