【数据结构初阶】四、线性表里的链表(带头+双向+循环 链表)

news2024/11/27 16:29:49

=========================================================================

相关代码gitee自取

C语言学习日记: 加油努力 (gitee.com)

 =========================================================================

接上期

【数据结构初阶】三、 线性表里的链表(无头+单向+非循环链表)_高高的胖子的博客-CSDN博客

 =========================================================================

                     

 引言 

通过上期单链表(无头+单向+非循环链表)介绍使用

我们可以知道顺序表和链表的区别

              

              

顺序表和链表的一些区别:

              

  • 单链表无头+单向+非循环链表只有一个后继指针next指向下一个结点
    双向链表不仅有后继指针next还有一个前驱指针prev指向上一个结点
                         
  • 上篇单链表只能顺着往后遍历不能倒着往回走
    会造成一些操作很困难回文逆置等操作),
    双向链表顺着往后遍历也能倒着往回遍历

更多区别图示:

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

               

                

回顾上期中提到的带头双向循环链表

           

带头双向循环链表

简介:

结构最复杂一般用在单独存储数据

实际中使用的链表数据结构,都是带头双向循环链表

另外这个结构虽然结构复杂

但是使用代码实现以后会发现结构会带来很多优势实现反而简单

图示:

         

~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

             

1 . 双向链表的实现
(带头+双向+循环 链表)

(详细解释在图片的注释中,代码分文件放最后)
                  

实现具体功能前的准备工作

  • 包含之后会用到的头函数
                               
  • 创建双向链表数据类型 -- 链表结点中数据域里存储的数据的类型
                 
  • 创建双向链表结点结构体(类型) -- 结点中应有 数据域  指针域
图示

            

            

---------------------------------------------------------------------------------------------

            

BuyLTNode函数 -- 创建双向循环链表结点

  • 为创建结点开辟动态空间,并检查是否开辟成功
                          
  • 开辟成功后初始化结点数据域指针域
                   
  • 最后返回开辟的空间地址
图示

            

            

---------------------------------------------------------------------------------------------

            

LTInit函数 -- 带头双向循环链表初始化函数

  • 初始化时先使用BuyLTNode函数创建哨兵位
                
  • 因为要实现循环
    所以让哨兵位后继指针next前驱指针prev都指向自己
                    
  • 初始化后返回链表哨兵位
图示

            

            

---------------------------------------------------------------------------------------------

            

LTPrint函数 -- 打印双向链表各结点数据域数据

  • assert断言头指针(哨兵位地址)不为空
                
  • 创建结点指针cur进行遍历
                    
  • 使用while循环进行遍历打印

图示

测试 -- LTPrint函数

            

            

---------------------------------------------------------------------------------------------

            

LTPushBack函数 -- 向链表尾部插入一个结点(尾插)

  • assert断言头指针(哨兵位地址)不为空
                
  • 通过哨兵位配合前驱指针prev获得尾结点地址
                    
  • 调用BuyLTNode函数为尾插操作创建尾插结点newnode

                     

  • 尾插结点原尾部结点连接
                     

  • 尾插结点哨兵位进行连接

图示

测试 -- LTPushBack函数

            

            

---------------------------------------------------------------------------------------------

            

LTPopBack函数 -- 删除链表尾部结点(尾删)

  • assert断言 头指针(哨兵位地址)不为空双向链表不为空链表
                
  • 通过哨兵位的前驱指针prev获得尾结点tail
    通过尾结点tail获得倒数第二个结点tailPrev
                    
  • 释放删除尾结点tail

                     

  • 倒数第二个结点tailPrev成为新的尾结点
    为保持循环,把tailPrev哨兵位连接起来

图示

测试 -- LTPopBack函数

            

            

---------------------------------------------------------------------------------------------

            

LTPushFront函数 -- 向链表头部插入一个结点(头插)

  • assert断言头指针(哨兵位地址)不为空
                
  • 调用BuyLTNode函数为头插操作创建头插结点newnode
                    
  • 创建一个first指针保存原本第一个结点地址

                     

  • 哨兵位后继指针next指向头插结点newnode
    头插结点newnode前驱指针prev指向哨兵位
                     

  • 头插结点newnode后继指针next指向原本头结点first
    原本头结点first前驱指针prev指向头插结点newnode

图示

测试 -- LTPushFront函数

            

            

---------------------------------------------------------------------------------------------

            

LTPopFront函数 -- 删除链表头部结点(头删)

  • assert断言 头指针(哨兵位地址)不为空双向链表不为空链表
                
  • 通过哨兵位后继结点next获得头结点地址
    再通过first结点获得第二个结点
                    
  • 释放头结点first

                     

  • 哨兵位后继结点next指向第二个结点second
    第二个结点的前驱指针prev指向哨兵位

图示

测试 -- LTPopFront函数

            

            

---------------------------------------------------------------------------------------------

            

LTSize函数 -- 求链表有效结点个数(求链表长度)

  • assert断言头指针(哨兵位地址)不为空
                
  • 创建变量size存放链表长度
    创建结点指针cur进行遍历
                    
  • 使用while循环遍历链表计算链表长度

                     

  • 最后返回链表长度size

图示

测试 -- LTSize函数

            

            

---------------------------------------------------------------------------------------------

            

LTFind函数 -- 在双向链表中查找数据域数据为x的结点地址

  • assert断言头指针(哨兵位地址)不为空
                
  • 创建遍历指针cur保存第一个结点地址
                    
  • 使用while循环进行遍历查找

                     

  • 未找到则返回空指针

图示

测试 -- LTFind函数

            

            

---------------------------------------------------------------------------------------------

            

LTInsert函数 -- 在pos结点之前插入数据域数据为x的结点

  • assert断言头指针(哨兵位地址)不为空
                
  • 通过pos结点获得前一个结点posPrev地址
                    
  • 使用BuyLTNode函数为插入结点开辟空间

                     

  • posPrev结点的后继指针next指向newnode
    newnode前驱指针prev指向posPrev
                     

  • newnode后继指针next指向pos
    pos结点前驱指针prev指向newnode

图示

测试 -- LTInsert函数

            

            

---------------------------------------------------------------------------------------------

            

LTErase函数 -- 删除pos结点

  • assert断言删除位置结点pos不为空
                
  • 保存删除结点pos的前一个结点posPrev地址
    保存删除结点pos的后一个结点posNext地址
                    
  • 释放掉pos结点

                     

  • 将pos前结点posPrev的后继指针指向posNext
    将pos后结点posNext的前驱指针指向posPrev

图示

测试 -- LTErase函数

            

            

---------------------------------------------------------------------------------------------

            

LTDestroy函数 -- 销毁链表

  • assert断言头指针(哨兵位地址)不为空
                
  • 创建遍历指针cur保存第一个结点地址
                    
  • 使用while循环遍历释放有效结点

                     

  • 最后释放哨兵位

图示

         

~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

             

2 . 对应代码

List.h -- 双向链表头文件

#pragma once

//双向链表头文件:

//包含之后需要用到的头文件:
#include<stdio.h>
#include<stdlib.h>
#include<assert.h>

//定义双向链表数据域数据类型:
typedef int LTDataType;

//创建双向链表结点类型:
typedef struct ListNode
{
	//数据域:
	LTDataType data;

	//双向链表指针域:
	 //后继指针--指向后一个结点:
	struct ListNode* next;
	 //前驱指针--指向前一个结点:
	struct ListNode* prev;

}LTNode; //类型简称LTNode



//函数声明:

//创建链表结点--创建双向循环链表结点
//接收要插入创建结点数据域的数据
//返回创建结点的地址
LTNode* BuyLTNode(LTDataType x);

//双向链表初始化--带头双向循环链表初始化函数
//返回初始化结点的地址
LTNode* LTInit();

//打印链表--打印双向链表各结点数据域数据
//接收链表头指针(phead)
LTNode* LTPrint(LTNode* phead);

//双向链表尾插函数--向链表尾部插入一个结点(尾插):
//接收链表头指针(phead)、要尾插进链表的值(x)
void LTPushBack(LTNode* phead, LTDataType x);

//双向链表尾删函数--删除链表尾部结点(尾删)
//接收链表头指针(phead)
void LTPopBack(LTNode* phead);

//双向链表头插函数--向链表头部插入一个结点(头插):
//接收链表头指针(phead)、要头插进链表的值(x)
void LTPushFront(LTNode* phead, LTDataType x);

//双向链表头删函数--删除链表头部结点(头删)
//接收链表头指针(phead)
void LTPopFront(LTNode* phead);

//求链表结点个数函数--求链表有效结点个数(求链表长度)
//接收链表头指针(phead)
int LTSize(LTNode* phead);

//双向链表查找函数--在双向链表中查找数据域数据为x的结点地址
//接收链表头指针(phead)、要在链表中查找的值(x)
LTNode* LTFind(LTNode* phead, LTDataType x);

//双向链表插入函数--在pos结点之前插入数据域数据为x的结点
//接收插入位置(pos)、要插入链表的值(x)
void LTInsert(LTNode* pos, LTDataType x);

//双向链表删除函数--删除pos结点
//接收要删除结点地址(pos)
void LTErase(LTNode* pos);

//双向链表销毁函数--销毁链表
//接收要销毁链表头系欸但(phead)
void LTDestroy(LTNode* phead);

            

            

---------------------------------------------------------------------------------------------

                

List.c -- 双向链表函数实现文件

#define _CRT_SECURE_NO_WARNINGS 1

//双向链表函数实现文件:

//包含双向链表头文件:
#include "List.h"

//函数实现:

//创建链表结点--创建双向循环链表结点
//接收要插入创建结点数据域的数据
LTNode* BuyLTNode(LTDataType x)
{
	//为创建结点开辟动态空间:
	LTNode* node = (LTNode*)malloc(sizeof(LTNode));
	//检查是否开辟失败:
	if (node == NULL)
		//返回NULL,开辟失败
	{
		//打印错误信息:
		perror("malloc fail");
		//终止程序:
		exit(-1);
	}

	//把x放入结点数据域:
	node->data = x;
	//设置双向链表指针域:
	node->next = NULL;
	node->prev = NULL;
	
	//开辟成功则返回开辟空间地址
	return node;
}



//链表初始化--带头双向循环链表初始化函数
//接收链表头指针(phead)
LTNode* LTInit()
{
	//初始化时先使用BuyLTNode函数创建哨兵位:
	LTNode* phead = BuyLTNode(-1); //返回哨兵位指针

	//因为要实现循环,
	//所以让哨兵位后继指针next和前驱指针prev都指向自己:
	phead->next = phead;
	phead->prev = phead;
	//这样即使链表为空,它也是有头有尾的,即哨兵位phead

	//初始化后返回链表哨兵位:
	return phead;
}



//打印链表--打印双向链表各结点数据域数据
//接收链表头指针(phead)
LTNode* LTPrint(LTNode* phead)
{
	//assert断言头指针(哨兵位地址)不为空:
	assert(phead != NULL);

	//创建结点指针cur进行遍历:
	//指针cur应该从哨兵位(头指针)下一个结点开始
	LTNode* cur = phead->next;
	
	printf("phead <==> ");
	//因为是循环链表,不是以NULL空指针结尾
	//所以应该是当指针cur遍历回到哨兵位就终止遍历:
	while (cur != phead)
	//如果只用哨兵位,链表为空,
	//phead->next还是phead,不会进行打印
	{
		//打印数据域内容:
		printf("%d <=> ", cur->data);

		//打印完当前结点数据域数据后cur移向下个结点:
		cur = cur->next;
	}

	//打印完一个链表后换行:
	printf("\n");
}



//向链表尾部插入一个结点(尾插):
//接收结点头指针(phead)、要尾插进链表的值(x)
void LTPushBack(LTNode* phead, LTDataType x)
{
	//assert断言头指针(哨兵位地址)不为空:
	assert(phead != NULL);

	//通过哨兵位找到尾结点:
	//因为是循环链表:
	//所以哨兵位(带头链表)的前一个结点就是尾结点
	LTNode* tail = phead->prev; 
	//调用前驱指针获得尾结点

	//调用BuyLTNode函数为尾插创建尾插结点newnode:
	LTNode* newnode = BuyLTNode(x);

	//尾插结点前驱指针prev指向原本的尾结点:
	newnode->prev = tail;
	//原本尾结点后继指针next指向尾插结点:
	tail->next = newnode;

	//尾插结点后继指针next指向头结点:
	newnode->next = phead;
	//头结点前驱指针指向尾插结点:
	phead->prev = newnode;

	//带头+双向+循环 链表:
	//对比单链表,因为有哨兵位不用考虑链表为空的情况,
	//且不需要二级指针,通过操纵哨兵位这个结构体,
	//替换用二级指针操作头指针的操作


	/*
	//第二种方法:复用LTInsert函数
	//在哨兵位前插入一个值x就是尾插了:
	LTInsert(phead, x);
	*/
}



//双向链表尾删函数--删除链表尾部结点(尾删)
//接收链表头指针(phead)
void LTPopBack(LTNode* phead)
{
	//assert断言头指针(哨兵位地址)不为空:
	assert(phead != NULL);
	
	//assert断言双向链表不是空链表:
	assert(phead->next != phead);
	//如果哨兵位的下一个结点还是哨兵位说明是空链表

	//通过哨兵位的前驱指针prev获得尾结点tail:
	LTNode* tail = phead->prev;

	//再通过尾结点tail获得倒数第二个结点tailPrev:
	LTNode* tailPrev = tail->prev;

	//释放(删除)尾结点tail:
	free(tail);

	//这时就让倒数第二个结点tailPrev成为新的尾结点
	//为保持循环,把tailPrev和哨兵位连接起来:
	tailPrev->next = phead; //tailPrev后继指针指向哨兵位
	phead->prev = tailPrev; //哨兵位前驱指针指向tailPrev

	//带头+双向+循环 链表:
	//对比单链表,这里双向链表在尾删时因为有哨兵位的存在
	//即使链表只剩一个结点,也不用进行判断单独处理进行置空
	//这一个结点删掉后,还有哨兵位存在

	/*
	//第二种方法:复用LTErase函数
	//传尾结点地址给LTErase函数即可:
	LTErase(phead->prev);
	*/
}



//双向链表头插函数--向链表头部插入一个结点(头插):
//接收链表头指针(phead)、要头插进链表的值(x)
void LTPushFront(LTNode* phead, LTDataType x)
{
	//第二种方法:多定义一个指针
	
	//assert断言头指针(哨兵位地址)不为空:
	assert(phead != NULL);

	//调用BuyLTNode函数为头插创建头插结点newnode:
	LTNode* newnode = BuyLTNode(x);

	//创建一个first指针保存原本第一个结点地址:
	LTNode* first = phead->next;

	//哨兵位后继指针next指向头插结点newnode:
	phead->next = newnode;

	//头插结点newnode前驱指针prev指向哨兵位:
	newnode->prev = phead;

	//头插结点newnode后继指针next指向原本头结点first:
	newnode->next = first;

	//原本头结点first前驱指针prev指向头插结点newnode:
	first->prev = newnode;


	/*
	//assert断言头指针(哨兵位地址)不为空:
	assert(phead != NULL);
	
	//第一种方法:需要注意连接的顺序
	
	//调用BuyLTNode函数为头插创建头插结点newnode:
	LTNode* newnode = BuyLTNode(x);
	
	//先将头插结点的后继节点next连接上原本头结点:
	newnode->next = phead->next;
	//哨兵位的后继指针指向的就是头结点
	
	//再将原本头结点的前驱指针prev指向头插结点newnode:
	phead->next->prev = newnode;
	
	//哨兵位连接上头插节点newnode:
	phead->next = newnode;
	
	//头插结点newnode的前驱指针指向哨兵位:
	newnode->prev = phead;
	*/


	/*
	//第三种方法:复用LTInsert函数
	//在哨兵位后一个结点前插入一个值x就是头插了:
	LTInsert(phead->next, x);
	*/
}



//双向链表头删函数--删除链表头部结点(头删)
//接收链表头指针(phead)
void LTPopFront(LTNode* phead)
{
	//assert断言头指针(哨兵位地址)不为空:
	assert(phead != NULL);

	//assert断言双向链表不是空链表:
	assert(phead->next != phead);
	//如果哨兵位的下一个结点还是哨兵位说明是空链表

	//通过哨兵位后继结点next获得头结点地址:
	LTNode* first = phead->next;

	//再通过first结点获得第二个结点:
	LTNode* second = first->next;

	//释放头结点first:
	free(first);

	//让哨兵位后继结点next指向第二个结点second:
	phead->next = second;

	//第二个结点的前驱指针prev指向哨兵位:
	second->prev = phead;

	//带头+双向+循环 链表:
	//对比单链表,这里双向链表在头删时因为有哨兵位的存在
	//即使链表只剩一个结点,也不用进行判断单独处理进行置空
	//这一个结点删掉后,还有哨兵位存在
	
	/*
	//第二种方法:复用LTErase函数
	//传第一个结点地址给LTErase函数即可:
	LTErase(phead->next);
	*/
}



//求链表结点个数函数
//接收链表头指针(phead)
int LTSize(LTNode* phead)
{
	//assert断言头指针(哨兵位地址)不为空:
	assert(phead != NULL);

	int size = 0; //存放链表长度

	//创建结点指针cur进行遍历:
	//指针cur应该从哨兵位(头指针)下一个结点开始
	LTNode* cur = phead->next;

	//因为是循环链表,不是以NULL空指针结尾
	//所以应该是当指针cur遍历回到哨兵位就终止遍历:
	while (cur != phead)
		//如果只有哨兵位,链表为空,
		//phead->next还是phead,不会进行打印
	{
		++size; //遍历一遍长度+1

		//cur移向下个结点:
		cur = cur->next;
	}

	//返回链表长度:
	return size;
}



//双向链表查找函数--在双向链表中查找数据域数据为x的结点地址
//接收链表头指针(phead)、要在链表中查找的值(x)
LTNode* LTFind(LTNode* phead, LTDataType x)
{
	//assert断言头指针(哨兵位地址)不为空:
	assert(phead != NULL);

	//创建遍历指针cur,从第一个结点开始:
	LTNode* cur = phead->next;

	//使用while循环进行遍历:
	while (cur != phead)
		//如果只有哨兵位,链表为空,
		//phead->next还是phead,不会进行打印
	{
		if (cur->data == x)
			//找到要找的值了:
		{
			return cur; //返回该值结点
		}

		//调整指针:
		cur = cur->next;
	}

	//未找到则返回空指针:
	return NULL;
}



//双向链表插入函数--在pos结点之前插入数据域数据为x的结点
//接收插入位置(pos)、要插入链表的值(x)
void LTInsert(LTNode* pos, LTDataType x)
{
	//assert断言插入位置pos不为空:
	assert(pos != NULL);

	//通过pos结点获得前一个结点posPrev地址:
	LTNode* posPrev = pos->prev;

	//使用BuyLTNode函数为插入结点开辟空间:
	LTNode* newnode = BuyLTNode(x);

	//posPrev结点的后继指针next指向newnode:
	posPrev->next = newnode;

	//newnode前驱指针prev指向posPrev:
	newnode->prev = posPrev;

	//newnode后继指针next指向pos:
	newnode->next = pos;

	//pos结点前驱指针prev指向newnode:
	pos->prev = newnode;
}



//双向链表删除函数--删除pos结点
//接收要删除结点地址(pos)
void LTErase(LTNode* pos)
{
	//assert断言删除位置结点pos不为空:
	assert(pos != NULL);

	//保存要删除结点pos的前一个结点posPrev地址:
	LTNode* posPrev = pos->prev;

	//保存要删除结点pos的后一个结点posNext地址:
	LTNode* posNext = pos->next;

	//释放掉pos结点:
	free(pos);

	//将pos前结点posPrev的后继指针指向posNext:
	posPrev->next = posNext;

	//将pos后结点posNext的前驱指针指向posPrev:
	posNext->prev = posPrev;
}



//双向链表销毁函数--销毁链表
//接收要销毁链表头系欸但(phead)
void LTDestroy(LTNode* phead)
{
	//assert断言头指针(哨兵位地址)不为空:
	assert(phead != NULL);

	//创建遍历指针cur,从第一个结点开始:
	LTNode* cur = phead->next;

	//使用while循环进行遍历释放:
	while (cur != phead)
	{
		//释放前先存储下一个结点地址:
		LTNode* next = cur->next;

		//释放当前结点:
		free(cur);

		//调整指针:
		cur = next;
	}
	
	//删除完有效结点后,最后再释放哨兵位:
	free(phead);
}

            

            

---------------------------------------------------------------------------------------------

                

Test.c -- 双向链表测试文件

#define _CRT_SECURE_NO_WARNINGS 1

//双向链表函数测试文件:

//包含双向链表头文件:
#include "List.h"

//测试函数--
//LTInit、LTPushBack、LTPrintf函数
void TestList1()
{
	//初始化一个双向链表:
	LTNode* plist = LTInit();

	//初始化后使用尾插函数插入数据:
	LTPushBack(plist, 1);
	LTPushBack(plist, 2);
	LTPushBack(plist, 3);
	LTPushBack(plist, 4);
	LTPushBack(plist, 5);
	  
	//打印当前双向链表:
	LTPrint(plist);
}


//测试函数--LTPopBack函数
void TestList2()
{
	//初始化一个双向链表:
	LTNode* plist = LTInit();
	//初始化后使用尾插函数插入数据:
	LTPushBack(plist, 1);
	LTPushBack(plist, 2);
	LTPushBack(plist, 3);
	//打印当前双向链表:
	LTPrint(plist);

	//进行尾删:
	LTPopBack(plist);
	//打印当前双向链表:
	LTPrint(plist);

	//进行尾删:
	LTPopBack(plist);
	//打印当前双向链表:
	LTPrint(plist);

	//进行尾删:
	LTPopBack(plist);
	//打印当前双向链表:
	LTPrint(plist);
}


//测试函数--LTPushFront函数
void TestList3()
{
	//初始化一个双向链表:
	LTNode* plist = LTInit();
	//初始化后使用尾插函数插入数据:
	LTPushBack(plist, 1);
	LTPushBack(plist, 2);
	LTPushBack(plist, 3);
	//打印当前双向链表:
	LTPrint(plist);

	//进行头插:
	LTPushFront(plist, 1000);
	//打印当前双向链表:
	LTPrint(plist);
}


//测试函数--LTPopFront函数
void TestList4()
{
	//初始化一个双向链表:
	LTNode* plist = LTInit();
	//初始化后使用尾插函数插入数据:
	LTPushBack(plist, 1);
	LTPushBack(plist, 2);
	LTPushBack(plist, 3);
	//打印当前双向链表:
	LTPrint(plist);

	//进行头删:
	LTPopFront(plist);
	//打印当前双向链表:
	LTPrint(plist);
}


//测试函数--LTSize函数
void TestList5()
{
	//初始化一个双向链表:
	LTNode* plist = LTInit();
	//初始化后使用尾插函数插入数据:
	LTPushBack(plist, 1);
	LTPushBack(plist, 2);
	LTPushBack(plist, 3);
	//打印当前双向链表:
	LTPrint(plist);

	//计算链表长度:
	int size = LTSize(plist);
	//打印当前双向链表:
	printf("链表长度为:%d", size);
}


//测试函数--LTFind函数
void TestList6()
{
	//初始化一个双向链表:
	LTNode* plist = LTInit();
	//初始化后使用尾插函数插入数据:
	LTPushBack(plist, 1);
	LTPushBack(plist, 2);
	LTPushBack(plist, 3);
	//打印当前双向链表:
	LTPrint(plist);

	//使用查找函数:
	LTNode* find = LTFind(plist, 2);
	//打印找到的地址
	printf("0x%xn", find);
}


//测试函数--LTInsert函数
void TestList7()
{
	//初始化一个双向链表:
	LTNode* plist = LTInit();
	//初始化后使用尾插函数插入数据:
	LTPushBack(plist, 1);
	LTPushBack(plist, 2);
	LTPushBack(plist, 3);
	//打印当前双向链表:
	LTPrint(plist);

	//使用插入函数:
	LTInsert(plist->next->next, 100);
	//打印当前双向链表:
	LTPrint(plist);
}


//测试函数--LTErase函数
void TestList8()
{
	//初始化一个双向链表:
	LTNode* plist = LTInit();
	//初始化后使用尾插函数插入数据:
	LTPushBack(plist, 1);
	LTPushBack(plist, 2);
	LTPushBack(plist, 3);
	//打印当前双向链表:
	LTPrint(plist);

	//使用删除函数:
	LTErase(plist->next->next);
	//打印当前双向链表:
	LTPrint(plist);
}

//主函数:
int main()
{
	//调用测试函数:
	//TestList1();
	//TestList2();
	//TestList3();
	//TestList4();
	//TestList5();
	//TestList6();
	//TestList7();
	TestList8();

	return 0;
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1028757.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

python+vue理发店管理系统

理发店管理系统主要实现角色有管理员和会员,管理员在后台管理用户表模块、token表模块、收藏表模块、商品分类模块、热卖商品模块、活动公告模块、留言反馈模块、理发师模块、会员卡模块、会员充值模块、会员模块、服务预约模块、服务项目模块、服务类别模块、热卖商品评论表模…

【IC设计】NoC(Network on Chip)调研

文章目录 SoC&#xff08;System on Chip&#xff09;片上系统SoC的概念SoC总线架构存在的问题 互联网络基础①什么是互联网络&#xff1f;②哪里有互联网络&#xff1f;③互联网络的意义&#xff1f;④互联网络的参数有哪些 NoC&#xff08;Network on Chip&#xff09;片上互…

【MySQL基础】--- 约束

个人主页&#xff1a;兜里有颗棉花糖 欢迎 点赞&#x1f44d; 收藏✨ 留言✉ 加关注&#x1f493;本文由 兜里有颗棉花糖 原创 收录于专栏【MySQL学习专栏】&#x1f388; 本专栏旨在分享学习MySQL的一点学习心得&#xff0c;欢迎大家在评论区讨论&#x1f48c; 目录 一、什么…

近距离看GPU计算-2

文章目录 前言1.SIMT和硬件多线程3.GPU的Memory Hierarchy 前言 本文转自公众号 GPU and Computing 在《近距离看GPU计算》系列第一篇里我们介绍了GPU的一些基础知识及其如何从图形加速设备演化到通用计算平台。本文我们会具体从处理单元设计和存储层次结构两个方面探讨GPU不…

多元化工具汇聚:企业如何提升协同效率?

在现代企业中&#xff0c;协同工作是不可或缺的。然而&#xff0c;随着企业规模的扩大&#xff0c;协同工作的难度也随之增加。针对这些挑战&#xff0c;我们推荐一款多元化工具—J2L3x。在这篇文章中&#xff0c;我们将介绍J2L3x的主要功能和如何利用它来提高企业的协同效率。…

UG\NX二次开发 一个分割曲线的工具

文章作者:里海 来源网站:王牌飞行员_里海_里海NX二次开发3000例,里海BlockUI专栏,C\C++-CSDN博客 简介: 今天有群友发了一个工具演示,是一个分割曲线的工具: 我当时想这位好兄弟怎么这么牛逼,原来啊,他跟你们一样喜欢看我的博客。他用我分享的分割曲线的…

go学习-GMP模型

GMP 好理解还是 GPM 好理解&#xff1f; 按照上述图&#xff0c;从上往下&#xff0c;GPM更适合理解 GMP 模型&#xff1a; Go 语言运行时系统中的 Goroutine、用于管理 Goroutine 调度的 Go Scheduler&#xff08;P&#xff09;、机器可用的逻辑处理器数量&#xff08;M&#…

【数据结构复习之路】线性表(严蔚敏版)万字详解主打基础

专栏&#xff1a;数据结构复习之路 数据结构的三要数&#xff1a;逻辑结构、数据的运算、存储结构&#xff08;物理结构&#xff09;。 我接下来要介绍的线性表&#xff0c;顾名思义也将从这三个大方向进行阐述&#xff1a; 一、线性表的定义——逻辑结构 线性表是具有相同…

Flutter绘制拖尾效果

演示&#xff1a; 代码&#xff1a; import dart:ui;import package:flutter/material.dart; import package:kq_flutter_widgets/widgets/chart/ex/extension.dart;class TrailingView extends StatelessWidget {const TrailingView({super.key});overrideWidget build(Build…

成绩发布系统攻略

作为一名教师&#xff0c;管理学生成绩是我们工作中的重要任务之一。传统的手工成绩记录和发布方式已经无法满足现代教育的需求。因此&#xff0c;制作一个高效、安全、便捷的学生成绩发布系统是至关重要的。本文将为您介绍如何制作学生成绩发布系统&#xff0c;以提高教学效率…

MyBatis-Plus的常用注解

一、TableName 在使用MyBatis-Plus实现基本的CRUD时&#xff0c;我们并没有指定要操作的表&#xff0c;只是在Mapper接口继承BaseMapper时&#xff0c;设置了泛型User&#xff0c;而操作的表为user表&#xff0c;由此得出结论&#xff0c;MyBatis-Plus在确定操作的表时&#xf…

Flutter实现PS钢笔工具,实现高精度抠图的效果。

演示&#xff1a; 代码&#xff1a; import dart:ui;import package:flutter/material.dart hide Image; import package:flutter/services.dart; import package:flutter_screenutil/flutter_screenutil.dart; import package:kq_flutter_widgets/widgets/animate/stack.dart…

react如何根据变量渲染组件

三元运算符useMemo 第一种方法的缺点&#xff1a;其他变量更改时&#xff0c;会再次进入三元运算符,例子如下&#xff1a; //这里有一个父组件:Father { n0 ? <Father><div>{111}</div></Father> : <div>{111}</div> }第二种方法如图 …

apk获取MD5方式记录

1&#xff0c;低版本android studio 我这里是Android studio Arctic Fox 直接使用keytool -printcert -jarfile xxx.apk获取 获取得到的效果&#xff1a; 2&#xff0c;高版本android studio 在高版本下&#xff0c;按照如下图点击打开到gradle。在③步骤下直接输入signning…

《向量数据库指南》——火山引擎向量数据库对正式外开放服务

向量数据库技术全景 经过长期的内部探索和优化,抖音采用的向量数据库产品结构如下图所示:基于云基础设施,提供经过深度打磨和优化的各个引擎,提供从多模态数据写入,到向量生成,再到在线检索,以及上线后的弹性调度和监控的一整套全链路解决方案。 火山引擎向量数据库的场…

C++ 里 ++i 是原子操作吗?

1.什么是原子操作 在多线程环境下,原子操作是指不会被线程调度机制打断的操作;这种操作一旦开始,就一直运行到结束,中间不会有任何 context switch (切换到另一个线程)。 原子操作可以确保某些特定操作在多线程条件下,不会由于线程切换而导致数据污染。比如,对一个变量的读/写…

ASEMI快恢复二极管S1FD40A180H参数,S1FD40A180H应用

编辑-Z S1FD40A180H参数描述&#xff1a; 型号&#xff1a;S1FD40A180H 最大直流反向电压VR&#xff1a;1800V 最大工作峰值反向电压VRWM&#xff1a;1440V 最大平均正向电流IF&#xff1a;40A 非重复正向浪涌电流IFSM&#xff1a;500A 操作和储存温度范围TJ ,TSTG&…

洞察2023:中国心室辅助装置行业竞争格局及市场份额

本文核心数据&#xff1a;代表性企业排名 ; 代表性企业优势分析等 1、中国心室辅助装置行业竞争梯队 人工心脏 ( Artificial Heart, AH ) 是机械辅助类器械的代表&#xff0c;用于替代或辅助心脏泵血功能。按照功能可分为心室辅助装置 ( Ventricular Assist Device&#xff0…

Python与数据分析--每天绘制Matplotlib库实例图片3张-第1天

目录 1.实例1--Bar color demo 2.实例2--Bar Label Demo 3.实例3--Grouped bar chart with labels 1.实例1--Bar color demo import matplotlib.pyplot as plt # 支持中文 plt.rcParams[font.sans-serif] [SimHei] # 用来正常显示中文标签 plt.rcParams[axes.unicode_minus…

Node.js环境安装与服务设置,结合内网穿透随时随地公网访问!

文章目录 前言1.安装Node.js环境2.创建node.js服务3. 访问node.js 服务4.内网穿透4.1 安装配置cpolar内网穿透4.2 创建隧道映射本地端口 5.固定公网地址 前言 Node.js 是能够在服务器端运行 JavaScript 的开放源代码、跨平台运行环境。Node.js 由 OpenJS Foundation&#xff0…