众所周知,链表是数据结构的重要知识之一,今天学习了链表的基础知识(部分)
以下,是链表歌基础功能的实现函数
首先先定义一个用作例子的结构体:
我将其放在了头文件里(linklist.h)
typedef char datatype;
typedef struct Node
{
union
{
datatype data;
int len;
};
struct Node *next;
}Linklist;
定义一个链表头创建函数,并在主函数内定义一个链表:L
//创建
Linklist *list_create()
{
Linklist* L = (Linklist*)malloc( sizeof(Linklist) );
if( NULL == L)
{
printf("创建失败\n");
return NULL;
}
//初始化
L->len = 0;
L->next = NULL;
printf("创建成功\n");
return L;
}
/**********************************/
/*******以下是主函数内容************/
Linklist *L = list_create();
if(NULL == L)
{
return -1;
}
接下来是功能段的辅助函数
首先是申请节点,之后添加链表元素时可直接调用
//节点申请函数
Linklist* node_add(datatype e)
{
Linklist *p = (Linklist*)malloc(sizeof(Linklist));
if(NULL == p)
{
printf("节点申请失败\n");
return NULL;
}
//数据存放
p->data = e;
p->next = NULL;
}
然后是判断是否为空,因为链表不像顺序表,它没有存储上限(除非内存耗尽),故无需进行 ”判满“操作
//判空
int list_empty( Linklist *L )
{
if(L->next == NULL)
{
printf("链表为空\n");
return 1;
}else
{
return 0;
}
}
遍历链表函数,用于检验代码段是否运行成功
//遍历
void list_show(Linklist *L)
{
if(NULL == L || list_empty(L)) //先判断非法
{
printf("表空或表非法,遍历失败\n");
return;
}
//遍历
printf("链表元素为:");
Linklist *q = L->next;
while(q != NULL)
{
printf("%c\t",q->data);
q = q->next;
}
printf("\n");
}
现在开始实现各种功能:
一:增
1:头插法:
输入为:’A‘ ,'B' , ' C'
输出为:’C‘ ,’B‘,’A‘
//头插
int list_intsrt_head(Linklist *L ,datatype e)
{
if(NULL == L)
{
printf("链表不合法\n");
return -1;
}
//申请节点
Linklist *p = node_add(e);
//完成头插
p->next = L->next;
L->next = p;
//表的变化
L->len++;
printf("成功\n");
}
2:尾插法(
输入为:’A‘ ,'B' , ' C'
输出为:’A‘ ,'B' , ' C'
//尾插
int list_intsrt_tail(Linklist *L, datatype e)
{
//判空
if(NULL == L)
{
printf("所给表不合法\n");
return -1;
}
//申请节点
Linklist *p = node_add(e);
//遍历指针指向最后一个结点
Linklist *q = L;
while(q->next != NULL)
{
q = q->next;
}
q->next = p;
//表的变化
L->len++;
}
3:指定任意位置插入(重点)
//任意位置插入(重点)
int list_intsrt_pos(Linklist *L, int pos, datatype e)
{
//逻辑判断
if(NULL==L || pos<1 || pos>L->len+1)
{
printf("表非法,无法插入\n");
return -1;
}
//申请节点
Linklist *p = node_add(e);
//查找要插入位置的前区节点
Linklist *q = find_node(L, pos-1);
//插入逻辑
p->next = q->next;
q->next = p;
//表的变化
L->len++;
printf("插入成功\n");
}
二:删
要点:注意删除后要释放内存,防止内存泄漏
4:从头开始删除
//头删
int delete_head(Linklist *L)
{
//判定逻辑
if(NULL == L || list_empty(L))
{
printf("删除失败\n");
return -1;
}
Linklist *p = L->next; //标记
L->next = p->next; //也可以写成L->next->next
free(p); //释放内存
p = NULL;
//表的变化
L->len--;
printf("删除成功\n");
}
5:从链表尾开始删除
//尾删
int delete_tail(Linklist *L)
{
//判定逻辑
if(NULL == L || list_empty(L))
{
printf("表违法,不存在表或表为空\n");
return -1;
}
//尾删
Linklist *p = L;
while(p->next->next != NULL)
{
p = p->next;
}
Linklist *q = p->next;
free(q);
p->next = NULL;
q = NULL;
//表的变化
L->len--;
printf("删除成功\n");
}
6:从任意位置删除(重点)
//任意位置删除
int delete_pos(Linklist *L, int pos)
{
//判定逻辑
if(NULL == L || list_empty(L))
{
printf("表违法,不存在表或表为空\n");
return -1;
}
//任意位置删除
//找到要删位置的前区
Linklist *q = find_node(L, pos-1);
//删除逻辑
Linklist *p = q->next;
q->next = p->next;
free(p);
//表的变化
L->len--;
}
三:查
7:按位置查找,返回查找到的节点地址
//按位置查找,返回查找到的节点地址
Linklist *find_node(Linklist *L, int pos)
{
//判断
if(NULL == L || pos<0 || pos>L->len)
{
printf("查找失败\n");
return NULL;
}
//查找节点
Linklist *q = L;
for(int i=1; i<=pos; i++ )
{
q = q->next;
}
return q; //返回找到的节点
}
8:按值查找,返回查找到的位置编号
//按值查找,返回查找到的位置
int list_search_value(Linklist *L, datatype e)
{
//判定逻辑
if(NULL == L || list_empty(L))
{
printf("查找失败\n");
return -1;
}
//查找逻辑
int index = 1;
Linklist *q = L->next;
for(int i=1; i<L->len; i++)
{
if(q->data == e)
{
return i;
}
}
return 0;
}
四:改
9:按值修改
//按值修改
int list_update_value(Linklist *L, datatype old_e, datatype new_e)
{
if(NULL == L || list_empty(L)) //先判断非法
{
printf("表空或表非法,无法修改\n");
return -1;
}
Linklist *q = L;
for(int i=0; i<L->len; i++)
{
q = q->next;
if(q->data == old_e)
{
q->data = new_e;
}
}
printf("修改完成\n");
}
10:按位置修改
//按位置修改
int list_update_pos(Linklist *L, int pos, datatype e)
{
if(NULL == L || list_empty(L)) //先判断非法
{
printf("表空或表非法,无法修改\n");
return -1;
}
Linklist *q = L;
for(int i=0; i<pos; i++)
{
q = q->next;
}
q->data = e;
printf("修改完成\n");
}
五:销毁表,防止内存泄漏
//销毁表
void list_delete_all(Linklist *L)
{
if(NULL == L)
{
printf("表不合法");
}
for(int i=0; L->next != NULL; i++)
{
delete_head(L);
}
free(L);
printf("\n表已删除完毕\n");
}
六:拓展,链表翻转(类似于数组转置)
因为链表不同于数组,单向链表不能让指针从后往前移动,故要采取别的办法
方案1:采用双循环
该方案缺点明显,其时间复杂度高
Linklist *p = L->next;
for(i=1; i<(L->len)/2; i++) //p从第一个值开始移动
{
Linklist *q = L->next;
for(j=0; j<=L->len-i; j--); //p每移动一次,q从第一位往后移动到倒数第i位
{
q = q->next;
}
{ /*q与p交换变量,代码就不过多赘述*/ }
p = p->next;
}
//此代码仅用于展现思路,不保证没有一些细节bug
于是我想到了第二个思路
方案2:数组法
用一个数组,存储每一位链表元素,再用头插法,一个一个重新赋值给链表,即可实现倒置。但是,这个方案问题还要严重。因为数组是有限大小,而链表是无限长度,不可能这样去一一对应的存储。其次,此方案还会消耗双倍的存储空间。故直接抛弃。
正当我百思不得解时,老师点醒了我。最终,在老师的提醒下,第三种链表特色的方案孕育而生。我们首先用一个指针p指向链表的头L,然后让链表头与其后的元素断开,再用指针p将其对应的元素用头插法存入L之后,即可实现倒置,只是要注意,要随时释放原本的废弃空间,防止内存泄漏。
方案3
//链表反转
void list_reverse(Linklist *L)
{
Linklist *q = L->next; //定义指针指向第一个元素
Linklist *S = L->next; //定义头指针
L->next = NULL; //断开链表头
while(S != NULL)
{
q = S;
S = S->next;
q->next = L->next; //用头插法插入
L->next = q;
}
}