【数据结构】线性表之《无头单链表》超详细实现

news2024/11/19 0:26:38

单链表

  • 一.链表的概念及结构
  • 二.顺序表与链表的区别与联系
  • 三.单链表的实现
    • 1.创建单链表
    • 2.初始化单链表
    • 3.购买节点
    • 4.打印单链表
    • 5.插入操作
      • 1.头插
      • 2.尾插
      • 3.给定位置之前插入
    • 6.删除操作
      • 1.头删
      • 2.尾删
      • 3.删除给定位置的结点
    • 7.查找数据
    • 8.修改数据
    • 9.求单链表长度
    • 10.清空单链表
    • 11.销毁单链表
  • 四.模块化源代码
    • 1.SingleLinkList.h
    • 2.SingleLinkList.c
    • 3.test.c
  • 五.链表必做OJ题

前言:在前一章节成功实现了顺序表后,对数据结构的理解已经初具雏形,但这只是启蒙阶段,接下来我们将进入链表的探索学习。链表作为数据结构的另一种形式,不仅仅是简单的表述,它承载了更多的内涵和抽象思维,有助于深入理解数据在计算机科学中的精髓。

一.链表的概念及结构

概念:链表是一种物理存储结构上非连续、非顺序的存储结构,数据元素的逻辑顺序是通过链表
中的指针链接次序实现的 。

typedef int SLLDataType; //增强程序的可维护性

typedef struct SLLNode
{
	SLLDataType data;     //数据域
	struct SLLNode* next; //指针域
}SLLNode;

在这里插入图片描述
实际中要实现的链表结构非常多样(2^3=8中)。

  1. 单向,双向。
  2. 带头,不带头。
  3. 循环,非循环。

虽然有这么多的链表的结构,但是我们实际中最常用还是两种结构:
在这里插入图片描述

  1. 无头单向非循环链表:结构简单,一般不会单独用来存数据。实际中更多是作为其他数据结
    构的子结构,如哈希桶图的邻接表等等。另外这种结构在笔试面试中出现很多。

  2. 带头双向循环链表:结构最复杂,一般用在单独存储数据。实际中使用的链表数据结构,都
    是带头双向循环链表。另外这个结构虽然结构复杂,但是使用代码实现以后会发现结构会带
    来很多优势,实现反而简单了,后面我们代码实现了就知道了。

二.顺序表与链表的区别与联系

顺序表
优点:空间连续,支持随机访问。
缺点:如果空间不够要增容,增容会付出一定的性能消耗,其次可能存在一定的空间浪费;头部或者中部左右的插入 ,删除效率低——>O(N)。

链表
优点:任意位置的插入删除的时间复杂度为O(1);没有增容消耗,按需申请节点空间,但是不用了记得直接释放。
缺点:以节点为单位存储,不支持随机访问。

三.单链表的实现

1.创建单链表

链表由节点组成,每个节点要存放数据下一个节点的地址(为了找到下一个节点)。由于存放的是不同类型的数据,所以定义一个结构体,成员则有:数据域,指针域。

单链表:指向该节点的指针。

typedef int SLLDataType; //增强程序的可维护性

typedef struct SLLNode //单链表节点
{
	SLLDataType data;     //数据域
	struct SLLNode* next; //指针域
}SLLNode;

SLLNode* plist;//单链表

2.初始化单链表

注意:以下函数中的参数 phead 对应 plistpphead 对应 &plist

为什么这么做呢?
答:值传递地址传递的问题。

  1. 无需改变链表的话(比如:打印链表,查找数据等…),只需传入值。
  2. 需要改变链表的话(头插,头删等…),需要传入地址。
  3. 即使 plist 是一级指针,但是传参时仍会创建 phead 存放 plist 。本质依旧是传值。

在这里插入图片描述

一般初始化我们都习惯赋值为0,即单链表plist(*pphead)赋值为NULL。

void SLLInit(SLLNode** pphead)
{
	assert(pphead); //断言
	*pphead = NULL;
}

3.购买节点

由于头插,尾插,按位置插入链表,都要先准备一个节点。为了减少代码的重复,直接对其进行封装,创建新节点的时候直接调用该接口就行。

SLLNode* BuyNode(SLLDataType x)
{
	SLLNode* newNode = (SLLNode*)malloc(sizeof(SLLNode)); //申请节点空间
	if (newNode == NULL) //申请失败
	{
		perror("malloc fail");
		exit(1);
	}
	//申请成功
	newNode->data = x;
	newNode->next = NULL;
	return newNode; //返回新节点
}

4.打印单链表

定义一个指针指向单链表,利用NULL这一结束条件,循环遍历打印即可,较为简单。

void SLLPrint(SLLNode* phead)
{
	SLLNode* cur = phead; //定位单链表的头节点
	while (cur != NULL)
	{
		printf("%d->", cur->data);
		cur = cur->next; //更新为下一节点
	}
	printf("NULL\n");
}

5.插入操作

1.头插

头插思想:创建新节点,新节点的指针域指向单链表的头节点(实际上就是单链表),再更新单链表的头节点指向新节点。

void SLLPushFront(SLLNode** pphead, SLLDataType x)
{
	assert(pphead);

	SLLNode* newNode = BuyNode(x); //购买节点
	newNode->next = *pphead; //新节点头插
	*pphead = newNode; //更新单链表的头节点
}

2.尾插

尾插思想

  1. 当单链表为NULL:单链表的头指针指向新节点。
  2. 当单链表不为NULL:找到尾节点,尾节点的指针域指向新节点。
void SLLPushBack(SLLNode** pphead, SLLDataType x)
{
	assert(pphead);

	SLLNode* newNode = BuyNode(x); //购买节点
	if (*pphead == NULL) //单链表为空
	{
		*pphead = newNode; //更新单链表的头节点
	}
	else //单链表不为空
	{
		SLLNode* tail = *pphead; //寻找尾节点
		while (tail->next != NULL)
		{
			tail = tail->next;
		}
		tail->next = newNode; //尾节点,链接新节点
	}
}

3.给定位置之前插入

思路:

  1. 当给定的位置恰好是头节点的地址时,直接调用头插。
  2. 否则要寻找 pos 指向的节点的前一个节点,新节点的指针域指向 pos 指向的节点,前一个节点的指针域指向新节点。
void SLLInsert(SLLNode** pphead, SLLNode* pos, SLLDataType x)
{
	assert(pphead);
	assert(pos);
	
	if (pos == *pphead) //pos是头节点的地址
	{
		SLLPushFront(pphead, x); //直接头插
	}
	else
	{
		SLLNode* newNode = BuyNode(x); //购买节点
		SLLNode* prev = *pphead; //定位pos前一个节点
		while (prev->next != pos)
		{
			prev = prev->next;
		}
		//插入操作
		newNode->next = pos; 
		prev->next = newNode; 
	}
}

6.删除操作

1.头删

思路:

  1. 当单链表为NULL:无需操作。
  2. 当单链表不为NULL:先保存头节点的下一个节点的指针,再释放头指针,最后更新头节点为保存的哪个头节点。
void SLLPopFront(SLLNode** pphead)
{
	assert(pphead);

	//单链表为空
	if (*pphead == NULL)
	{
		return; //无需释放,直接退出
	}
	else
	{
		SLLNode* cur = (*pphead)->next; //定位头节点的下一个节点
		free(*pphead); //释放头节点
		*pphead = cur; //更新单链表的头节点
	}
}

2.尾删

思路略复杂:

  1. 当单链表为NULL:无需操作。
  2. 当单链表只有一个节点:释放头节点,将头指针置为NULL。
  3. 当单链表有多个节点:先找到尾节点的前一个节点并保存,释放尾节点,将保存的节点的指针域置为NULL。
void SLLPopBack(SLLNode** pphead)
{
	assert(pphead);

	//单链表为空
	if (*pphead == NULL)
	{
		return; //无需释放,直接退出
	}
	//单链表只有一个节点
	else if ((*pphead)->next == NULL) //注意:加上括号
	{
		free(*pphead); //释放头节点
		*pphead = NULL; //单链表置为NULL
	}
	//单链表有多个节点
	else
	{
		SLLNode* prev = NULL; //定位尾节点前一个节点
		SLLNode* tail = *pphead; //定位尾节点
		while (tail->next != NULL)
		{
			prev = tail;
			tail = tail->next;
		}
		free(tail); //释放尾节点
		tail = NULL; //置为NULL,预防野指针
		prev->next = NULL; //变为尾节点后置为NULL
	}
}

3.删除给定位置的结点

思路:

  1. 当待删除的节点为头节点时,直接调用头删即可。
  2. 否则保存待删除的节点的前一个节点,将该节点的指针域指向待删除的节点的下一个节点,最后释放待删除的节点即可。
void SLLErase(SLLNode** pphead, SLLNode* pos)
{
	assert(pphead);
	assert(pos);

	if (pos == *pphead) //pos是头节点的地址
	{
		SLLPopFront(pphead); //直接头删
	}
	else
	{
		SLLNode* prev = *pphead; //定位pos前一个节点
		while (prev->next != pos)
		{
			prev = prev->next;
		}
		prev->next = pos->next; //删除过程
		free(pos); //释放节点
		pos = NULL; //置为NULL,预防野指针
	}
}

7.查找数据

思路:循环遍历单链表即可,找到返回地址,未找到返回NULL。

SLLNode* SLLFind(SLLNode* phead, SLLDataType x)
{
	SLLNode* cur = phead; //定位值为x的节点
	while (cur != NULL) //遍历单链表
	{
		if (cur->data == x)
		{
			return cur; //找到了,返回节点的地址
		}
		cur = cur->next;
	}
	return NULL; //找不到,返回NULL
}

8.修改数据

思路:直接通过SLLFind函数得到地址,在该处修改即可,较为简单,同时SLLErase与SLLInsert函数都要通过SLLFind函数得到地址。

void SLLModify(SLLNode** pphead, SLLNode* pos, SLLDataType x)
{
	assert(pphead);
	assert(pos); //防止对NULL解引用导致程序崩溃
	pos->data = x; //直接修改就行了
}

9.求单链表长度

思路:利用头指针,向后循环遍历直到不为空即可。

int SLLLength(SLLNode* phead)
{
	int len = 0;
	SLLNode* cur = phead;
	while (cur != NULL)
	{
		cur = cur->next;
		len++;
	}
	return len;
}

10.清空单链表

思路:这里不像顺序表一样,顺序表只需释放一个指针arr(连续开辟的空间),而单链表物理上是不连续的,需要释放每一个节点,循环遍历单链表即可。

void SLLClear(SLLNode** pphead)
{
	assert(pphead);

	//从头开始逐个释放
	SLLNode* cur = *pphead;
	while (cur != NULL)
	{
		*pphead = cur->next;
		free(cur);
		cur = *pphead;
	}
}

11.销毁单链表

思路:自认为销毁与清空单链表没有太大区别

void SLLDestory(SLLNode** pphead)
{
	assert(pphead);

	SLLClear(pphead); //与清空单链表无区别
}

四.模块化源代码

1.SingleLinkList.h

//#pragma once 防止头文件被重复包含,导致效率下降
#ifndef __SINGLELINKLIST_H__
#define __SINGLELINKLIST_H__

#include<stdio.h>
#include<stdlib.h>
#include<assert.h>

typedef int SLLDataType; //增强程序的可维护性

typedef struct SLLNode
{
	SLLDataType data;     //数据域
	struct SLLNode* next; //指针域
}SLLNode;

void SLLInit(SLLNode** pphead);//初始化单链表(需要修改单链表,传地址)

SLLNode* BuyNode(SLLDataType x);//购买节点

void SLLPrint(SLLNode* phead);//打印单链表(无需修改单链表,传值)

void SLLPushBack(SLLNode** pphead, SLLDataType x);//尾插(同理,传地址)

void SLLPushFront(SLLNode** pphead, SLLDataType x);//头插

void SLLPopBack(SLLNode** pphead);//尾删

void SLLPopFront(SLLNode** pphead);//头删

SLLNode* SLLFind(SLLNode* phead, SLLDataType x);//查找

void SLLInsert(SLLNode** pphead, SLLNode* pos, SLLDataType x);//插入:通过《SLLFind函数》找到pos,在pos前插入x

void SLLErase(SLLNode** pphead, SLLNode* pos);//删除:通过《SLLFind函数》找到pos,删除pos位置的值

void SLLModify(SLLNode** pphead, SLLNode* pos, SLLDataType x);//修改:通过《SLLFind函数》找到pos,修改pos位置的值

int SLLLength(SLLNode* phead);//求单链表的长度

void SLLClear(SLLNode** pphead);//清空单链表

void SLLDestory(SLLNode** pphead);//销毁单链表

#endif

2.SingleLinkList.c

#define _CRT_SECURE_NO_WARNINGS 1

#include"SingleLinkList.h"

void SLLInit(SLLNode** pphead)
{
	assert(pphead); //断言
	*pphead = NULL;
}

SLLNode* BuyNode(SLLDataType x)
{
	SLLNode* newNode = (SLLNode*)malloc(sizeof(SLLNode)); //申请节点空间
	if (newNode == NULL) //申请失败
	{
		perror("malloc fail");
		exit(1);
	}
	//申请成功
	newNode->data = x;
	newNode->next = NULL;
	return newNode; //返回新节点
}

void SLLPrint(SLLNode* phead)
{
	SLLNode* cur = phead; //定位单链表的头节点
	while (cur != NULL)
	{
		printf("%d->", cur->data);
		cur = cur->next; //更新为下一节点
	}
	printf("NULL\n");
}

void SLLPushBack(SLLNode** pphead, SLLDataType x)
{
	assert(pphead);

	SLLNode* newNode = BuyNode(x); //购买节点
	if (*pphead == NULL) //单链表为空
	{
		*pphead = newNode; //更新单链表的头节点
	}
	else //单链表不为空
	{
		SLLNode* tail = *pphead; //寻找尾节点
		while (tail->next != NULL)
		{
			tail = tail->next;
		}
		tail->next = newNode; //尾节点,链接新节点
	}
}

void SLLPushFront(SLLNode** pphead, SLLDataType x)
{
	assert(pphead);

	SLLNode* newNode = BuyNode(x); //购买节点
	newNode->next = *pphead; //新节点头插
	*pphead = newNode; //更新单链表的头节点
}

void SLLPopBack(SLLNode** pphead)
{
	assert(pphead);

	//单链表为空
	if (*pphead == NULL)
	{
		return; //无需释放,直接退出
	}
	//单链表只有一个节点
	else if ((*pphead)->next == NULL) //注意:加上括号
	{
		free(*pphead); //释放头节点
		*pphead = NULL; //单链表置为NULL
	}
	//单链表有多个节点
	else
	{
		SLLNode* prev = NULL; //定位尾节点前一个节点
		SLLNode* tail = *pphead; //定位尾节点
		while (tail->next != NULL)
		{
			prev = tail;
			tail = tail->next;
		}
		free(tail); //释放尾节点
		tail = NULL; //置为NULL,预防野指针
		prev->next = NULL; //变为尾节点后置为NULL
	}
}

void SLLPopFront(SLLNode** pphead)
{
	assert(pphead);

	//单链表为空
	if (*pphead == NULL)
	{
		return; //无需释放,直接退出
	}
	else
	{
		SLLNode* cur = (*pphead)->next; //定位头节点的下一个节点
		free(*pphead); //释放头节点
		*pphead = cur; //更新单链表的头节点
	}
}

SLLNode* SLLFind(SLLNode* phead, SLLDataType x)
{
	SLLNode* cur = phead; //定位值为x的节点
	while (cur != NULL) //遍历单链表
	{
		if (cur->data == x)
		{
			return cur; //找到了,返回节点的地址
		}
		cur = cur->next;
	}
	return NULL; //找不到,返回NULL
}

void SLLInsert(SLLNode** pphead, SLLNode* pos, SLLDataType x)
{
	assert(pphead);
	assert(pos);
	
	if (pos == *pphead) //pos是头节点的地址
	{
		SLLPushFront(pphead, x); //直接头插
	}
	else
	{
		SLLNode* newNode = BuyNode(x); //购买节点
		SLLNode* prev = *pphead; //定位pos前一个节点
		while (prev->next != pos)
		{
			prev = prev->next;
		}
		//插入操作
		newNode->next = pos; 
		prev->next = newNode; 
	}
}

void SLLErase(SLLNode** pphead, SLLNode* pos)
{
	assert(pphead);

	if (pos == *pphead) //pos是头节点的地址
	{
		SLLPopFront(pphead); //直接头删
	}
	else
	{
		SLLNode* prev = *pphead; //定位pos前一个节点
		while (prev->next != pos)
		{
			prev = prev->next;
		}
		prev->next = pos->next; //删除过程
		free(pos); //释放节点
		pos = NULL; //置为NULL,预防野指针
	}
}

void SLLModify(SLLNode** pphead, SLLNode* pos, SLLDataType x)
{
	assert(pphead);
	assert(pos); //防止对NULL解引用导致程序崩溃
	pos->data = x; //直接修改就行了
}

int SLLLength(SLLNode* phead)
{
	int len = 0;
	SLLNode* cur = phead;
	while (cur != NULL)
	{
		cur = cur->next;
		len++;
	}
	return len;
}

void SLLClear(SLLNode** pphead)
{
	assert(pphead);

	//从头开始逐个释放
	SLLNode* cur = *pphead;
	while (cur != NULL)
	{
		*pphead = cur->next;
		free(cur);
		cur = *pphead;
	}
}

void SLLDestory(SLLNode** pphead)
{
	assert(pphead);

	SLLClear(pphead); //与清空单链表无区别
}

3.test.c

#define _CRT_SECURE_NO_WARNINGS 1

#include"SingleLinkList.h"

enum //匿名枚举
{
	EXIT,
	PUSHBACK,
	PUSHFRONT,
	POPBACK,
	POPFRONT,
	INSERT,
	ERASE,
	FIND,
	MODIFY,
	PRINT,
	LENGTH,
	CLEAR
};

void Menu()
{
	printf("*************单链表************\n");
	printf("****1.尾插           2.头插****\n");
	printf("****3.尾删           4.头删****\n");
	printf("****5.插入           6.删除****\n");
	printf("****7.查找           8.修改****\n");
	printf("****9.打印          10.长度****\n");
	printf("***11.清空           0.退出****\n");
	printf("*******************************\n");
}

int main()
{
	SLLNode* plist;
	SLLInit(&plist);
	int select = 0;       //操作选项
	SLLDataType value;    //接收值
	SLLDataType value1;   //接收值
	SLLNode* pos = NULL;  //接收指针
	do
	{
		Menu();
		printf("请输入您的操作:");
		scanf("%d", &select);
		switch (select)
		{
		case EXIT:
			printf("退出单链表!\n");
			break;
		case PUSHBACK:
			printf("请输入您要尾插的值(输入-1代表结束):");
			while ((scanf("%d", &value), value != -1)) //逗号表达式
			{
				SLLPushBack(&plist, value);
			}
			break;
		case PUSHFRONT:
			printf("请输入您要头插的值(输入-1代表结束):");
			do
			{
				scanf("%d", &value);
				if (value != -1)
				{
					SLLPushFront(&plist, value);
				}
			} while (value != -1);
			break;
		case POPBACK:
			SLLPopBack(&plist);
			break;
		case POPFRONT:
			SLLPopFront(&plist);
			break;
		case INSERT:
			printf("请输入您要插入到《何值前面》以及《插入的值》:");
			scanf("%d %d", &value1, &value);
			pos = SLLFind(plist, value1);
			if (pos != NULL)
			{
				SLLInsert(&plist, pos, value);
			}
			else
			{
				printf("该值不存在,无法插入!\n");
			}
			break;
		case ERASE:
			printf("请输入您要删除的值:");
			scanf("%d", &value);
			pos = SLLFind(plist, value);
			if (pos != NULL)
			{
				SLLErase(&plist, pos);
			}
			else
			{
				printf("该值不存在,无法删除!\n");
			}
			break;
		case FIND:
			printf("请输入您要查找的值:");
			scanf("%d", &value);
			int ret = SLLFind(plist, value);
			if (ret == -1)
			{
				printf("您要查找的值不存在!\n");
			}
			else
			{
				printf("您要查找的值存在!\n");
			}
			break;
		case MODIFY:
			printf("请输入您要《要修改的值》以及《修改后的值》:");
			scanf("%d %d", &value1, &value);
			pos = SLLFind(plist, value1);
			if (pos != NULL)
			{
				SLLModify(&plist, pos, value);
			}
			else
			{
				printf("该值不存在,无法修改!\n");
			}
			break;
		case PRINT:
			SLLPrint(plist);
			break;
		case LENGTH:
			printf("单链表的长度:%d\n", SLLLength(plist));
			break;
		case CLEAR:
			SLLClear(&plist);
			break;
		}
	} while (select);
	SLLDestory(&plist); //记得最后要销毁,防止内存泄漏
	return 0;
}

五.链表必做OJ题

  1. 反转单链表
  2. 链表的中间结点
  3. 合并两个有序链表
  4. 判断链表是否有环?
  5. 求环形链表的入口点?

以后还会更新其余的链表:带头,循环,双链等等组合的链表。

创作不易,如果能帮到你的话能赏个三连吗?感谢啦!!!

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

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

相关文章

python代码

# 请在______处使用一行代码或表达式替换# 注意&#xff1a;请不要修改其他已给出代码s input("请输入一个字符串:") print("{:*^30}".format(s))# 请在______处使用一行代码或表达式替换 # # 注意&#xff1a;请不要修改其他已给出代码a, b 0, 1 while …

程序员们,能告诉我你们为什么选择arch linux吗?

在开始前刚好我有一些资料&#xff0c;是我根据网友给的问题精心整理了一份「linux的资料从专业入门到高级教程」&#xff0c; 点个关注在评论区回复“888”之后私信回复“888”&#xff0c;全部无偿共享给大家&#xff01;&#xff01;&#xff01; Arch Linux 受到程序员青…

Qt|海康摄像头多个页面展示问题

为大家分享一个使用海康摄像头的小功能&#xff0c;希望对大家有用~ 使用场景&#xff1a; 在程序中多个不同功能页面需要展示摄像头的实时预览画面&#xff0c;该如何高效的展示呢&#xff1f; 对于海康摄像头的实时预览接口调用流程&#xff0c;如下所示&#xff1a; 按照流…

GD32F4xx 移植agile_modbus软件包与电能表通信

目录 1. agile_modbus1.1 简介1.2 下载2. agile_modbus使用2.1 源码目录2.2 移植3. 通信调试3.1 代码3.3 通信测试1. agile_modbus 1.1 简介 agile_modbus是一个轻量级的Modbus协议栈,主要特点: 支持RTU和TCP协议,采用纯C语言开发,不涉及任何硬件接口,可直接在任何形式的…

Java学习 (一) 环境安装及入门程序

一、安装java环境 1、获取软件包 https://www.oracle.com/java/technologies/downloads/ .exe 文件一路装过去就行&#xff0c;最好别装c盘 &#xff0c;我这里演示的时候是云主机只有C盘 2、配置环境变量 我的电脑--右键属性--高级系统设置--环境变量 在环境变量中添加如下配…

有路网整体布局

有路网地址 <!DOCTYPE html> <html lang"en"> <head><meta charset"UTF-8"><meta name"viewport" content"widthdevice-width, initial-scale1.0"><title>Document</title><style>.…

昇思25天学习打卡营第1天 | 快速入门

内容介绍&#xff1a;通过MindSpore的API来快速实现一个简单的深度学习模型。 具体内容&#xff1a; 1. 导包 import mindspore from mindspore import nn from mindspore.dataset import vision, transforms from mindspore.dataset import MnistDataset 2. 处理数据 fro…

Gobject tutorial 六

Instantiatable classed types Initialization and destruction 类型的实例化是通过函数g_tpye_create_instance()实现的。这个函数首先会查找与类型相关的GTypeInfo结构体&#xff0c;之后&#xff0c;查询结构体中的instance_size和 instance policy即 n_preallocs(在 2.10版…

Nuxt3页面开发实战探索

title: Nuxt3页面开发实战探索 date: 2024/6/19 updated: 2024/6/19 author: cmdragon excerpt: 摘要&#xff1a;这篇文章是关于Nuxt3页面开发实战探索的。它介绍了Nuxt3的基础入门&#xff0c;安装与配置&#xff0c;项目结构&#xff0c;内置组件与功能&#xff0c;以及页…

持续集成jenkins+gitee

首先要完成gitee部署&#xff0c;详见自动化测试git的使用-CSDN博客 接下来讲如何从git上自动拉取代码&#xff0c;实现jenkins无人值守&#xff0c;定时执行测试&#xff0c;生成测试报告。 需要这三个安装包 由于目前的jenkins需要至少java11到java17的版本&#xff0c;所以…

深度解析消费者最关心的车联网核心问题

随着科技的迅猛发展&#xff0c;车联网&#xff08;V2X&#xff09;或智能网联汽车成为了提供车辆非视距信息的独特解决方案。它们是传感器技术的关键补充&#xff0c;通过车联网&#xff08;V2X&#xff09;&#xff0c;交通工具可以与其他车辆或基础设施进行信息交流。车联网…

upload-labs第十三关教程

upload-labs第十三关教程 第十三关一、源代码分析代码审计 二、绕过分析1&#xff09;0x00绕过a.上传eval.pngb.使用burpsuite进行拦截修改之前&#xff1a;修改之后&#xff1a;进入hex模块&#xff1a; c.放包上传成功&#xff1a; d.使用中国蚁剑进行连接 2&#xff09;%00绕…

Java 打包编译、运行报错

无法访问com.sun.beans.introspect.PropertyInfo-CSDN博客 [ERROR] COMPILATION ERROR : [INFO] ------------------------------------------------------------- [ERROR] sa-base/src/main/java/net/lab1024/sa/base/module/support/datatracer/service/DataTracerChangeCon…

若依框架自定义开发使用学习笔记(1)

因为我是跳着学的&#xff0c;原理那些都没咋看。 代码自动生成&#xff0c;依赖sql表 在ruoyi数据库中&#xff0c;创建你想要的表&#xff0c;这里我创建了个购物车表&#xff0c;由于空间有限&#xff0c;只能拍到这么多。 然后就可以在前端自动生成代码 点击导入按钮 …

华为---- RIP路由协议基本配置

08、RIP 8.1 RIP路由协议基本配置 8.1.1 原理概述 RIP(Routing Information Protocol,路由协议)作为最早的距离矢量IP路由协议&#xff0c;也是最先得到广泛使用的一种路由协议&#xff0c;采用了Bellman-Ford算法&#xff0c;其最大的特点就是配置简单。 RIP协议要求网络中…

如何学习 Java 中的 Socket 编程,进行网络通信

Socket编程是网络编程的核心技术之一&#xff0c;它使得不同主机之间可以进行数据通信。Java提供了丰富的网络编程API&#xff0c;使得编写网络应用程序变得相对简单和直观。本文将详细讲解如何学习Java中的Socket编程&#xff0c;并通过示例代码展示如何实现网络通信。 一、S…

特征标注——OpenCV

特征标注 导入必要的库创建窗口显示原始图片和标注后的图片存储用户选择的图片路径字体样式和大小定义了select_image函数定义了annotate_landmarks()函数设置按钮调整图片标签的位置设置图片位置主事件循环运行显示&#xff1a;全部代码 导入必要的库 import tkinter as tk: 导…

细说MCU输出互补型PWM波形时设置死区时间的作用

目录 一、工程背景 二、死区时间的作用 一、工程背景 在作者的文章里建立工程时&#xff0c;为配置输出互补型PWM波形曾经设置了死区时间&#xff0c;DEAD100个定时器的时间周期&#xff08;简称实例1&#xff09;&#xff1a;细说MCU输出互补型PWM波形的实现方法-CSDN博客 …

【Python教程】如何搭建一个高效的Python开发环境?结尾附安装包直通车

前言&#xff1a; Python 丰富的函数库和组件库是这门语言强大的核心原因&#xff01;但我们不可能去记忆所有的方法名和参数名&#xff0c;往往只能记住一些常用的或者某个方法开头的几个字母。这个时候一个好的开发工具就需要能聪明地“猜”出你想输入的代码&#xff0c;并给…

数据结构基础(基于c++)

数据结构基础&#xff08;基于c&#xff09; 文章目录 数据结构基础&#xff08;基于c&#xff09;前言1. 递归、迭代、时间复杂度、空间复杂度2. 数据结构 数组与链表1. 数组2. 链表3. 动态数组4. 数组与链表对比 前言 参考资料&#xff1a;Hello 算法 (hello-algo.com) 1. 递…