【数据结构】单链表的C语言实现--万字详解介绍

news2024/9/24 6:01:17

在这里插入图片描述

​📝个人主页:@Sherry的成长之路
🏠学习社区:Sherry的成长之路(个人社区)
📖专栏链接:数据结构
🎯长路漫漫浩浩,万事皆有期待

文章目录

  • 1.链表
    • 1.1 链表的概念及结构:
    • 1.2 顺序表的缺陷
    • 1.3 链表的意义:
  • 2.链表的分类:
    • 2.1 单向或双向:
    • 2.2 带头或不带头:
    • 2.3 循环或非循环:
  • 3.单链表的实现:
    • 3.1 工程文件:
    • 3.2 接口设计
    • 3.3 `重点`接口实现:
      • ①.创建新节点:
      • ②.打印单链表:
      • ③.单链表尾插:
      • ④.单链表头插:
      • ⑤.单链表尾删:
      • ⑥.单链表头删:
      • ⑦.单链表查找:
      • ⑧.单链表插入:
        • Ⅰ、单链表前插:
        • Ⅱ、单链表后插:
      • ⑨.单链表删除:
        • I、删除pos位置的节点
        • II、删除pos位置之后的节点
      • ⑩.单链表销毁:
  • 4.链表实现全部源码:
    • 4.1 SList.h:
    • 4.2 SList.c:
    • 4.3 test.c:
  • 5.总结:

1.链表

1.1 链表的概念及结构:

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

1.2 顺序表的缺陷

在之前的博客中,我们对顺序表有了较深层次的认知,同时我们发现,顺序表在创建和使用时,存在一些问题

  1. 当空间不足时,顺序表需要进行增容,但由于顺序表本质为数组,数据在物理上连续存放,因此在扩容时要付出较大的空间代价。为避免频繁扩容,基本每次都扩容 2 倍,当扩容次数增多时,就很有可能会造成一定程度的空间浪费。
  2. 由于顺序表内数据连续存储,我们在向顺序表中位置插入或删除数据时,就需要挪动大量的数据,效率不高

1.3 链表的意义:

人们针对顺序表的这些缺陷,设计出了链表这个概念来克服这些不足。
而链表克服这些缺陷的方法是,将每一个节点划分为数据与指针两部分数据部分用于存储数据而指针部分用于指向下一节点。这样做的好处是通过指针指向下一节点,于是数据的物理存储就可以不必连续,于是无论是在扩容,还是在插入与删除数据时,都能快速、方便的实现,并且不会造成很大的空间浪费。

2.链表的分类:

2.1 单向或双向:

请添加图片描述

2.2 带头或不带头:

请添加图片描述

2.3 循环或非循环:

请添加图片描述
(注:虽然有很多种不同的链表结构,但是最常用的链表结构主要是两种:无头单向非循环链表与带头双向循环链表)

无头单向非循环链表结构简单,一般不会单独用来存数据。实际中更多是作为其他数据结构的子结构,如哈希桶、图的邻接表等等。
带头双向循环链表结构最复杂,一般用在单独存储数据。虽然它结构复杂,但在实际使用中使用代码实现后,会带来很多优势,实现反而更加简单。并且在我们的实际中所使用的链表数据结构,一般都是带头双向循环链表。

3.单链表的实现:

3.1 工程文件:

与顺序表的实现方式类似,使用三个文件进行代码书写:

SeqList.h:存放函数声明、包含其他头文件、定义宏。
SeqList.c:书写函数定义,书写函数实现。
test.c:书写程序整体执行逻辑。

其中,我们的接口实现主要研究的是函数实现文件 SeqList.c 中的内容。

单链表和顺序表的结构略有不同,单链表的主结构为节点
一个节点由两部分组成:datanextdata为我们存储数据的地方。而next则为下一个节点的地址。

typedef struct SListNode
{
	int data;
	struct SListNode* next;
}SLTNode;

上面我们设计的是不带哨兵位的单链表。这种结构设计相对于带哨兵位的链表的缺点就是设计接口函数时需要考虑链表是否为空的情况。

哨兵位也叫哨兵节点,哑节点。该节点并不存储任何数据,只是为了方便操作而引入这个节点。起一个站岗放哨的作用。所以形象的叫它哨兵位。如果一个链表有哨兵位,那么链表的第一个元素应该是链表第二个节点对应的元素。这时链表永不为空,这就可以避免边界问题的处理,简化代码与减少代码出错的可能性。

3.2 接口设计

void SLPrint(SLNode* SLHead);    //打印单链表
SLNode* BuyListNode(SLDataType x);    //申请新节点
void SLPushBack(SLNode** pphead, SLDataType x);    //单链表尾插
void SLPushFront(SLNode** pphead, SLDataType x);    //单链表头插
void SLPopBack(SLNode** pphead);    //单链表尾删
void SLPopFront(SLNode** pphead);    //单链表头删
void SLFind(SLNode* phead, SLDataType x);    //单链表查找
void SLInsertFront(SLNode** pphead, SLNode* pos, SLDataType x);    //单链表前插
void SLInsertAfter(SLNode* pos, SLDataType x);    //单链表后插
void SLErase(SLNode** pphead, SLNode* pos);    //单链表删除
void SLEraseAfter(SLNode** pphead, SLNode* pos);//单链表后删除
void SLDestory(SLNode** pphead);    //单链表销毁

我们发现绝大多数接口的参数都为二级指针,这是为什么?
我们先了解一下平时单链表的测试用例:

void TestList()
{
	SLTNode* plist = NULL;
}

链表的一开始是空的。所以我们插入数据时,需要让plist指向我们新节点。就相当于改变plist的指向。plist是一级指针,那么要改变plist就要传它的地址&plist,为二级指针,所以也需要用二级指针来接收该参数。

就好比当我们要改变一个int类型的变量时a,我们需要传它的地址&a,那么函数的形参就应该用int* 接收;对于指针也是这样,一个int* 的指针变量p,它也是变量,我们需要改变这个值,就应该传它的地址&p,那么函数参数就应该那int* * 接收。

而对于一些接口就不需要传二级指针,就拿打印来说吧,因为我并不需要改变plist,我只需要通过结构体指针访问结构内的next成员,并迭代到下一个节点,然后打印出数据就可以,所以不需要传二级指针。

3.3 重点接口实现:

这里是本文的重点,即 SeqList.c 文件中的接口具体实现:

①.创建新节点:

在各项操作前,需动态创建新节点,再对新创建的节点进行操作。

SLNode* BuyListNode(SLDataType x)
{
	SLNode* newnode = (SLNode*)malloc(sizeof(SLNode));
    if (newnode == NULL)
	{
		perror("newnode:>");
		exit(-1);
	}
	newnode->data = x;
	newnode->next = NULL;
	return newnode;
}

②.打印单链表:

执行操作前无需进行断言,原因是循环条件为空指针,则条件不满足并停止循环,不会出现死循环。
根据指针循环操作,打印数据后使指针指向节点内存放下一节点地址的指针 next。

void SListPrint(SLNode* SLHead)
{
	SLNode* cur = SLHead;
	while (cur != NULL)
	{
		printf("%d->", cur->data);
		cur = cur->next;
	}
	printf("NULL\n");
}

③.单链表尾插:

  1. 如果此时头节点为空指针,说明此时单链表内没有节点,此时只需要将申请来的新节点作为头节点即可。
  2. 头节点不为空,则应当首先找到尾节点,使尾结点指针指向申请来的新节点后,再使新节点指针指向空即可。
void SLPushBack(SLNode** pphead, SLDataType x)
{
	SLNode* newnode = BuyListNode(x);
	if (*pphead == NULL)
	{
		*pphead = newnode;
	}
	else
	{
		//找到尾结点
		SLNode* tail = *pphead;
		while (tail->next != NULL)
		{
			tail = tail->next;
		}
		tail->next = newnode;
	}
}

测试接口:
在这里插入图片描述

④.单链表头插:

头插实现非常简单,首先申请新节点,先使新节点的指针指向原来的头节点,再使链表头指向新节点即可。
这里不需要考虑空节点的原因是,就算链表内没有内容,即首节点为空,新节点指针指向空也没有问题(申请新节点时原本就指向空)。

void SLPushFront(SLNode** pphead, SLDataType x)
{
	SLNode* newnode = BuyListNode(x);
	newnode->next = *pphead;
	*pphead = newnode;
}

测试接口:
在这里插入图片描述

⑤.单链表尾删:

执行操作前需要进行非空判断,防止传入空指针

  • 若链表内只有一个节点,则直接释放并置空即可。
  • 若有一个以上节点,则当执行下面三个步骤:
  1. 首先寻找尾节点,找到后将其释放并置空
  2. 但在释放前,必须将倒数第二个节点的指针指向空,否则将在指向最后一个被释放的节点时,变成野指针。
  3. 单向链表无法回溯,于是我们应当定义另一个指针,在每次移动尾节点指针前保存当前节点的位置
void SLPopBack(SLNode** pphead)
{
	//判断链表是否为空:
	if (*pphead == NULL)
	{
		return;
	}
	//若只有一个节点,直接释放并置空即可:
	if ((*pphead)->next == NULL)
	{
		free(*pphead);
		*pphead = NULL;
	}
	//若有多个节点则执行多步尾删:
	else
	{
		SLNode* tail = *pphead;
		SLNode* prev = NULL;
		//找到尾节点:
		while (tail->next != NULL)
		{
			prev = tail;
			tail = tail->next;
		}
		free(tail);
		tail = NULL;
		prev->next = NULL;
	}
}

测试接口:
在这里插入图片描述

⑥.单链表头删:

执行操作前判断指针非空,防止对空指针进行操作
使链表头指向第二个节点,接着将第一个节点释放并置空即可。

void SLPopFront(SLNode** pphead)
{
	if (*pphead == NULL)
	{
		return;
	}
	else
	{
		SLNode* next = (*pphead)->next;
		free(*pphead);
		*pphead = next;
	}
}

测试接口:
在这里插入图片描述

⑦.单链表查找:

采用遍历思想,依次对每一个节点的数据进行判断,符合条件即进行打印,不符合条件则继续向后遍历,直至指向 NULL,并且因此无需进行非空判断

void SLFind(SLNode* phead, SLDataType x)
{
	SLNode* cur = phead;
	int count = 1;
	while (cur)
	{
		if (cur->data == x)
		{
			printf("第%d个节点:%p -> %d\n", count++, cur, x);
		}
			cur = cur->next;
	}
}

测试接口:
在这里插入图片描述

⑧.单链表插入:

单链表在插入新节点时有两种插入方式,一种是在目标节点前方插入,另一种是在目标节点后方插入,因此需要分别进行实现:

Ⅰ、单链表前插:

前插有两种情况:

  1. 首节点处插入,此时即相当于头插操作
  2. 非首节点前插入,此时首先申请新节点,接着遍历单链表,找到插入位置后,使新节点指向目标节点后插入其前方。
void SLInsertFront(SLNode** pphead, SLNode* pos, SLDataType x)
{
	//首先申请新节点
	SLNode* newnod = BuyListNode(x);
	//头插
	if (*pphead == pos)
	{
		newnod->next = *pphead;
		*pphead = newnod;
	}
	else
	{
		//找到目标节点 pos 的前一个节点:
		SLNode* PostPrev = *pphead;
		while (PostPrev->next != pos)
		{
			PostPrev = PostPrev->next;
		}
		//找到后插入新节点:
		PostPrev->next = newnod;
		newnod->next = pos;
	}
}

测试接口:
同下图

Ⅱ、单链表后插:

在前插操作时,我们需要遍历整个链表来查找插入位置,因此效率较为低下,于是我们通常使用后插的方式插入新节点。后插直接申请节点,插入目标位置后即可。

void SLInsertAfter(SLNode* pos, SLDataType x)
{
	SLNode* newnode = BuyListNode(x);
	newnode->next = pos->next;
	pos->next = newnode;
}

测试接口:
在这里插入图片描述

⑨.单链表删除:

I、删除pos位置的节点

分为两种情况进行处理:

  1. 一种情况是整个链表中只有一个节点,则删除节点相当于释放整个数组并置空
  2. 另一种情况是含有一个以上节点,此时只需要让目标节点的前一个节点指针指向后一个节点,再释放目标节点并置空即可。
void SLErase(SLNode** pphead, SLNode* pos)
{
	//头删
	if (*pphead == pos)
	{
		*pphead = pos->next;
		free(pos);
		pos = NULL;
	}
	else
	{	
		//找到目标节点 pos 的前一个节点:
		SLNode* prev = *pphead;
		while (prev->next != pos)
		{
			prev = prev->next;
		}
		prev->next = pos->next;
		free(pos);
		pos = NULL;
	}
}

测试接口:
同下图

II、删除pos位置之后的节点

和在pos位置之前插入一样,我们发现删除pos位置的节点,也很麻烦。因为需要找pos的前一个位置,所以这种设计也比较麻烦。
所以有时我们会删除pos位置之后的节点,这样就很方便了
既然要删除pos位置之后的节点,那么我就需要将pos位置的节点pos位置的 next 的 next 链接起来。那么我们首先用next拷贝记录一下pos->next ,然后,再将pos->next赋值为next->next,也就是pos往后的两个节点。最后释放next位置的节点即可。
注意:pos位置的下一个节点不能是尾结点后面的节点

// 删除指定pos位置后的一个节点
void SLEraseAfter(SLNode** pphead, SLNode* pos)
{
	assert(pos);
	assert(pos->next);// 删除的不能是尾结点后面的位置
	SLNode* next = pos->next;// 拷贝pos的下一个节点
	pos->next = next->next;// 将pos的next变为下一个节点的next
	free(next);// 释放之前pos的下一个节点
	next = NULL;
}

测试接口:
在这里插入图片描述

⑩.单链表销毁:

遍历整个单链表,将每一个节点都进行释放并置空即可。
注意:记得把*pphead置空,防止野指针问题出现

void SListDestory(SLNode** pphead)
{
	if (*pphead == NULL)
	{
		return;
	}
	SLNode* cur = *pphead;
	while (cur)
	{
		SLNode* next = cur->next;
		free(cur);
		cur = next;
	}
	*pphead = NULL;
}

测试接口:
在这里插入图片描述

4.链表实现全部源码:

4.1 SList.h:

#pragma once

#include<stdio.h>
#include<assert.h>
#include<stdlib.h>
#include<string.h>
typedef int SLDataType;
 
//单链表节点结构:
typedef struct SListNode
{
	SLDataType data;
	struct SListNode* next;
}SLNode;
 
void SLPrint(SLNode* SLHead);    //打印单链表
SLNode* BuyListNode(SLDataType x);    //申请新节点
void SLPushBack(SLNode** pphead, SLDataType x);    //单链表尾插
void SLPushFront(SLNode** pphead, SLDataType x);    //单链表头插
void SLPopBack(SLNode** pphead);    //单链表尾删
void SLPopFront(SLNode** pphead);    //单链表头删
void SLFind(SLNode* phead, SLDataType x);    //单链表查找
void SLInsertFront(SLNode** pphead, SLNode* pos, SLDataType x);    //单链表前插
void SLInsertAfter(SLNode* pos, SLDataType x);    //单链表后插
void SLErase(SLNode** pphead, SLNode* pos);    //单链表删除
void SLEraseAfter(SLNode** pphead, SLNode* pos);//单链表后删除
void SLDestory(SLNode** pphead);    //单链表销毁

4.2 SList.c:

#define _CRT_SECURE_NO_WARNINGS 1
#include"SList.h"	
//打印单链表:
void SLPrint(SLNode* SLHead)
{
	SLNode* cur = SLHead;
	while (cur != NULL)
	{
		printf("%d->", cur->data);
		cur = cur->next;
	}
	printf("NULL\n");
}
 
//申请节点:
SLNode* BuyListNode(SLDataType x)
{
	SLNode* newnode = (SLNode*)malloc(sizeof(SLNode));
	if (newnode == NULL)
	{
		perror("newnode:>");
		exit(-1);
	}
	newnode->data = x;
	newnode->next = NULL;
	return newnode;
}
 
//单链表尾插:
void SLPushBack(SLNode** pphead, SLDataType x)
{
	SLNode* newnode = BuyListNode(x);
	if (*pphead == NULL)
	{
		*pphead = newnode;
	}
	else
	{
		//找到尾结点
		SLNode* tail = *pphead;
		while (tail->next != NULL)
		{
			tail = tail->next;
		}
		tail->next = newnode;
	}
}
 
//单链表头插:
void SLPushFront(SLNode** pphead, SLDataType x)
{
	SLNode* newnode = BuyListNode(x);
	newnode->next = *pphead;
	*pphead = newnode;
}
 
//单链表尾删:
void SLPopBack(SLNode** pphead)
{
	//判断链表是否为空:
	if (*pphead == NULL)
	{
		return;
	}
	//若只有一个节点,直接释放并置空即可:
	if ((*pphead)->next == NULL)
	{
		free(*pphead);
		*pphead = NULL;
	}
	//若有多个节点则执行多步尾删:
	else
	{
		SLNode* tail = *pphead;
		SLNode* prev = NULL;
		//找到尾节点:
		while (tail->next != NULL)
		{
			prev = tail;
			tail = tail->next;
		}
		free(tail);
		tail = NULL;
		prev->next = NULL;
	}
}
 
//单链表头删
void SLPopFront(SLNode** pphead)
{
	if (*pphead == NULL)
	{
		return;
	}
	else
	{
		SLNode* next = (*pphead)->next;
		free(*pphead);
		*pphead = next;
	}
}
 
//单链表查找
void SLFind(SLNode* phead, SLDataType x)
{
	SLNode* cur = phead;
	int count = 1;
	while (cur)
	{
		if (cur->data == x)
		{
			printf("第%d个节点:%p -> %d\n", count++, cur, x);
		}
			cur = cur->next;
	}
}
 
//单链表前插:
void SLInsertFront(SLNode** pphead, SLNode* pos, SLDataType x)
{
	//首先申请新节点
	SLNode* newnod = BuyListNode(x);
	if (*pphead == pos)
	{
		newnod->next = *pphead;
		*pphead = newnod;
	}
	else
	{
		//找到目标节点 pos 的前一个节点:
		SLNode* PostPrev = *pphead;
		while (PostPrev->next != pos)
		{
			PostPrev = PostPrev->next;
		}
		//找到后插入新节点:
		PostPrev->next = newnod;
		newnod->next = pos;
	}
}
 
//单链表后插:
void SLInsertAfter(SLNode* pos, SLDataType x)
{
	SLNode* newnode = BuyListNode(x);
	newnode->next = pos->next;
	pos->next = newnode;
}
 
//单链表删除:
void SLErase(SLNode** pphead, SLNode* pos)
{
	if (*pphead == pos)
	{
		*pphead = pos->next;
		free(pos);
		pos = NULL;
	}
	else
	{
		SLNode* prev = *pphead;
		while (prev->next != pos)
		{
			prev = prev->next;
		}
		prev->next = pos->next;
		free(pos);
		pos = NULL;
	}
}
// 删除指定pos位置后的一个节点
void SLEraseAfter(SLNode** pphead, SLNode* pos)
{
	assert(pos);
	assert(pos->next);// 删除的不能是尾结点后面的位置
	SLNode* next = pos->next;// 拷贝pos的下一个节点
	pos->next = next->next;// 将pos的next变为下一个节点的next
	free(next);// 释放之前pos的下一个节点
	next = NULL;
} 
//单链表销毁:
void SLDestory(SLNode** pphead)
{
	if (*pphead == NULL)
	{
		return;
	}
	SLNode* cur = *pphead;
	while (cur)
	{
		SLNode* next = cur->next;
		free(cur);
		cur = next;
	}
	*pphead = NULL;
}

4.3 test.c:

#define _CRT_SECURE_NO_WARNINGS 1 

#include "SList.h"

// 测试尾插、带节点的头插
void TestList1()
{
	SLNode* plist = NULL;
	//SLPushBack(&plist, 1);
	//SLPushBack(&plist, 2);
	//SLPushBack(&plist, 3);
	//SLPushBack(&plist, 4);
	//SLPrint(plist);
	//SLPushBack(&plist, 5);
	//SLPrint(plist);

	SLPushFront(&plist, 1);
	SLPushFront(&plist, 2);
	SLPushFront(&plist, 3);
	SLPushFront(&plist, 4);
	SLPrint(plist);
	SLPushFront(&plist, 5);
	SLPrint(plist);

}

// 测试头插、尾删
void TestList2()
{
	SLNode* plist = NULL;

	SLPushFront(&plist, 1);
	SLPushFront(&plist, 2);
	SLPushFront(&plist, 3);
	SLPushFront(&plist, 4);
	SLPushFront(&plist, 5);
	SLPrint(plist);

	SLPopBack(&plist);
	SLPopBack(&plist);
	SLPopBack(&plist);
	SLPopBack(&plist);
	SLPrint(plist);
	SLPopBack(&plist);
	SLPrint(plist);

}

// 测试头删
void TestList3()
{
	SLNode* plist = NULL;

	SLPushFront(&plist, 1);
	SLPushFront(&plist, 2);
	SLPushFront(&plist, 3);
	SLPushFront(&plist, 4);
	SLPushFront(&plist, 5);
	SLPrint(plist);

	SLPopFront(&plist);
	SLPrint(plist);

	SLPopFront(&plist);
	SLPrint(plist);

	SLPopFront(&plist);
	SLPrint(plist);

	SLPopFront(&plist);
	SLPrint(plist);

	SLPopFront(&plist);
	SLPrint(plist);

}


void TestList4()
{
	SLNode* plist = NULL;
	SLPushFront(&plist, 1);
	SLPushFront(&plist, 2);
	SLPushFront(&plist, 3);
	SLPushFront(&plist, 2);
	SLPushFront(&plist, 5);

	SLNode* pos = SLFind(plist, 2);
	int i = 1;
	while (pos)
	{
		printf("第%d个pos节点:%p->%d\n", i++, pos, pos->data);
		pos = SLFind(pos->next, 2);
	}

	// 修改 3->30
	pos = SLFind(plist, 3);
	if (pos)
	{
		pos->data = 30;
	}
	SLPrint(plist);
}

void TestList5()
{
	SLNode* plist = NULL;
	SLPushFront(&plist, 1);
	SLPushFront(&plist, 2);
	SLPushFront(&plist, 3);
	SLPushFront(&plist, 2);
	SLPushFront(&plist, 5);

	// 3前面插入一个30
	SLNode* pos = SLFind(plist, 3);
	if (pos)
	{
		SLInsertFront(&plist, pos, 30);
	}
	SLPrint(plist);

	// 5后面插入一个10
	pos = SLFind(plist, 5);
	if (pos)
	{
		SLInsertAfter( pos, 10);
	}
	SLPrint(plist);
}
void TestList6()
{
	SLNode* plist = NULL;
	SLPushFront(&plist, 1);
	SLPushFront(&plist, 2);
	SLPushFront(&plist, 3);
	SLPushFront(&plist, 2);
	SLPushFront(&plist, 5);

	SLPrint(plist);
	// 删除3位置节点
	SLNode* pos = SLFind(plist, 3);
	if (pos)
	{
		SLErase(&plist, pos);
	}
	SLPrint(plist);

	// 删除4位置之后节点
	pos = SLFind(plist, 5);
	if (pos)
	{
		SLEraseAfter(&plist, pos);
	}
	SLPrint(plist);

	SLDestory(&plist);
	SLPrint(plist);

}


int main()
{
	//TestList1();
	//TestList2();
	//TestList3();
	//TestList4();
	//TestList5();
	TestList6();
	return 0;
}


5.总结:

今天我们认识并学习了单向无头链表的相关概念、结构与接口实现,并且针对每个常用的功能接口都进行了实现分解,并对各个接口的各项注意点都进行了强调说明,希望我的文章和讲解能对大家的学习提供一些帮助。

当然,本文仍有许多不足之处,欢迎各位小伙伴们随时私信交流、批评指正!我们下期见~

在这里插入图片描述

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

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

相关文章

基于MSAM-YOLOv5的内河航道船舶识别方法

摘 要 针对内河航道上无人船识别目标时受背景复杂性和分布多样性影响而存在漏检的问题&#xff0c;提出了一种基于YOLOv5(you only look once)的算法。首先&#xff0c;提出了一种注意力模块MSAM(multiscale attention module&#xff0c;多尺度注意力模块)&#xff0c;MSA…

【新型密集采样网络:遥感超分】

Remote Sensing Image Super-Resolution Using Novel Dense-Sampling Networks &#xff08;基于新型密集采样网络的遥感图像超分辨率&#xff09; 超分辨率技术在提高遥感数据的空间分辨率、克服星载成像系统的物理限制等方面发挥着重要作用。基于卷积神经网络&#xff08;C…

可视化技术第一节课

主要内容通过echarts实现在网页上显示图表实现不同类型图表的转换实际操作&#xff08;1&#xff09;安装echarts如果想要安装echarts可以参考&#xff1a;安装步骤当然我们可以直接在我们的代码里面添加连接直接引用<script src"http://cdn.bootcss.com/echarts/4.8.0…

buu [BJDCTF2020]rsa_output 1 SameMod 1

[BJDCTF2020]rsa_output 1&#xff1a; 题目描述&#xff1a; {210583393373542878475341075446136053050154410905089240941988166912191033995268001128024163830889952539088574602667269256158268953033778016148293640346244751958599979431463055883159391307774504851…

2.27日报

情报上传接口添加字段&#xff1a;问题分配 调试手机号登录接口 解决困惑已久的bug&#xff1a;解析不到token; 问题描述&#xff1a; 在局域网下&#xff0c;前端页面请求时在请求头里携带token信息&#xff0c;后台获取不到header里的token参数&#xff0c;但是使用postma…

阶段八:服务框架高级(第六章:ElasticSearch3)

阶段八&#xff1a;服务框架高级&#xff08;第六章&#xff1a;ElasticSearch3&#xff09;Day-第六章&#xff1a;ElasticSearch分布式搜索引擎30.学习目标1.数据聚合1.1.聚合的种类1.2.DSL实现聚合1.2.1.Bucket聚合语法&#xff08;桶聚合&#xff09;1.2.2.聚合结果排序1.2…

软件测试面试题 —— 整理与解析(2)

&#x1f60f;作者简介&#xff1a;博主是一位测试管理者&#xff0c;同时也是一名对外企业兼职讲师。 &#x1f4e1;主页地址&#xff1a;&#x1f30e;【Austin_zhai】&#x1f30f; &#x1f646;目的与景愿&#xff1a;旨在于能帮助更多的测试行业人员提升软硬技能&#xf…

python学生信息管理系统

wx供重浩&#xff1a;创享日记 对话框发送&#xff1a;python学生信息 免费获取完整源码源文件配置教程说明等 在IDLE中运行《学生信息管理系统》即可进入如图1所示的系统主界面。在该界面中可以选择要使用功能对应的菜单进行不同的操作。在选择功能菜单时&#xff0c;有两种方…

SpringBoot2入门 第一个HelloWorld(参考尚硅谷SpringBoot2零基础入门教程)

文章目录0 官方文档1 系统要求2 maven设置3 HelloWorld3.0 需求3.1 创建Maven工程3.2 引入依赖3.3 创建主程序3.4 编写业务3.5 测试3.6 简化配置3.7 简化部署0 官方文档 https://docs.spring.io/spring-boot/docs/2.3.4.RELEASE/reference/html/index.html 1 系统要求 想要运…

RFS自动化测试工具安装与使用总结

一&#xff0c;调试 1&#xff0c;在调试时&#xff0c;总时提示“无法打开浏览器” 解决办法&#xff1a; 1&#xff0c;把浏览器的代理关闭 2&#xff0c;把浏览器的显示比例调到100% 3&#xff0c;在IE设置的安全选项中&#xff0c;把启用保护模式开启 4&#xff0c;去除进…

layui框架学习(12:进度条)

进度条是应用系统中的常见元素&#xff0c;无论是上传文件、下载文件、加载内容时都会显示进度条&#xff0c;Layui支持设置线条形进度条的样式&#xff0c;同时支持通过element模块动态操作进度条。   进度条样式分为两级结构&#xff0c;顶层一般为div元素&#xff0c;其cl…

【Opencv项目实战】背景替换:动态背景移除与替换(cvzone)

文章目录一、项目思路二、环境布置2.1、cvzone安装2.2、MediaPipe安装2.3、常见问题2.4、注意事项三、算法详解3.1、segmentor.removeBG()&#xff1a;去除背景&#xff08;抠出图像中的人&#xff09;3.2、cvzone.stackImages()&#xff1a;堆叠图像3.3、fpsReader.update()&a…

Hot Chocolate 构建 GraphQL .Net Core 服务

Hot Chocolate 是 .NET 平台下的一个开源组件库, 您可以使用它创建 GraphQL 服务, 它消除了构建成熟的 GraphQL 服务的复杂性, Hot Chocolate 可以连接任何服务或数据源&#xff0c;并创建一个有凝聚力的服务&#xff0c;为您的消费者提供统一的 API。 我会在 .NET 应用中使用…

mysql一两种索引方式hash和btree

1. Hash索引&#xff1a; Hash 索引结构的特殊性&#xff0c;其检索效率非常高&#xff0c;索引的检索可以一次定位&#xff0c;不像B-Tree 索引需要从根节点到枝节点&#xff0c;最后才能访问到页节点这样多次的IO访问&#xff0c;所以 Hash 索引的查询效率要远高于 B-Tree 索…

FFmpeg 编译和集成

背景FFmpeg 是一款知名的开源音视频处理软件&#xff0c;它提供了丰富而友好的接口支持开发者进行二次开发。FFmpeg 读作 “ef ef em peg” &#xff0c;其中的 “FF” 指的是 “Fast Forward”&#xff0c;“mpeg” 则是 “Moving Picture Experts Group” &#xff08;动态图…

隧道代理的工作原理是什么,为何爬虫使用起来更高效?

在网络爬虫领域&#xff0c;使用HTTP代理是非常普遍的一种技术手段。而隧道代理则是HTTP代理中的一种&#xff0c;它是指将请求通过隧道传输到代理服务器上&#xff0c;并由代理服务器向目标服务器发送请求&#xff0c;从而达到隐藏真实IP的目的。那么&#xff0c;隧道HTTP代理…

告别空指针让代码变优雅,Optional使用图文例子源码解读

一、前言 我们在开发中最常见的异常就是NullPointerException&#xff0c;防不胜防啊&#xff0c;相信大家肯定被坑过&#xff01; 这种基本出现在获取数据库信息中、三方接口&#xff0c;获取的对象为空&#xff0c;再去get出现&#xff01; 解决方案当然简单&#xff0c;只…

华为OD机试模拟题 用 C++ 实现 - 最优资源分配(2023.Q1)

最近更新的博客 【华为OD机试模拟题】用 C++ 实现 - 最多获得的短信条数(2023.Q1)) 文章目录 最近更新的博客使用说明最优资源分配题目输入输出描述备注示例一输入输出说明示例二输入输出说明Code使用说明 参加华为od机试,一定要注

2023 Java 分布式 面试 大纲

前段时间 &#xff0c;公司部门的HR找到我&#xff0c;说来了几份简历 &#xff0c;都是三年所有的开发 让我面一下&#xff0c; HR那边 一面核对了基本的信息 二面技术&#xff0c;是由我来接手&#xff0c;然后问了 一些分布式的问题 &#xff0c;大部分都是在围绕着SpringCl…

这回稳了!电力巡检低功耗摄像头全新来袭

最近的狂飙成为大家的话题&#xff0c;互联网的发展让很多信息都很透明&#xff0c;这个也是我比较喜欢和各位技术大咖一起分享一些当下比较前沿的解决方案 春回大地&#xff0c;疫情远去&#xff0c;我们也没有理由逃避不去努力&#xff0c;在互相网的各种平台去获取各种自己需…