栏目分类:
子分类:
返回
名师互学网用户登录
快速导航关闭
当前搜索
当前分类
子分类
实用工具
热门搜索
名师互学网 > IT > 软件开发 > 后端开发 > C/C++/C#

数据结构与算法笔记 - Lesson03 - 链表(单链表+带头双向循环链表)C语言实现(没有伪代码)

C/C++/C# 更新时间: 发布时间: IT归档 最新发布 模块sitemap 名妆网 法律咨询 聚返吧 英语巴士网 伯小乐 网商动力

数据结构与算法笔记 - Lesson03 - 链表(单链表+带头双向循环链表)C语言实现(没有伪代码)

数据结构与算法

Lesson03 - 链表C语言实现(没有伪代码)


文章目录

数据结构与算法前言1 什么是链表

1.1 基本概念1.2 链表的类型 二、无头单链表的实现(纯C语言)

2.1 创建单个结点2.2 打印2.3 尾插2.4 尾删2.5 头插2.6 头删2.7 按值查找2.8 在pos位置之后插入 x2.9 在pos位置之前插入x2.10 删除pos位置之后的元素 3 带头双向循环链表的实现(纯C语言)

3.1 创建单个结点3.2 初始化链表,并返回一个头结点的指针3.3 打印链表3.4 尾插3.5 头插3.6 尾删3.7 头删3.8 按值查找 - 返回结点的位置3.9 在pos位置之前插入x3.10 删除pos位置的元素 //后记


前言

 Lesson03 主要讲解数据结构中的 - 链表,以及链表的C语言实现。

 本篇文章没有伪代码,帮助大家在理解顺序表的前提下,自己动手用C语言实现链表

1 什么是链表 1.1 基本概念

 线性表的链式存储又称为链表
 它是指通过一组任意的存储单元来存储线性表的数据结构
 由于是任意结点,还得符合线性表的逻辑结构特点,因此每一个节点除了放自身的数据元素,还必须放一个指向其后继的指针

 类似于下图:

 虽然在内存中它们是不连续的,但在逻辑上是相连的

1.2 链表的类型

相比较与顺序表,链表的类型就丰富多彩了

 一个结点可以存很多信息,除了指向后继的指针,我们可以添加一个指向前驱的指针,于是可分成单向和双向
 如果我们添加一个哨兵位的头结点,可以将插入和删除等操作变得容易一点,以此可分为带头结点和不带头结点
 如果将尾结点的 next 指针指向头结点,这样就形成了一个循环结构,能够很好地找到头尾,因此可分为循环和非循环

通过排列组合,可以组合出8中不同的链表,它们各有优缺点,本篇文章重点介绍单链表和带头双向循环链表

 单链表

typedef int SListDataType;

typedef struct SListNode
{
	//存储结点数据
	SListDataType val;
	//指向下个结点的指针
	struct SListNode* next;

}ListNode;

 带头双向循环链表

typedef int SListDataType;

typedef struct SListNode
{
	SListDataType val;
	struct SListNode* next;
	struct SListNode* prev;
}ListNode;

二、无头单链表的实现(纯C语言) 2.1 创建单个结点

 此接口为了方便增加新数据元素时,需要不断动态申请空间的问题

//创建单个结点
ListNode* BuyListNode(SListDataType x)
{
	ListNode* tmp = (ListNode*)malloc(sizeof(ListNode));
	if (tmp == NULL)
	{
		perror("BuyListNode::malloc");
	}
	tmp->next = NULL;
	tmp->val = x;
}
2.2 打印

 遍历整个链表

void ListPrint(ListNode* pl)
{
	ListNode* i = pl;
	//遍历整个链表,直到遇到NULL
	while (i)
	{
		printf("%d ", pl->val);
		i = i->next;
	}
	printf("n");

}
2.3 尾插

 先找到链表的最后一个结点,然后将新建的结点放在他的后面

//尾插
void ListPushBack(ListNode** ppl, SListDataType x)
{
	ListNode* tmp = BuyListNode(x);
	//如果传过来的是NULL指针,说明是第一个结点
	if ((*ppl) == NULL)
	{
		(*ppl) = tmp;
		return;
	}
	ListNode* tail= (*ppl);
	//寻找尾结点
	while (tail->next)
	{
		tail= tail->next;
	}
	tail->next = tmp;
}
2.4 尾删

 找到最后一个结点,将其 free 掉
 但是不能直接 free ,因为我们要将倒数第二个结点的 next 置成 NULL,因此我们需要多加一个结点,用来保存尾结点的前一个结点

void ListPopBack(ListNode** ppl)
{
	//如果是NULL指针,不必删除
	if ((*ppl) == NULL)
	{
		return;
	}
	//如果只有一个结点,直接free
	if ((*ppl)->next == NULL)
	{
		free((*ppl));
		(*ppl) = NULL;
		return;
	}
	//定义一个prev记录前一个结点
	ListNode* prev = NULL;
	ListNode* tail = *ppl;
	while (tail->next)
	{
		prev = tail;
		tail = tail->next;
	}
	free(tail);
	prev->next = NULL;
}
2.5 头插

 创建一个新结点,连在原来链表的前面即可

//从头部插入一个节点
void ListPushFront(SListNode** pplist, SListDateType x)
{
	//即使传的NULL指针也没关系
	SListNode* newnode = BuySListNode(x);
	newnode->next = *pplist;
	//修改指针,使其指向新的第一个结点
	(*pplist) = newnode;
	newnode = NULL;
}
2.6 头删

 free 第一个结点,修改指针
 处理一个NULL指针情况即可

//从头部删除一个节点
void SListPopFront(SListNode** pplist)
{
	if (*pplist == NULL) return;
	//修改第一个结点的指针
	SListNode* tmp = *pplist;
	(*pplist) = tmp->next;
	free(tmp);
	tmp = NULL;
}
2.7 按值查找

 遍历整个链表,找到返回结点指针,没有找到返回 NULL

//按值查找,返回找到后的地址
SListNode* SListFind(SListNode* plist, SListDateType x)
{
	while (plist)
	{
		if (plist->date == x)
		{
			return plist;
		}
		plist = plist->next;
	}
	return NULL;
}
2.8 在pos位置之后插入 x

 一定要先修改 newnode 的指针,如果先修改 pos 的指针,那么整个链表就会断开

//在pos位置之后插入x
void SListInsertAfter(SListNode* pos, SListDateType x)
{
	assert(pos);
	
	SListNode* newnode = BuySListNode(x);
	//先修改newnode的指针
	newnode->next = pos->next;
	pos->next = newnode;
}
2.9 在pos位置之前插入x

void SListInsert(SListNode** pplist, SListNode* pos, SListDateType x)
{
	assert(pos);
	
	SListNode* newnode = BuySListNode(x);

	//如果是第一个结点,相当于头插
	if ((*pplist) == pos)
	{
		newnode->next = *pplist;
		*pplist = newnode;
	}
	else
	{
		//先找到pos位置的前一个结点
		SListNode* prev = *pplist;
		while (prev->next != pos)
		{
			prev = prev->next;
		}
		//找到后修改指针
		newnode->next = pos;
		prev->next = newnode;
	}
2.10 删除pos位置之后的元素

 注意讨论 pos 为NULL,和 pos 为唯一结点的情况

void SListEraseAfter(SListNode* pos)
{
	if (pos == NULL && pos->next == NULL)
	{
		return;
	}
	else
	{
		//记录后一个结点
		SListNode* tmp = pos->next;
		//让pos结点指向后一个结点的后一个结点
		pos->next = pos->next->next;
		free(tmp);
		tmp = NULL;
	}
}
3 带头双向循环链表的实现(纯C语言) 3.1 创建单个结点

 此接口为了方便增加新数据元素时,需要不断动态申请空间的问题

//创建一个新结点
ListNode* BuyListNode(LTDataType x)
{
	ListNode* newnode = (ListNode*)malloc(sizeof(ListNode));
	if (newnode == NULL)
	{
		perror("BuyListNode::malloc");
	}
	newnode->next = NULL;
	newnode->prev = NULL;
	newnode->data = x;
	return newnode;
}

3.2 初始化链表,并返回一个头结点的指针

 创建一个头结点

ListNode* ListInit()
{
	ListNode* head = BuyListNode(0);
	//由于是循环链表,当只有头结点时,prev和next都指向自己
	head->next = head;
	head->prev = head;
	return head;
}
3.3 打印链表

 遍历整个链表

void ListPrint(ListNode* phead)
{
	ListNode* cur = phead->next;
	//循环链表判断结尾的标志是最后一个结点的next指向head
	while (cur != phead)
	{
		printf("%d->", cur->data);
		cur = cur->next;
	}
	printf("NULLn");
}
3.4 尾插

 找到最后一个结点,然后插入新结点

void ListPushBack(ListNode* phead, LTDataType x)
{
	assert(phead);

	ListNode* newnode = BuyListNode(x);
	//不必遍历整个链表去找尾结点,头结点的prev即使尾结点
	ListNode* tail = phead->prev;
	//修改指针,使其还是一个循环链表
	tail->next = newnode;
	newnode->prev = tail;
	newnode->next = phead;
	phead->prev = newnode;
}
3.5 头插

 创建一个新结点,连在原来链表的前面即可

//从头部插入一个结点x
void ListPushFront(ListNode* phead, LTDataType x)
{
	assert(phead);

	ListNode* newnode = BuyListNode(x);
	//相当于在头结点和第二个结点之间插入新结点
	newnode->prev = phead;
	newnode->next = phead->next;
	phead->next->prev = newnode;
	phead->next = newnode;
}
3.6 尾删

 head 的 prev 即是尾

//从尾部删除一个结点
void ListPopBack(ListNode* phead)
{
	assert(phead);
	//如果只有头结点,不必删除
	if (phead->next == phead) return;
	//记录尾结点以及尾结点的前驱
	ListNode* tail = phead->prev;
	ListNode* p_tail = tail->prev;
	free(tail);
	tail = NULL;
	//修改指针使其还是循环链表
	phead->prev = p_tail;
	p_tail->next = phead;
}
3.7 头删

 记录第一个结点,以及第二个结点,方便修改指针

void ListPopFront(ListNode* phead)
{
	assert(phead);
	if (phead->next == phead) return;
	//记录第一个结点,以及第二个结点,方便修改指针
	ListNode* first = phead->next;
	ListNode* n_first = first->next;
	free(first);
	first = NULL;
	phead->next = n_first;
	n_first->prev = phead;
}
3.8 按值查找 - 返回结点的位置

 遍历链表,寻找 x

ListNode* ListFind(ListNode* phead, LTDataType x)
{
	assert(phead);
	
	ListNode* cur = phead->next;
	//注意尾结点的条件
	while (cur != phead)
	{
		if (cur->data == x)
		{
			return cur;
		}
		cur = cur->next;
	}
	return NULL;
}
3.9 在pos位置之前插入x

 注意修改指针的顺序

void ListInsert(ListNode* pos, LTDataType x)
{
	assert(pos);

	ListNode* prev = pos->prev;
	ListNode* newnode = BuyListNode(x);
	prev->next = newnode;
	newnode->prev = prev;
	newnode->next = pos;
	pos->prev = newnode;
}
3.10 删除pos位置的元素

 删除结点一定要注意不能让链表断开

void ListErase(ListNode* pos)
{
	assert(pos);


	ListNode* prev = pos->prev;
	ListNode* next = pos->next;
	prev->next = next;
	next->prev = prev;
	free(pos);

}

 综上就是关于链表最核心的知识,对您有帮助的老铁,还望关注、点赞、收藏一键三连哦~~~

//后记

 以后每周会更新一到两篇学习数据结构的笔记,用来记录自己的学习历程,复习巩固所学的知识。
 文中概念或者代码有任何错误的地方,希望大佬们在评论区指出,本人会虚心接受并且改正。

转载请注明:文章转载自 www.mshxw.com
本文地址:https://www.mshxw.com/it/783966.html
我们一直用心在做
关于我们 文章归档 网站地图 联系我们

版权所有 (c)2021-2022 MSHXW.COM

ICP备案号:晋ICP备2021003244-6号