【数据结构之单链表】

数据结构学习笔记---003

  • 数据结构之单链表
    • 1、什么是单链表?
      • 1.1、概念及结构
    • 2、单链表接口的实现
      • 2.1、单链表的SList.h
        • 2.1.1、定义单链表的结点存储结构
        • 2.1.2、声明单链表各个接口的函数
      • 2.2、单链表的SList.c
        • 2.2.1、遍历打印链表
        • 2.2.2、销毁单链表
        • 2.2.3、打印单链表元素
        • 2.2.4、单链表的基本操作
      • 3.3、单链表的main.c
        • 3.3.1、TestSL1()
        • 3.3.2、TestSL2()
    • 4、单链表巩固练习
      • 4.1、单链表巩固练习题01 --- 求链表的中间节点
      • 4.2、单链表巩固练习题02 --- 移除链表元素
      • 4.3、单链表巩固练习题03 --- 链表分割
    • 5、单链表总结

数据结构之单链表

前言:
前篇学习了 数据结构的顺序表 那么这篇继续学习数据结构线性表的第二种存储方法,链式存储的单链表。
/知识点汇总/

1、什么是单链表?

1.1、概念及结构

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

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

单链表通过一组任意的存储单元来存储线性表中的数据元素,不需要使用地址连续的存储单元,因此它不要求在逻辑上相邻的两个元素在物理位置上也相邻。

单链表(Singly Linked List)是一种常用的数据结构,它由若干个节点组成,每个节点包含两部分:数据域和指针域。
数据域用于存储数据,而指针域则用于指向下一个节点的地址。 单链表中每个节点只有一个指针域,指向下一个节点,最后一个节点的指针域指向NULL,表示链表的结尾。

2、单链表接口的实现

在实际应用中,单链表的存储思想就是用指针表示各结点之间的逻辑关系。 本质就是掌握好结点间的链式处理。

然后与前篇学的顺序表不同,就不存在扩容的问题了,但仍然需要注意及时释放动态开辟的内存空间等问题
实现过程继续采用阶段性测试,既方便调试也方便及时解决问题。
另外单链表又分为带头结点和不带头结点的方式,这里用不带头的进行编写,具体可根据实际符合的应用场景决定。

2.1、单链表的SList.h

2.1.1、定义单链表的结点存储结构

因为是采用的多种类型的数据,所以适用于结构体类型。然后知道了需要一个数据域和指针域,所以定义结构体如下所示:

//定义单链表的动态存储
typedef int SLNDataType;//这里的重命名主要作用是,不能保证每次使用都是整型,所以只需要改这里为其它类型更健壮和便利
//Single List
typedef struct SLNode    //定义单链表结点类型为结构体类型包括数据域和指针域
{SLNDataType val;         //结点数据域struct SLNode* next; //结点指针域,存放下一个结点的地址
}SLNode;
2.1.2、声明单链表各个接口的函数
//遍历打印链表
void SLPrint(SLNode* pphead);
//尾插
void SLTPushBack(SLNode** pphead, SLNDataType x);
//头插
void SLTPushFront(SLNode** pphead, SLNDataType x);
//尾删
void SLTPopBack(SLNode** pphead);
//头删
void SLTPopFront(SLNode** pphead);
//查找
SLNode* SLTFind(SLNode* phead, SLNDataType x);
//pphaed = NULL ,错误
//*pphead = NULL,空表
//(*pphead)->next = NULL,一个结点
//在pos位置的前面插入
void SLTInsert(SLNode** pphead,SLNode* pos, SLNDataType x);
//删除pos的位置结点
void SLTErase(SLNode** pphead, SLNode* pos);
//在pos的位置之后的操作,就不存在头插头删的情况,参数就不用传plist了
//在pos的位置之后插入
void SLTInsertAfter(SLNode* pos, SLNDataType x);
//在pos的位置之后删除
void SLTEraseAfter(SLNode* pos);
//销毁单链表
void SLTDestory(SLNode** pphead);

2.2、单链表的SList.c

主要还是要完成 SList.h 接口对应的 .c 功能函数。
有了前篇的意识和基础,对于单链表的初始化可以有但没必要,因为不带头结点本身不用就是空表,那么就体现在后面建立结点函数的写法中对初始化的处理了。但为了对比和理解,把带头结点的常规写法也写一下,但后面继续以不带头的结点写。
初始化单链表(带头结点)

Node* InitList()
{Node* first = (Node*)malloc(sizeof(Node)); //生成头结点first->next = NULL;                        //头结点的指针域置空return first;
}
2.2.1、遍历打印链表

为了打印单链表的元素,这里需要像顺序表那样遍历,所以引用一个工作指针,依次访问指针域打印各个结点的数据域中元素。工作指针的本质是读数据域而不作更改,防止写动数据等问题。

//遍历打印链表
void SLPrint(SLNode* phead)
{SLNode* cur = phead;//定义工作指针,防止更改了phead的地址while (cur != NULL){printf("%d->", cur->val);//读数据域cur = cur->next;        //偏移指针域}printf("NULL\n");
}
2.2.2、销毁单链表

为了避免忘记销毁开辟的动态内存空间。所以这里使用动态存储方法,那么通常把初始化和销毁一块就写出来了。

//顺序表的销毁
void SLDestory(SL* ps1)
{//暴力检查assert(ps1);//一定不能为空if (ps1->a != NULL){free(ps1->a);ps1->a = NULL;ps1->size = 0;ps1->capacity = 0;}
}
2.2.3、打印单链表元素

为了直观的体现数据元素是否成功操作,所以接着写出打印接口函数。

//打印顺序表
void SLPrint(SL* ps1)
{//暴力检查assert(ps1);//一定不能为空for (int i = 0; i < ps1->size; i++){printf("%d ", ps1->a[i]);}printf("\n");
}
2.2.4、单链表的基本操作

完成了上述函数的功能,那么就可以实现单链表的基本操作了。插入和删除以及查找(无非就是增删改查)。
头插和头删;尾插和尾删。
其次,对于开辟新结点的操作多个函数都涉及到,所以可以封装成单独的CreateNode函数,如下所示:

//开辟新结点,且next置空
SLNode* CreateNode(SLNDataType x)
{SLNode* newnode = (SLNode*)malloc(sizeof(SLNode));if (newnode == NULL){perror("malloc fail");//return;exit(-1);}newnode->val = x;newnode->next = NULL;return newnode;
}

尾插、头插、尾删、头删的操作,需要注意一下,判空和空表的情况。

//尾插
void SLTPushBack(SLNode** pphead, SLNDataType x)
{assert(pphead);//空结点的处理SLNode* newnode = CreateNode(x);if (*pphead == NULL){*pphead = newnode;}else{//找尾(链表不为空,只需要下面的部分,但尽可能的健壮代码,所以需要考虑链表为空的情况)SLNode* tail = *pphead;//while (tail != NULL),容易掉的坑,这样写画图很清楚的看到,链表并没有链接起来;而且除了作用域tail销毁,尾结点会存在内存泄漏的问题while (tail->next != NULL){tail = tail->next;}//因为多个函数都需要开辟新节点,所以直接封装为函数//而且不能直接SLNode* newNode:来定义结构体来开辟,因为这里只能作用域为局部变量,所以需要malloc才行。//SLNode* newNode = (SListNode*)malloc(sizeof(SLNode));//SLNode* newnode = CreateNode(x);已放开头tail->next = newnode;}
}//头插
void SLTPushFront(SLNode** pphead, SLNDataType x)
{assert(pphead);SLNode* newnode = CreateNode(x);newnode->next = *pphead;*pphead = newnode;
}
//尾删
void SLTPopBack(SLNode** pphead)
{//注意pphead一定不能为空,所以需要检查assert(*pphead);assert(pphead);//区分一个结点和多个结点//这两句的位置不同,对应的写法不同/*SLNode* tail = *pphead;//Node* prev = *pphead;//错误,初始化为*phead时,如果首结点被free时,那么prev就会成为野指针。SLNode* prev = NULL;if (tail->next == NULL)//一个节点{free(tail);tail = NULL;}*/if ((*pphead)->next == NULL)//一个节点{free(*pphead);*pphead = NULL;}else//找尾{//这两句的位置可以写这儿SLNode* tail = *pphead;//Node* prev = *pphead;//错误,初始化为*phead时,如果首结点被free时,那么prev就会成为野指针。SLNode* prev = NULL;while (tail->next != NULL){/*tail = tail->next;prev = tail;*///错误,注意顺序是不可以交换的,因为当tail到空时,还赋给prev就导致为野指针了prev = tail;tail = tail->next;}free(tail);tail = NULL;prev->next = NULL;//一种简化写法/*SLNode* tail = *pphead;while (tail->next->next != NULL){tail = tail->next;}free(tail->next);//释放的是指针指向的内存空间,而不是指针本身tail->next = NULL;*/}
}
//头删
void SLTPopFront(SLNode** pphead)
{assert(*pphead);assert(pphead);//if ((*pphead)->next == NULL)//一个节点//{//	free(*pphead);//	*pphead = NULL;//}//一个以及以上结点都能处理//方法1://SLNode* tail = *pphead;//tail = tail->next;//free(*pphead);//*pphead = tail;	//方法2://SLNode* tail = *pphead;//*pphead = (*pphead)->next;//free(tail);//方法3:SLNode* tmp = (*pphead)->next;;free(*pphead);//注意顺序不能交换*pphead = tmp;
}

查找、在pos位置的前面插入、删除pos的位置、在pos的位置之后插入和在pos的位置之后删除操作。

//查找
SLNode* SLTFind(SLNode* phead, SLNDataType x)
{assert(phead);SLNode* cur = phead;//定义工作指针,防止更改了phead的地址while (cur != NULL){if (cur->val == x)return cur;cur = cur->next;        //偏移指针域}return NULL;
}
//在pos位置的前面插入
void SLTInsert(SLNode** pphead, SLNode* pos, SLNDataType x)
{/*assert(pphead);assert(*pphead);assert(pos);*///这种检查严格限定了pos一定为链表里的有效节点assert(pphead);assert((!pos && !(*pphead)) ||(pos && *pphead));//要么都是空,要么都不是空//对空处理if (*pphead == pos){//调用头插SLTPushFront(pphead, x);}else//找插入结点的前一个结点{SLNode* prev = *pphead;while (prev->next != pos){prev = prev->next;}SLNode* newnode = CreateNode(x);prev->next = newnode;newnode->next = pos;}
}
//删除pos的位置
void SLTErase(SLNode** pphead, SLNode* pos)
{assert(pphead);assert(*pphead);assert(pos);//头结点梳理if ((*pphead)->next == NULL){//调用头删SLTPopFront(pphead);}else{SLNode* prev = *pphead;if (prev == pos){//调用头删SLTPopFront(pphead);}else{while (prev->next != pos){prev = prev->next;}prev->next = pos->next;free(pos);pos = NULL;}}
}//在pos的位置之后插入
void SLTInsertAfter(SLNode* pos, SLNDataType x)
{assert(pos);SLNode* newnode = CreateNode(x);//经典错误,单链表断开,自己指向了自己/*pos->next = newnode;newnode->next = pos->next;*/newnode->next = pos->next;pos->next = newnode;
}//扩展:只给pos位置,如何完成想在pos之前插入呢?
//只需要在pos后面插入,交换pos的结点值即可//在pos的位置之后删除
void SLTEraseAfter(SLNode* pos)
{assert(pos);assert(pos->next);//因为当只有一个结点时,cur=pos->next,为空,所以对pos->next访问就非法访问了。SLNode* cur = pos->next;pos->next = cur->next;free(cur);cur = NULL;
}

3.3、单链表的main.c

简单的写几个测试应用,目的是检测各个接口函数是否满足需求,是否存在一些bug。

3.3.1、TestSL1()

主要检测尾插、头插、打印和销毁接口函数,以及参数的传址调用和传值调用。

#include "SList.h"
//测试1:
void TestSLT1()
{SLNode* plist = NULL;//SLTPushBack(plist, 1);//传参为SLNode*,要想实现操作,还得使用二级指针,所以这里必须传地址 ==>不然phead只是plist的临时拷贝SLTPushBack(&plist, 1);SLTPushBack(&plist, 2);SLTPushBack(&plist, 3);SLTPushBack(&plist, 4);//尾插SLPrint(plist);//打印SLTPopBack(&plist);//尾删SLPrint(plist);SLTPushFront(&plist, 5);//头插SLPrint(plist);SLTPushFront(&plist, 6);//头插SLPrint(plist);SLTPopBack(&plist);//尾删SLPrint(plist);SLTPopBack(&plist);//尾删SLPrint(plist);
}
int main()
{TestSLT1();//TestSLT2();//TestSLT3();//TestSLT4();//TestSLT5();return 0;
}

测试效果展示
在这里插入图片描述

3.3.2、TestSL2()

主要检测接口函数是否满足需求,并深刻认识指针、二级指针的应用。

#include "Seqlist.h"
//测试二:
void TestSLT2()
{SLNode* plist = NULL;//SLTPushBack(plist, 1);//传参为SLNode*,要想实现操作,还得使用二级指针,所以这里必须传地址 ==>不然phead只是plist的临时拷贝SLTPushBack(&plist, 1);SLTPushBack(&plist, 2);SLTPushBack(&plist, 3);SLTPushBack(&plist, 4);//尾插SLPrint(plist);//打印SLTPopFront(&plist);SLPrint(plist);
}
int main()
{//TestSL1();TestSL2();//TestSL3();//TestSL4();//TestSL5();//TestSL6();//TestSL7();return 0;
}

效果展示
在这里插入图片描述

4、单链表巩固练习

4.1、单链表巩固练习题01 — 求链表的中间节点

求链表的中间节点 — 经典快慢指针

#include "SList.h"
//定义单链表的结构体类型
struct ListNode
{int val;struct ListNode* next;
};
struct ListNode* middleNode(struct ListNode* head)
{struct ListNode* slow = head;struct ListNode* fast = head;while (fast && fast->next){slow = slow->next;fast = fast->next->next;}return slow;
}int main()
{SLNode* plist = NULL;//SLTPushBack(plist, 1);//传参为SLNode*,要想实现操作,还得使用二级指针,所以这里必须传地址 ==>不然phead只是plist的临时拷贝SLTPushBack(&plist, 1);SLTPushBack(&plist, 2);SLTPushBack(&plist, 3);SLTPushBack(&plist, 4);//尾插SLPrint(plist);//打印struct ListNode* pos = middleNode(plist);printf("pos = %d\n", pos->val);return 0;
}

4.2、单链表巩固练习题02 — 移除链表元素

移除链表元素 – 返回新的头结点
方法1:遍历删除

#include "SList.h"
//定义单链表的结构体类型
struct ListNode
{int val;struct ListNode* next;
};
struct ListNode* removeELements(struct ListNode* head, int val)
{struct ListNode* prev = NULL;//prev的作用,是在于删除对应结点后,能找到next断开之后再链接struct ListNode* cur = head;while (cur){if (cur->val == val){struct ListNode* tmp = cur->next;free(cur);if (prev)//必须判断首节点是否val,被free后,可能为空prev->next = tmp;else//否则,新的头结点更新head = tmp;cur = tmp;}else{prev = cur;cur = cur->next;}}return head;
}int main()
{struct ListNode* n1 = (struct ListNode*)malloc(sizeof(struct ListNode));struct ListNode* n2 = (struct ListNode*)malloc(sizeof(struct ListNode));struct ListNode* n3 = (struct ListNode*)malloc(sizeof(struct ListNode));struct ListNode* n4 = (struct ListNode*)malloc(sizeof(struct ListNode));n1->val = 7;n2->val = 7;n3->val = 2;n4->val = 3;n1->next = n2;n2->next = n3;n3->next = n4;n4->next = NULL;struct ListNode* head = removeELements(n1, 7);SLPrint(head);return 0;
}

方法2:遍历把不是val的结点链接到新链表,添加尾指针

#include "SList.h"
//定义单链表的结构体类型
struct ListNode
{int val;struct ListNode* next;
};
//打印链表
void SLPrint1(struct ListNode* phead)
{struct ListNode* cur = phead;//定义工作指针,防止更改了phead的地址while (cur != NULL){printf("%d->", cur->val);//读结点域cur = cur->next;        //偏移指针域}printf("NULL\n");
}
struct ListNode* removeELements(struct ListNode* head, int val)
{struct ListNode* newnode = NULL;struct ListNode* tail = NULL;struct ListNode* cur = head;while (cur){//如果不是val把结点拿到新链表(新链表为空)if (cur->val != val){//尾插:两种情况if (tail == NULL){newnode = tail = cur;}else{tail->next = cur;tail = tail->next;}cur = cur->next;}else{struct ListNode* tmp = cur;cur = cur->next;free(tmp);}}if(tail)//注意最后尾节点的判空,防止野指针,同时判空处理空表tail->next = NULL;head = newnode;return head;
}int main()
{struct ListNode* n1 = (struct ListNode*)malloc(sizeof(struct ListNode));struct ListNode* n2 = (struct ListNode*)malloc(sizeof(struct ListNode));struct ListNode* n3 = (struct ListNode*)malloc(sizeof(struct ListNode));struct ListNode* n4 = (struct ListNode*)malloc(sizeof(struct ListNode));n1->val = 7;n2->val = 7;n3->val = 2;n4->val = 3;n1->next = n2;n2->next = n3;n3->next = n4;n4->next = NULL;struct ListNode* head = removeELements(n1, 7);SLPrint1(head);return 0;
}

方法3:添加哨兵位头结点
哨兵位的头结点,不存储有效数据置NULL

#include "SList.h"
//定义单链表的结构体类型
struct ListNode
{int val;struct ListNode* next;
};
//打印链表
void SLPrint1(struct ListNode* phead)
{struct ListNode* cur = phead;//定义工作指针,防止更改了phead的地址while (cur != NULL){printf("%d->", cur->val);//读结点域cur = cur->next;        //偏移指针域}printf("NULL\n");
}
struct ListNode* removeELements(struct ListNode* head, int val)
{struct ListNode* newnode = NULL;struct ListNode* tail = NULL;struct ListNode* cur = head;//添加哨兵位newnode = tail = (struct ListNode*)malloc(sizeof(struct ListNode));while (cur){//如果不是val把结点拿到新链表(新链表为空)if (cur->val != val){//尾插:两种情况tail->next = cur;tail = tail->next;cur = cur->next;}else{struct ListNode* tmp = cur;cur = cur->next;free(tmp);}}//哨兵位方法前面简化,而这里就需要处理tail->next = NULL;struct ListNode* tmp = newnode;newnode = newnode->next;free(tmp);head = newnode;//头结点next为首节点符合题目要求return head;
}int main()
{struct ListNode* n1 = (struct ListNode*)malloc(sizeof(struct ListNode));struct ListNode* n2 = (struct ListNode*)malloc(sizeof(struct ListNode));struct ListNode* n3 = (struct ListNode*)malloc(sizeof(struct ListNode));struct ListNode* n4 = (struct ListNode*)malloc(sizeof(struct ListNode));n1->val = 7;n2->val = 7;n3->val = 2;n4->val = 3;n1->next = n2;n2->next = n3;n3->next = n4;n4->next = NULL;struct ListNode* head = removeELements(n1, 7);SLPrint1(head);return 0;
}

4.3、单链表巩固练习题03 — 链表分割

链表分割 — 返回重新排列后的头指针,相对顺序不变
3 6 1 8 4 6 9 — val=5分割
3 1 4 (5)6 8 6 9
思路1:不带哨兵位,比较val小的放一个链表,大于等于val的放一个链表,再合并两个链表,同时注意判断两边链表为空的情况,即全比val大或者小的处理,所以相对于带头结点的操作就比较复杂。
思路2:带2个哨兵位,直接比较后,执行相应的尾插操作即可,最后合并。
建议采用带头结点的方法,更好处理这题

#include "SList.h"
//#include <cstddef>//定义单链表的结构体类型
struct ListNode
{int val;struct ListNode* next;
};
//打印链表
void SLPrint1(struct ListNode* phead)
{struct ListNode* cur = phead;//定义工作指针,防止更改了phead的地址while (cur != NULL){printf("%d->", cur->val);//读结点域cur = cur->next;        //偏移指针域}printf("NULL\n");
}
struct ListNode* removeELements(struct ListNode* phead, int x)
{struct ListNode* head1;struct ListNode* head2;struct ListNode* tail1;struct ListNode* tail2;//oj题,一般malloc不会申请失败,可不作检查head1 = tail1 = (struct ListNode*)malloc(sizeof(struct ListNode));head2 = tail2 = (struct ListNode*)malloc(sizeof(struct ListNode));struct ListNode* cur = phead;while (cur){//尾插 -- < val -->tail1if (cur->val < x){tail1->next = cur;tail1 = tail1->next;}else//尾插 -- >= val -->tail2{tail2->next = cur;tail2 = tail2->next;}cur = cur->next;//判断一个走一个}//链接tail1和tail2tail1->next = head2->next;tail2->next = NULL;//更新头结点phead = head1->next;free(head1);free(head2);return phead;
}int main()
{struct ListNode* n1 = (struct ListNode*)malloc(sizeof(struct ListNode));struct ListNode* n2 = (struct ListNode*)malloc(sizeof(struct ListNode));struct ListNode* n3 = (struct ListNode*)malloc(sizeof(struct ListNode));struct ListNode* n4 = (struct ListNode*)malloc(sizeof(struct ListNode));n1->val = 7;n2->val = 7;n3->val = 2;n4->val = 3;n1->next = n2;n2->next = n3;n3->next = n4;n4->next = NULL;struct ListNode* head = removeELements(n1, 6);SLPrint1(head);return 0;
}

5、单链表总结

主要有以下两点
与顺序表不同,单链表的元素不是连续存储的,而是通过指针相连形成链式结构。因此,单链表具有以下优缺点。
优点
支持动态内存分配。由于单链表不需要预先分配一段连续的空间,因此可以根据实际需求动态地申请、释放节点空间,避免浪费内存。
支持高效的插入、删除操作。由于单链表中的节点是通过指针相连的,因此在插入、删除一个节点时,只需要修改其前驱节点或后继节点的指针即可,时间复杂度为O(1)O(1)O(1)。
缺点
不支持随机访问。由于单链表中的节点不是连续存储的,因此不能像数组一样通过下标来直接访问一个元素,需要从头节点开始遍历整个链表才能访问任意位置的元素。

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

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

相关文章

VM进行TCP/IP通信

OK就变成这样 vm充当服务端的话也是差不多的操作 点击连接 这里我把端口号换掉了因为可能被占用报错了&#xff0c;如果有报错可以尝试尝试换个端口号 注&#xff1a; 还有一个点在工作中要是充当服务器&#xff0c;要去网络这边看下他的ip地址 拉到最后面

【github】github设置项目为私有

点击setting change to private 无脑下一步

web架构师编辑器内容-创建业务组件和编辑器基本行为

编辑器主要分为三部分&#xff0c;左侧是组件模板库&#xff0c;中间是画布区域&#xff0c;右侧是面板设置区域。 左侧是预设各种组件模板进行添加 中间是使用交互手段来更新元素的值 右侧是使用表单的方式来更新元素的值。 大致效果&#xff1a; 左侧组件模板库 最初的模板…

基于JSP+Servlet+Mysql的调查管理系统

基于JSPServletMysql的调查管理系统 一、系统介绍二、功能展示1.项目内容2.项目骨架3.数据库3.登录4.注册3.首页5.系统管理 四、其它1.其他系统实现五.获取源码 一、系统介绍 项目名称&#xff1a;基于JSPServlet的调查管理系统 项目架构&#xff1a;B/S架构 开发语言&#…

在Next.js和React中搭建Cesium项目

在Next.js和React中搭建Cesium项目&#xff0c;需要确保Cesium能够与服务端渲染(SSR)兼容&#xff0c;因为Next.js默认是SSR的。Cesium是一个基于WebGL的地理信息可视化库&#xff0c;通常用于在网页中展示三维地球或地图。下面是一个基本的步骤&#xff0c;用于在Next.js项目中…

.raw 是一个 Anndata 包中的对象,用于存储原始的单细胞数据。scanpy种如何查看 .raw 对象的内容,

1查看 .raw 对象的内容&#xff0c;可以使用以下方法&#xff1a; .raw 是一个 Anndata 包中的对象&#xff0c;用于存储原始的单细胞数据。 使用 .X 属性查看原始数据矩阵&#xff1a;.raw.X 这将返回一个 Numpy 数组&#xff0c;其中包含原始数据的数值。 使用 .var_names 属…

nodejs微信小程序+python+PHP兴趣趣班预约管理系统设计与实现-计算机毕业设计推荐

目 录 摘 要 I ABSTRACT II 目 录 II 第1章 绪论 1 1.1背景及意义 1 1.2 国内外研究概况 1 1.3 研究的内容 1 第2章 相关技术 3 2.1 nodejs简介 4 2.2 express框架介绍 6 2.4 MySQL数据库 4 第3章 系统分析 5 3.1 需求分析 5 3.2 系统可行性分析 5 3.2.1技术可行性&#xff1a;…

ARM作业1

汇编实现三个灯闪烁 汇编代码&#xff1a; .text .global _start _start: 设置GPIOE,GPIOF时钟使能LDR R0,0X50000A28 LDR R1,[R0] ORR R1,R1,#(0x3<<4) STR R1,[R0] 设置PE10,PF10,PE8为输出 LED1LDR R0,0X50006000LDR R1,[R0]ORR R1,R1,#(0X1<<20)BIC R1…

力扣每日一题day38[106. 从中序与后序遍历序列构造二叉树]

给定两个整数数组 inorder 和 postorder &#xff0c;其中 inorder 是二叉树的中序遍历&#xff0c; postorder 是同一棵树的后序遍历&#xff0c;请你构造并返回这颗 二叉树 。 示例 1: 输入&#xff1a;inorder [9,3,15,20,7], postorder [9,15,7,20,3] 输出&#xff1a;[…

华为鸿蒙(HarmonyOS):连接一切,智慧无限

华为鸿蒙是一款全场景、分布式操作系统&#xff0c;旨在构建一个真正统一的硬件生态系统。该操作系统于2019年8月首次发布&#xff0c;并被设计为可以应用于各种设备&#xff0c;包括智能手机、智能手表、智能电视、车载系统等多种智能设备。 推荐一套最新版的鸿蒙4.0开发教程 …

从零开发短视频电商 在AWS上SageMaker部署模型自定义日志输入和输出示例

从零开发短视频电商 在AWS上SageMaker部署模型自定义日志输入和输出示例 怎么部署自定义模型请看&#xff1a;从零开发短视频电商 在AWS上用SageMaker部署自定义模型 都是huaggingface上的模型或者fine-tune后的。 为了适配jumpstart上部署的模型的http输入输出&#xff0c;我…

Java设计模式之单例模式以及如何防止通过反射破坏单例模式

单例模式 单例模式使用场景 ​ 什么是单例模式&#xff1f;保障一个类只能有一个对象&#xff08;实例&#xff09;的代码开发模式就叫单例模式 ​ 什么时候使用&#xff1f; 工具类&#xff01;&#xff08;一种做法&#xff0c;所有的方法都是static&#xff0c;还有一种单…

使用 Elasticsearch 检测抄袭 (一)

作者&#xff1a;Priscilla Parodi 抄袭可以是直接的&#xff0c;涉及复制部分或全部内容&#xff0c;也可以是释义的&#xff0c;即通过更改一些单词或短语来重新表述作者的作品。 灵感和释义之间是有区别的。 即使你得出类似的结论&#xff0c;也可以阅读内容&#xff0c;获得…

Chrome浏览器http自动跳https问题

现象&#xff1a; Chrome浏览器访问http页面时有时会自动跳转https&#xff0c;导致一些问题。比如&#xff1a; 开发阶段访问dev环境网址跳https&#xff0c;后端还是http&#xff0c;导致接口跨域。 复现&#xff1a; 先访问http网址&#xff0c;再改成https访问&#xf…

Springboot+vue的装饰工程管理系统(有报告),Javaee项目,springboot vue前后端分离项目

演示视频&#xff1a; Springbootvue的装饰工程管理系统&#xff08;有报告&#xff09;&#xff0c;Javaee项目&#xff0c;springboot vue前后端分离项目 项目介绍&#xff1a; 本文设计了一个基于Springbootvue的前后端分离的装饰工程管理系统&#xff0c;采用M&#xff08…

vue3开发一个todo List

创建新的 Vue 3 项目&#xff1a; 按装vue3的 工具 npm install -g vue/cli创建一个新的 Vue 3 项目&#xff1a; vue create vue3-todolist进入项目目录&#xff1a; cd vue3-todolist代码&#xff1a; 在项目的 src/components 目录下&#xff0c;创建一个新的文件 Todo…

洛谷 NOIP2016 普及组 回文日期

这道题目本来是不难想思路的。。。。。。 然而我第一次做的时候改了蛮久才把代码完全改对&#xff0c;主要感觉还是不够细心&#xff0c;敲的时候也没注意见检查一些小错误&#xff0c;那么接下来不说废话&#xff0c;请看题干&#xff1a; 接下来请看输入输出的样例以及数据范…

听GPT 讲Rust源代码--src/tools(23)

File: rust/src/tools/clippy/rustc_tools_util/src/lib.rs 在Rust源代码中&#xff0c;rust/src/tools/clippy/rustc_tools_util/src/lib.rs文件的作用是为Clippy提供了一些实用工具和辅助函数。 该文件中定义了VersionInfo结构体&#xff0c;它有三个字段&#xff0c;分别为m…

Web组态可视化编辑器-by组态

演示地址&#xff1a; http://www.by-lot.com http://www.byzt.net web组态可视化编辑器&#xff1a;引领未来可视化编辑的新潮流 随着网络的普及和快速发展&#xff0c;web组态可视化编辑器应运而生&#xff0c;为人们在网络世界中创建和编辑内容提供了更加便捷的操作方式。这…

【Spring实战】配置多数据源

文章目录 1. 配置数据源信息2. 创建第一个数据源3. 创建第二个数据源4. 创建启动类及查询方法5. 启动服务6. 创建表及做数据7. 查询验证8. 详细代码总结 通过上一节的介绍&#xff0c;我们已经知道了如何使用 Spring 进行数据源的配置以及应用。在一些复杂的应用中&#xff0c;…