手撕双链表

目录

 

1.双向链表的介绍

2.带头双向循环链表

​编辑 主体

 ​编辑准备工作

​编辑链表头部节点

 ​编辑添加新节点

 ​编辑打印

​编辑头插头删尾插尾删

​编辑头插

​编辑尾插

 ​编辑头删

 ​编辑尾删

​编辑查找

​编辑pos位置前插入

​编辑删除pos位置结点

结言:


前言回顾

        C语言数据结构(单链表)

        上期我们介绍了数据结构中,单链表的结构和实现,链表的每个结点都只能存储一个地址,用来访问下一个结点的数据,这样的话有很多事情就会显得很不方便,很拘束。那有没有更好的链表呢,本期交给大家带来双向链表

手撕双链表_第1张图片

1.双向链表的介绍

        双向链表的节点通常包含两个部分:数据部分和指针部分。数据部分用于存储节点所包含的数据,指针部分包含两个指针,一个指向前一个节点,一个指向后一个节点。

        双向链表的优点是可以在常数时间内在任意位置插入或删除节点,因为只需要修改相邻节点的指针即可。而在单向链表中,如果要在某个位置插入或删除节点,则需要遍历链表找到该位置的前一个节点。

        双向链表相对于单向链表也有一些缺点。首先,双向链表需要额外的指针来存储前一个节点的地址,因此占用的内存空间比单向链表更大。其次,双向链表在插入或删除节点时需要修改两个指针的值,而单向链表只需要修改一个指针的值,因此操作起来更复杂。

到这里,想必大家就对双向链表有了个大概的认识,告诉你个小秘密哦:其实双向链表的实现比单链表要简单上不少,只是在数据的结构上双向链表看起来不让人觉得简单,别怕都是纸老虎,往下看一步步手撕它。


2.带头双向循环链表

        有没有感觉光听名字就觉得它不简单,还是那句话:别怕都是纸老虎。当你学会了这些对链表就会有了基本的掌握,不再害怕它。

 主体

手撕双链表_第2张图片

 准备工作

在链表的实现前,我们先把链表所需要用到的先准备好,这样实现的时候会省心很多。

链表头部节点

        在对链表一系列的操作之前,我们首要需要的就是头节点,有了头节点后续数据的插入删除都会变得简单。

List* ListCreate()
{
	List* newnode = (List*)malloc(sizeof(List));
	if (newnode == NULL)
	{
		perror("malloc fail");
		exit(-1);
	}
	newnode->prev = newnode;
	newnode->next = newnode;
	newnode->val = 0;
	return newnode;
}

因为是循环的双向链表,所以头结点初始化的时候,两个指针都是指向自己。

手撕双链表_第3张图片

 添加新节点

在插入数据中,必不可少的就是节点的创建,然后再链接到表中

List* BuyListNode(ListDatatype x)
{
	List* newnode = (List*)malloc(sizeof(List));
	if (newnode == NULL)
	{
		perror("malloc fail");
		exit(-1);
	}
	newnode->val = x;
	newnode->prev = NULL;
	newnode->next = NULL;
	return newnode;
}

 打印

当链表有了数据以后,为了直观的方便我们对数据增删查改的观察,打印就起到了作用

void ListPrint(List* pead)
{
	assert(pead);
	List* cur = pead->next;

	printf("pead:");
	while (cur != pead)
	{
		printf("《=》%d", cur->val);
		cur = cur->next;
	}
	printf("\n");
}

头插头删尾插尾删

在链表的头部和尾部分别进行对数据插入和删除

头插

void ListPushFront(List* pead, ListDatatype x)
{
	assert(pead);
	List* newnode = BuyListNode(x);
	
	List* cur =pead->next;
	
	pead->next = newnode;
	newnode->prev = pead;
	newnode->next = cur;
	cur->prev = newnode;
	//ListInsert(pead->next, x);这是在pos位置前插入数据,这里可进行复用,后面会有实现
}

手撕双链表_第4张图片

 新结点的前指针指向前一个节点,后指针指向后一个节点,就进行了链接。

尾插

void ListPushBack(List* pead, ListDatatype x)
{
	assert(pead);

	List* newnode = BuyListNode(x);
	List* cur = pead->prev;

	pead->prev = newnode;
	newnode->next = pead;
	cur->next = newnode;
	newnode->prev = cur;
	//ListInsert(pead, x);这是在pos位置前插入数据,这里可进行复用,后面会有实现
}

手撕双链表_第5张图片

在尾部插入和头插同理,需要改变的只有相邻节点间的指针

 头删

void ListPopFront(List* pead)
{
	assert(pead);
	assert(pead->next !=pead);
	List* cur = pead->next;
	List* second = cur->next;

	free(cur);
	pead->next = second;
	second->prev = pead;
}

删除首节点,然后使头部指针指向后一个节点

手撕双链表_第6张图片

 尾删

void ListPopBack(List* pead)
{
	assert(pead);
	assert(pead->prev);
	List* cur = pead->prev;
	List* second = cur->prev;

	free(cur);
	second->next = pead;
	pead->prev = second;
}

删除尾节点,然后使头部指针与尾节点前的节点链接手撕双链表_第7张图片

 查找与指定位置的插入与删除

我们可以通过对链表的遍历,然后找到数据的位置,进行插入、删除、更改等操作

查找

List* ListFind(List* pead, ListDatatype x)
{
	assert(pead);

	List* cur = pead->next;
	while (cur != pead)
	{
		if (cur->val == x)
		{
			return cur;
		}
		cur = cur->next;
	}
	return NULL;
}

给定一个元素的数据,然后在链表中进行遍历,找到后返回元素地址 

pos位置前插入

void ListInsert(List* pos, ListDatatype x)
{
	assert(pos);
	List* cur = pos->prev;
	List* newnode = BuyListNode(x);

	newnode->prev = cur;
	cur->next = newnode;
	newnode->next = pos;
	pos->prev = newnode;
}

删除pos位置结点

void ListErase(List* pos)
{
	assert(pos);
	List* cur = pos->prev;
	List* second = pos->next;
	
	cur->next = second;
	second->prev = cur;
	free(pos);
}

        以上在pos位置进行的操作,只要我们有了地址,就可以进行操作,这些都可以对前面的头插头删尾插尾删进行复用。

链表销毁

当链表我们不再需要使用的时候,就需要将其进行销毁,因为这些空间都是在堆上进行开辟的

void ListDestroy(List* head) 
{
	assert(head);

	List* cur = head->next;
	while (cur != head) 
	{
		List* tmp = cur;
		cur = cur->next;
		free(tmp);
	}
	free(head);
}

结言:

        今天内容就到这里啦,时间不知不觉就8月了,距离暑假的结束也已经步入了倒计时,不知道大家这段时间有没有沉下心来好好学习呢。送给大家一句话:今天你做别人不愿意做的事,明天你就可以做到别人做不到的事。坚持的人只有少数,你只要静下心来,该有的都有得到,我与诸君共勉。当然也十分希望大家动动小手指,给博主一键三连,大家的支持是我前行的最大动力。 

手撕双链表_第8张图片

你可能感兴趣的:(C语言数据结构,数据结构,链表)