【不带头节点的单链表】


前言

单链表分为:带头节点的单链表、不带头节点的单链表
链表不存在 ”满“ 这个概念,所以凡是链表的结构体中,其函数实现时均不需要执行判满操作


一、结构体设计

不带头节点的单链表,结构体应该怎样设计?

// 不带头节点的结构体设计
typedef int ELEM_TYPE;

// 有效数据节点结构体设计
typedef struct Node
{
	ELEM_TYPE data;  // 数据域 -- 保存有效值
	struct Node* next;  // 指针域 -- 保存下一个有效数据元素的地址
}Node, PNode;

二、函数实现

1. 初始化

由于不带头节点的单链表不存在头节点,因此初始化只需要对头指针进行赋初值,将其置为NULL即可
代码如下

//初始化函数(对于头指针进行赋初值)
void no_head_Init_list(struct Node **phead)//struct Node ** == PNode *
{
	//assert  phead!=NULL

	*phead = NULL;
	//修改一个指针,如果没有有效节点的话,将其指针指向NULL
	//这个指针保存第一个有效数据节点的地址   phead   *phead(保存的第一个有效数据的地址)   (*phead).next   
}

2. 购买一个新节点

因为链表不存在满这个概念,因此无需判满操作
从堆区申请一个节点大小的内存,并将申请好内存的地址返回。
代码如下

//购买一个新节点
struct Node *no_head_BuyNode(ELEM_TYPE val)
{
	struct Node*pnewnode = (struct Node*)malloc(1 * sizeof(struct Node));
	assert(pnewnode != NULL);
	if(pnewnode == NULL)
	{
		return NULL;
	}
	pnewnode->data = val;
	pnewnode->next = NULL; 

	return pnewnode;
}

3. 头插

*phead == 第一个元素的地址
在这里插入图片描述
代码如下

//头插
bool no_head_Insert_head(PNode *phead, ELEM_TYPE val)
{
	//assert
	assert(phead != NULL);
	if(NULL == phead)
	{
		return false;
	}
	//1.创建新节点
	struct Node* pnewnode = (struct Node *)malloc(1*sizeof(struct Node));
	assert(pnewnode != NULL);
	pnewnode->data = val;
	pnewnode->next = NULL;

	//2.找到合适的插入位置 (头插函数 已经找到插入位置)

	//3.插入
	pnewnode->next = *phead;
    *phead = pnewnode;

	return true;
}

4. 尾插

  1. 正常情况下,只需要正常插入即可。
    在这里插入图片描述
    但是需要将for循环修改,p不再指向头节点,而是指向首元素的地址
    在这里插入图片描述

  2. 此时有一个问题,如果在尾插之前,单链表中已经有数据节点,则正常插入;但是在尾插之前,如果单链表是一个空链,那么尾插就可以理解为头插操作,当然,执行尾插操作时,可以直接替换成头插操作

代码如下

//尾插
bool no_head_Insert_tail(struct Node **phead, ELEM_TYPE val)
{
	//assert
	if(no_head_IsEmpty(phead))//如果我是一个空链,则执行头插函数 不要执行尾插函数
	{
		return no_head_Insert_head(phead, val);
	}
	//1.创建新节点
	struct Node* pnewnode = no_head_BuyNode(val);
	assert(pnewnode!= NULL);
	//2.找到合适的插入位置
	struct Node *p;
	for( p= *phead; p->next!=NULL; p=p->next);//这里存在一个bug:如果是空链的话,*phead == p == NULL,那么语句2:p->next会报写入异常(在上面处理一下这个bug即可)
	//此时 指针p停留在尾结点,而不是尾结点的下一个节点
	//3.插入
	pnewnode->next = p->next;
	p->next = pnewnode;

	return true;
}

5. 按位置插入

pos == 0 相当于头插,pos ==length 相当于尾插
在这里插入图片描述

代码如下

//按位置插入(pos=0 相当于头插  pos==length 相当于尾插)
bool no_head_Insert_pos(struct Node **phead, int pos, ELEM_TYPE val)
{
	//assert   phead  pos
	assert(phead != NULL);
	assert(pos >=0 && pos<=no_head_Get_length(phead));//pos == length合法  相当于尾插 但是这个位置不能用于删除
	if(pos == 0)
	{
		return no_head_Insert_head(phead, val);
	}

	//1.创建新节点
	struct Node* pnewnode = no_head_BuyNode(val);
	assert(pnewnode!= NULL);

	//2.找到合适的插入位置    
	struct Node *p = *phead;  
	for(int i=1; i<pos; i++)这里存在一个bug:如果pos = 0,p没有办法跑到合适的位置,提前处理掉即可
	{
		p=p->next;
	}

	//3.插入
	pnewnode->next = p->next;
	p->next = pnewnode;
	return true;
}

6. 头删

链表删除元素需要对其进行判空操作
代码如下

bool no_head_Del_head(struct Node **phead)
{
	//assert
	//删除操作:需要判空
	if(no_head_IsEmpty(phead)) 
	{
		return false;
	}

	struct Node *p =*phead;
	*phead = p->next;
	free(p);

	return true;
}

7. 尾删

  1. 链表删除元素需要对其进行判空操作
  2. 尾删需要申请两个临时指针p、q
    p:指向待删除节点
    q:指向待删除节点的上一个节点

【注】单链表有一个特点:可以通过上一个节点找到下一个节点
在这里插入图片描述
3. 临时指针p、q怎样找到自己合适的指向位置
第一种方案:p先通过NULL找;q再通过NULL找
第二种方案:q直接通过q-next-next判断是否为NULL来直接找到q的合适的位置;p就直接等于q->next
4. 但是申请两个临时指针,其指向可能存在异常情况:
第一种异常:空链
第二种异常:有节点存在,但是只有一个
在这里插入图片描述

代码如下

bool no_head_Del_head(struct Node **phead)
{
	//assert
	//删除操作:需要判空
	if(no_head_IsEmpty(phead)) 
	{
		return false;
	}

	struct Node *p =*phead;
	*phead = p->next;
	free(p);

	return true;
}

8. 按位置删

按位置删除元素需要额外判断删除位置是否合法,需要对特殊位置特殊处理
在这里插入图片描述

代码如下

//按位置删(pos==0 相当于头删   pos==length-1 相当于尾删(pos==length非法))
bool no_head_Del_pos(struct Node **phead, int pos)
{
	assert(phead != NULL);
	assert(pos >=0 && pos<no_head_Get_length(phead));

	if(no_head_IsEmpty(phead))
	{
		return false;
	}
	if(pos == 0)
	{
		return no_head_Del_head(phead);
	}

	struct Node *q = *phead;
	for(int i=1; i<pos; i++)
	{
		q = q->next;
	}
	struct Node *p = q->next;

	q->next = p->next;
	free(p);

	return true;
}

总结

  1. 不带头节点的单链表要区别于带头节点的单链表
  2. 不带头节点的单链表的结构体设计
  3. 不带头节点的函数实现

补充代码

#include <stdio.h>
  • 1
    点赞
  • 14
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值