双向循环链表增删查改C语言实现

在这里插入图片描述

双向链表的结构

在这里插入图片描述
在这里插入图片描述

双向链表:
每个结点存在两个指针域,分别存储该结点的前驱结点引用和后继结点引用,从任意一个结点出发,都能通过前驱引用以及后继引用完成整个链表结点的访问。

所以不难看出,单向链表能干的事,双向链表也能干!但是正是因为这一特性,相比于单链表,双向链表在访问其他结点上带来方便的同时,将占用更多的资源,因此在使用的时候可以根据自己的场景来决定使用何种数据结构。

和单链表改造的循环链表比起来,共同点是这两个结构都能够从任意结点通过不同的操作访问到链表的所有结点。但是除此之外,二者无论是结构还是访问方式等都不尽相同,何处不同,阅读完本文后自见分晓。

应用场景:单链表适用于解决一条道走到黑的访问场景,比如给正在排队测温的人测量,只需要测试一次就下一位,无需重复测试;循环链表适用于具有环状数据结构的场景,比如丢手绢游戏;双向链表则适合需要提供多个方向访问功能的数据结构,比如老师在考场监考,可以来回走动(假设这个考场座位是顺序的)

双向链表的实现

双向链表的声明

#pragma once
#include<stdio.h>
#include<stdlib.h>
#include<assert.h>
typedef int LTDataType;

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

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

双向链表的实现

#include"DSLTNode.h"
// 创建返回链表的头结点.
ListNode* ListCreate(int x) 
{
	ListNode *tmp = (ListNode *)malloc(sizeof(ListNode));
	tmp->data = x;
	tmp->next = tmp;
	tmp->prev = tmp;
	return tmp;
}

//链表初始化
ListNode * ListInit()
{
	ListNode *head = ListCreate(0);
	head->next = head;
	head->prev = head;
	return head;
}
// 双向链表销毁
void ListDestory(ListNode** plist) 
{
	ListNode *cur = *plist;
	while (cur != *plist) 
	{
		ListNode* next = cur->next;
		cur = next;
		free(next);
	}
	free(*plist);
	*plist = NULL;
}
// 双向链表打印
void ListPrint(ListNode* plist) 
{
	assert(plist);
	ListNode *cur = plist->next;
	while (cur != plist) 
	{
		printf("%d->",cur->data);
		cur = cur->next;
	}
	printf("NULL\n");
}
// 双向链表尾插
void ListPushBack(ListNode* plist, LTDataType x) 
{
	assert(plist);
	ListNode *newNode = ListCreate(x);
	newNode->data = x;

	ListNode *listtali = plist->prev;
	listtali->next = newNode;
	newNode->prev = listtali;
	newNode->next = plist;
	plist->prev = newNode;
}
// 双向链表尾删
void ListPopBack(ListNode* plist) 
{
	assert(plist);
	assert(plist->next != plist);
	ListNode *tail = plist->prev;
	ListNode *prevtail = tail->prev;
	free(tail);
	prevtail->next = plist;
	plist->prev = prevtail;
	
}
// 双向链表头插
void ListPushFront(ListNode* phead, LTDataType x) 
{
	assert(phead);
	ListNode *newNode = ListCreate(x);

	ListNode *first = phead->next;
	

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

}
// 双向链表头删
void ListPopFront(ListNode* plist) 
{
	assert(plist);
	assert(plist->next != plist);
	
	ListNode *first = plist->next;
	ListNode *second = first->next;

	plist->next = second;
	second->prev = plist;
	free(first);
}
// 双向链表查找
ListNode* ListFind(ListNode* plist, LTDataType x) 
{
	assert(plist);
	ListNode *cur = plist->next;
	while (cur != plist) 
	{
		if (cur->data == x) 
		{
			return cur;
		}
		cur = cur->next;
	}
	return NULL;
}
// 双向链表在pos的前面进行插入
void ListInsert(ListNode* pos, LTDataType x) 
{
	ListNode *newNode = ListCreate(x);
	ListNode *prev = pos->prev;

	prev->next = newNode;
	newNode->prev = prev;

	newNode->next = pos;
	pos->prev = newNode;
}
// 双向链表删除pos位置的节点
void ListErase(ListNode* pos) 
{
	ListNode *prev = pos->prev;
	ListNode *next = pos->next;

	prev->next = next;
	next->prev = prev;
}

结点的创建

初始化一个带两个指针的结点,需要将他的前驱指针和后继指针都指向他自己本身

// 创建返回链表的头结点.
ListNode* ListCreate(int x) 
{
	ListNode *tmp = (ListNode *)malloc(sizeof(ListNode));
	tmp->data = x;
	tmp->next = tmp;
	tmp->prev = tmp;
	return tmp;
}

头结点的创建

带哨兵位的头节点,作用是为了实现一个带头的双向链表

//链表初始化
ListNode * ListInit()
{
	ListNode *head = ListCreate(0);
	head->next = head;
	head->prev = head;
	return head;
}

链表的销毁

将每一个结点取下来,将他释放掉,由于需要将头节点给释放并且将头节点置空,所以需要用到二级指针

// 双向链表销毁
void ListDestory(ListNode** plist) 
{
	ListNode *cur = *plist;
	while (cur != *plist) 
	{
		ListNode* next = cur->next;
		cur = next;
		free(next);
	}
	free(*plist);
	*plist = NULL;
}

链表的打印

从头节点的下一个结点开始遍历一次,将结点的data打印一次,当再次回到头节点的时候循环终止,链表就已经被遍历一次了

// 双向链表打印
void ListPrint(ListNode* plist) 
{
	assert(plist);
	ListNode *cur = plist->next;
	while (cur != plist) 
	{
		printf("%d->",cur->data);
		cur = cur->next;
	}
	printf("NULL\n");
}

链表的尾插

找到链表的尾,这里可以通过头节点的前驱指针来找到链表的尾tail,将tail的后继指针指向newNode,newNode的前驱指针指向tail,最后让newNode成为新的尾,newNode的后继指针指向头节点,头结点的前驱指针指向newNode这样新结点就尾插到链表的后面了
在这里插入图片描述

// 双向链表尾插
void ListPushBack(ListNode* plist, LTDataType x) 
{
	assert(plist);
	ListNode *newNode = ListCreate(x);
	newNode->data = x;

	ListNode *listtali = plist->prev;
	listtali->next = newNode;
	newNode->prev = listtali;
	newNode->next = plist;
	plist->prev = newNode;
}

链表的尾删

通过头节点找到尾tail,再找到尾结点的前一个结点prev,将prev的next指向头节点,再将头节点的前驱指针指向prev,最后释放尾结点
在这里插入图片描述

// 双向链表尾删
void ListPopBack(ListNode* plist) 
{
	assert(plist);
	assert(plist->next != plist);
	ListNode *tail = plist->prev;
	ListNode *prevtail = tail->prev;
	free(tail);
	prevtail->next = plist;
	plist->prev = prevtail;
	
}

链表的头插

头插并不是插入在哨兵位的节点前面,而是插入在哨兵位节点的下一个位置,把他看作第一个结点,先将头节点的下一个结点first找出来,将新的结点的后继指针指向first,first的前驱指针指向新结点,再让哨兵位结点的后继指针指向新结点,新结点的前驱指针指向哨兵位结点
在这里插入图片描述

// 双向链表头插
void ListPushFront(ListNode* phead, LTDataType x) 
{
	assert(phead);
	ListNode *newNode = ListCreate(x);

	ListNode *first = phead->next;
	

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

}

链表的头删

在这里插入图片描述

// 双向链表头删
void ListPopFront(ListNode* plist) 
{
	assert(plist);
	assert(plist->next != plist);
	
	ListNode *first = plist->next;
	ListNode *second = first->next;

	plist->next = second;
	second->prev = plist;
	free(first);
}

查找

遍历一遍原链表,找出值为x的结点就将他返回

// 双向链表查找
ListNode* ListFind(ListNode* plist, LTDataType x) 
{
	assert(plist);
	ListNode *cur = plist->next;
	while (cur != plist) 
	{
		if (cur->data == x) 
		{
			return cur;
		}
		cur = cur->next;
	}
	return NULL;
}

指定位置前插入

在这里插入图片描述

// 双向链表在pos的前面进行插入
void ListInsert(ListNode* pos, LTDataType x) 
{
	ListNode *newNode = ListCreate(x);
	ListNode *prev = pos->prev;

	prev->next = newNode;
	newNode->prev = prev;

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

指定位置删除

在这里插入图片描述

// 双向链表删除pos位置的节点
void ListErase(ListNode* pos) 
{
	ListNode *prev = pos->prev;
	ListNode *next = pos->next;

	prev->next = next;
	next->prev = prev;
}

总结顺序表和链表(双向带头循环)

  • 顺序表优点:
    1、按下标进行随机访问
    2、cpu高速缓存命中比较高

  • 顺序表缺点:
    1、空间不够需要增容,一定程度的性能消耗,有一定的空间浪费
    2、头部或者中间插入删除数据,需要挪动数据,效率比较低,挪动数据的时间复杂度O(N)

  • 链表表优点:
    1、按需申请内存,需要存一个数据,就申请一块内存,也不浪费空间
    2、任意位置插入删除不需要挪动数据,时间复杂度O(1)

  • 顺序表缺点:
    1、空间不够需要增容,一定程序的性能消耗
    2、头部或者中间插入删除数据,需要挪动数据,效率比较低,挪动数据的时间复杂度O(N)

总结:顺序表和链表是相辅相成的,互相弥补对方的缺点

  • 75
    点赞
  • 67
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 10
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 10
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

爱生活,爱代码

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值