数据结构— 单链表的基本操作

目录

1.关于顺序表的问题及思考

2.链表的概念

3.单链表的实现


就在今天哈登打出了很好的表现,自从来到费城,腿筋的伤势、角色的改变等等,从初三了解开始,他的比赛也是看一场少一场,小小致敬一下!

放在文章的前面,也是在激励自己。希望我也可以像他一样,向他致敬!!!

 

1.关于顺序表的问题及思考

(1)顺序表从中间或是头部插入数据,时间复杂度为O(n)。

(2)增容需要申请空间,扩容有可能会拷贝数据,释放旧空间,会有一些消耗。

(3)增加的空间会产生不增不够用,增了会浪费的情况。

如何解决这样的问题?可以看看链表的结构。

2.链表的概念

链表是一种物理存储结构上非连续,非顺序的存储结构,数据元素的逻辑顺序是通过链表中的指针依次连接来实现的。

所以对于链表的结构进行理解,单链表就像是一列火车,火车每一节车厢之间的连接部分就像是指针的指向下一个结点,但是也有一点不同的是火车在运行的时候,其实每一个车厢在物理上是连续的。但是链表的结点一般是在堆上申请来的空间,有可能物理空间是不连续的,只是在逻辑上是连续的。

3.单链表的实现

链表的分类有很多,我们现在一起看看单链表的基本操作的实现。

(1)新建一个工程

SList.h(单链表结点的定义、头文件、函数的声明)

SList.c(接口函数的具体实现)

test.c(测试)

(2)结点的定义

typedef int SLTDataType;
typedef struct SListNode
{
	SLTDataType data;
	struct SListNode* next;
}SLTNode;

(3) 打印函数

单链表中数据的打印就像是单链表开始学习的开胃赛,在吃大餐前,先和大家上一点凉菜。

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

通过找尾的方式来遍历单链表用于打印每个元素。

(4)增

在进行增加数据的操作之前,需要创建新的结点,我们可以先写一个这样的函数便于头插和尾插时可以复用。这个函数返回创建新的节点。

SLTNode* BuyLTNode(SLTDataType x)
{
	SLTNode* newnode = (SLTNode*)malloc(sizeof(SLTNode));
	if (newnode == NULL)
	{
		perror("malloc fail");
		return NULL;
	}
	newnode->data = x;
	newnode->next = NULL;
	return newnode;
}

1.尾插

void SLPushBack(SLTNode** pphead, SLTDataType x)
{
	assert(pphead);
	SLTNode* newnode = BuyLTNode(x);
	if (*pphead == NULL)
	{
		*pphead = newnode;
	}
	else
	{
		SLTNode* tail = *pphead;
		while (tail->next != NULL)
		{
			tail = tail->next;
		}
		tail->next = newnode;
	}
}

这里难以理解的是为什么函数的形参是二级指针?

这是因为当链表为空的时候,头指针指向NULL,在插入数据的时候需要头指针指向新的结点,也就是改变一级指针,所以就需要二级指针作为形参来接收。(如果用一级指针是无法对于头指针做出实际的改变的,形参是实参的一份临时拷贝,对于形参的改变不影响实参的改变)。

那么头插也是一个道理

2.头插

void SLPushFront(SLTNode** pphead, SLTDataType x)
{
	SLTNode* newnode = BuyLTNode(x);
	newnode->next = *pphead;
	*pphead = newnode;
}

只实现头插和尾插当然是不够的我们还可以是实现在指定位置pos的前后增加数据。

3.在pos之前插入


void SLInsert(SLTNode** pphead, SLTNode* pos, SLTDataType x)
{
	assert(pphead);
	assert(pos);
	if (pos == *pphead)
	{
		SLPushFront(pphead,x);
	}
	else
	{
		SLTNode* prev = *pphead;
		while (prev->next != pos)
		{
			prev = prev->next;
		}
		SLTNode* newnode = BuyLTNode(x);
		prev->next = newnode;
		newnode->next = pos;
	}
}

4.在pos之后插入

void SLInsertAfter(SLTNode* pos, SLTDataType x)
{
	assert(pos);
	SLTNode* newnode = BuyLTNode(x);
	newnode->next = pos->next;
	pos->next = newnode;

}

这里使用一级指针是因为在pos之后插入数据证明链表不为空,无需对链表头指针进行改变。只是对于结构体的尾节点修改而已。

(5)删

1.尾删

void SLPOpBack(SLTNode** pphead)
{
	assert(*pphead);
	if ((*pphead)->next == NULL)
	{
		free(*pphead);
		*pphead = NULL;
	}
	else
	{
		SLTNode* tail = *pphead;
		while (tail->next->next)
		{
			tail = tail->next;
		}
		free(tail->next);
		tail->next = NULL;
	}
}

2.头删

void SLPOpFront(SLTNode** pphead)
{
	assert(*pphead);
	if ((*pphead)->next == NULL)
	{
		free(*pphead);
		*pphead = NULL;
	}
	else
	{
		SLTNode* tmp = *pphead;
		*pphead = tmp->next;
		free(tmp);
	}
}

3.删除pos位置的值

//删除pos位置的值
void SLErase(SLTNode** pphead, SLTNode* pos)
{
	assert(pphead);
	assert(*pphead);
	if (pos == *pphead)
	{
		SLPOpFront(pphead);
	}
	else
	{
		SLTNode* prev = *pphead;
		while (prev->next != pos)
		{
			prev = prev->next;
		}
		prev->next = pos->next;
		free(pos);
	}
}

4.删除pos位置之后的值

void SLEraseAfter(SLTNode* pos)
{
	assert(pos);
	assert(pos->next);
	SLTNode* next = pos->next;
	next->next = pos->next;
	free(next);
}

(6)查和改

单链表中数据的查找相比于增和删容易一些。就是通过遍历的方式,找到返回该结点,找不到就返回NULL。

SLTNode* SLFind(SLTNode* phead, SLTDataType x)
{
	SLTNode* cur = phead;
	while (cur)
	{
		if (cur->data == x)
		{
			return cur;
		}
	}
	return NULL;
}

对于单链表数据的更改,可以和单链表的查找一起配合使用,想要修改哪个数据,找到之后就可以进行修改。类似于这样:

SLTNode* pos = SLFind(plist,4);
	if (pos)
	{
		SLInsertAfter( pos, 1);
	}

如果返回结点不为空,证明找到了,在进行修改(你想要头插、尾插、头删、尾删就是你自己的事了)。例如上面的例子,我就在pos之后插入了1。

(7)销毁链表

链表的销毁不可以直接free(phead),链表存储数据是物理上不连续的,所以就挨个销毁。

void SLTDestory(SLTNode** pphead)
{
	assert(pphead);
	SLTNode* cur = *pphead;
	while (cur)
	{
		SLTNode* next = cur->next;
		free(cur);
		cur = next;
	}
	*pphead = NULL;
}

关于test.c文件就用于测试接口函数是否符合我们的预期。

#define _CRT_SECURE_NO_WARNINGS
#include"SList.h"
void Test1()
{
	SLTNode* plist = NULL;
	SLPushFront(&plist, 1);
	SLPushFront(&plist, 2);
	SLPOpBack(&plist);
	SLPushFront(&plist, 3);
	SLPOpFront(&plist);
	SLPushFront(&plist, 4);
	SLPushBack(&plist,5);
	SLTNode* pos = SLFind(plist,4);
	if (pos)
	{
		SLInsertAfter( pos, 1);
	}
	pos = SLFind(plist,4);
	if (pos)
	{
		SLErase(&plist,pos);
	}
	SLPrint(plist);
	SLTDestory(&plist);
}
int main()
{
	Test1();
	return 0;
}

结果如下:

 SList.h如下:

#pragma once
#include<stdio.h>
#include<assert.h>
#include<stdlib.h>

typedef int SLTDataType;
typedef struct SListNode
{
	SLTDataType data;
	struct SListNode* next;
}SLTNode;

void SLPrint(SLTNode* phead);

void SLPushFront(SLTNode** pphead,SLTDataType x);
void SLPushBack(SLTNode** pphead,SLTDataType x);
void SLPOpFront(SLTNode** pphead);
void SLPOpBack(SLTNode** pphead);

//单链表查找
SLTNode* SLFind(SLTNode* phead,SLTDataType x);

//在pos之前插入
void SLInsert(SLTNode** pphead,SLTNode* pos,SLTDataType x);
//在pos之后插入
void SLInsertAfter(SLTNode* pos,SLTDataType x);
//删除pos位置的值
void SLErase(SLTNode** pphead,SLTNode* pos);
//删除pos位置后面的值
void SLEraseAfter(SLTNode* pos);

void SLTDestory(SLTNode** pphead);

 

今天分享到这里,希望大家一起提高!!!

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值