数据结构_链表
链表的定义
链表是物理存储单元上非连续的、非顺序的存储结构,数据元素的逻辑顺序是通
过链表的指针地址实现,每个元素包含两个结点,一个是存储元素的数据域 (内
存空间),另一个是指向下一个结点地址的指针域。根据指针的指向,链表能形
成不同的结构,例如单链表,双向链表,循环链表等。
链表的分类
单向链表
创建一个节点
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;
}
}
}
}
- 先定义一个指针去指向我们创建的链表,由于物理地址不连续,且可以任意的删减,所以使用的堆空间。 malloc申请、free释放
- 先定义节点,为其节点的数据域指针分配指定的存储类型的空间,再做内存拷贝
malloc申请 free 释放 mencpy 内存拷贝