数据结构--链表

本文介绍了链表的基本概念,包括数据域和指针域,然后详细阐述了单向无头不循环链表的实现,包括插入、删除和其他操作。接着,讨论了双向有头循环链表的结构和实现,包括创建、销毁、插入、删除等功能。总结中强调了两种链表结构的特点和优缺点。
摘要由CSDN通过智能技术生成


一、链表介绍

        链表也是线性表的一种,分为数据域和指针域。数据域记录数据,指针域记录的是下一个节点的地址。由于每一个节点都有存放着下一个节点的指针,于是指针将节点一个个链接起来,整个结构就像一条链,于是称为链表。

        链表分类可按照是否有头节点、单双向、能否循环。以下为单向、无头、不循环的链表结构。


二、单向无头不循环链表实现


1.节点结构

        如上所说,结构体两个成员,一个记录数据一个记录下一个节点地址。

typedef int SLTDateType;

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

2.接口函数实现

1.插入

        先设置一个取一个新节点的函数,方便插入。

SLTNode* BuyListNode(SLTDateType x)
{
	SLTNode* newNode = (SLTNode*)malloc(sizeof(SLTNode));
	if (newNode == NULL)
	{
		printf("malloc fail\n");
		exit(-1);
	}
	newNode->data = x;
	newNode->next = NULL;
	return newNode;
}

        由于没有头节点,所以当链表为空的时候插入需要另外处理一下。其他时候就利用next指针域将原来链表的节点与新节点连接起来。

void SListPushBack(SLTNode** pphead, SLTDateType x)
{
	SLTNode* tail = *pphead;
	SLTNode* newNode = BuyListNode(x);
	if (*pphead == NULL)
	{
		*pphead = newNode;
	}
	else
	{
		while (tail->next != NULL)
		{
			tail = tail->next;
		}
		tail->next = newNode;
	}
}

void SListPushFront(SLTNode** pphead, SLTDateType x)
{
	
	SLTNode* newNode = BuyListNode(x);
	newNode->next = *pphead;
	*pphead = newNode;
}

// 在pos位置之前去插入一个节点
void SListInsert(SLTNode** pphead, SLTNode* pos, SLTDateType x)
{
	SLTNode* cur = *pphead;
	SLTNode* newNode = BuyListNode(x);
	if (pos == *pphead)
	{
		*pphead = newNode;
		newNode->next = pos;
	}
	else
	{
		while (cur->next != pos)
		{
			cur = cur->next;
		}
		cur->next = newNode;
		newNode->next = pos;
	}
}

2.删除

        删除节点要释放相应的内存空间,重新连接链表,同时也需要注意链表为空时的边界情况。

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

void SListPopFront(SLTNode** pphead)
{
	assert(*pphead);
	SLTNode* front = *pphead;
	*pphead = front->next;
	free(front);
	front = NULL;
}

void SListErase(SLTNode** pphead, SLTNode* pos)
{
	assert(*pphead);
	SLTNode* cur = *pphead;
	SLTNode* prev = NULL;
	if (pos == *pphead)
	{
		*pphead = cur->next;
		free(cur);
		cur = NULL;
	}
	else
	{
		while (cur->next != pos)
		{
			prev = cur;
			cur = cur->next;
		}
		cur->next = cur->next->next;
		free(pos);
		pos = NULL;
	}

3.其他

void SListPrint(SLTNode* phead)
{
	SLTNode* cur = phead;
	if (cur == NULL)
	{
		printf("NULL");
	}
	while (cur)
	{
		printf("%d->", cur->data);
		cur = cur->next;
	}
}

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

void SListDestory(SLTNode** pphead)
{
	SLTNode* cur = *pphead;
	while (*pphead != NULL)
	{
		SListPopFront(pphead);
	}
}

三、双向有头循环链表

1.节点结构

        双向有头循环链表是链表中最复杂的结构,虽然结构复杂但是在接口的功能实现上相较于上一种结构的链表简单很多。


        next域记录下一个节点地址,prev记录前一个节点地址,data域记录数据。

typedef int LTDataType;
typedef struct ListNode
{
	LTDataType data;
	struct ListNode* next;
	struct ListNode* prev;
}ListNode;

2.接口函数实现

1.链表创建和销毁

        初始只有一个头节点时,节点的next和prev都指向自己本身。

ListNode* ListCreate()
{
	ListNode *tailNode = (ListNode*)malloc(sizeof(ListNode));
	if (tailNode == NULL)
	{
		exit(-1);
	}
	tailNode->next = tailNode;
	tailNode->prev = tailNode;
	return tailNode;
}
void ListDestory(ListNode* pHead)
{
	while (pHead->next != pHead)
	{
		ListPopFront(pHead);
	}
	free(pHead);
	pHead = NULL;
}

2.头尾插入和删除

        由于存在头节点,不需要考虑从空表开始插入等边界情况。以及两个方向的指针都有也方便了插入和删除。

void ListPushBack(ListNode* pHead, LTDataType x)
{
	assert(pHead);
	ListNode* tail = pHead->prev;
	ListNode* newNode = BuyNode(x);
	tail->next = newNode;
	newNode->prev = tail;
	newNode->next = pHead;
	pHead->prev = newNode;
}
void ListPopBack(ListNode* pHead)
{
	assert(pHead);
	assert(pHead->next != pHead);
	ListNode* tail = pHead->prev;
	tail->prev->next = pHead;
	pHead->prev = tail->prev;
	free(tail);
}
void ListPushFront(ListNode* pHead, LTDataType x)
{
	assert(pHead);
	ListNode* next = pHead->next;
	ListNode* newNode = BuyNode(x);
	pHead->next = newNode;
	newNode->prev = pHead;
	next->prev = newNode;
	newNode->next = next;
}

void ListPopFront(ListNode* pHead)
{
	assert(pHead);
	assert(pHead->next != pHead);
	ListNode* next = pHead->next;
	ListNode* nextNext = next->next;
	pHead->next = nextNext;
	nextNext->prev = pHead;
	free(next);
}

3.指定位置插入删除

        利用ListFind函数找到位置后,传入位置进行增删操作,都很方便。

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

void ListInsert(ListNode* pos, LTDataType x)
{
	assert(pos);
	ListNode* posPrev = pos->prev;
	ListNode* newNode = BuyNode(x);
	posPrev->next = newNode;
	newNode->prev = posPrev;
	newNode->next = pos;
	pos->prev = newNode;
}
void ListErase(ListNode* pos)
{
	assert(pos);
	assert(pos->next != pos);
	ListNode* posPrev = pos->prev;
	ListNode* posNext = pos->next;
	posPrev->next = posNext;
	posNext->prev = posPrev;
	free(pos);
}


总结

        无头单向不循环链表:结构简单,当时由于没有头节点,而且链表上只能单向走,实现操作困难。但相较于顺序表的话,有着节约空间,插入删除不需要找尾,更加方便。

        有头双向循环链表:结构复杂,也正是结构上的复杂设置,让我们操作链表更加方便。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值