数据结构(2)--单链表

距离上次写博客已经是一个月前了......为什么会这样呢,当时学到栈和队列来着,现在已经学完了数据结构了,寻思着写博客直接从头写复习一遍,估计效果可能不如边学边写好,但总是要写的,以后给自己复习看看。

一. 单链表的定义

单链表是单向,非连续,但逻辑线性的数据结构。。

单链表由一个个节点串联而成,仅能从头节点向后遍历。

节点组成:数据域(存储数据) + 指针域(存储下一个节点地址)

核心特征

  • 尾节点指针指向 null

  • 内存不连续,动态分配空间

  • 无随机访问能力,只能顺序遍历

与数组的区别:

数组:连续内存,支持随机访问,查询快,增删慢,需提前分配空间,易浪费/溢出

单链表 :非连续内存,不支持随机访问,查询慢,增删极快,动态扩容无浪费

基础代码

1.结构体定义;

cpp 复制代码
typedef struct SListNode
{
	SLTypeData data;
	struct SListNode* next;
}SLNode;

这就是单链表的结构体定义,很简单,就是一个节点数据+下一个节点的地址,就这么一个个链起来。所以叫单链表。

2.初始化;

cpp 复制代码
void InitList(SLNode** head)
{
	*head = NULL; 
}

是的,就这么简单,唯一需要注意的点是函数传递的是二级指针,因为需要改变节点本身。

举个例子,比如:

cpp 复制代码
SLNode *L;
InitList(&L);

外部传递过来的就是指针的指针,也就是二级指针。如果只传InitList(L),相当于只传了一个拷贝的地址给函数,并不会改变L自身。只有传它的指针才能改变自身。

3.单链表的判空;

cpp 复制代码
bool SLEmpty(SLNode* phead)
{
return *phead==NULL;
}

很简单,没啥说的。

核心基础操作

核心基础操作包括插入,删除,遍历,查找。我们一个个来看。

在写之前,需要先写一个申请新节点的函数SLByNode;

cpp 复制代码
SLNode* SLByNode(SLTypeData x)//申请空间
{
	SLNode* newnode = (SLNode*)malloc(sizeof(SLNode));//使用malloc申请动态空间
	if (newnode == NULL)
	{
		perror("malloc fail");//打印错误信息
		return NULL;
	}
	newnode->data = x;
	newnode->next = NULL;

	return newnode;
}

1.插入(尾插)

cpp 复制代码
void SLpushback(SLNode** phead, SLTypeData x)//尾插
{
	SLNode* newnode = SLByNode(x);//新节点申请空间

	if (*phead == NULL)//如果链表是空链表
	{
		*phead = newnode;//直接插入即可
	}
	else//非空链表
	{
		SLNode* tail = *phead;
		while (tail->next)
		{
			tail = tail->next;//寻找链表尾部
		}
		//tail此时就是链表尾部节点
		tail->next = newnode;//将新节点插入链表尾部节点
	}
}

这还是当初过年的时候写的,我这注释写得也是真详细,毕竟当时理解起来有点困难。

现在看起来真是简单。

2.头插

cpp 复制代码
void SLHeadInsert(SLNode** phead, SLTypeData x)//头插
{
	assert(phead);
	SLNode* newnode = SLByNode(x);//给新节点申请空间
	newnode->next = *phead;//让新节点的下一个节点指向头节点
	*phead = newnode;//更新头指针,使其指向新节点,新节点成为新的链表头
}

对比起尾插,头插是简单得多了。

3.尾删

cpp 复制代码
void SLdelback(SLNode** phead)//尾删
{
	assert(phead && *phead);
	//链表只有一个节点
	if ((*phead)->next == NULL)
	{
		free(*phead);
		*phead = NULL;
	}
	else//链表有多个节点
	{
		SLNode* tail = *phead;//用来找尾节点
		SLNode* prev = *phead;//保存尾结点的前驱
		while (tail->next)//
		{
			prev = tail;
			tail = tail->next;
		}
      //此时tail是尾节点,prev是倒数第二个节点
		free(tail);//删除尾节点
		tail = NULL;
		prev->next = NULL;//让倒数第二个节点成为新的尾结点
	}
}

我觉得现在看懂这些代码很轻松了,更需要注意的是细节问题了。比如要不要考虑只有一个节点的问题,要怎么设计代码更好等。

4.头删

cpp 复制代码
void SLdelhead(SLNode** phead)//头删
{
assert(phead&&*phead);

SLNode*next=(*phead)->next;
free(*phead);
*phead=next;
}

5.查找

cpp 复制代码
SLNode* SListFind(SLNode* phead, SLTypeData x)//查找
{
	SLNode* pcur = phead;
	while (pcur)
	{
		if (pcur->data == x)
		{
			return pcur;
		}
		pcur = pcur->next;
	}
	return NULL;
}

简单易懂,但如果现在我写不知道能不能记住最后要加一个return NULL;

6.在指定位置之后删除

cpp 复制代码
void SLInsert(SLNode** phead, SLNode* pos, int x)//指定位置之前插入
{
	assert(phead && *phead);
	assert(pos);

SLNode* newnode = SLByNode(x);
	
if (pos == *phead)//如果链表只有一个节点
{
	SLHeadInsert(phead, x);//直接头插
}
else
{
	SLNode* newnode = SLByNode(x);
	SLNode* prev = *phead;
	while (prev != NULL && prev->next != pos)
	{
		prev = prev->next;
	}
	prev->next = newnode;
	newnode->next = pos;
}

}

这代码有点难度了,并不是说难看懂,而是细节有点多,刚才我自己尝试写过一次,漏了好几个细节,首先就是assert断言,我居然忘记了还需要断言pos,接着是没考虑到链表只有一个节点的情况,不过写完了检查一下还是发现了。

7.在指定位置之后插入

cpp 复制代码
//指定位置之后插入
void SLTInsertAfter(SLNode* pos, SLTypeData x)
{
	assert(pos);
	SLNode* newnode = SLByNode(x);
	newnode->next = pos->next;
	pos->next = newnode;
}

这比起刚才的那个指定位置头插简单多了。

8.指定位置删除

cpp 复制代码
//删除指定位置节点
void SLTErase(SLNode** phead, SLNode* pos)
{
	assert(*phead && phead);
	assert(pos);
	if (pos == *phead)
	{
		SLdelhead(phead);//直接头删
	}
	else
	{
		SLNode* prev = *phead;
		while (prev->next != pos)
		{
			prev = prev->next;
		}
		prev->next = pos->next;
		free(pos);
		pos = NULL;
	}
}

还行,应该没问题。

9.链表的销毁

cpp 复制代码
//销毁链表
void SListDestroy(SLNode** phead)
{
	assert(*phead && phead);
	SLNode* pcur = *phead;
	while (pcur)
	{
		SLNode* next = pcur->next;
		free(pcur);
		pcur = next;
	}
	*phead = NULL;
}

这个就需要重点注意了,我都已经忘记了需要循环销毁了。

结语

以上就是单链表的复习情况了,看起来还挺简单的,没有刚开始学的那种困难的感觉了。

我觉得比顺序表简单。^-^。

相关推荐
刘马想放假4 天前
Modbus 全栈技术解析:TCP、RTU、ASCII、RTU over TCP
数据结构·网络协议
北域码匠5 天前
冒泡排序太慢?鸡尾酒排序双向优化,原生 C# 零第三方库完整代码
数据结构·排序算法·泛型·c# 算法·鸡尾酒排序·原生 c# 开发·冒泡排序优化·嵌入式算法
Darling噜啦啦11 天前
列表转树算法深度解析:从 Map 到 Reduce 的两种实现,面试高频考点
数据结构·算法·面试
小小工匠12 天前
Redis - 事务机制:能实现 ACID 属性吗
数据结构·redis·性能优化·并发·持久化
玖玥拾12 天前
C/C++ 数据结构(七)栈、容器适配器
c语言·数据结构·c++··容器适配器
Qres82112 天前
算法复键——树状数组
数据结构·算法
牛油果子哥q13 天前
并查集(DSU)超精讲,路径压缩、按秩合并、万能模板、连通性判定、最小生成树与刷题实战全解
数据结构·c++·最小生成树·并查集
凌波粒13 天前
LeetCode--491.递增子序列(回溯算法)
数据结构·算法·leetcode
WL学习笔记13 天前
单项不带头不循环链表
数据结构·链表
小糯米60113 天前
JS 数组
数据结构·算法·排序算法