数据结构——无头不循环单链表

链表是顺序表的一种,它是由一个一个的结点组成,逻辑上是一种顺序表,在物理地址上看来是离散的,这些结点随机分配在内存中的各个位置,要想将他们链接起来则需要依靠我们的指针

首先:结点的结构由一个指向下一个结点的指针和数据段所组成(如下图)

typedef int ElemType;

typedef struct SLNode 
{
	struct SLNode* next;
	ElemType a;
}SLNode;

接口实现预览

void SLPrint(SLNode* plist);

void SLPushBack(SLNode** pplist, Elemtype x);

void SLPopBack(SLNode** pplist);

void SLPushFront(SLNode** pplist, Elemtype x);

void SLPopFront(SLNode** pplist);

void SLPushPosBefore(SLNode** pplist, SLNode* pos, Elemtype x);

void SLPushPosAfter(SLNode* pos, Elemtype x);

SLNode* SLFind(SLNode* pplist, Elemtype x);

int SLSize(SLNode* plist);

//删除pos之前和删除pos之后
void SLErasePosAfter(SLNode* pos);

void SLErasePosBefore(SLNode** pplist, SLNode* pos);

int SLIsEmpty(SLNode* plist);

首先我们先准备一下创建一个结点打印整个链表这两个接口,方便我们后续的使用和查看

需要注意!!新建的结点的next指针一定要置空!!

void SLPrint(SLNode* pplist) 
{
	SLNode* cur = pplist;
	while (cur) 
	{
		printf("%d->", cur->data);
		cur = cur->next;
	}
	printf("NULL\n");
}

SLNode* SLBuyNode(ElemType x) 
{
	SLNode* newnode = (SLNode*)malloc(sizeof(SLNode));
	newnode->next = NULL;
	newnode->data = x;
	return newnode;
}

尾插和尾删

1.尾插

插入分为两种情况:第一种 链表为空的   第二种 链表有元素

第一种,创建一个结点直接链接到头指针

第二种,创建一个结点,遍历整个链表找到最后一个结点,将最后一个结点的next链接到这个新的结点上

实现如下:

void SLPushBack(SLNode** pplist,Elemtype x)
{
	SLNode* newnode = BuySLNode(x);
	//如果节点为空  头指针直接等于新节点的地址
	if (*pplist == NULL) 
	{
		*pplist = newnode;
	}
	else 
	{
		//不是空节点时  创建一个新的节点 插到尾部   所以要找尾
		SLNode* cur = *pplist;
		while (cur->next!=NULL)//cur的下一个是空则停止遍历
		{
			cur = cur->next;
		}
		cur->next = newnode;
	}

2.尾删

分三种情况: 第一种 一个结点都没有什么也不干

                      第二种 只有一个结点删除该结点,将指针设置为空

                      第三种 两个或两个以上的结点,我用一个pre指针存储尾删的上一个结点,tail存储准备删除的结点,遍历一遍使tail结点是最后一个结点,让pre->next指针指向空

实现如下

void SLPopBack(SLNode** pplist)
{
	//如果为空链表,则什么也不干
	if (*pplist == NULL) 
	{
		return;
	}
	else if( (* pplist)->next==NULL)//只有一个节点
	{
		free(*pplist);
		*pplist = NULL;
	}
	else//有两个及以上的节点个数
	{
		//遍历找到要删除的最后那个节点 并且要找到他的前一个节点
		SLNode* tail = *pplist;
		SLNode* prev = NULL;
		while(tail->next)
		{
			prev = tail;
			tail = tail->next;
		}//找到了
		free(tail);
		prev->next = NULL;
	}
}

测试一下啊,尾插几个元素,打印一遍,删除几个元素再打印一遍

 头插和头删

1.头插

头插分两种情况:第一种 链表为空,则直接将头指针指向新的结点,其他什么也不干

                             第二种 链表有一个或者一个以上的结点,则需要将新结点指向头指针指向的结点,因为头指针指向的结点就是第一个结点,再将头指针指向新的结点,完成头插

注意一定要先让新结点指向原来的第一个结点,如果让头指针先指向新结点的话,就会丢失原来第一个结点

假设链表为空,他的操作和不为空其实是一样的,以下一段代码足够了

void SLPushFront(SLNode** pplist ,Elemtype x)
{
	SLNode* newnode = BuySLNode(x);
	newnode->next = *pplist;
	*pplist = newnode;
}

2.头删

头删分为两种情况:第一种 空链表,我们什么也不敢,或者你可以给个断言之类的看个人喜好

                                第二种 非空链表,用一个结点指针存储要删除的结点,这样就不会丢失结点地址了,再把头结点指向要删除的结点的下一个结点

 代码段如下

void SLPopFront(SLNode** pplist) 
{
	//如果是空节点 就什么也不干
	if (*pplist == NULL) 
	{
		return;
	}
	else//一个和多个节点的删除方式一样,一个的删除后下一个节点为空 
	{
		SLNode* next = (*pplist)->next;
		free(*pplist);
		*pplist = next;
	}
}

测试一下

在给定的pos位置处插入和删除

那么有个问题我们怎么知道pos的位置呢?答案是得我们自己找,就需要给定一个查找的词条如我要找到链表中1的位置,我们传入1就可以了,不过链表不适合随机访问所以我们要从头开始遍历才行,所消耗的时间也会大大增加,不过我们也要实现一下,代码如下

找到了就返回那个节点的地址,如果没找到就返回NULL

SLNode* SLFind(SLNode* pplist,Elemtype x) 
{
	SLNode* cur = pplist;
	//遍历一遍如果找到返回地址
	while (cur) //如果是空的的话就不找了,说明没找到,返回空
	{
		//进来了就说明现在不是空
		if (cur->data == x) 
		{//找到了
			return cur;
		}
		//没找到遍历
		cur = cur->next;
	}
	return NULL;
}

插入

1.插入又分为在pos前插入和在pos之后插入

pos前插入

由于我们使用的是无头不循环单链表,所以我们没有办法知道pos之前的那个结点的位置,所以我们只能遍历一遍来找到pos之前的位置,那么我们则需要两个指针来干这个活

第一个cur指针,他存储的是当前位置的结点的地址,第二个指针prev存cur前一个结点的地址,当cur==pos时,prev正好是cur上一个结点,这样就可以在prev后插入结点了,插入结点前面以及讲过了,就不再重复了

void SLInsertBefore(SLNode** pplist, SLNode* pos, Elemtype x) 
{
	assert(pos);
	SLNode* newnode = BuySLNode(x);
	//需要两个指针 ,一前一后,找到pos的位置时前一个指针正好是pos之前那个节点
	
	if (pos==*pplist)//如果只有一个节点 那就是头插  要注意啊
	{
		newnode->next = *pplist;
		*pplist = newnode;
	}
	else//两个及两个以上的节点的操作 
	{
		SLNode* prev = NULL;
		SLNode* next = *pplist;
		while (next != pos)
		{
			prev = next;
			next = next->next;
		}//找到了next==pos节点开始插入
		newnode->next = pos;
		prev->next = newnode;
	}
}

2.在pos后插入结点就很容易了,只需要将新节点插入到pos后就行了,但是要注意需要先让新节点的next存入pos的下一个节点的地址 哦!否则链表就断开了

void SLInsetAfter(SLNode* pos, Elemtype x) 
{
	assert(pos);
	SLNode* newnode = BuySLNode(x);
	newnode->next = pos->next;
	pos->next = newnode;
}

测试一下:

 

删除

1.在pos位置之后删除

这个也相对简单,只需要将pos后的节点删除即可,但是要注意要先保存删除节点的下一个节点哦,要不然链表会断开,让pos去链接上删除节点的下一个节点

也要考虑到如果pos后面没有节点的情况,一般我们不处理直接return,有特殊要求再处理

void SLErasePosAfter(SLNode* pos) 
{
	if (pos->next==NULL) 
	{
		return;
	}
	else 
	{
		SLNode* next = pos->next;
		pos->next = next->next;
		free(next);
	}
}

2.删除pos位置之前的节点

和插入相同,我们并不知道pos前一个节点的位置,并且我们还得知道pos节点的上上一个节点的地址,不然无法链接,这就相对麻烦一些了

也要考虑很多种情况,如果pos前没有节点了,或者pos之前只有一个节点

代码如下:

void SLErasePosBefore(SLNode** pplist, SLNode* pos) 
{
	SLNode* cur = *pplist;
	SLNode* prev = NULL;
	if (pos == *pplist) 
	{
		return;
	}
	else if(( * pplist)->next==pos)
	{
		free(cur);
		*pplist = pos;
	}
	else 
	{
		while (cur->next != pos)
		{
			prev = cur;
			cur = cur->next;
		}
		free(cur);
		prev->next = pos;
	}
}

 测试一下

其他接口

计算链表的长度size

int SLSize(SLNode* plist) 
{
	int count = 0;
	SLNode* cur = plist;
	while (cur) 
	{
		count++;
		cur = cur->next;
	}
	return count;
}

判断链表是否为空链表

int SLIsEmpty(SLNode* plist) 
{
	return plist == NULL;
}

测试一下

感谢观看不妨点个关注?您的支持是我最大的动力,如有错误请大力指正,谢谢!!!

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值