数据结构——双向循环链表

本文介绍了双向链表的结构体建立、初始化、节点操作(包括创建、插入、删除、查找)以及链表的销毁。详细讲解了头插、尾插、头删、尾删等操作,并提供了C语言的实现代码。此外,还对比了顺序表和链表在存储空间、随机访问和插入删除效率上的区别。
摘要由CSDN通过智能技术生成

目录

结构体的建立

初始化

创建节点 

 头插节点 

尾插节点 

头删节点 

尾删节点 

 节点查找

节点前插入

 节点删除

 销毁链表

 Dlist.h

 Dlist.c

顺序表和链表的区别 


结构体的建立

typedef  int LTDatatype;
typedef struct ListNode
{
	struct ListNode* next;
	struct ListNode* prev;
	LTDatatype data;
}LTNode;

初始化

/*void ListInit(LTNode**pphead);*///初始化,初始化要传指针,要建立一个哨兵节点,要实质上把plist改变
LTNode* ListInit();//初始化,也可以不用二级,用一个返回值就行
LTNode* ListInit()
{
	LTNode* guard= (LTNode*)malloc(sizeof(LTNode));
	if (guard == NULL)
	{
		return NULL;
	}
	guard->next = guard;
	guard->prev = guard;
	return guard;
}

创建节点 

LTNode* BuyNode(LTDatatype x)
{
	LTNode* guard = (LTNode*)malloc(sizeof(LTNode));
	if (guard == NULL)
	{
		return NULL;
	}
	guard->next = NULL;
	guard->data = x;
	guard->prev = NULL;
	return guard;
}

 头插节点 

void ListPushFront(LTNode* pphead, LTDatatype x)
{
	LTNode* newnode = BuyNode(x);
	LTNode* next = pphead->next;
	newnode->next = next;
	next->prev = newnode;
	newnode->prev = pphead;
	pphead->next = newnode;
}

尾插节点 

void ListPushBack(LTNode* pphead, LTDatatype x)
{
	assert(pphead);//pphead不可能为空
	LTNode* newnode = BuyNode(x);
	LTNode* tail = pphead->prev;//先找到尾
	tail->next = newnode;//开始尾插
	newnode->prev = tail;
	newnode->next = pphead;
	pphead->prev =newnode;
}//这种写法即使pphead是NULL,依然能插入,因为通过tail找到了pphead

头删节点 

void ListPopFront(LTNode* pphead)
{
	LTNode* next = pphead->next;
	pphead->next = next->next;
	next->next->prev = pphead;
	free(next);
}

尾删节点 

void ListPopBack(LTNode* pphead)
{
	assert(pphead);
	assert(!ListEmpty(pphead));//判断是否为空
	LTNode* tail1 = pphead->prev;
	LTNode* tail2 = tail1->prev;
	free(tail1);
	tail1 = NULL;
	tail2->next = pphead;
	pphead->prev = tail2;
}
bool ListEmpty(LTNode* pphead)
{
	assert(pphead);
		return pphead->next == pphead;//检测是否只剩头节点
}

 节点查找

LTNode* ListFind(LTNode* pphead,LTDatatype x)
{
	assert(pphead);
	size_t n = 0;
	LTNode* cur = pphead->next;
	while (cur != pphead)
	{
		if (cur->data == x)
		{
			return cur;
		}
	}
}

节点前插入

void ListInsert(LTNode* pos, LTDatatype x)//在pos之前插入,若在pphead前插入,实际会变成尾插
{
	assert(pos);
	LTNode* prev = pos->prev;
	LTNode* newnode = BuyNode(x);
	prev->next = newnode;
	newnode->prev = prev;
	newnode->next = pos;
	pos->prev = newnode;
}

 节点删除

void ListErase(LTNode* pos)
{
	assert(pos);
	LTNode* prev = pos->prev;
	LTNode* next = pos->next;
	prev->next = next;
	next->prev = prev;
	free(pos);
	pos = NULL;
}

 销毁链表

void ListDestory(LTNode* pphead)//传一级,二级都可以
{
	assert(pphead);
	//1.先释放带数字的节点,最后释放头哨兵
	LTNode* cur = pphead->next;
	while (cur != pphead)
	{
		LTNode* next = cur->next;
		free(cur);
		cur = next;
	}
	free(pphead);
	pphead = NULL;//如果传一级指针,这里的置空不起作用,若想起作用,要传二级指针
}
//建议:使用一级指针,然后让调用ListDestory的人置空,保持接口的一致性,这里最后回到主函数把plist=NULL;即可

 Dlist.h

#pragma once
#include<stdio.h>
#include<string.h>
#include<assert.h>
#include<stdlib.h>
#include<stdbool.h>
typedef  int LTDatatype;
typedef struct ListNode
{
	struct ListNode* next;
	struct ListNode* prev;
	LTDatatype data;
}LTNode;
/*void ListInit(LTNode**pphead);*///初始化,初始化要传指针,要建立一个哨兵节点,要实质上把plist改变
LTNode* ListInit();//初始化,也可以不用二级,用一个返回值就行
void ListPushBack(LTNode* pphead,LTDatatype x);//不需要传地址,因为再怎么改变都不会改变哨兵位的头,改的是哨兵位的指向
void ListPushFront(LTNode* pphead, LTDatatype x);
void ListPopBack(LTNode* pphead);
void ListPrint(LTNode* pphead);
void ListPopFront(LTNode* pphead);
bool ListEmpty(LTNode* pphead);
size_t ListSize(LTNode* pphead);
LTNode* ListFind(LTNode* pphead, LTDatatype x);
void ListInsert(LTNode* pos, LTDatatype x);
void ListErase(LTNode* pos);
void ListDestory(LTNode* pphead);

 Dlist.c

#define _CRT_SECURE_NO_WARNINGS
#include"Dlist.h"
LTNode* ListInit()
{
	LTNode* guard= (LTNode*)malloc(sizeof(LTNode));
	if (guard == NULL)
	{
		return NULL;
	}
	guard->next = guard;
	guard->prev = guard;
	return guard;
}
LTNode* BuyNode(LTDatatype x)
{
	LTNode* guard = (LTNode*)malloc(sizeof(LTNode));
	if (guard == NULL)
	{
		return NULL;
	}
	guard->next = NULL;
	guard->data = x;
	guard->prev = NULL;
	return guard;
}
void ListPrint(LTNode* pphead)
{
	assert(pphead);
	printf("guard<=>");
	LTNode* cur = pphead->next;
	while (cur!= pphead)
	{
		printf("%d<=>", cur->data);
		cur= cur->next;
	}
	printf("\n");
}
void ListPushBack(LTNode* pphead, LTDatatype x)
{
	assert(pphead);//pphead不可能为空
	LTNode* newnode = BuyNode(x);
	LTNode* tail = pphead->prev;//先找到尾
	tail->next = newnode;//开始尾插
	newnode->prev = tail;
	newnode->next = pphead;
	pphead->prev =newnode;
}//这种写法即使pphead是NULL,依然能插入,因为通过tail找到了pphead

void ListPushFront(LTNode* pphead, LTDatatype x)
{
	LTNode* newnode = BuyNode(x);
	LTNode* next = pphead->next;
	newnode->next = next;
	next->prev = newnode;
	newnode->prev = pphead;
	pphead->next = newnode;
}
void ListPopBack(LTNode* pphead)
{
	assert(pphead);
	assert(!ListEmpty(pphead));//判断是否为空
	LTNode* tail1 = pphead->prev;
	LTNode* tail2 = tail1->prev;
	free(tail1);
	tail1 = NULL;
	tail2->next = pphead;
	pphead->prev = tail2;
}
bool ListEmpty(LTNode* pphead)
{
	assert(pphead);
		return pphead->next == pphead;//检测是否只剩头节点
}
void ListPopFront(LTNode* pphead)
{
	LTNode* next = pphead->next;
	pphead->next = next->next;
	next->next->prev = pphead;
	free(next);
}
size_t ListSize(LTNode* pphead)
{
	size_t n = 0;
	LTNode* cur = pphead->next;
	while (cur != pphead)
	{
		++n;
		cur = cur->next;
	}
	return n;
}
LTNode* ListFind(LTNode* pphead,LTDatatype x)
{
	assert(pphead);
	size_t n = 0;
	LTNode* cur = pphead->next;
	while (cur != pphead)
	{
		if (cur->data == x)
		{
			return cur;
		}
	}
}
void ListInsert(LTNode* pos, LTDatatype x)//在pos之前插入,若在pphead前插入,实际会变成尾插
{
	assert(pos);
	LTNode* prev = pos->prev;
	LTNode* newnode = BuyNode(x);
	prev->next = newnode;
	newnode->prev = prev;
	newnode->next = pos;
	pos->prev = newnode;
}
void ListErase(LTNode* pos)
{
	assert(pos);
	LTNode* prev = pos->prev;
	LTNode* next = pos->next;
	prev->next = next;
	next->prev = prev;
	free(pos);
	pos = NULL;
}
void ListDestory(LTNode* pphead)//传一级,二级都可以
{
	assert(pphead);
	//1.先释放带数字的节点,最后释放头哨兵
	LTNode* cur = pphead->next;
	while (cur != pphead)
	{
		LTNode* next = cur->next;
		free(cur);
		cur = next;
	}
	free(pphead);
	pphead = NULL;//如果传一级指针,这里的置空不起作用,若想起作用,要传二级指针
}
//建议:使用一级指针,然后让调用ListDestory的人置空,保持接口的一致性,这

顺序表和链表的区别 

不同点顺序表链表
存储空间上物理上一定连续逻辑上连续,但物理上不一定
连续
随机访问支持O(1)不支持:O(N)
任意位置插入或者删除
元素
可能需要搬移元素,效率低
O(N)
只需修改指针指向
插入动态顺序表,空间不够时需要
扩容
没有容量的概念
应用场景元素高效存储+频繁访问任意位置插入和删除频繁
缓存利用率

顺序表优点:1.尾插尾删的效率很高

                      2.可以随机访问(通过下标),链表需要遍历

                      3.和链表相比cpu高速缓存面中率很高

寄存器相当于锅 ,如有数组{1,2,3,4},这四个元素是存在主存里面的,数据结构是在主存里面管理我们的数据,链表也一样(堆是一个虚拟的内存的划分)

越往上速度越快,成本更高 ,L1,L2,L3被称为CPU的高速缓存

代码编译好了就是指令,通过CPU去运行指令,CPU访问通过访问内存数据运行指令,

CPU执行指令,不会直接访问内存

1.先看数据在不在三级缓存,在(命中),就直接访问

2.不在(不命中),先加载到缓存,再访问

顺序表的缺点:1.头部和中部的插入删除效率低——O(N)

                         2.扩容(动态开辟空间)——性能消耗,有一定空间浪费

链表的优点:1.任意位置插入删除效率很高O(1)

                      2.按需申请释放(不存在空间浪费)

链表的缺点:1.不支持随机访问(现实中随机访问很重要)

             

  • 5
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 4
    评论
评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

头发没有代码多

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

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

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

打赏作者

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

抵扣说明:

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

余额充值