链式结构的增删改查

Pre

前面我们实现了单向的链表的增删查改操作,可以发现单向链表是有弊端的:尾插需要遍历,找到尾结点,时间复杂度是O(N)。

今天来实现一个更完美的链式结构:带头双向循环链表 的增删查改。

如图所示

在这里插入图片描述

结构

typedef int LTDataType;
typedef struct LTNode{
    struct LTNode* next; //指向后1个结点
    struct LTNode* prev; //指向前1个结点
    LTDataType value; //存放数据
}LTNode;

获得1个结点

LTNode* BuyLTNode(LTDataType x) {
	LTNode* newNode = (LTNode*)malloc(sizeof(LTNode));
	if (newNode == NULL) {
		perror("BuyLTNode::malloc failed.");
		exit(-1);
	}
	newNode->next = NULL;
	newNode->prev = NULL;
	newNode->value = x;
	return newNode;
}

初始化

初始化时,我们Buy上1个结点,作为虚拟头结点:不存放有效数据,并且next和prev都指向自己

在这里插入图片描述

LTNode* InitLT() {
	LTNode* newNode = BuyLTNode(-1); //Buy 1个结点
	newNode->next = newNode; //指向自己
	newNode->prev = newNode; //指向自己

	return newNode;
}

那么,用户调用时可以这样:

void test_1(){
    LTNode* plist = InitLT(); //此时plist指向1个虚拟头结点
}

那么接下来所有的结点,都应该依附在plist之后。

注:有些地方把这样的不存放有效数据的头结点叫做哨兵位头结点

插入数据

尾插

直接看代码

void PushBackLT(LTNode* phead, LTDataType x) {
	assert(phead); //阻止用户传入NULL
	LTNode* newNode = BuyLTNode(x);
    
    /* 下面仅仅需要改变指针指向就能完成插入 */
    
	LTNode* tail = phead->prev; //仅通过phead即可找到尾结点

	tail->next = newNode;
	newNode->prev = tail;
	newNode->next = phead;
	phead->prev = newNode;
}

可以看到,虽然我们让链表结构复杂了一点,但可以提高效率,尾插时不再需要像单链表一样去遍历链表找到tail了,只需要改一下指针指向。

头插

void PushFrontLT(LTNode* phead, LTDataType x) {
	assert(phead);
	LTNode* newNode = BuyLTNode(x);

	newNode->next = phead->next;
	phead->next->prev = newNode;
	phead->next = newNode;
	newNode->prev = phead;
}

pos位置前插入

void InsertLT(LTNode* pos, LTDataType x) {
	assert(pos);
	LTNode* newNode = BuyLTNode(x);

	newNode->prev = pos->prev;
	pos->prev->next = newNode;
	newNode->next = pos;
	pos->prev = newNode;
}

那么,实际实现的时候,可以先写完void InsertLT(LTNode* pos, LTDataType x) ,然后头插、尾插复用即可。

:pos位置的结点如何获得?——》通过后面的查找数据接口SearchLT()获得。

尾插

void PushBackLT(LTNode* phead, LTDataType x) {
	assert(phead);
	InsertLT(phead, x); //复用
}

头插

void PushFrontLT(LTNode* phead, LTDataType x) {
	assert(phead);
	InsertLT(phead->next, x); //复用
}

删除数据

删除pos位置数据

void EraseLT(LTNode* pos) {
	assert(pos);

	LTNode* after = pos->next;
	pos->prev->next = after;
	after->prev = pos->prev;
	free(pos);
}

尾删

void PopBackLT(LTNode* phead) {
	assert(phead);
	if (phead->next == phead) {
		printf("链表已删完\n");
		return;
	}
	EraseLT(phead->prev);
}

头删

void PopFrontLT(LTNode* phead) {
	assert(phead);
	if (phead->next == phead) {
		printf("链表已删完\n");
		return;
	}
	EraseLT(phead->next);
}

查改数据

查找数据

LTNode* SearchLT(LTNode* phead, LTDataType x) {
	assert(phead);
	LTNode* cur = phead->next;
	while (cur != phead) {
		if (cur->value == x) {
			return cur;
		}
		cur = cur->next;
	}
	return NULL;
}

修改数据

可以通过SearchLT()获得的pos进行修改。

例如,现在我们有一个链表,存放的数据是[ 1 , 2 , 3 , 4 , 5 ],我们希望把3改成30,代码实现如下

int main() {
	LTNode* plist = InitLT();
	PushFrontLT(plist, 5);
	PushFrontLT(plist, 4);
	PushFrontLT(plist, 3);
	PushFrontLT(plist, 2);
	PushFrontLT(plist, 1);

	PrintLT(plist);// <= 1 <=> 2 <=> 3 <=> 4 <=> 5 =>
    
	//修改数据
	LTNode* pos = SearchLT(plist, 3);
	if (pos != NULL) {
		pos->value = 30;
	}
	PrintLT(plist);// <= 1 <=> 2 <=> 30 <=> 4 <=> 5 =>

	return 0;
}

销毁链表

任何通过malloc出来的空间都要记得释放,防止内存泄漏。

void DestroyLT(LTNode* phead) {
	assert(phead);
	LTNode* cur = phead->next;
	while (cur != phead) {
		LTNode* after = cur->next;
		free(cur);
		cur = after;
	}
	free(phead);

}

其他接口

可视化

随便实现,可有可无,仅仅是为了方便看而已。

void PrintLT(LTNode* phead) {
	assert(phead);
	if (phead->next == phead) {
		printf("链表无有效数据\n");
		return;
	}
	LTNode* cur = phead->next;
	printf("dummyHead<=");
	while (cur->next != phead) {
		printf(" %d ", cur->value);
		printf("<=>");
		cur = cur->next;
	}
	printf(" %d =>", cur->value);
	printf("\n");
}

在这里插入图片描述

获得链表有效数据个数

size_t SizeLT(LTNode* phead) {
	assert(phead);
	LTNode* cur = phead->next;
	size_t size = 0;
	while (cur != phead) {
		size++;
		cur = cur->next;
	}
	return size;
}

判断链表是否空

// 记得需要引头文件
#include <stdbool.h>

bool IsEmptyLT(LTNode* phead) {
	assert(phead);
	return phead->next == phead;
}

End

谢谢观看~

(1)用带表头的链表存放输入的数据,每读入一个数,按升序顺序插入到链表中,链表中允许两个结点有相同值。链表的头结点存放链表后面的结点个数,初始化时就生成头结点(初值为0)。链表翻转是把数据逆序(变成降序),注意,头结点不动。翻转后要再翻转一次,恢复升序后才能插入新元素,否则会出错。 (2)先定义堆栈的几个基本操作,再设计一主函数利用堆的操作完成以下功能:假设一个算术表达式中可以包含三种括号:()[]{},且这三种括号可以按任意次序嵌套使用(如:...[...{...}...[...]...]...(...))。编写判别给定表达式中所含括号是否正确配对出现的算法,已知表达式已存入数据元素为字符的单链表中。 (3)先定义队列的几个基本操作,再设计一主函数利用队列的操作完成以下功能:键盘输入的字符可以临时存入键盘的缓冲区中。为了充分利用缓冲区的空间,往往将缓冲区设计成链式循环队列的结构,并为循环队列结构的缓冲区设置一个队首指针和一个队尾指针。每输入一个字符到缓冲区中,就将尾指针后移,链入缓冲区的循环队列之中;每输出一个字符号,就将队头指针前移,将它从缓冲队列中删除。假设有两个进程同时存在于一个应用程序中,第一个进程连续在屏幕上显示字符“X”,第二个进程不断检查键盘上是否有输入,若有则读入用户键入的字符,将其保存到键盘缓冲区中。
评论 9
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值