双向链表(个人学习)

1. 链表分类

1.1.单链表                 1.2.双链表

1.3.带头(哨兵位)   1.4.不带头

1.5.循环                     1.6.不循环

一共8种链表

两种特殊链表:

 2. 代码实现 带头循环双向链表

typedef LTdatatype int;//定义结构体中data的类型,方便修改

typedef struct ListNode//定义双向链表的结构体
{
	struct ListNode* prev;//指向上一块空间的地址
	LTdatatype data;
	struct ListNode* next;//指向下一块空间的地址
}LTNode;

2.1. 创建头结点,创建节点

phead是头结点这块空间的地址,创建头结点要把prev和next都指向当前的空间 

 创建头结点

1.二级指针

void LTList(LTNode** phead)//创建头结点(传入二级指针,修改主函数里的空指针)
{
	*phead = BuyLTNode(-1);//随便存的数字
	(*phead)->prev = *phead;
	(*phead)->next = *phead;
}

2.返回地址

LTNode*LTList(LTNode*phead)
{
    phead=BuyLTNode(-1);
    phead->prev=phead;
    phead->nexr=phead;

    return phead;
}

创建节点

LTNode* BuyLTNode(LTdatatype x)//返回创建节点的地址
{
	LTNode* node = (LTNode*)malloc(sizeof(LTNode));
	if (node == NULL)//判断是否创建成功
	{
		printf("malloc error");
		exit(-1);
	}
    node->data=x;//创建成功输入数据
    node->prev=NULL;
    node->next=NULL;
	return node;
}

2.2.尾插

  注意:next和prev指向的是前一块结构体的地址,不是直接前一个prev与下一个的next相连

相比于单链表尾插,带头双向链表不需要判断链表是否为空,在传入的时候已经将链表的head传入,只需要在后面插入即可。

 只传入头时,尾插也可以使用

void LTPushBack(LTNode*phead,LTdatatype x)
{
   assert(phead);//判断传入的是不是空指针(哪怕链表里没东西,哨兵位也要有指针)
   
   LTNode*tail=phead->prev;//找到最后一块空间的地址
   LTNode* newnode=BuyLTNode(x);//创建新的空间

   newnode->prev=tail;//连接最后一块空间和newnode
   tail->next=newnode;

   newnode->next=phead;//完成头和尾的连接,实现循环
   phead->prev=newnode;

2.3.输出

void LTPrint(LTNode* phead)
{
	assert(phead);
	LTNode* cur = phead->next;//phead后面的节点
    printf("phead<->");
 	while (cur != phead)//往后遍历,直到遇见phead停下
	{
		printf("%d<->", cur->data);
		cur = cur->next;
	}
    prinft("phead\n");
}

 2.4.尾删

 

void LTPopBack(LTNode* phead)
{
	assert(phead);
	assert(phead->next != phead);//判断链表是否为空
	LTNode* tail = phead->prev->prev;
	LTNode* del = phead->prev;

	free(del);
	tail->next = phead;
	phead->prev = tail;
	
}

 链表空了就会报警告

 2.5.头插

 1.创建新的变量存储phead->next(方便理解)

void LTPushFront(LTNode* phead, LTdatatype x)
{
	assert(phead);
	LTNode* newnode = BuyLTNode(x);
	LTNode* cur = phead->next;

	newnode->next = cur;
	newnode->prev = phead;

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

2.不创建新的参数(搞清楚先后指针指向的顺序,顺序搞混就寄) 

void LTPushFront(LTNode* phead, LTdatatype x)
{
	assert(phead);
	LTNode* newnode = BuyLTNode(x);

	newnode->next = phead->next;
	phead->next->prev = newnode;

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

 2.6.头删

void LTPopFront(LTNode* phead)
{
	assert(phead);
	assert(phead->next != phead);
	LTNode* cur = phead->next->next;
	LTNode* del = phead->next;

	free(del);

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

 

 2.7.数据Size

类似于LTPrint,将链表包含数据的空间全部遍历一遍

int LTSize(LTNode* phead)
{
	assert(phead);
	int size = 0;
	LTNode* cur = phead->next;
	while (cur != phead)
	{
		size++;
		cur = cur->next;
	}

	return size;
}

如果结构体的data只是int类型,我们可以将head中的data作为个数,当数据删除或者增加的时候直接对phead->data修改即可
但是这里我们的数据类型是用LTdatatype定义的,就不能使用上述方法存数据数量(如果LTdatatype是char类型,int就不能正常使用)

 2.7.查找

直接遍历查找即可

LTNode* LTFind(LTNode* phead, LTdatatype x)
{
	assert(phead);
	LTNode* cur = phead->next;
	while (cur->data != x&&cur!=phead)
	{
		cur = cur->next;
	}
	return cur;
}

2.8.pos前插入
 

void LTInsert(LTNode* pos, LTdatatype x)
{
	assert(pos);
	LTNode* cur = pos->prev;
	LTNode* newnode = BuyLTNode(x);

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

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

与上面头插类似,只需要找到pos前一块空间的位置,然后将新的空间newnode与前后空间连接起来即可

2.9.删除pos位

void LTErase(LTNode* pos)
{
	assert(pos);
	LTNode* posprev = pos->prev;
	LTNode* posnext = pos->next;

	free(pos);
	posprev->next=posnext;
	posnext->prev = posprev;

}

3.链表和顺序表的区别

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

 

 

 在循环输出等情况下,顺讯表效率更快

与内存向寄存器输入数据相关,内存输入数据,会将输入数据的左右两侧地址的数据也传入寄存器(每次传入数据的量是固定的,不管传入多少数据,代价是相同的,而且车坐满了再发车划算),因为顺序表是个数组,左右两侧的数据就是需要的数据,效率会更高,

链表的每块空间可能不连续,所以传入的也只是需要的那一块空间

  • 3
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值