数据结构——链表1

众所周知,链表是数据结构的重要知识之一,今天学习了链表的基础知识(部分)

以下,是链表歌基础功能的实现函数

首先先定义一个用作例子的结构体:

我将其放在了头文件里(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;
	}
}

  • 5
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 2
    评论
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

老K殿下

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值