【数据结构与算法】——单链表的原理及C语言实现

news2024/12/28 22:20:10

数据结构与算法——链表原理及C语言实现

  • 链表的原理
  • 链表的基本属性设计
    • 创建一个空链表
    • 链表的遍历(显示数据)
    • 释放链表内存空间
  • 链表的基本操作设计(增删改查)
    • 链表插入节点
    • 链表删除节点
    • 链表查找节点
    • 增删改查测试程序
  • 链表的复杂操作程序设计
    • 单链表的反转
    • 相邻节点最大值
    • 有序链表的合并
    • 链表的排序

参考博文1:【数据结构与算法】程序内功篇三–单链表
参考博文2:链表基础知识详解(非常详细简单易懂)
参考博文3:关于链表,看这一篇就足够了!(新手入门)
参考博文4:单链表——单链表的定义及基本操作

链表的原理

链表含义
  由于顺序表的插入删除操作需要移动大量的元素,影响了运行效率,因此引入了线性表的链式存储——单链表

单链表的特点:

  • 单链表不要求逻辑上相邻的两个元素在物理位置上也相邻,因此链表两个节点的存储空间不相邻。
  • 单链表是非随机的存储结构,即不能直接找到表中某个特定的结点。查找某个特定的结点时,需要从表头开始遍历,依次查找。
  • 对于每个链表结点,分为存放数据的数据域以及存放下个节点地址的地址域

链表节点的定义:

typedef int data_t;

//结点定义
typedef struct node{
	data_t data;		//结点数据域
	struct node *next;	//结点后继指针域
}listNode;

链表的基本属性设计

创建一个空链表

  通常会用头指针来标识一个单链表,头指针为NULL时表示一个空表。但是,为了操作方便,会在单链表的第一个结点之前附加一个结点,称为头结点。头结点的数据域可以不设任何信息,也可以记录表长等信息。头结点的指针域指向线性表的第一个元素结点。

创建空链表(头指针)主要分为:①申请节点内存空间  ②成员变量赋初始值  ③返回头节点 三大步骤:

  • 功能:创建一个空链表  node为虚拟头节点
  • 参数:void
  • 返回值:头节点地址
/*
功能:创建一个空链表  node为虚拟头节点
参数:void
返回值:头节点地址
*/
listNode* link_create()
{
	//申请内存空间
	listNode* node = (listNode *)malloc(sizeof(listNode));
	if(node == NULL){
		printf("link_create: malloc error\n");
		return NULL;
	}
	
	//链表成员变量赋值
	node->data = 0;
	node->next = NULL;
	
	//返回链表地址
	return node;
}

链表的遍历(显示数据)

第一步:输出第一个节点的数据域,输出完毕后,让指针保存后一个节点的地址

第二步:输出移动地址对应的节点的数据域,输出完毕后,指针继续后移

第三步:以此类推,直到节点的指针域为NULL

通过遍历链表读取链表的数据并显示:

  • 功能:显示链表数据
  • 参数:para: 链表头
  • 返回值:成功返回0   失败返回-1
/*
功能:显示链表数据
参数:para: 链表头   
返回值:成功返回0;    失败返回-1
*/
int link_show(listNode* head)
{
	//入口参数检查
	if(head == NULL)
		return -1;
	
	//遍历链表 
	while(head->next != NULL)
	{
		printf("%d ", head->next->data);
		head = head->next;
	}
	printf("\n");
	return 0;
}

释放链表内存空间

通过遍历链表节点,释放每个节点的内存空间

  • 功能:释放链表内存空间
  • 参数:para: 链表头
  • 返回值: NULL
/*
功能:释放链表内存空间
参数:para :链表头          
返回值: NULL
*/
listNode* link_free(listNode* head)
{
	//入口参数检查
	if(head == NULL){
		printf("list_insert: para error\n");
		return NULL;
	}
	
	//封装临时节点
	listNode *temp = head;
	while(head != NULL)
	{
		temp = head;
		//printf("free: %d\n", head->data);
		//这两行不能反 必须先指向下一个再释放当前地址
		head = head->next;	
		free(temp);
	}
	
	return NULL;
}

链表的基本操作设计(增删改查)

链表插入节点

同顺序表一样,向链表中增添元素,根据添加位置不同,可分为以下 3 种情况:

  • 插入到链表的头部(头节点之后),作为首元节点;
  • 插入到链表中间的某个位置;
  • 插入到链表的最末端,作为链表中最后一个数据元素;

虽然新元素的插入位置不固定,但是链表插入元素的思想是固定的,只需做以下两步操作,即可将新元素插入到指定的位置:

  • 将新结点的 next 指针指向插入位置后的结点;
  • 将插入位置前结点的 next 指针指向插入结点;

例如,我们在链表{1,2,3,4}的基础上分别实现在头部、中间部位、尾部插入新元素 5,其实现过程如下图 所示:

头插法程序设计:

  • 功能:链表头插法插入数据
  • 参数:para1:链表头    para2:插入的数据
  • 返回值:成功返回0;   失败返回-1
    /*
    功能:链表头插法插入数据
    参数:para1: 链表头  	para2: 插入的数据
    返回值:成功返回0;    失败返回-1
    */
    int link_push_front(listNode* head, data_t value)
    {
    	//入口参数检查
    	if(head == NULL)
    		return -1;
    			
    	//封装节点
    	listNode* node = (listNode *)malloc(sizeof(listNode));
    	if(node == NULL){
    		printf("link_push_front malloc error\n");
    		return -1;
    	}
    	
    	//头插法
    	node->data = value;			//插入行节点数据
    	node->next = head->next;	//将新节点连接到原来的头
    	head->next = node;			//更新链表头
    	
    	return 0;
    }
    

任意位置插入元素程序设计:

  • 功能:在链表特定位置插入一个元素
  • 参数: para1:链表头    para2:插入的元素值    para3:特定位置的索引
  • 返回值: 失败返回-1    成功返回0
    /*
    功能:在链表特定位置插入一个元素
    参数:	para1:链表头	para2:插入的元素值	para3:特定位置的索引
    返回值: 失败返回-1    成功返回0
    */
    int list_insert(listNode* head, data_t value, int index)
    {
    	//入口参数检查
    	if(head == NULL || index < 0){
    		printf("list_insert: para error\n");
    		return -1;
    	}
    	//封装节点
    	listNode* node = (listNode *)malloc(sizeof(listNode));
    	if(node == NULL){
    		printf("link_push_front malloc error\n");
    		return -1;
    	}
    	node->data = value;
    	
    	//遍历到目标索引处
    	int pos = 0;
    	while(pos < index && head->next != NULL)
    	{
    		pos++; 
    		head = head->next;
    	}
    	
    	//索引过大
    	if(head->next == NULL && index - pos > 0){
    		printf("index invalid\n");
    		return -1;
    	}
    	
    	//插入数据
    	node->next = head->next;
    	head->next = node;
    	
    	return 0;
    }
    

尾插法程序设计:

  • 功能:链表尾插法插入数据
  • 参数:para1: 链表头    para2:插入的数据
  • 返回值:成功返回0;     失败返回-1
    /*
    功能:链表尾插法插入数据
    参数:para1: 链表头    para2: 插入的数据
    返回值:成功返回0;     失败返回-1
    */
    int link_push_back(listNode* head, data_t value)
    {
    	//入口参数检查
    	if(head == NULL)
    		return -1;
    		
    	//封装节点
    	listNode *node = (listNode *)malloc(sizeof(listNode));
    	if(node == NULL){
    		printf("link_push_back malloc error\n");
    		return -1;
    	}
    	//新节点赋值
    	node->data = value;
    	node->next = NULL;
    	
    	//遍历链表 
    	while(head->next != NULL)
    	{
    		head = head->next;
    	}
    	
    	//尾插入节点
    	head->next = node;
    	
    	return 0;
    }
    

链表删除节点

  从链表中删除指定数据元素时,实则就是将存有该数据元素的节点从链表中摘除,但作为一名合格的程序员,要对存储空间负责,对不再利用的存储空间要及时释放。因此,从链表中删除数据元素需要进行以下 2 步操作:

  • 将结点从链表中摘下来;
  • 手动释放掉结点,回收被结点占用的存储空间;

其中,从链表上摘除某节点的实现非常简单,只需找到该节点的直接前驱节点 temp,执行一行程序:

temp->next = temp->next->next;

根据数据值删除某个节点

  • 功能:根据数据值 删除某个节点
  • 参数: para1:链表头  para2:删除数值
  • 返回值: 失败返回-1  成功返回0
    /*
    功能:根据数据值 删除某个节点
    参数:	para1:链表头	para2:删除数值
    返回值: 失败返回-1    成功返回0
    */
    int list_delete_val(listNode* head, int val)
    {
    	//入口参数检查
    	if(head == NULL){
    		printf("list_insert: para error\n");
    		return -1;
    	}
    	
    	//遍历链表
    	while(head->next != NULL && head->next->data != val)
    	{
    		head = head->next;
    	}
    
    	//链表中无该数据
    	if(head->next == NULL && head->data != val){
    		printf("no such value\n");
    		return -1;	
    	}
    	
    	listNode *temp = head->next;	//暂存需释放空间的节点
    	head->next = head->next->next;	//跳跃拉链,即删除了中间节点
    	free(temp);						//释放节点
    	temp = NULL;					//避免野指针
    	
    	return 0;	
    }
    

根据索引删除某个节点

  • 功能:根据索引 删除某个节点 (链表其实没有所谓的索引,即第几个节点-1)
  • 参数: para1:链表头   para2:删除的索引
  • 返回值: 失败返回-1    返回 0
    /*
    功能:根据索引 删除某个节点 (链表其实没有所谓的索引,即第几个节点-1)
    参数:	para1:链表头	para2:删除的索引
    返回值: 失败返回-1    成功返回0
    */
    int link_delete_index(listNode* head, int index)
    {
    	//入口参数检查
    	if(head == NULL || index < 0){
    		printf("link_delete_index: para error\n");
    		return -1;
    	}
    	
    	//遍历链表
    	int pos = 0;
    	while(pos < index && head != NULL)
    	{
    		//节点遍历完了
    		if(head->next == NULL){	
    			printf("index error\n");
    			return -1;
    		}
    		pos++;					//索引
    		head = head->next;		//指针偏移
    	}
    	//printf("data: %d\n", head->data);
    	//判断后一个元素是否为空
    	if(head->next == NULL)
    	{
    		printf("index error\n");
    		return -1;
    	}
    	
    	listNode *temp = head->next;	//暂存需释放空间的节点
    	head->next = head->next->next;	//跳跃拉链,即删除了中间节点
    	free(temp);						//释放节点
    	temp = NULL;					//避免野指针
    	
    	return 0;	
    }
    

链表查找节点

按数值查找:
  查找数据value在单链表link中的节点索引。
  算法思想:从单链表的第一个结点开始,依次比较表中各个结点的数据域的值,若某结点数据域的值等于value,则返回该节点的索引;若整个单链表中没有这样的结点,则返回-1。

  • 功能:查找某个元素的下标索引
  • 参数:para1:链表头 para2:查找的某个元素值
  • 返回值: 失败返回-1  成功返回元素的下标索引
/*
功能:查找某个元素的下标索引
参数:para1:链表头	para2:查找的某个元素值
返回值: 失败返回-1    成功返回元素的下标索引
*/
int list_search(listNode* head, data_t val)
{
	//入口参数检查
	if(head == NULL){
		printf("list_insert: para error\n");
		return -1;
	}
	
	//pos 记录下标位置
	int pos = -1;
	while(head->next != NULL)
	{
		pos++;
		if(head->next->data == val)	//判断是否存在val
			return pos;
		head = head->next;
	}
	return -1;
}

增删改查测试程序

//-------------测试程序-------------
void link_test()
{
	listNode *head = link_create();
	int data1[] = {-3, 2, 9, 5, 101};
	int data2[] = {4, 100, 0};
	
	//尾插法插入数据
	for( int i = 0; i < 5; i++)
		link_push_back(head, data1[i]);
	
	//头插法插入数据
	for( int i = 0; i < 3; i++)	
		link_push_front(head, data2[i]);
	
	//特定位置插入数据
	link_insert(head,66,1);
	
	//显示原链表
	printf("src link:");
	link_show(head);
	
	link_delete_index(head, 2);	//删除下标为2的节点
	link_delete_val(head, 66);	//删除数据为66的节点
	printf("delete:  ");
	link_show(head);
	
	//查找
	int ret = link_getVal( head, 2);
	int idx = link_search( head, 101);
	printf("data[2]: %d\n", ret);
	printf("101's index: %d\n", idx);
	
	head = link_free(head);	
}

链表的复杂操作程序设计

单链表的反转

算法思路: 依次取原链表中各结点,将其作为新链表首结点插入head结点
即获取原链表的每个节点,在新链表进行头插法插入:

  1. 判断是否为空 / 是否只有1个节点
  2. 断开链表,一分为二,分为第一个节点和后面链表
  3. 遍历从第二个后面起的节点,头插法循环插入

  • 功能:单链表的反转
  • 参数: para:   链表头
  • 返回值: 失败返回-1   成功返回0
int link_reverse(listNode* head)
{
	//入口参数检查
	if(head == NULL ){
		printf("head is NULL.\n");
		return -1;
	}
	
	//只有一个节点
	if(head->next == NULL || head -> next ->next == NULL ){
		return 0;
	}
	
	//p指向待操作的节点
	listNode *p = head->next->next;	//新链表头
	head->next->next = NULL;		//链表一分为二
	listNode *q = p;
	
	//遍历后续节点,以尾插法插入新的链表
	while(p != NULL)
	{
		q = p;
		p = p->next;
		
		//头插法 插入q
		q->next = head->next;
		head->next = q;
	}
	
	return 0;
}

测试程序:

void link_reverse_test()
{
	listNode *head = link_create();
	int data1[] = {-3, 2, 9, 5, 3};
	for(int i = 0; i < 5; i++)
		link_push_back(head, data1[i]);
		
	//打印原链表
	printf("link: ");
	link_show(head);
	
	//翻转链表
	link_reverse(head);
	
	//打印翻转后的链表
	printf("reverse: ");
	link_show(head);
	
	//释放内存空间
	head = link_free(head);
}

相邻节点最大值

算法思路: 设q,p分别为链表中相邻两结点指针,其中p在前,q在后,求q->data + q->data为最大的那一组值,返回其相应的指针q即可:

  1. 节点个数 <= 2,退出
  2. 初始化辅助变量(遍历节点指针p,q(p在前,q在后),两数之和sum,新节点指针ret指向head,并以头节点一分为二)
  3. 遍历p和q,以及sum

  • 功能:求相邻节点最大值,返回最大值第一个节点地址,最大值通过参数传递
  • 参数: para1:链表头 para2:最大和的值的地址
  • 返回值: 失败返回NULL 成功返回第一个节点指针
//求链表中相邻两节点data值之和为最大的第一节点的指针
/*
功能:求相邻节点最大值,返回最大值第一个节点地址
最大值通过参数传递
参数:	para1:链表头 	para2:最大和的值的地址
返回值: 失败返回NULL  成功返回第一个节点指针
*/
listNode *list_adjmax(listNode *head, data_t *value)
{
	if(head == NULL)
	{
		printf("head is NULL\n");
		return NULL;
	}
	
	if(head->next == NULL || head->next->next == NULL || head->next->next->next == NULL)
		return head;

	//构造辅助变量
	listNode *ret = head->next;			//结果链表尾指针
	listNode *p   = head->next->next;	//遍历链表的指针, p在前面
	listNode *q   = head->next;			//遍历链表的指针, q在后
	data_t max = q->data + p->data ;	//初始化两数之和
	
	
	while(p->next != NULL)
	{
		//p q更新
		p = p->next;
		q = q->next;
		
		//比较最大值
		if(q->data + p->data > max)
		{
			max = q->data + p->data;	//更新最大值
			ret = q;					//更新最大值第一个节点
		}
	}
	
	//返回相邻最大值的第一个节点指针,并通过参数传回最大值
	*value = max;
	return ret;
}

测试程序:

void list_adjmax_test()
{
	listNode *head = link_create();
	int data1[] = {-3, 2, 9, 5, 3};
	for(int i = 0; i < 5; i++)
		link_push_back(head, data1[i]);
		
	//打印原链表
	printf("link: ");
	link_show(head);
	
	//计算最大两数之和及第一个节点
	int sum;
	listNode *ret = list_adjmax(head, &sum);
	
	//打印新节点数及其两数最大之和
	printf("data: %d\nsum: %d", ret->data, sum);
	
	//释放内存空间
	head = link_free(head);	
}

有序链表的合并

算法思想: 设指针p、q分别指向表A和B中的结点,若p -> data <= q -> data,则p进入结果表,否则q结点进入结果表。

  1. 参数判断(链表是否为空)
  2. 分别将链表一分为二,p,q分别指向两个链表的新的头,结果存放在以ret为尾节点的链表中
  3. 通过p,q指针遍历两个链表,将小的数值插入到ret结果链表中形成新的合并链表
  • 功能:合并两个链表,合并至head1
  • 参数: para1: 链表1头节点  para2: 链表2头节点
  • 返回值: 失败返回 -1  成功返回 0
//合并两个有序链表
/*
功能:合并两个链表,合并至head1
参数:	para1:链表1头节点	para2:链表2头节点
返回值: 失败返回-1  成功返回0
*/
int link_merge(listNode *head1, listNode *head2)
{
	//入口参数检查
	if( head1 == NULL || head2 == NULL){
		printf("head1 || head2 error\n");
		return -1;
	}
	
	//变量初始化
	listNode *p = head1->next;
	listNode *q = head2->next;
	listNode *ret = head1;
	head1->next = NULL;
	head2->next = NULL;
	
	while(p != NULL && q != NULL)
	{
		if(p->data <= q->data)
		{
			ret->next = p;		//p接入ret链表
			p = p->next;		//更新p
			ret = ret->next;	//更新新表尾
			ret->next = NULL;	//置空新表尾
		}
		else
		{
			ret->next = q;		//q接入ret链表
			q = q->next;		//更新q
			ret = ret->next;	//更新新表尾
			ret->next = NULL;	//置空新表尾
		}
	}
	
	//把多的p或q接入到ret链表
	if( p != NULL)
		ret->next = p;
	if( q != NULL)
		ret->next = q;
		
	return 0;
}

测试程序:

void link_merge_test()
{
	listNode *head1 = link_create();
	listNode *head2 = link_create();
	int data1[] = {1, 2, 4, 6, 8};
	int data2[] = {2, 5, 6, 22, 96, 128};
	
	//插入数据
	for(int i = 0; i < 5; i++)
		link_push_back(head1, data1[i]);
		
	for(int i = 0; i < 6; i++)
		link_push_back(head2, data2[i]);	
	
	//打印原有序链表
	printf("link1: ");
	link_show(head1);
	printf("link2: ");
	link_show(head2);
	
	//合并
	printf("merge: ");
	link_merge(head1, head2);
	link_show(head1);
	
	//去重
	printf("purge: ");
	link_purge(head1);
	link_show(head1);
	
	head1 = link_free(head1);	
	head2 = link_free(head2);	
}

链表的排序

  • 如果链表为空或只有一个结点,不需要排序
  • 先将第一个结点与后面所有的结点依次对比数据域,只要有比第一个结点数据域小的,则交换位置 ,交换之后,拿新的第一个结点的数据域与下一个结点再次对比,如果比他小,再次交换,以此类推
  • 第一个结点确定完毕之后,接下来再将第二个结点与后面所有的结点对比,直到最后一个结点也对比完毕为止
int link_sort(listNode *head )
{
	//头节点为空
    if (head == NULL){
        printf("head is NULL\n");
        return -1;
    }
    //只有1个节点
    if (head->next == NULL){
        printf("only one node\n");
        return 0;
    }
    
    listNode *q, *p, temp;
    p = head->next;			//从第一个节点开始
    while (p->next != NULL)
    {
        q = p->next;		//q从基准元素的下个元素开始
        while (q != NULL)
        {
            if (p->data > q->data)	//后面的元素小
            {
            	//交换值
                temp = *q;			
                *q = *p;
                *p = temp;
				
				//交换地址
                temp.next = q->next;
                q->next = p->next;
                p->next = temp.next;
            }
            q = q->next;
        }
        p = p->next;
    }
    return 0;
}

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

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

相关文章

当人工智能遇上教育,会擦出怎样的火花?

在这个时代&#xff0c;科技的风暴正以前所未有的速度席卷全球。其中&#xff0c;人工智能&#xff0c;这个被誉为21世纪的“科技之星”&#xff0c;正悄然改变着我们的生活。但是&#xff0c;当人工智能遇上传统教育领域时&#xff0c;你猜会发生什么&#xff1f; 有人说&…

element-ui button 组件源码分享

element-ui button 源码分享&#xff0c;基于对源码的理解&#xff0c;编写一个简单的 demo&#xff0c;主要分三个模块来分享&#xff1a; 一、button 组件的方法。 1.1 在方法这块&#xff0c;button 组件内部通过暴露 click 方法实现&#xff0c;具体如下&#xff1a; 二、…

勇敢的小刺猬

故事名称&#xff1a;《勇敢的小刺猬》 角色&#xff1a; 小明&#xff08;刺猬&#xff09;小鸟森林医生邪恶的狐狸 场景&#xff1a;森林 【场景1&#xff1a;森林里的小路上】 小明&#xff08;边走边哼着歌&#xff09;&#xff1a;今天的阳光真好&#xff0c;真是个适合帮…

盘点那些硬件+项目学习套件:STM32U5单片机开发板及入门常见问题解答

华清远见20岁了~过去3年里&#xff0c;华清远见研发中心针对个人开发板业务&#xff0c;打造了多款硬件项目学习套件&#xff0c;涉及STM32单片机、嵌入式、物联网、人工智能、鸿蒙、ESP32、阿里云IoT等多技术方向。 今天我们来盘点一下&#xff0c;比较受欢迎几款“硬件项目”…

ubuntu22.04安装部署02:禁用显卡更新

一、查看可用显卡驱动 ubuntu-drivers devices 二、查看显卡信息 # -i表示不区分大小写 lspci | grep -i nvidia nvidia-smi 三、查看已安装显卡驱动 cat /proc/driver/nvidia/version 四、锁定显卡升级 使用cuda自带额显卡驱动&#xff0c;居然无法&#xff0c;找到如何锁…

模拟请求ElasticSearch

Step1 安装chrome的这个插件 Step2 打开插件&#xff0c;GET的json填什么。 在IDEA的debug模式&#xff0c;走到Java代码的searchBuilder&#xff0c; 在这个searchBuilder变量里&#xff0c;对里面query变量点右侧 view按钮&#xff0c; IDEA里会显示出一个json&#xff…

ref和reactive

看尤雨溪说&#xff1a;为什么Vue3 中应该使用 Ref 而不是 Reactive&#xff1f;

Multisim14.0仿真(四十二)基于74LS183的8位表决器设计

一、74LS183简介&#xff1a; 74LS183是一种4位高速全加器&#xff0c;用于数字电路中的加法运算。74LS183输入端包括两个4位二进制数和一个进位信号&#xff0c;输出端包括1个4位二进制数和一个进位信号。 74LS138具有快速响应、低功耗灯特点&#xff0c;能实现高校的数字匀速…

接口和抽象类【Java面向对象知识回顾②】

Java中的抽象类和接口是两种常见的抽象概念&#xff0c;它们都能够帮助我们实现抽象化和多态性&#xff0c;但是它们在一些细节上有所不同 抽象类 抽象类是一种特殊的类&#xff0c;不能被实例化&#xff0c;只能被继承。抽象类具有类的所有特性&#xff0c;包括成员变量、成员…

链式二叉树(3)

目录 Main函数 ​ 二叉树第K层的节点个数 整体思路 分析理解 注意事项 二叉树查找值为x的节点 整体思路 分析理解 注意事项 Main函数 #include<stdio.h> #include<stdlib.h> #include<string.h> #include<assert.h> #include<math.h&g…

MATLAB怎么读取txt文件

在MATLAB中可以使用以下几种方式读取txt文本文件: importdata函数 A importdata(data.txt) 这会返回一个包含文本数据的cell数组。 dlmread函数 A dlmread(data.txt,,) 这会将文本文件中的数据读取为数值矩阵,其中’,指定了数据之间的分隔符。 textscan函数 fid fopen(…

算法41:掉落的方块(力扣699题)----线段树

题目&#xff1a;https://leetcode.cn/problems/falling-squares/description/ 在二维平面上的 x 轴上&#xff0c;放置着一些方块。 给你一个二维整数数组 positions &#xff0c;其中 positions[i] [lefti, sideLengthi] 表示&#xff1a;第 i 个方块边长为 sideLengthi &…

CSS是一门需要单独学习的技术吗?

CSS (Cascading Style Sheets) &#xff0c;做前端开发的人都很清楚&#xff0c;因为这是他们的一项必不可少的技能。我以前也是知道CSS&#xff0c;但从来没有单独学习过&#xff0c;认为就它只是用来渲染网页的表现层效果&#xff0c;定制页面和内元素的布局、颜色和字体等&a…

学习Android的第二天

目录 Android User Interface 用户界面 UI Android View与ViewGroup的概念 Android View android.view.View android.view.View XML 属性 android:id 属性 Android ViewGroup android.view.ViewGroup ViewGroup.LayoutParams ViewGroup.MarginLayoutParams ViewGr…

深度学习(12)--Mnist分类任务

一.Mnist分类任务流程详解 1.1.引入数据集 Mnist数据集是官方的数据集&#xff0c;比较特殊&#xff0c;可以直接通过%matplotlib inline自动下载&#xff0c;博主此处已经完成下载&#xff0c;从本地文件中引入数据集。 设置数据路径 from pathlib import Path# 设置数据路…

Pytest框架测试

Pytest 是什么? pytest 能够支持简单的单元测试和复杂的功能测试;pytest 可以结合 Requests 实现接口测试; 结合 Selenium、Appium 实现自动化功能测试;使用 pytest 结合 Allure 集成到 Jenkins 中可以实现持续集成。pytest 支持 315 种以上的插件;为什么要选择 Pytest 丰…

2024年第九届信号与图像处理国际会议(ICSIP 2024)

2024第九届信号与图像处理国际会议&#xff08;ICSIP 2024&#xff09;将于2024年7月12-14日在中国南京召开。ICSIP每年召开一次&#xff0c;在过去的七年中吸引了1200多名与会者&#xff0c;是展示信号和图像处理领域最新进展的领先国际会议之一。本次将汇集来自亚太国家、北美…

基于SpringBoot+Vue的师生疫情健康信息管理登记平台,附源码

博主介绍&#xff1a;✌程序员徐师兄、7年大厂程序员经历。全网粉丝12w、csdn博客专家、掘金/华为云/阿里云/InfoQ等平台优质作者、专注于Java技术领域和毕业项目实战✌ &#x1f345;文末获取源码联系&#x1f345; &#x1f447;&#x1f3fb; 精彩专栏推荐订阅&#x1f447;…

python+PyQt5 左右声道测试

UI&#xff1a; 源代码&#xff1a; # -*- coding: utf-8 -*-# Form implementation generated from reading ui file MicrophoneWinFrm.ui # # Created by: PyQt5 UI code generator 5.15.2 # # WARNING: Any manual changes made to this file will be lost when pyuic5 is …

Java正则表达式之Pattern和Matcher

目录 前言一、Pattern和Matcher的简单使用二、Pattern详解2.1 Pattern 常用方法2.1.1 compile(String regex)2.1.2 matches(String regex, CharSequence input)2.1.3 split(CharSequence input)2.1.4 pattern()2.1.5 matcher(CharSequence input) 三、Matcher详解3.1 Matcher 常…