数据结构——链表相关(用C语言实现)

目录

一、链表的概念

二、链表的分类

1、单向或双向

2、带头或者不带头

3、循环或非循环

4、无头单项非循环

5、带头双向循环

三、不带头单项不循环链表的实现

1、结构体的设计及其相关接口。

2、各个接口的详细实现

(1)动态申请一个节点

(2)单链表的打印

(3)单链表的头插

(4)单链表的尾插

(5)单链表的尾删

(6)头删

(7)查找

(8)在pos位置之后插入

(9)删除pos位置之后的值

(10)单链表的销毁

四、带头双向循环链表的实现

1、结构体的设计及其相关接口

2、各个接口的详细实现

(1)动态申请一个节点

(2)创建头节点

(3)链表的打印

(4)链表的尾插

(5)链表的尾删

(6)链表的头插

(7)链表的头删

(8)查找

(9)在pos位置插入

(10)删除pos位置节点

(11)链表的销毁

(12)对其进行小小的优化

        

        我们再学习数据结构时,链表是一个十分重要的一个知识点,一下将会对齐进行介绍以及相关功能的实现。

一、链表的概念

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

二、链表的分类

        链表结构有很多中,可以分为一下8种结构。

1、单向或双向

2、带头或者不带头

3、循环或非循环

4、无头单项非循环

5、带头双向循环

        使用较多的是不带头单项不循环链表和带头双向循环链表。一下对齐进行详细的实现。

三、不带头单项不循环链表的实现

1、结构体的设计及其相关接口。

typedef int SLTDateType;
typedef struct SListNode
{
	SLTDateType data;
	struct SListNode* next;
}SListNode;

// 动态申请一个节点
SListNode* BuySListNode(SLTDateType x);
// 单链表打印
void SListPrint(SListNode* plist);
// 单链表尾插
void SListPushBack(SListNode** pplist, SLTDateType x);
// 单链表的头插
void SListPushFront(SListNode** pplist, SLTDateType x);
// 单链表的尾删
void SListPopBack(SListNode** pplist);
// 单链表头删
void SListPopFront(SListNode** pplist);
// 单链表查找
SListNode* SListFind(SListNode* plist, SLTDateType x);
// 单链表在pos位置之后插入x
// 分析思考为什么不在pos位置之前插入?
void SListInsertAfter(SListNode* pos, SLTDateType x);
// 单链表删除pos位置之后的值
// 分析思考为什么不删除pos位置?
void SListEraseAfter(SListNode* pos);
// 单链表的销毁
void SListDestroy(SListNode* plist);

2、各个接口的详细实现

(1)动态申请一个节点

        此单链表不能设计成固定的大小,要根据使用者的实际需求来申请节点。

SListNode* BuySListNode(SLTDateType x)
{
	SListNode* newnode = (SListNode*)malloc(sizeof(SListNode));
	if (newnode == NULL)
	{
		perror("malloc fail");
		return NULL;
	}
	newnode->data = x;
	newnode->next = NULL;
	return newnode;
}

(2)单链表的打印

void SListPrint(SListNode* plist)
{
	SListNode* cur = plist;
	while (cur != NULL)
	{
		printf("%d->", cur->data);
		cur = cur->next;
	}
	printf("NULL\n");
}

(3)单链表的头插

        再实现单链表的头插时,因为要对链表本身进行改动,所以要传链表的地址,再函数实现上就要用二级指针来接受,如果用一级指针接受只是实参的一份临时拷贝不会改变原链表所以要传二级指针才能改变原链表。

void SListPushFront(SListNode** pplist, SLTDateType x)
{
	SListNode* newnode = BuySListNode(x);
	newnode->next = *pplist;
	*pplist = newnode;
}

(4)单链表的尾插

        尾插时单链表只有一个头指针,所以要设置一个尾指针来找到链表的尾部才能够插入。

void SListPushBack(SListNode** pplist, SLTDateType x)
{
	SListNode* newnode = BuySListNode(x);
	if (*pplist == NULL)
	{
		*pplist = newnode;
	}
	else
	{
		SListNode* tail = *pplist;
		while (tail->next != NULL)
		{
			tail = tail->next;
		}
		tail->next = newnode;
	}
}

(5)单链表的尾删

        尾删时不仅要设置一个尾指针,还要设置一个尾部前一个指针,并且把尾部前一个指针制空,这要才能够free掉尾节点时不会造成内存泄漏。

void SListPopBack(SListNode** pplist)
{
	assert(*pplist);
	if ((*pplist)->next == NULL)
	{
		free(*pplist);
		*pplist = NULL;
	}
	else
	{
		SListNode* pre = NULL;
		SListNode* tail = *pplist;
		while (tail != NULL)
		{
			pre = tail;
			tail = tail->next;
		}
		free(tail);
		pre->next = NULL;
	}
}

(6)头删

void SListPopFront(SListNode** pplist)
{
	SListNode* del = *pplist;
	*pplist = (*pplist)->next;
	free(del);
}

(7)查找

SListNode* SListFind(SListNode* plist, SLTDateType x)
{
	assert(plist);
	SListNode* cur = plist;
	while (cur)
	{
		if (cur->data == x)
		{
			return cur;
		}
		cur = cur->next;
	}
	return NULL;
}

(8)在pos位置之后插入

        该函数要配合查找函数共同使用,要先找到pos所在的位置。

void SListInsertAfter(SListNode* pos, SLTDateType x)
{
	assert(pos);
	SListNode* newnode = BuySListNode(x);
	newnode->next = pos->next;
	pos->next = newnode;
}

(9)删除pos位置之后的值

        该函数也要配合查找函数使用

void SListEraseAfter(SListNode* pos)
{
	assert(pos);
	assert(pos->next);
	SListNode* next = pos->next;
	pos->next = next->next;
	free(next);
}

(10)单链表的销毁

void SListDestroy(SListNode* plist)
{
	SListNode* cur;
	while (plist)
	{
		cur = plist;
		plist = plist->next;
		free(cur);
	}
}

四、带头双向循环链表的实现

1、结构体的设计及其相关接口

typedef int LTDataType;
typedef struct ListNode
{
	LTDataType _data;
	struct ListNode* _next;
	struct ListNode* _prev;
}ListNode;

// 创建返回链表的头结点.
ListNode* ListCreate();
// 双向链表销毁
void ListDestory(ListNode* pHead);
// 双向链表打印
void ListPrint(ListNode* pHead);
// 双向链表尾插
void ListPushBack(ListNode* pHead, LTDataType x);
// 双向链表尾删
void ListPopBack(ListNode* pHead);
// 双向链表头插
void ListPushFront(ListNode* pHead, LTDataType x);
// 双向链表头删
void ListPopFront(ListNode* pHead);
// 双向链表查找
ListNode* ListFind(ListNode* pHead, LTDataType x);
// 双向链表在pos的前面进行插入
void ListInsert(ListNode* pos, LTDataType x);
// 双向链表删除pos位置的节点
void ListErase(ListNode* pos);

2、各个接口的详细实现

(1)动态申请一个节点

ListNode* BuyListNode(LTDataType x)
{
	ListNode* newnode = (struct ListNode*)malloc(sizeof(struct ListNode));
	if (newnode == NULL)
	{
		perror("malloc fail!");
		return NULL;
	}
	newnode->_data = x;
	newnode->_next = NULL;
	newnode->_prev = NULL;
}

(2)创建头节点

ListNode* ListCreate()
{
	ListNode* head = BuyListNode(-1);
	head->_next = head;
	head->_prev = head;
	return head;
}

(3)链表的打印

        注意打印的循环结束条件,要设置一个cur指针,当此指针的next是头节点时结束。

void ListPrint(ListNode* pHead)
{
	assert(pHead);
	printf("头节点<==>");
	ListNode* cur = pHead->_next;
	while (cur != pHead)
	{
		printf("%d<==>", cur->_data);
		cur = cur->_next;
	}
	printf("\n");
}

(4)链表的尾插

        要注意先后顺序,防止链表断链。

void ListPushBack(ListNode* pHead, LTDataType x)
{
	ListNode* tail = pHead->_prev;
	ListNode* newnode = BuyListNode(x);
	tail->_next = newnode;
	newnode->_prev = tail;
	newnode->_next = pHead;
	pHead->_prev = newnode;
}

(5)链表的尾删

void ListPopBack(ListNode* pHead)
{
	ListNode* tail = pHead->_prev;
	ListNode* tailprev = tail->_prev;
	free(tail);
	tailprev->_next = pHead;
	pHead->_prev = tailprev;
}

(6)链表的头插

        也要注意先后顺序,先链接后面再链前面,防止断链。

void ListPushFront(ListNode* pHead, LTDataType x)
{
	ListNode* first = pHead->_next;
	ListNode* newnode = BuyListNode(x);
	newnode->_next = first;
	first->_prev = newnode;
	pHead->_next = newnode;
	newnode->_prev = pHead;
}

(7)链表的头删

void ListPopFront(ListNode* pHead)
{
	ListNode* first = pHead->_next;
	ListNode* second = first->_next;
	pHead->_next = second;
	second->_prev = pHead;
	free(first);
}

(8)查找

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;
}

(9)在pos位置插入

        此函数要配合查找函数使用

void ListInsert(ListNode* pos, LTDataType x)
{
	assert(pos);
	ListNode* newnode = BuyListNode(x);
	ListNode* posprev = pos->_prev;
	posprev->_next = newnode;
	newnode->_prev = posprev;
	newnode->_next = pos;
	pos->_prev = newnode;
}

(10)删除pos位置节点

        此函数要配合查找函数使用

void ListErase(ListNode* pos)
{
	assert(pos);
	ListNode* posPrev = pos->_prev;
	ListNode* posNext = pos->_next;

	posPrev->_next = posNext;
	posNext->_prev = posPrev;
	free(pos);
}

(11)链表的销毁

void ListDestory(ListNode* pHead)
{
	assert(pHead);
	ListNode* cur = pHead->_next;
	while (cur != pHead)
	{
		ListNode* next = cur->_next;
		free(cur);
		cur = next;
	}
	free(pHead);
}

(12)对其进行小小的优化

        在完成了在某个位置的插入与删除后,尾插、尾删、头插、头删就可以进行小小的优化,尾插就是在头的位置插入,未删要先判断该链表是否为空链表,不是就调用在pos位置删除的函数,位置是头的prev,头插就是在头节点的next位置插入,头删也要先判断链表是否为空,不是就删除头节点的next位置。

//判断是否为空链表
bool IsEmpty(ListNode* pHead)
{
	assert(pHead);
	return pHead->_next == pHead;
}

//尾插
void ListPushBack(ListNode* pHead, LTDataType x)
{
	assert(pHead);
	ListInsert(pHead, x);
}

//尾删
void ListPopBack(ListNode* pHead)
{
	assert(pHead);
	assert(!IsEmpty(pHead));
	ListErase(pHead->_prev);
	
}

//头插
void ListPushFront(ListNode* pHead, LTDataType x)
{
	assert(pHead);
	ListInsert(pHead->_next, x);
}

//头删
void ListPopFront(ListNode* pHead)
{
	assert(pHead);
	assert(!IsEmpty(pHead));
	ListErase(pHead->_next);
}

        以上就是此篇文章的全部内容,如有问题欢迎批评指正。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值