数据结构(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;
}

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

结语

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

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

相关推荐
cpp_25011 小时前
P2947 [USACO09MAR] Look Up S
数据结构·c++·算法·题解·单调栈·洛谷
小蒋学算法2 小时前
算法-乘法表中第K小的数-二分
数据结构·算法
m0_738120724 小时前
渗透测试基础——PHP 序列化数据结构与反序列化机制详解
android·服务器·网络·数据结构·安全·php
少司府5 小时前
C++进阶:红黑树
开发语言·数据结构·c++·b树·二叉树·红黑树
不会C语言的男孩5 小时前
C++ Primer 第19章:特殊工具与技术
数据结构·c++
hnjzsyjyj6 小时前
东方博宜OJ 1010:数组元素的排序 ← 桶排序
数据结构·排序算法·桶排序
小欣加油6 小时前
Leetcode31 下一个排列
数据结构·c++·算法·leetcode·职场和发展
凤凰院凶涛QAQ6 小时前
《Java版数据结构 & 集合类剖析》链表与LinkedList:节点手拉手,增删不用愁
java·数据结构·链表
无限进步_6 小时前
【Linux】进程状态、僵尸与孤儿、进程调度
linux·运维·服务器·开发语言·数据结构·算法