-
【功能模块】【操作步骤&问题现象】1、对象新增时需要新增数组字段2、【截图信息】【日志信息】(可选,上传日志内容或者附件)
-
一、什么是选择排序? 选择排序(Selection sort)是一种简单直观的排序算法。它的工作原理是:第一次从待排序的中数据元素选出最小(或最大)的一个元素,存放在序列的起始位置,然后再从剩余的未排序元素中寻找到最小(大)元素,然后放到已排序的序列的末尾。以此类推,直到全部待排序的数据元素的个数为零。选择排序是不稳定的排序方法。二、选择排序思路首先在未排序序列中找到最小(大)元素,存放到排序序列的起始位置,然后,再从剩余未排序元素中继续寻找最小(大)元素,然后放到已排序序列的末尾。以此类推,直到所有元素均排序完毕。三、排序过程例:定义一个数组 int a[8] = {9,3,7,2,6,1,5,8},要求利用选择排序的方法将数组从小到大排序。排序的次数:因为每排好一个元素,那么所需要排的元素个数减一,直到排到倒数第二个元素停止,将倒数第二个元素也排好后,整体数组排序就完成了。所以排序的次数 = 元素个数 - 1。(冒泡排序的排序次数与该排序的排序次数计算方法相同)9,3,7,2,6,1,5,8第一次排序:假设首元素作为整体元素数据最小值,然后从该元素的后一个元素开始每个元素都与该最小值进行比较,假如有比该元素小的值,就用一个变量去记住下标值,最后比较完成后,把两个元素互换位置即可。第一次排序结果:{1,3,7,2,6,9,5,8}1,3,7,2,6,9,5,8第二次排序:因为第一次排序选择的是将首元素作为最小值,最终经过互换位置,首元素排序完成,第二次排序就不需要排序首元素,只需要排序除首元素以外的元素,然后在依照第一次排序的原理进行排序。第二次排序结果:{1,2,7,3,6,9,5,8}然后根据第一次排序和第二次排序的原理,最终的排序结果为:{1,2,3,5,6,7,8,9}四、代码的实现#include <stdio.h> void arr_out(int a[8])//输出函数{ int i = 0; for(i = 0;i < 8;i++) { printf("%d ",a[i]); } printf("\n");} void arr_sort(int *p,int n){ int i,j; int min = 0; for(i = 0;i < n - 1;i++)//排序次数 { min = i; for(j = i + 1;j < n;j++) { if(p[j] < p[min]) { min = j;//记录交换的元素下标值 } } if(i != min) { int temp = p[i]; p[i] = p[min]; p[min] = temp; } }} int main(){ int a[8] = {0}; int i = 0; for(i = 0;i < 8;i++) { scanf("%d",&a[i]); } arr_sort(a,8);//排序函数 arr_out(a);//输出函数 return 0;}原文链接:https://blog.csdn.net/m0_59083833/article/details/123971321
-
memset是一个初始化函数,作用是将某一块内存中的全部设置为指定的值。void *memset(void *s, int c, size_t n); 1s指向要填充的内存块。c是要被设置的值。n是要被设置该值的字符数。返回类型是一个指向存储区s的指针。需要说明的几个地方一、不能任意赋值memset函数是按照字节对内存块进行初始化,所以不能用它将int数组出初始化为0和-1之外的其他值(除非该值高字节和低字节相同)。其实c的实际范围应该在0~255,因为memset函数只能取c的后八位给所输入范围的每个字节。也就是说无论c多大只有后八位二进制是有效的。=================================================================================================对于int a[4];memset(a, -1, sizeof(a)) 与 memset(a, 511, sizeof(a)) 所赋值的结果一样都为-1:因为 -1 的二进制码为(11111111 11111111 11111111 11111111);511 的二进制码为(00000000 00000000 00000001 11111111);后八位均为(11111111),所以数组中的每个字节都被赋值为(11111111)。注意int占四个字节,例如a[0]的四个字节都被赋值为(11111111),那么a[0](11111111 11111111 11111111 11111111),即a[0] = -1。二、注意所要赋值的数组的元素类型先来看两个例子:例一:对char类型的数组a初始化,设置元素全为’1’int main(){ char a[4]; memset(a,'1',4); for(int i=0; i<4; i++){ cout<<a[i]<<" "; } return 0;}例二:对int类型的数组a初始化,设置元素值全为1int main(){ int a[4]; memset(a,1,sizeof(a)); for(int i=0; i<4; i++){ cout<<a[i]<<" "; } return 0;}1、首先要说明的第一点 对于第二个程序,数组a是整型的,一般int所占内存空间为4个字节,所以在使用memset赋值时,下面的语句是错误的:int a[4];memset(a,1,4);由于memset函数是以字节为单位进行赋值的,所以上述代码是为数组a的前4个字节进行赋值,那么所得到的执行结果就只能是:正确的memset语句应为:memset(a,1,16); //int所占内存为4字节的情况memset(a,1,sizeof(a));至于为什么不是预期得到的1,将在下面的第二点进行说明。当然,不同的机器上int的大小可能不同,所以最好用sizeof()函数。2、为什么第一个程序可以正确赋值1而第二个不可以?这就又回到了刚刚说的第一个问题,memset函数中只能取c的后八位赋给每个字节。第一个程序中,数组a是字符型的,字符型占据的内存大小就是1Byte,而memset函数也是以字节为单位进行赋值的,所以输出正确。第二个程序中,数组a是整型的,整型占据的内存大小为4Byte,而memset函数还是按照字节为单位进行赋值,将1(00000001)赋给每一个字节。那么对于a[0]来说,其值为(00000001 00000001 00000001 00000001),即十进制的16843009。关于所要赋值的字符数的写法先来看一个示例:#include<bits/stdc++.h>using namespace std;void fun1(int a[]){ memset(a,-1,sizeof(a)); }int main(){ int a[6]; fun1(a); for(int i=0; i<6; i++){ cout<<a[i]<<" "; } return 0;}当数组作为参数传递时,其传递的实际上是一个指针,这个指针指向数组的首地址,如果用sizeof(a)函数得到的只是指针的长度,而不是数组的长度。解决方案:在函数中加入数组长度参数,在传递前先获取数组长度,然后将数组长度作为参数传递进去。#include<bits/stdc++.h>using namespace std;void fun1(int a[], int len){ memset(a,-1,len); }int main(){ int a[6]; int len = sizeof(a); fun1(a,len); for(int i=0; i<6; i++){ cout<<a[i]<<" "; } return 0;}具体用法实例初始化数组char str[100];memset(str,0,100);清空结构体类型的变量typedef struct Stu{ char name[20]; int cno;}Stu;Stu stu1; memset(&stu1, 0 ,sizeof(Stu));Stu stu2[10]; //数组memset(stu2, 0, sizeof(Stu)*10);此外,如果结构体中有数组的话还是需要对数组单独进行初始化处理的。————————————————版权声明:本文为CSDN博主「薛定谔的猫ovo」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。原文链接:https://blog.csdn.net/weixin_44162361/article/details/115790452
-
第一章:绪论1.1数据结构的基本概念1.数据:数据是信息的载体,是描述客观事物属性的数、字符以及所有能输入到计算机中并被程序识别和处理的符号的集合。2.数据元素:数据元素是数据的基本单位,通常作为一个整体进行考虑和处理。一个数据元素可由若干数据项组成,数据项是构成数据元素的不可分割的最小单位。例如,学生记录就是一个数据元素,它由学号、姓名、性别等数据项组成。3.数据对象:数据对象是具有相同性值的数据元素的集合,是数据的一个子集。4.数据类型:数据类型是一个值的集合和定义再此集合上的一组操作的总称。1)原子类型。其值不可再分的数据类型。如bool 和int 类型。2)结构类型。其值可以再分解为若干成分(分量)的数据类型。3)抽象数据类型。抽象数据组织及与之相关的操作。5.数据结构:数据结构是相互之间存在一种或多种特定关系的数据元素的集合。1.2数据结构的三要素1.数据的逻辑结构:逻辑结构是指数据元素之间的逻辑关系,即从逻辑关系上描述数据。逻辑结构包括:集合结构:结构中的数据元素之间除“同属一个集合”外,别无其它关系。线性结构:结构中的数据元素之间只存在一对一的关系,除了第一个元素,所有元素都有唯一前驱;除了最后一个元素,所有元素都有唯一后继。树形结构:结构中数据元素之间存在一对多的关系。图状结构:数据元素之间是多对多的关系。2.数据的存储结构(物理结构):存储结构是指数据结构在计算机中的表示(又称映像),也称物理结构。存储结构包括:顺序存储:把逻辑上相邻的元素存储在物理位置也相邻的存储单元中,元素之间的关系由存储单元的邻接关系来体现。链式存储:逻辑上相邻的元素在物理位置上可以不相邻,借助指示元素存储地址的指针来表示元素之间的逻辑关系。索引存储:在存储元素信息的同时,还建立附加的索引表,索引表中的每项称为索引项,索引项的一般形式是(关键字,地址)散列存储:根据元素的关键字直接计算出该元素的存储地址,又称哈希(Hash)存储。3.数据的运算:施加在数据上的运算包括运算的定义何实现。运算的定义是针对逻辑结构的,指出运算的功能;运算的实现是针对存储结构的,指出运算的具体操作步骤。1.3算法的基本概念程序=数据结构+算法算法(algorithm)是对特定问题求解步骤的一种描述,它是指令的有限序列,其中的每条指令表示一个或多个操作。算法的特性:1.有穷性:一个算法必须总在执行有穷步之后结束,且每一步都可在有穷时间内完成。2.确定性:算法中每条指令必须有确定的含义,对于相同的输入只能得到相同的输出。3.可行性:算法中描述的操作都可以通过已经实现的基本运算执行有限次来实现。4.输入:一个算法有零个或多个输入,这些输入取自于某个特定的对象的集合。5.输出:一个算法有一个多个输出,这些输出是与输入有着某种特定关系的量。好的算法达到的目标:正确性:算法应能够正确的求接问题。可读性:算法应具有良好的可读性,以帮助人们理解。健壮性:输入非法数据时,算法能适当地做出反应或进行处理,而不会产生莫名奇妙地输出结果。效率与低存储量需求:效率是指算法执行的时间,存储量需求是指算法执行过程中所需要的最大存储空间,这两者都与问题的规模有关。1.4算法的时间复杂度一般情况下,算法中基本操作重复执行的次数是问题规模n的某个函数f(n),算法的时间量度记作T(n)=O(n),它表示随问题规模n的增大而增大,算法执行时间的增长率和f(n)的增长率相同,称作算法的渐近时间复杂度,简称时间复杂度。1.5算法的空间复杂度算法的空间复杂度S(n)定义为该算法所耗费的存储空间,它是问题规模n的函数。记为S(n)=O(g(n))。第二章:线性表2.1线性表的定义线性表是具有相同数据类型的n(n>0)个数据元素的有限序列,其中n为表长,当n=0时线性表是一个空表。2.2顺序表的定义2.2.1静态分配://顺序表的实现--静态分配#include<stdio.h>#define MaxSize 10 //定义表的最大长度 typedef struct{ int data[MaxSize];//用静态的"数组"存放数据元素 int length; //顺序表的当前长度 }SqList; //顺序表的类型定义(静态分配方式) void InitList(SqList &L){ for(int i=0;i<MaxSize;i++){ L.data[i]=0; //将所有数据元素设置为默认初始值 } L.length=0;}int main(){ SqList L;//声明一个顺序表 InitList(L);//初始化一个顺序表 for(int i=0;i<MaxSize;i++){ printf("data[%d]=%d\n",i,L.data[i]); } return 0; }2.2.2动态分配//顺序表的实现——动态分配#include<stdio.h>#include<stdlib.h>//malloc、free函数的头文件 #define InitSize 10 //默认的最大长度typedef struct{ int *data;//指示动态分配数组的指针 int MaxSize; //顺序表的最大容量 int length; //顺序表的当前长度 }SeqList; //初始化void InitList(SeqList &L){ //用malloc 函数申请一片连续的存储空间 L.data =(int*)malloc(InitSize*sizeof(int)) ; L.length=0; L.MaxSize=InitSize;} //增加动态数组的长度void IncreaseSize(SeqList &L,int len){ int *p=L.data; L.data=(int*)malloc((L.MaxSize+len)*sizeof(int)); for(int i=0;i<L.length;i++){ L.data[i]=p[i]; //将数据复制到新区域 } L.MaxSize=L.MaxSize+len; //顺序表最大长度增加len free(p); //释放原来的内存空间 } int main(void){ SeqList L; //声明一个顺序表 InitList(L);//初始化顺序表 IncreaseSize(L,5); return 0; }顺序表的特点:随机访问 ,可以在O(1)时间内找到第i个元素。存储密度高,每个节点只存储数据元素拓展容量不方便插入、删除操作不方便,需要移动大量元素2.2顺序表的基本操作1.插入操作 :平均时间复杂度O(n)bool ListInsert(SqList &L, int i, int e){ //判断i的范围是否有效 if(i<1||i>L.length+1) return false; if(L.length>MaxSize) //当前存储空间已满,不能插入 return false; for(int j=L.length; j>i; j--){ //将第i个元素及其之后的元素后移 L.data[j]=L.data[j-1]; } L.data[i-1]=e; //在位置i处放入e L.length++; //长度加1 return true;}2.删除操作:平均时间复杂度O(n)bool LisDelete(SqList &L, int i, int &e){ // e用引用型参数 //判断i的范围是否有效 if(i<1||i>L.length) return false; e = L.data[i-1] //将被删除的元素赋值给e for(int j=L.length; j>i; j--){ //将第i个后的元素前移 L.data[j-1]=L.data[j]; } L.length--; //长度减1 return true;}3.按位查找(获取L表中第i个位置的值):平均时间复杂度O(1)#define MaxSize 10 //定义最大长度 typedef struct{ ElemType data[MaxSize]; //用静态的“数组”存放数据元素 int Length; //顺序表的当前长度}SqList; //顺序表的类型定义ElemType GetElem(SqList L, int i){ // ...判断i的值是否合法 return L.data[i-1]; //注意是i-1}4.按值查找:平均时间复杂度O(n)#define InitSize 10 //定义最大长度 typedef struct{ ElemTyp *data; //用静态的“数组”存放数据元素 int Length; //顺序表的当前长度}SqList; //在顺序表L中查找第一个元素值等于e的元素,并返回其位序int LocateElem(SqList L, ElemType e){ for(int i=0; i<L.lengthl i++) if(L.data[i] == e) return i+1; //数组下标为i的元素值等于e,返回其位序i+1 return 0; //推出循环,说明查找失败}2.3线性表的链式表示2.3.1 单链表的定义定义: 线性表的链式存储又称单链表,它是指通过一组任意的存储单元来存储线性表中的数据元素。typedef struct LNode{//定义单链表结点类型 ElemType data; //数据域 struct LNode *next;//指针域}LNode, *LinkList;1234可以利用typedef关键字——数据类型重命名:type<数据类型><别名>单链表的两种实现方式:不带头结点的单链表```bashtypedef struct LNode{ ElemType data; struct LNode *next;}LNode, *LinkList;//初始化一个空的单链表bool InitList(LinkList &L){ //注意用引用 & L = NULL; //空表,暂时还没有任何结点; return true;}void test(){ LinkList L; //声明一个指向单链表的指针: 头指针 //初始化一个空表 InitList(L); //...}//判断单链表是否为空bool Empty(LinkList L){ if (L == NULL) return true; else return false;}头结点:代表链表上头指针指向的第一个结点,不带有任何数据。带头结点的单链表typedef struct LNode{ ElemType data; struct LNode *next;}LNode, *LinkList;//初始化一个单链表(带头结点)bool InitList(LinkList &L){ L = (LNode*) malloc(sizeof(LNode)); //头指针指向的结点——分配一个头结点(不存储数据) if (L == NULL) //内存不足,分配失败 return false; L -> next = NULL; //头结点之后暂时还没有结点 return true;}void test(){ LinkList L; //声明一个指向单链表的指针: 头指针 //初始化一个空表 InitList(L); //...}//判断单链表是否为空(带头结点)bool Empty(LinkList L){ if (L->next == NULL) return true; else return false;}带头结点和不带头结点的比较:不带头结点:写代码麻烦!对第一个数据节点和后续数据节点的处理需要用不同的代码逻辑,对空表和非空表的处理也需要用不同的代码逻辑; 头指针指向的结点用于存放实际数据;带头结点:头指针指向的头结点不存放实际数据,头结点指向的下一个结点才存放实际数据;2.3.2单链表上基本操作的实现1.按位序插入(带头结点):==ListInsert(&L, i, e): ==在表L中的第i个位置上插入指定元素e = 找到第i-1个结点(前驱结点),将新结点插入其后;其中头结点可以看作第0个结点,故i=1时也适用。typedef struct LNode{ ElemType data; struct LNode *next;}LNode, *LinkList;//在第i个位置插入元素e(带头结点)bool ListInsert(LinkList &L, int i, ElemType e){ //判断i的合法性, i是位序号(从1开始) if(i<1) return False; LNode *p; //指针p指向当前扫描到的结点 int j=0; //当前p指向的是第几个结点 p = L; //L指向头结点,头结点是第0个结点(不存数据) //循环找到第i-1个结点 while(p!=NULL && j<i-1){ //如果i>lengh, p最后会等于NULL p = p->next; //p指向下一个结点 j++; } if (p==NULL) //i值不合法 return false; //在第i-1个结点后插入新结点 LNode *s = (LNode *)malloc(sizeof(LNode)); //申请一个结点 s->data = e; s->next = p->next; p->next = s; //将结点s连到p后,后两步千万不能颠倒qwq return true;}平均时间复杂度:O(n)2.按位序插入(不带头结点)==ListInsert(&L, i, e): ==在表L中的第i个位置上插入指定元素e = 找到第i-1个结点(前驱结点),将新结点插入其后; 因为不带头结点,所以不存在“第0个”结点,因此!i=1 时,需要特殊处理——插入(删除)第1个元素时,需要更改头指针L;typedef struct LNode{ ElemType data; struct LNode *next;}LNode, *LinkList;bool ListInsert(LinkList &L, int i, ElemType e){ if(i<1) return false; //插入到第1个位置时的操作有所不同! if(i==1){ LNode *s = (LNode *)malloc(size of(LNode)); s->data =e; s->next =L; L=s; //头指针指向新结点 return true; } //i>1的情况与带头结点一样!唯一区别是j的初始值为1 LNode *p; //指针p指向当前扫描到的结点 int j=1; //当前p指向的是第几个结点 p = L; //L指向头结点,头结点是第0个结点(不存数据) //循环找到第i-1个结点 while(p!=NULL && j<i-1){ //如果i>lengh, p最后会等于NULL p = p->next; //p指向下一个结点 j++; } if (p==NULL) //i值不合法 return false; //在第i-1个结点后插入新结点 LNode *s = (LNode *)malloc(sizeof(LNode)); //申请一个结点 s->data = e; s->next = p->next; p->next = s; return true;}3.指定结点的后插操作:InsertNextNode(LNode *p, ElemType e): 给定一个结点p,在其之后插入元素e; 根据单链表的链接指针只能往后查找,故给定一个结点p,那么p之后的结点我们都可知,但是p结点之前的结点无法得知;typedef struct LNode{ ElemType data; struct LNode *next;}LNode, *LinkList;bool InsertNextNode(LNode *p, ElemType e){ if(p==NULL){ return false; } LNode *s = (LNode *)malloc(sizeof(LNode)); //某些情况下分配失败,比如内存不足 if(s==NULL) return false; s->data = e; //用结点s保存数据元素e s->next = p->next; p->next = s; //将结点s连到p之后 return true;} //平均时间复杂度 = O(1)//有了后插操作,那么在第i个位置上插入指定元素e的代码可以改成:bool ListInsert(LinkList &L, int i, ElemType e){ if(i<1) return False; LNode *p; //指针p指向当前扫描到的结点 int j=0; //当前p指向的是第几个结点 p = L; //L指向头结点,头结点是第0个结点(不存数据) //循环找到第i-1个结点 while(p!=NULL && j<i-1){ //如果i>lengh, p最后4鸟会等于NULL p = p->next; //p指向下一个结点 j++; } return InsertNextNode(p, e)}王道书代码:bool InsertPriorNode(LNode *p, LNode *s){ if(p==NULL || S==NULL) return false; s->next = p->next; p->next = s; ///s连接到p ELemType temp = p->data; //交换数据域部分 p->data = s->data; s->data = temp; return true;} 5.按位序删除节点(带头结点)ListDelete(&L, i, &e): 删除操作,删除表L中第i个位置的元素,并用e返回删除元素的值;头结点视为“第0个”结点;思路:找到第i-1个结点,将其指针指向第i+1个结点,并释放第i个结点;typedef struct LNode{ ElemType data; struct LNode *next;}LNode, *LinkList;bool ListDelete(LinkList &L, int i, ElenType &e){ if(i<1) return false; LNode *p; //指针p指向当前扫描到的结点 int j=0; //当前p指向的是第几个结点 p = L; //L指向头结点,头结点是第0个结点(不存数据) //循环找到第i-1个结点 while(p!=NULL && j<i-1){ //如果i>lengh, p最后会等于NULL p = p->next; //p指向下一个结点 j++; } if(p==NULL) return false; if(p->next == NULL) //第i-1个结点之后已无其他结点 return false; LNode *q = p->next; //令q指向被删除的结点 e = q->data; //用e返回被删除元素的值 p->next = q->next; //将*q结点从链中“断开” free(q) //释放结点的存储空间 return true;}时间复杂度分析:最坏,平均时间复杂度:O(n)最好时间复杂度:删除第一个结点 O(1)6.指定结点的删除bool DeleteNode(LNode *p){ if(p==NULL) return false; LNode *q = p->next; //令q指向*p的后继结点 p->data = p->next->data; //让p和后继结点交换数据域 p->next = q->next; //将*q结点从链中“断开” free(q); return true;} //时间复杂度 = O(1)2.3.3单链表的查找按位查找==GetElem(L, i): ==按位查找操作,获取表L中第i个位置的元素的值;LNode * GetElem(LinkList L, int i){ if(i<0) return NULL; LNode *p; //指针p指向当前扫描到的结点 int j=0; //当前p指向的是第几个结点 p = L; //L指向头结点,头结点是第0个结点(不存数据) while(p!=NULL && j<i){ //循环找到第i个结点 p = p->next; j++; } return p; //返回p指针指向的值}平均时间复杂度O(n)按值查找LocateElem(L, e):按值查找操作,在表L中查找具有给定关键字值的元素;LNode * LocateElem(LinkList L, ElemType e){ LNode *P = L->next; //p指向第一个结点 //从第一个结点开始查找数据域为e的结点 while(p!=NULL && p->data != e){ p = p->next; } return p; //找到后返回该结点指针,否则返回NULL}2.3.4求单链表的长度== Length(LinkList L)==:计算单链表中数据结点(不含头结点)的个数,需要从第一个结点看是顺序依次访问表中的每个结点。算法的时间复杂度为O(n)。int Length(LinkList L){ int len=0; //统计表长 LNode *p = L; while(p->next != NULL){ p = p->next; len++; } return len;}2.3.5单链表的创建操作1.头插法建立单链表(平均时间复杂度O(n))思路:每次都将生成的结点插入到链表的表头。LinkList List_HeadInsert(LinkList &L){ //逆向建立单链表 LNode *s; int x; L = (LinkList)malloc(sizeof(LNode)); //建立头结点 L->next = NULL; //初始为空链表,这步不能少! scanf("%d", &x); //输入要插入的结点的值 while(x!=9999){ //输入9999表结束 s = (LNode *)malloc(sizeof(LNode)); //创建新结点 s->data = x; r->next = L->next; L->next = s; //将新结点插入表中,L为头指针 scanf("%d", &x); } return L; }2.尾插法建立单链表(时间复杂度O(n))思路:每次将新节点插入到当前链表的表尾,所以必须增加一个尾指针r,使其始终指向当前链表的尾结点。好处:生成的链表中结点的次序和输入数据的顺序会一致。LinkList List_TailInsert(LinkList &L){ //正向建立单链表 int x; //设ElemType为整型int L = (LinkList)malloc(sizeof(LNode)); //建立头结点(初始化空表) LNode *s, *r = L; //r为表尾指针 scanf("%d", &x); //输入要插入的结点的值 while(x!=9999){ //输入9999表结束 s = (LNode *)malloc(sizeof(LNode)); s->data = x; r->next = s; r = s //r指针指向新的表尾结点 scanf("%d", &x); } r->next = NULL; //尾结点指针置空 return L;}链表的逆置:算法思想:逆置链表初始为空,原表中结点从原链表中依次“删除”,再逐个插入逆置链表的表头(即“头插”到逆置链表中),使它成为逆置链表的“新”的第一个结点,如此循环,直至原链表为空;LNode *Inverse(LNode *L){ LNode *p, *q; p = L->next; //p指针指向第一个结点 L->next = NULL; //头结点指向NULL while (p != NULL){ q = p; p = p->next; q->next = L->next; L->next = q; } return L;2.3.6双链表双链表中节点类型的描述:`typedef struct DNode{ //定义双链表结点类型 ElemType data; //数据域 struct DNode *prior, *next; //前驱和后继指针}DNode, *DLinklist;双链表的初始化(带头结点)typedef struct DNode{ //定义双链表结点类型 ElemType data; //数据域 struct DNode *prior, *next; //前驱和后继指针}DNode, *DLinklist;//初始化双链表bool InitDLinkList(Dlinklist &L){ L = (DNode *)malloc(sizeof(DNode)); //分配一个头结点 if(L==NULL) //内存不足,分配失败 return false; L->prior = NULL; //头结点的prior指针永远指向NULL L->next = NULL; //头结点之后暂时还没有结点 return true;}void testDLinkList(){ //初始化双链表 DLinklist L; // 定义指向头结点的指针L InitDLinkList(L); //申请一片空间用于存放头结点,指针L指向这个头结点 //...}//判断双链表是否为空bool Empty(DLinklist L){ if(L->next == NULL) //判断头结点的next指针是否为空 return true; else return false;}双链表的插入操作后插操作InsertNextDNode(p, s): 在p结点后插入s结点bool InsertNextDNode(DNode *p, DNode *s){ //将结点 *s 插入到结点 *p之后 if(p==NULL || s==NULL) //非法参数 return false; s->next = p->next; if (p->next != NULL) //p不是最后一个结点=p有后继结点 p->next->prior = s; s->prior = p; p->next = s; return true;}按位序插入操作:思路:从头结点开始,找到某个位序的前驱结点,对该前驱结点执行后插操作;前插操作:思路:找到给定结点的前驱结点,再对该前驱结点执行后插操作;双链表的删除操作删除p节点的后继节点//删除p结点的后继结点bool DeletNextDNode(DNode *p){ if(p==NULL) return false; DNode *q =p->next; //找到p的后继结点q if(q==NULL) return false; //p没有后继结点; p->next = q->next; if(q->next != NULL) //q结点不是最后一个结点 q->next->prior=p; free(q); return true;}//销毁一个双链表bool DestoryList(DLinklist &L){ //循环释放各个数据结点 while(L->next != NULL){ DeletNextDNode(L); //删除头结点的后继结点 free(L); //释放头结点 L=NULL; //头指针指向NULL }}双链表的遍历操作前向遍历while(p!=NULL){ //对结点p做相应处理,eg打印 p = p->prior;}后向遍历while(p!=NULL){ //对结点p做相应处理,eg打印 p = p->next;}注意:双链表不可随机存取,按位查找和按值查找操作都只能用遍历的方式实现,时间复杂度为O(n)2.3.7循环链表1.循环单链表最后一个结点的指针不是NULL,而是指向头结点typedef struct LNode{ ElemType data; struct LNode *next; }DNode, *Linklist;/初始化一个循环单链表bool InitList(LinkList &L){ L = (LNode *)malloc(sizeof(LNode)); //分配一个头结点 if(L==NULL) //内存不足,分配失败 return false; L->next = L; //头结点next指针指向头结点 return true;}//判断循环单链表是否为空(终止条件为p或p->next是否等于头指针)bool Empty(LinkList L){ if(L->next == L) return true; //为空 else return false;}//判断结点p是否为循环单链表的表尾结点bool isTail(LinkList L, LNode *p){ if(p->next == L) return true; else return false;}单链表和循环单链表的比较:**单链表:**从一个结点出发只能找到该结点后续的各个结点;对链表的操作大多都在头部或者尾部;设立头指针,从头结点找到尾部的时间复杂度=O(n),即对表尾进行操作需要O(n)的时间复杂度;**循环单链表:**从一个结点出发,可以找到其他任何一个结点;设立尾指针,从尾部找到头部的时间复杂度为O(1),即对表头和表尾进行操作都只需要O(1)的时间复杂度;==优点:==从表中任一节点出发均可找到表中其他结点。2.循环双链表表头结点的prior指向表尾结点,表尾结点的next指向头结点typedef struct DNode{ ElemType data; struct DNode *prior, *next; }DNode, *DLinklist;//初始化空的循环双链表bool InitDLinkList(DLinklist &L){ L = (DNode *) malloc(sizeof(DNode)); //分配一个头结点 if(L==NULL) //内存不足,分配失败 return false; L->prior = L; //头结点的prior指向头结点 L->next = L; //头结点的next指向头结点}void testDLinkList(){ //初始化循环单链表 DLinklist L; InitDLinkList(L); //...}//判断循环双链表是否为空bool Empty(DLinklist L){ if(L->next == L) return true; else return false;}//判断结点p是否为循环双链表的表尾结点bool isTail(DLinklist L, DNode *p){ if(p->next == L) return true; else return false;}双链表的插入(循环双链表):bool InsertNextDNode(DNode *p, DNode *s){ s->next = p->next; p->next->prior = s; s->prior = p; p->next = s;双链表的删除//删除p的后继结点qp->next = q->next;q->next->prior = p;free(q);双向循环链表:和单链的循环表类似,双向链表也可以有循环表,让头结点的前驱指针指向链表的最后一个结点,让最后一个结点的后继指针指向头结点。————————————————版权声明:本文为CSDN博主「胖胖的懒羊羊」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。原文链接:https://blog.csdn.net/qq_44867340/article/details/119455799
-
题目001: 在Python中如何实现单例模式。点评:单例模式是指让一个类只能创建出唯一的实例,这个题目在面试中出现的频率极高,因为它考察的不仅仅是单例模式,更是对Python语言到底掌握到何种程度,建议大家用装饰器和元类这两种方式来实现单例模式,因为这两种方式的通用性最强,而且也可以顺便展示自己对装饰器和元类中两个关键知识点的理解。方法一: 使用装饰器实现单例模式。from functools import wrapsdef singleton(cls): """单例类装饰器""" instances = {} @wraps(cls) def wrapper(*args, **kwargs): if cls not in instances: instances[cls] = cls(*args, **kwargs) return instances[cls] return wrapper@singletonclass President: pass扩展:装饰器是Python中非常有特色的语法,用一个函数去装饰另一个函数或类,为其添加额外的能力。通常通过装饰来实现的功能都属横切关注功能,也就是跟正常的业务逻辑没有必然联系,可以动态添加或移除的功能。装饰器可以为代码提供缓存、代理、上下文环境等服务,它是对设计模式中代理模式的践行。在写装饰器的时候,带装饰功能的函数(上面代码中的wrapper函数)通常都会用functools模块中的wraps再加以装饰,这个装饰器最重要的作用是给被装饰的类或函数动态添加一个__wrapped__属性,这个属性会将被装饰之前的类或函数保留下来,这样在我们不需要装饰功能的时候,可以通过它来取消装饰器,例如可以使用President = President.__wrapped__来取消对President类做的单例处理。需要提醒大家的是:上面的单例并不是线程安全的,如果要做到线程安全,需要对创建对象的代码进行加锁的处理。在Python中可以使用threading模块的RLock对象来提供锁,可以使用锁对象的acquire和release方法来实现加锁和解锁的操作。当然,更为简便的做法是使用锁对象的with上下文语法来进行隐式的加锁和解锁操作。方法二: 使用元类实现单例模式。class SingletonMeta(type): """自定义单例元类""" def __init__(cls, *args, **kwargs): cls.__instance = None super().__init__(*args, **kwargs) def __call__(cls, *args, **kwargs): if cls.__instance is None: cls.__instance = super().__call__(*args, **kwargs) return cls.__instanceclass President(metaclass=SingletonMeta): pass扩展:Python是面向对象的编程语言,在面向对象的世界中,一切皆为对象。对象是通过类来创建的,而类本身也是对象,类这样的对象是通过元类来创建的。我们在定义类时,如果没有给一个类指定父类,那么默认的父类是object,如果没有给一个类指定元类,那么默认的元类是type。通过自定义的元类,我们可以改变一个类默认的行为,就如同上面的代码中,我们通过元类的__call__魔术方法,改变了President类的构造器那样。补充:关于单例模式,在面试中还有可能被问到它的应用场景。通常一个对象的状态是被其他对象共享的,就可以将其设计为单例,例如项目中使用的数据库连接池对象和配置对象通常都是单例,这样才能保证所有地方获取到的数据库连接和配置信息是完全一致的;而且由于对象只有唯一的实例,因此从根本上避免了重复创建对象造成的时间和空间上的开销,也避免了对资源的多重占用。再举个例子,项目中的日志操作通常也会使用单例模式,这是因为共享的日志文件一直处于打开状态,只能有一个实例去操作它,否则在写入日志的时候会产生混乱。题目002:不使用中间变量,交换两个变量a和b的值。点评:典型的送人头的题目,通常交换两个变量需要借助一个中间变量,如果不允许使用中间变量,在其他编程语言中可以使用异或运算的方式来实现交换两个变量的值,但是Python中有更为简单明了的做法。方法一:a = a ^ bb = a ^ ba = a ^ b方法二:a, b = b, a扩展:需要注意,a, b = b, a这种做法其实并不是元组解包,虽然很多人都这样认为。Python字节码指令中有ROT_TWO指令来支持这个操作,类似的还有ROT_THREE,对于3个以上的元素,如a, b, c, d = b, c, d, a,才会用到创建元组和元组解包。想知道你的代码对应的字节码指令,可以使用Python标准库中dis模块的dis函数来反汇编你的Python代码。题目003:写一个删除列表中重复元素的函数,要求去重后元素相对位置保持不变。点评:这个题目在初中级Python岗位面试的时候经常出现,题目源于《Python Cookbook》这本书第一章的第10个问题,有很多面试题其实都是这本书上的原题,所以建议大家有时间好好研读一下这本书。def dedup(items): no_dup_items = [] seen = set() for item in items: if item not in seen: no_dup_items.append(item) seen.add(item) return no_dup_items如果愿意也可以把上面的函数改造成一个生成器,代码如下所示。def dedup(items): seen = set() for item in items: if item not in seen: yield item seen.add(item)扩展:由于Python中的集合底层使用哈希存储,所以集合的in和not in成员运算在性能上远远优于列表,所以上面的代码我们使用了集合来保存已经出现过的元素。集合中的元素必须是hashable对象,因此上面的代码在列表元素不是hashable对象时会失效,要解决这个问题可以给函数增加一个参数,该参数可以设计为返回哈希码或hashable对象的函数。题目004:假设你使用的是官方的CPython,说出下面代码的运行结果。点评:下面的程序对实际开发并没有什么意义,但却是CPython中的一个大坑,这道题旨在考察面试者对官方的Python解释器到底了解到什么程度。a, b, c, d = 1, 1, 1000, 1000print(a is b, c is d)def foo(): e = 1000 f = 1000 print(e is f, e is d) g = 1 print(g is a)foo()运行结果:True FalseTrue FalseTrue上面代码中a is b的结果是True但c is d的结果是False,这一点的确让人费解。CPython解释器出于性能优化的考虑,把频繁使用的整数对象用一个叫small_ints的对象池缓存起来造成的。small_ints缓存的整数值被设定为[-5, 256]这个区间,也就是说,在任何引用这些整数的地方,都不需要重新创建int对象,而是直接引用缓存池中的对象。如果整数不在该范围内,那么即便两个整数的值相同,它们也是不同的对象。CPython底层为了进一步提升性能还做了另一个设定,对于同一个代码块中值不在small_ints缓存范围内的整数,如果同一个代码块中已经存在一个值与其相同的整数对象,那么就直接引用该对象,否则创建新的int对象。需要大家注意的是,这条规则对数值型适用,但对字符串则需要考虑字符串的长度,这一点大家可以自行证明。扩展:如果你用PyPy(另一种Python解释器实现,支持JIT,对CPython的缺点进行了改良,在性能上优于CPython,但对三方库的支持略差)来运行上面的代码,你会发现所有的输出都是True。题目005:Lambda函数是什么,举例说明的它的应用场景。点评:这个题目主要想考察的是Lambda函数的应用场景,潜台词是问你在项目中有没有使用过Lambda函数,具体在什么场景下会用到Lambda函数,借此来判断你写代码的能力。因为Lambda函数通常用在高阶函数中,主要的作用是通过向函数传入函数或让函数返回函数最终实现代码的解耦合。Lambda函数也叫匿名函数,它是功能简单用一行代码就能实现的小型函数。Python中的Lambda函数只能写一个表达式,这个表达式的执行结果就是函数的返回值,不用写return关键字。Lambda函数因为没有名字,所以也不会跟其他函数发生命名冲突的问题。扩展:面试的时候有可能还会考你用Lambda函数来实现一些功能,也就是用一行代码来实现题目要求的功能,例如:用一行代码实现求阶乘的函数,用一行代码实现求最大公约数的函数等。fac = lambda x: __import__('functools').reduce(int.__mul__, range(1, x + 1), 1)gcd = lambda x, y: y % x and gcd(y % x, x) or xLambda函数其实最为主要的用途是把一个函数传入另一个高阶函数(如Python内置的filter、map等)中来为函数做解耦合,增强函数的灵活性和通用性。下面的例子通过使用filter和map函数,实现了从列表中筛选出奇数并求平方构成新列表的操作,因为用到了高阶函数,过滤和映射数据的规则都是函数的调用者通过另外一个函数传入的,因此这filter和map函数没有跟特定的过滤和映射数据的规则耦合在一起。items = [12, 5, 7, 10, 8, 19]items = list(map(lambda x: x ** 2, filter(lambda x: x % 2, items)))print(items) # [25, 49, 361]扩展:用列表的生成式来实现上面的代码会更加简单明了,代码如下所示。items = [12, 5, 7, 10, 8, 19]items = [x ** 2 for x in items if x % 2]print(items) # [25, 49, 361]题目006:说说Python中的浅拷贝和深拷贝。点评:这个题目本身出现的频率非常高,但是就题论题而言没有什么技术含量。对于这种面试题,在回答的时候一定要让你的答案能够超出面试官的预期,这样才能获得更好的印象分。所以回答这个题目的要点不仅仅是能够说出浅拷贝和深拷贝的区别,深拷贝的时候可能遇到的两大问题,还要说出Python标准库对浅拷贝和深拷贝的支持,然后可以说说列表、字典如何实现拷贝操作以及如何通过序列化和反序列的方式实现深拷贝,最后还可以提到设计模式中的原型模式以及它在项目中的应用。浅拷贝通常只复制对象本身,而深拷贝不仅会复制对象,还会递归的复制对象所关联的对象。深拷贝可能会遇到两个问题:一是一个对象如果直接或间接的引用了自身,会导致无休止的递归拷贝;二是深拷贝可能对原本设计为多个对象共享的数据也进行拷贝。Python通过copy模块中的copy和deepcopy函数来实现浅拷贝和深拷贝操作,其中deepcopy可以通过memo字典来保存已经拷贝过的对象,从而避免刚才所说的自引用递归问题;此外,可以通过copyreg模块的pickle函数来定制指定类型对象的拷贝行为。deepcopy函数的本质其实就是对象的一次序列化和一次返回序列化,面试题中还考过用自定义函数实现对象的深拷贝操作,显然我们可以使用pickle模块的dumps和loads来做到,代码如下所示。import picklemy_deep_copy = lambda obj: pickle.loads(pickle.dumps(obj))列表的切片操作[:]相当于实现了列表对象的浅拷贝,而字典的copy方法可以实现字典对象的浅拷贝。对象拷贝其实是更为快捷的创建对象的方式。在Python中,通过构造器创建对象属于两阶段构造,首先是分配内存空间,然后是初始化。在创建对象时,我们也可以基于“原型”对象来创建新对象,通过对原型对象的拷贝(复制内存)就完成了对象的创建和初始化,这种做法更加高效,这也就是设计模式中的原型模式。在Python中,我们可以通过元类的方式来实现原型模式,代码如下所示。import copyclass PrototypeMeta(type): """实现原型模式的元类""" def __init__(cls, *args, **kwargs): super().__init__(*args, **kwargs) # 为对象绑定clone方法来实现对象拷贝 cls.clone = lambda self, is_deep=True: \ copy.deepcopy(self) if is_deep else copy.copy(self)class Person(metaclass=PrototypeMeta): passp1 = Person()p2 = p1.clone() # 深拷贝p3 = p1.clone(is_deep=False) # 浅拷贝题目007:Python是如何实现内存管理的?点评:当面试官问到这个问题的时候,一个展示自己的机会就摆在面前了。你要先反问面试官:“你说的是官方的CPython解释器吗?”。这个反问可以展示出你了解过Python解释器的不同的实现版本,而且你也知道面试官想问的是CPython。当然,很多面试官对不同的Python解释器底层实现到底有什么差别也没有概念。所以,千万不要觉得面试官一定比你强,怀揣着这份自信可以让你更好的完成面试。Python提供了自动化的内存管理,也就是说内存空间的分配与释放都是由Python解释器在运行时自动进行的,自动管理内存功能极大的减轻程序员的工作负担,也能够帮助程序员在一定程度上解决内存泄露的问题。以CPython解释器为例,它的内存管理有三个关键点:引用计数、标记清理、分代收集。引用计数:对于CPython解释器来说,Python中的每一个对象其实就是PyObject结构体,它的内部有一个名为ob_refcnt 的引用计数器成员变量。程序在运行的过程中ob_refcnt的值会被更新并藉此来反映引用有多少个变量引用到该对象。当对象的引用计数值为0时,它的内存就会被释放掉。typedef struct _object { _PyObject_HEAD_EXTRA Py_ssize_t ob_refcnt; struct _typeobject *ob_type;} PyObject;以下情况会导致引用计数加1:对象被创建对象被引用对象作为参数传入到一个函数中对象作为元素存储到一个容器中以下情况会导致引用计数减1:用del语句显示删除对象引用对象引用被重新赋值其他对象一个对象离开它所在的作用域持有该对象的容器自身被销毁持有该对象的容器删除该对象可以通过sys模块的getrefcount函数来获得对象的引用计数。引用计数的内存管理方式在遇到循环引用的时候就会出现致命伤,因此需要其他的垃圾回收算法对其进行补充。标记清理:CPython使用了“标记-清理”(Mark and Sweep)算法解决容器类型可能产生的循环引用问题。该算法在垃圾回收时分为两个阶段:标记阶段,遍历所有的对象,如果对象是可达的(被其他对象引用),那么就标记该对象为可达;清除阶段,再次遍历对象,如果发现某个对象没有标记为可达,则就将其回收。CPython底层维护了两个双端链表,一个链表存放着需要被扫描的容器对象(姑且称之为链表A),另一个链表存放着临时不可达对象(姑且称之为链表B)。为了实现“标记-清理”算法,链表中的每个节点除了有记录当前引用计数的ref_count变量外,还有一个gc_ref变量,这个gc_ref是ref_count的一个副本,所以初始值为ref_count的大小。执行垃圾回收时,首先遍历链表A中的节点,并且将当前对象所引用的所有对象的gc_ref减1,这一步主要作用是解除循环引用对引用计数的影响。再次遍历链表A中的节点,如果节点的gc_ref值为0,那么这个对象就被标记为“暂时不可达”(GC_TENTATIVELY_UNREACHABLE)并被移动到链表B中;如果节点的gc_ref不为0,那么这个对象就会被标记为“可达“(GC_REACHABLE),对于”可达“对象,还要递归的将该节点可以到达的节点标记为”可达“;链表B中被标记为”可达“的节点要重新放回到链表A中。在两次遍历之后,链表B中的节点就是需要释放内存的节点。分代回收:在循环引用对象的回收中,整个应用程序会被暂停,为了减少应用程序暂停的时间,Python 通过分代回收(空间换时间)的方法提高垃圾回收效率。分代回收的基本思想是:对象存在的时间越长,是垃圾的可能性就越小,应该尽量不对这样的对象进行垃圾回收。CPython将对象分为三种世代分别记为0、1、2,每一个新生对象都在第0代中,如果该对象在一轮垃圾回收扫描中存活下来,那么它将被移到第1代中,存在于第1代的对象将较少的被垃圾回收扫描到;如果在对第1代进行垃圾回收扫描时,这个对象又存活下来,那么它将被移至第2代中,在那里它被垃圾回收扫描的次数将会更少。分代回收扫描的门限值可以通过gc模块的get_threshold函数来获得,该函数返回一个三元组,分别表示多少次内存分配操作后会执行0代垃圾回收,多少次0代垃圾回收后会执行1代垃圾回收,多少次1代垃圾回收后会执行2代垃圾回收。需要说明的是,如果执行一次2代垃圾回收,那么比它年轻的代都要执行垃圾回收。如果想修改这几个门限值,可以通过gc模块的set_threshold函数来做到。题目008:说一下你对Python中迭代器和生成器的理解。点评:很多人面试者都会写迭代器和生成器,但是却无法准确的解释什么是迭代器和生成器。如果你也有同样的困惑,可以参考下面的回答。迭代器是实现了迭代器协议的对象。跟其他编程语言不通,Python中没有用于定义协议或表示约定的关键字,像interface、protocol这些单词并不在Python语言的关键字列表中。Python语言通过魔法方法来表示约定,也就是我们所说的协议,而__next__和__iter__这两个魔法方法就代表了迭代器协议。可以通过for-in循环从迭代器对象中取出值,也可以使用next函数取出迭代器对象中的下一个值。生成器是迭代器的语法升级版本,可以用更为简单的代码来实现一个迭代器。扩展:面试中经常让写生成斐波那契数列的迭代器,大家可以参考下面的代码。class Fib(object): def __init__(self, num): self.num = num self.a, self.b = 0, 1 self.idx = 0 def __iter__(self): return self def __next__(self): if self.idx < self.num: self.a, self.b = self.b, self.a + self.b self.idx += 1 return self.a raise StopIteration()如果用生成器的语法来改写上面的代码,代码会简单优雅很多。def fib(num): a, b = 0, 1 for _ in range(num): a, b = b, a + b yield a题目009:正则表达式的match方法和search方法有什么区别?点评:正则表达式是字符串处理的重要工具,所以也是面试中经常考察的知识点。在Python中,使用正则表达式有两种方式,一种是直接调用re模块中的函数,传入正则表达式和需要处理的字符串;一种是先通过re模块的compile函数创建正则表达式对象,然后再通过对象调用方法并传入需要处理的字符串。如果一个正则表达式被频繁的使用,我们推荐用re.compile函数创建正则表达式对象,这样会减少频繁编译同一个正则表达式所造成的开销。match方法是从字符串的起始位置进行正则表达式匹配,返回Match对象或None。search方法会扫描整个字符串来找寻匹配的模式,同样也是返回Match对象或None。题目010:下面这段代码的执行结果是什么。def multiply(): return [lambda x: i * x for i in range(4)]print([m(100) for m in multiply()])运行结果:[300, 300, 300, 300]1上面代码的运行结果很容易被误判为[0, 100, 200, 300]。首先需要注意的是multiply函数用生成式语法返回了一个列表,列表中保存了4个Lambda函数,这4个Lambda函数会返回传入的参数乘以i的结果。需要注意的是这里有闭包(closure)现象,multiply函数中的局部变量i的生命周期被延展了,由于i最终的值是3,所以通过m(100)调列表中的Lambda函数时会返回300,而且4个调用都是如此。如果想得到[0, 100, 200, 300]这个结果,可以按照下面几种方式来修改multiply函数。方法一:使用生成器,让函数获得i的当前值。def multiply(): return (lambda x: i * x for i in range(4))print([m(100) for m in multiply()])或者def multiply(): for i in range(4): yield lambda x: x * iprint([m(100) for m in multiply()])方法二:使用偏函数,彻底避开闭包。from functools import partialfrom operator import __mul__def multiply(): return [partial(__mul__, i) for i in range(4)]print([m(100) for m in multiply()])题目011:Python中为什么没有函数重载?点评:C++、Java、C#等诸多编程语言都支持函数重载,所谓函数重载指的是在同一个作用域中有多个同名函数,它们拥有不同的参数列表(参数个数不同或参数类型不同或二者皆不同),可以相互区分。重载也是一种多态性,因为通常是在编译时通过参数的个数和类型来确定到底调用哪个重载函数,所以也被称为编译时多态性或者叫前绑定。这个问题的潜台词其实是问面试者是否有其他编程语言的经验,是否理解Python是动态类型语言,是否知道Python中函数的可变参数、关键字参数这些概念。首先Python是解释型语言,函数重载现象通常出现在编译型语言中。其次Python是动态类型语言,函数的参数没有类型约束,也就无法根据参数类型来区分重载。再者Python中函数的参数可以有默认值,可以使用可变参数和关键字参数,因此即便没有函数重载,也要可以让一个函数根据调用者传入的参数产生不同的行为。题目012:用Python代码实现Python内置函数max。点评:这个题目看似简单,但实际上还是比较考察面试者的功底。因为Python内置的max函数既可以传入可迭代对象找出最大,又可以传入两个或多个参数找出最大;最为关键的是还可以通过命名关键字参数key来指定一个用于元素比较的函数,还可以通过default命名关键字参数来指定当可迭代对象为空时返回的默认值。下面的代码仅供参考:def my_max(*args, key=None, default=None): """ 获取可迭代对象中最大的元素或两个及以上实参中最大的元素 :param args: 一个可迭代对象或多个元素 :param key: 提取用于元素比较的特征值的函数,默认为None :param default: 如果可迭代对象为空则返回该默认值,如果没有给默认值则引发ValueError异常 :return: 返回可迭代对象或多个元素中的最大元素 """ if len(args) == 1 and len(args[0]) == 0: if default: return default else: raise ValueError('max() arg is an empty sequence') items = args[0] if len(args) == 1 else args max_elem, max_value = items[0], items[0] if key: max_value = key(max_value) for item in items: value = item if key: value = key(item) if value > max_value: max_elem, max_value = item, value题目013:写一个函数统计传入的列表中每个数字出现的次数并返回对应的字典。点评:送人头的题目,不解释。def count_letters(items): result = {} for item in items: if isinstance(item, (int, float)): result[item] = result.get(item, 0) + 1 return result也可以直接使用Python标准库中collections模块的Counter类来解决这个问题,Counter是dict的子类,它会将传入的序列中的每个元素作为键,元素出现的次数作为值来构造字典。from collections import Counterdef count_letters(items): counter = Counter(items) return {key: value for key, value in counter.items() \ if isinstance(key, (int, float))}题目014:使用Python代码实现遍历一个文件夹的操作。点评:基本也是送人头的题目,只要用过os模块就应该知道怎么做。Python标准库os模块的walk函数提供了遍历一个文件夹的功能,它返回一个生成器。import osg = os.walk('/Users/Hao/Downloads/')for path, dir_list, file_list in g: for dir_name in dir_list: print(os.path.join(path, dir_name)) for file_name in file_list: print(os.path.join(path, file_name))说明:os.path模块提供了很多进行路径操作的工具函数,在项目开发中也是经常会用到的。如果题目明确要求不能使用os.walk函数,那么可以使用os.listdir函数来获取指定目录下的文件和文件夹,然后再通过循环遍历用os.isdir函数判断哪些是文件夹,对于文件夹可以通过递归调用进行遍历,这样也可以实现遍历一个文件夹的操作。题目015:现有2元、3元、5元共三种面额的货币,如果需要找零99元,一共有多少种找零的方式?点评:还有一个非常类似的题目:“一个小朋友走楼梯,一次可以走1个台阶、2个台阶或3个台阶,问走完10个台阶一共有多少种走法?”,这两个题目的思路是一样,如果用递归函数来写的话非常简单。from functools import lru_cache@lru_cache()def change_money(total): if total == 0: return 1 if total < 0: return 0 return change_money(total - 2) + change_money(total - 3) + \ change_money(total - 5)说明:在上面的代码中,我们用lru_cache装饰器装饰了递归函数change_money,如果不做这个优化,上面代码的渐近时间复杂度将会是O ( 3 N ) O(3^N)O(3 N ),而如果参数total的值是99,这个运算量是非常巨大的。lru_cache装饰器会缓存函数的执行结果,这样就可以减少重复运算所造成的开销,这是空间换时间的策略,也是动态规划的编程思想。题目016:写一个函数,给定矩阵的阶数n,输出一个螺旋式数字矩阵。例如:n = 2,返回:1 24 312例如:n = 3,返回:1 2 38 9 47 6 5这个题目本身并不复杂,下面的代码仅供参考。def show_spiral_matrix(n): matrix = [[0] * n for _ in range(n)] row, col = 0, 0 num, direction = 1, 0 while num <= n ** 2: if matrix[row][col] == 0: matrix[row][col] = num num += 1 if direction == 0: if col < n - 1 and matrix[row][col + 1] == 0: col += 1 else: direction += 1 elif direction == 1: if row < n - 1 and matrix[row + 1][col] == 0: row += 1 else: direction += 1 elif direction == 2: if col > 0 and matrix[row][col - 1] == 0: col -= 1 else: direction += 1 else: if row > 0 and matrix[row - 1][col] == 0: row -= 1 else: direction += 1 direction %= 4 for x in matrix: for y in x: print(y, end='\t') print()题目017:阅读下面的代码,写出程序的运行结果。items = [1, 2, 3, 4] print([i for i in items if i > 2])print([i for i in items if i % 2])print([(x, y) for x, y in zip('abcd', (1, 2, 3, 4, 5))])print({x: f'item{x ** 2}' for x in (2, 4, 6)})print(len({x for x in 'hello world' if x not in 'abcdefg'}))点评:生成式(推导式)属于Python的特色语法之一,几乎是面试必考内容。Python中通过生成式字面量语法,可以创建出列表、集合、字典。[3, 4][1, 3][('a', 1), ('b', 2), ('c', 3), ('d', 4)]{2: 'item4', 4: 'item16', 6: 'item36'}6题目018:说出下面代码的运行结果。class Parent: x = 1class Child1(Parent): passclass Child2(Parent): passprint(Parent.x, Child1.x, Child2.x)Child1.x = 2print(Parent.x, Child1.x, Child2.x)Parent.x = 3print(Parent.x, Child1.x, Child2.x)点评:运行上面的代码首先输出1 1 1,这一点大家应该没有什么疑问。接下来,通过Child1.x = 2给类Child1重新绑定了属性x并赋值为2,所以Child1.x会输出2,而Parent和Child2并不受影响。执行Parent.x = 3会重新给Parent类的x属性赋值为3,由于Child2的x属性继承自Parent,所以Child2.x的值也是3;而之前我们为Child1重新绑定了x属性,那么它的x属性值不会受到Parent.x = 3的影响,还是之前的值2。1 1 11 2 13 2 3123题目19:说说你用过Python标准库中的哪些模块。点评:Python标准库中的模块非常多,建议大家根据自己过往的项目经历来介绍你用过的标准库和三方库,因为这些是你最为熟悉的,经得起面试官深挖的。模块名 介绍sys 跟Python解释器相关的变量和函数,例如:sys.version、sys.exit()os 和操作系统相关的功能,例如:os.listdir()、os.remove()re 和正则表达式相关的功能,例如:re.compile()、re.search()math 和数学运算相关的功能,例如:math.pi、math.e、math.coslogging 和日志系统相关的类和函数,例如:logging.Logger、logging.Handlerjson / pickle 实现对象序列化和反序列的模块,例如:json.loads、json.dumpshashlib 封装了多种哈希摘要算法的模块,例如:hashlib.md5、hashlib.sha1urllib 包含了和URL相关的子模块,例如:urllib.request、urllib.parseitertools 提供各种迭代器的模块,例如:itertools.cycle、itertools.productfunctools 函数相关工具模块,例如:functools.partial、functools.lru_cachecollections / heapq 封装了常用数据结构和算法的模块,例如:collections.dequethreading / multiprocessing 多线程/多进程相关类和函数的模块,例如:threading.Threadconcurrent.futures / asyncio 并发编程/异步编程相关的类和函数的模块,例如:ThreadPoolExecutorbase64 提供BASE-64编码相关函数的模块,例如:bas64.encodecsv 和读写CSV文件相关的模块,例如:csv.reader、csv.writerprofile / cProfile / pstats 和代码性能剖析相关的模块,例如:cProfile.run、pstats.Statsunittest 和单元测试相关的模块,例如:unittest.TestCase题目20:__init__和__new__方法有什么区别?Python中调用构造器创建对象属于两阶段构造过程,首先执行__new__方法获得保存对象所需的内存空间,再通过__init__执行对内存空间数据的填充(对象属性的初始化)。__new__方法的返回值是创建好的Python对象(的引用),而__init__方法的第一个参数就是这个对象(的引用),所以在__init__中可以完成对对象的初始化操作。__new__是类方法,它的第一个参数是类,__init__是对象方法,它的第一个参数是对象。题目21:输入年月日,判断这个日期是这一年的第几天。方法一:不使用标准库中的模块和函数。def is_leap_year(year): """判断指定的年份是不是闰年,平年返回False,闰年返回True""" return year % 4 == 0 and year % 100 != 0 or year % 400 == 0def which_day(year, month, date): """计算传入的日期是这一年的第几天""" # 用嵌套的列表保存平年和闰年每个月的天数 days_of_month = [ [31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31], [31, 29, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31] ] days = days_of_month[is_leap_year(year)][:month - 1] return sum(days) + date方法二:使用标准库中的datetime模块。import datetimedef which_day(year, month, date): end = datetime.date(year, month, date) start = datetime.date(year, 1, 1) return (end - start).days + 1题目22:平常工作中用什么工具进行静态代码分析。点评:静态代码分析工具可以从代码中提炼出各种静态属性,这使得开发者可以对代码的复杂性、可维护性和可读性有更好的了解,这里所说的静态属性包括:代码是否符合编码规范,例如:PEP-8。代码中潜在的问题,包括:语法错误、缩进问题、导入缺失、变量覆盖等。代码中的坏味道。代码的复杂度。代码的逻辑问题。工作中静态代码分析主要用到的是Pylint和Flake8。Pylint可以检查出代码错误、坏味道、不规范的代码等问题,较新的版本中还提供了代码复杂度统计数据,可以生成检查报告。Flake8封装了Pyflakes(检查代码逻辑错误)、McCabe(检查代码复杂性)和Pycodestyle(检查代码是否符合PEP-8规范)工具,它可以执行这三个工具提供的检查。题目23:说一下你知道的Python中的魔术方法。点评:魔术方法也称为魔法方法,是Python中的特色语法,也是面试中的高频问题。魔术方法 作用__new__、__init__、__del__ 创建和销毁对象相关__add__、__sub__、__mul__、__div__、__floordiv__、__mod__ 算术运算符相关__eq__、__ne__、__lt__、__gt__、__le__、__ge__ 关系运算符相关__pos__、__neg__、__invert__ 一元运算符相关__lshift__、__rshift__、__and__、__or__、__xor__ 位运算相关__enter__、__exit__ 上下文管理器协议__iter__、__next__、__reversed__ 迭代器协议__int__、__long__、__float__、__oct__、__hex__ 类型/进制转换相关__str__、__repr__、__hash__、__dir__ 对象表述相关__len__、__getitem__、__setitem__、__contains__、__missing__ 序列相关__copy__、__deepcopy__ 对象拷贝相关__call__、__setattr__、__getattr__、__delattr__ 其他魔术方法题目24:函数参数*arg和**kwargs分别代表什么?Python中,函数的参数分为位置参数、可变参数、关键字参数、命名关键字参数。*args代表可变参数,可以接收0个或任意多个参数,当不确定调用者会传入多少个位置参数时,就可以使用可变参数,它会将传入的参数打包成一个元组。**kwargs代表关键字参数,可以接收用参数名=参数值的方式传入的参数,传入的参数的会打包成一个字典。定义函数时如果同时使用*args和**kwargs,那么函数可以接收任意参数。题目25:写一个记录函数执行时间的装饰器。点评:高频面试题,也是最简单的装饰器,面试者必须要掌握的内容。方法一:用函数实现装饰器。from functools import wrapsfrom time import timedef record_time(func): @wraps(func) def wrapper(*args, **kwargs): start = time() result = func(*args, **kwargs) print(f'{func.__name__}执行时间: {time() - start}秒') return result return wrapper方法二:用类实现装饰器。类有__call__魔术方法,该类对象就是可调用对象,可以当做装饰器来使用。from functools import wrapsfrom time import timeclass Record: def __call__(self, func): @wraps(func) def wrapper(*args, **kwargs): start = time() result = func(*args, **kwargs) print(f'{func.__name__}执行时间: {time() - start}秒') return result return wrapper说明:装饰器可以用来装饰类或函数,为其提供额外的能力,属于设计模式中的代理模式。扩展:装饰器本身也可以参数化,例如上面的例子中,如果不希望在终端中显示函数的执行时间而是希望由调用者来决定如何输出函数的执行时间,可以通过参数化装饰器的方式来做到,代码如下所示。from functools import wrapsfrom time import timedef record_time(output): """可以参数化的装饰器"""def decorate(func):@wraps(func)def wrapper(*args, **kwargs):start = time()result = func(*args, **kwargs)output(func.__name__, time() - start)return result return wrapperreturn decorate题目26:什么是鸭子类型(duck typing)?鸭子类型是动态类型语言判断一个对象是不是某种类型时使用的方法,也叫做鸭子判定法。简单的说,鸭子类型是指判断一只鸟是不是鸭子,我们只关心它游泳像不像鸭子、叫起来像不像鸭子、走路像不像鸭子就足够了。换言之,如果对象的行为跟我们的预期是一致的(能够接受某些消息),我们就认定它是某种类型的对象。在Python语言中,有很多bytes-like对象(如:bytes、bytearray、array.array、memoryview)、file-like对象(如:StringIO、BytesIO、GzipFile、socket)、path-like对象(如:str、bytes),其中file-like对象都能支持read和write操作,可以像文件一样读写,这就是所谓的对象有鸭子的行为就可以判定为鸭子的判定方法。再比如Python中列表的extend方法,它需要的参数并不一定要是列表,只要是可迭代对象就没有问题。说明:动态语言的鸭子类型使得设计模式的应用被大大简化。————————————————版权声明:本文为CSDN博主「五包辣条!」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。原文链接:https://blog.csdn.net/AI19970205/article/details/125182872
-
本次实验我们将是循环渐进的过程一步步的实现如何从一个单链表的节点类一步步的延申到增删改查等操作上编写程序实现一下内容:创建一个单链表节点类,至少包括一个data和next两个域;创建一个单链表类;创建单链表判空方法;创建一个单链表对象SL,判定是否为空单链表,并输出结果。程序代码:class Node(object):#结点类 def __init__(self,elem):#设置构造方法赋值 self.elem=elem self.next=None #不知道地址所以设置为空class tz(object): '''单链表''' def __init__(self): self.__head=Nonedef is_empty(self): return self.__head==Noneif __name__=="__main__": SL=tz() print(SL.is_empty())我们在这里class一个node节点类 设置一个构造方法并指向本身的元素 因为单链表刚开始头节点为空我们这里设置程self.node=none程序输出:true尝试了一下省略“class Node(object):”可以正常运行但是不用object会简洁在1基础上,创建单链表头插方法,向SL中连续插入data的值分别为4,3,2,1的4个节点;创建求链表长度方法;求插入节点后的SL的长度,并输出结果。程序代码:def toubu(self,item): node=Node(item) node.next=self.__head self.__head=nodedef changdu(self): cur =self.__head count=0 while cur!=None: count+=1 cur=cur.next return count程序输入:SL.toubu(4)SL.toubu(3)SL.toubu(2)SL.toubu(1)print(SL.changdu())程序输出:True 创建def头部的构造方法和changdu的构造方法 并让其循环加入数字来判断指针是否为空的条件最后返回count在程序输出结果即可4在2基础上,创建单链表遍历方法,输出SL中的值,中间用逗号分隔。程序代码:def bianli(self): a=self.__head while a: print(a.item,end=' ') a=a.next print('') 程序输入:SL.bianli() 程序输出:1234,在2基础上,创建单链表尾插法,向SL中连续插入data的值分别为5,7,9,11,13的5个节点;插入后输出SL中的值,中间用空格分隔,并输出表长。程序代码:def bianli(self): a=self.__head while a: print(a.item,end=' ') a=a.next print('')def weibu(self,item): node=Node(item) if self.is_empty(): self.__head=node else: b=self.__head while b.next!=None: b=b.next b.next=node 程序输入:SL.weibu(5)SL.weibu(7)SL.weibu(9)SL.weibu(11)SL.weibu(13)print(SL.changdu()) 程序输出:1 2 3 4 5 7 9 11 13尾插法:我们首先创建个构造方法遍历整个的元素 并让指针永远指向下一个节点 类似于头插法的方式创建尾插并输入信息 最后print输出即可9创建在任意位置插入的节点的方法,实现在data值为5的节点之后插入一个data值为6的节点,插入后输出SL中的值,中间用空格分隔,并输出表长。程序代码:def renyi(self,pos,item): if pos<=0: self.toubu(item) elif pos>(self.changdu()-1): self.weibu(item) else: per=self.__head count=0 while count<pos-1: count+=1 per=per.next node=Node(item) node.next=per.next per.next=node程序输入:SL.renyi(5,6)SL.bianli()print(SL.changdu()) 程序输出:1 2 3 4 5 6 7 9 11 1310创建单链表中删除指定元素的方法,实现在SL中data值为1、13和6的节点,每删除一个节点,进行一次SL中值的输出,中间用空格分隔,并输出表长。程序代码:def shanchu(self,item): cur=self.__head pre=None while cur!=None: if cur.item==item: if cur==self.__head: self.__head=cur.next else: pre.next=cur.next break else: pre=cur cur=cur.next程序输入:SL.shanchu(1)SL.bianli()SL.shanchu(13)SL.bianli()SL.shanchu(6)SL.bianli()print(SL.changdu())程序输出:2 3 4 5 6 7 9 11 132 3 4 5 6 7 9 112 3 4 5 7 9 117创建单链表查找指定元素的方法,实现在SL中查找data值为9和data值为15的节点,并输出查找的结果(是否存在?),如果表SL中存在,一并输出节点在单链表中的位置(设第一个节点的位置为1)。程序代码:def weizhi(self,item): tt=self.__head i=1 while tt != None: if tt.item != item: i=i+1 tt=tt.next else: return i def chazhao(self,item): cur = self.__head while cur != None: if cur.item == item: print("Ture,该节点在链表中的位置为:",end='') return self.weizhi(item) else: cur = cur.next return False程序输入:print(SL.chazhao(9))print(SL.chazhao(15))程序输出:Ture,该节点在链表中的位置为:6False创建单链表查找指定位置元素的方法,实现在SL中查找第5个元素,并将找到的元素输出。程序代码:def tzzs(self,item): cur= self.__head i=1 while cur!= None: if i!=item: i+=1 cur=cur.next else: return cur.item程序输入:print(SL.tzzs(5))程序输出:7在单链表递增有序的基础上,创建将元素x插入,保持其有序性方法;实现在SL中插入data值为8的元素,使SL仍然有序,输出SL的值。程序代码:def xiaozhen(self,item): node=Node(item) if self.is_empty(): self.__head=node else: cur=self.__head i=0 while cur!=None: if cur.item<item: i+=1 cur=cur.next else: return self.renyi(i,item)程序输入:SL.xiaozhen(8)SL.bianli()程序输出:234578911针对单链表创建删除自第i个元素起连续k个元素方法;实现在SL中从第2个元素开始的连续3个元素,输出SL的值。程序代码: def duang(self,pos,item): j=0 while j<item: self.shanchu(self.tzzs(pos)) j+=1SL.duang(2,3)SL.bianli()程序输出:278911本次实验的全部代码:class Node: def __init__(self,item): self.item=item self.next=Noneclass tz: def __init__(self): self.__head=None def is_empty(self): return self.__head==None def toubu(self,item): node=Node(item) node.next=self.__head self.__head=node def changdu(self): cur =self.__head count=0 while cur!=None: count+=1 cur=cur.next return count def bianli(self): a=self.__head while a: print(a.item,end=' ') a=a.next print('') def weibu(self,item): node=Node(item) if self.is_empty(): self.__head=node else: b=self.__head while b.next!=None: b=b.next b.next=node def renyi(self,pos,item): if pos<=0: self.toubu(item) elif pos>self.changdu()-1: self.weibu(item) else: per=self.__head count=0 while count<pos-1: count+=1 per=per.next node=Node(item) node.next=per.next per.next=node def shanchu(self,item): cur=self.__head pre=None while cur!=None: if cur.item==item: if cur==self.__head: self.__head=cur.next else: pre.next=cur.next break else: pre=cur cur=cur.next def weizhi(self,item): tt=self.__head i=1 while tt != None: if tt.item != item: i=i+1 tt=tt.next else: return i def chazhao(self,item): cur = self.__head while cur != None: if cur.item == item: print("Ture,该节点在链表中的位置为:",end='') return self.weizhi(item) else: cur = cur.next return False def tzzs(self,item): cur= self.__head i=1 while cur!= None: if i!=item: i+=1 cur=cur.next else: return cur.item def xiaozhen(self,item): node=Node(item) if self.is_empty(): self.__head=node else: cur=self.__head i=0 while cur!=None: if cur.item<item: i+=1 cur=cur.next else: return self.renyi(i,item) def duang(self,pos,item): j=0 while j<item: self.shanchu(self.tzzs(pos)) j+=1if __name__=="__main__": SL=tz() print(SL.is_empty()) SL.toubu(4) SL.toubu(3) SL.toubu(2) SL.toubu(1) print(SL.changdu()) SL.weibu(5) SL.weibu(7) SL.weibu(9) SL.weibu(11) SL.weibu(13) SL.bianli() print(SL.changdu()) SL.renyi(5,6) SL.bianli() print(SL.changdu()) SL.shanchu(1) SL.bianli() SL.shanchu(13) SL.bianli() SL.shanchu(6) SL.bianli() print(SL.changdu()) print(SL.chazhao(9)) print(SL.chazhao(15)) print(SL.tzzs(5)) SL.xiaozhen(8) SL.bianli() SL.duang(2,3) SL.bianli()输出结果:True41 2 3 4 5 7 9 11 1391 2 3 4 5 6 7 9 11 13102 3 4 5 6 7 9 11 132 3 4 5 6 7 9 112 3 4 5 7 9 117Ture,该节点在链表中的位置为:6False72 3 4 5 7 8 9 112 7 8 9 11 线性表和顺序链表的关系 灵活运用了线性表中的概念包括头插法尾插法和游标删除等还要考虑在一个线性表中的特殊情况例如在insert插入时的cur.next和cur.next+1之间的关系及 包括 索引从0开始的pos在remove中判断当前节点是不是头节点及怎么判断头节点的方法 if cur.next == self.__head等 在查找的def中查找节点是否存在就要对应一个特殊情况 链表是否为空链表 查找链表是否存在及遍历链表比对数据 查看节点是否存在 并理解了什么叫后继节点在开始设置构造方法进行赋值 如果不知道一个地址则可以设置成功 用cur游标移动遍历元素 遍历每个节点所以 count=0 用count来记录数量
-
本次实验我们将是循环渐进的过程一步步的实现如何从一个单链表的节点类一步步的延申到增删改查等操作上编写程序实现一下内容:创建一个单链表节点类,至少包括一个data和next两个域;创建一个单链表类;创建单链表判空方法;创建一个单链表对象SL,判定是否为空单链表,并输出结果。程序代码:class Node(object):#结点类 def __init__(self,elem):#设置构造方法赋值 self.elem=elem self.next=None #不知道地址所以设置为空class tz(object): '''单链表''' def __init__(self): self.__head=Nonedef is_empty(self): return self.__head==Noneif __name__=="__main__": SL=tz() print(SL.is_empty())我们在这里class一个nide节点类 设置一个构造方法并指向本身的元素 因为单链表刚开始头节点为空我们这里设置程self.node=none程序输出:true尝试了一下省略“class Node(object):”可以正常运行但是不用object会简洁在1基础上,创建单链表头插方法,向SL中连续插入data的值分别为4,3,2,1的4个节点;创建求链表长度方法;求插入节点后的SL的长度,并输出结果。程序代码:def toubu(self,item): node=Node(item) node.next=self.__head self.__head=nodedef changdu(self): cur =self.__head count=0 while cur!=None: count+=1 cur=cur.next return count程序输入:SL.toubu(4)SL.toubu(3)SL.toubu(2)SL.toubu(1)print(SL.changdu())程序输出:True 创建def头部的构造方法和changdu的构造方法 并让其循环加入数字来判断指针是否为空的条件最后返回count在程序输出结果即可4在2基础上,创建单链表遍历方法,输出SL中的值,中间用逗号分隔。程序代码:def bianli(self): a=self.__head while a: print(a.item,end=' ') a=a.next print('') 程序输入:SL.bianli() 程序输出:1234,在2基础上,创建单链表尾插法,向SL中连续插入data的值分别为5,7,9,11,13的5个节点;插入后输出SL中的值,中间用空格分隔,并输出表长。程序代码:def bianli(self): a=self.__head while a: print(a.item,end=' ') a=a.next print('')def weibu(self,item): node=Node(item) if self.is_empty(): self.__head=node else: b=self.__head while b.next!=None: b=b.next b.next=node 程序输入:SL.weibu(5)SL.weibu(7)SL.weibu(9)SL.weibu(11)SL.weibu(13)print(SL.changdu()) 程序输出:1 2 3 4 5 7 9 11 13尾插法:我们首先创建个构造方法遍历整个的元素 并让指针永远指向下一个节点 类似于头插法的方式创建尾插并输入信息 最后print输出即可9创建在任意位置插入的节点的方法,实现在data值为5的节点之后插入一个data值为6的节点,插入后输出SL中的值,中间用空格分隔,并输出表长。程序代码:def renyi(self,pos,item): if pos<=0: self.toubu(item) elif pos>(self.changdu()-1): self.weibu(item) else: per=self.__head count=0 while count<pos-1: count+=1 per=per.next node=Node(item) node.next=per.next per.next=node程序输入:SL.renyi(5,6)SL.bianli()print(SL.changdu()) 程序输出:1 2 3 4 5 6 7 9 11 1310创建单链表中删除指定元素的方法,实现在SL中data值为1、13和6的节点,每删除一个节点,进行一次SL中值的输出,中间用空格分隔,并输出表长。程序代码:def shanchu(self,item): cur=self.__head pre=None while cur!=None: if cur.item==item: if cur==self.__head: self.__head=cur.next else: pre.next=cur.next break else: pre=cur cur=cur.next程序输入:SL.shanchu(1)SL.bianli()SL.shanchu(13)SL.bianli()SL.shanchu(6)SL.bianli()print(SL.changdu())程序输出:2 3 4 5 6 7 9 11 132 3 4 5 6 7 9 112 3 4 5 7 9 117创建单链表查找指定元素的方法,实现在SL中查找data值为9和data值为15的节点,并输出查找的结果(是否存在?),如果表SL中存在,一并输出节点在单链表中的位置(设第一个节点的位置为1)。程序代码:def weizhi(self,item): tt=self.__head i=1 while tt != None: if tt.item != item: i=i+1 tt=tt.next else: return i def chazhao(self,item): cur = self.__head while cur != None: if cur.item == item: print("Ture,该节点在链表中的位置为:",end='') return self.weizhi(item) else: cur = cur.next return False程序输入:print(SL.chazhao(9))print(SL.chazhao(15))程序输出:Ture,该节点在链表中的位置为:6False创建单链表查找指定位置元素的方法,实现在SL中查找第5个元素,并将找到的元素输出。程序代码:def tzzs(self,item): cur= self.__head i=1 while cur!= None: if i!=item: i+=1 cur=cur.next else: return cur.item程序输入:print(SL.tzzs(5))程序输出:7在单链表递增有序的基础上,创建将元素x插入,保持其有序性方法;实现在SL中插入data值为8的元素,使SL仍然有序,输出SL的值。程序代码:def xiaozhen(self,item): node=Node(item) if self.is_empty(): self.__head=node else: cur=self.__head i=0 while cur!=None: if cur.item<item: i+=1 cur=cur.next else: return self.renyi(i,item)程序输入:SL.xiaozhen(8)SL.bianli()程序输出:234578911针对单链表创建删除自第i个元素起连续k个元素方法;实现在SL中从第2个元素开始的连续3个元素,输出SL的值。程序代码: def duang(self,pos,item): j=0 while j<item: self.shanchu(self.tzzs(pos)) j+=1SL.duang(2,3)SL.bianli()程序输出:278911本次实验的全部代码:class Node: def __init__(self,item): self.item=item self.next=Noneclass tz: def __init__(self): self.__head=None def is_empty(self): return self.__head==None def toubu(self,item): node=Node(item) node.next=self.__head self.__head=node def changdu(self): cur =self.__head count=0 while cur!=None: count+=1 cur=cur.next return count def bianli(self): a=self.__head while a: print(a.item,end=' ') a=a.next print('') def weibu(self,item): node=Node(item) if self.is_empty(): self.__head=node else: b=self.__head while b.next!=None: b=b.next b.next=node def renyi(self,pos,item): if pos<=0: self.toubu(item) elif pos>self.changdu()-1: self.weibu(item) else: per=self.__head count=0 while count<pos-1: count+=1 per=per.next node=Node(item) node.next=per.next per.next=node def shanchu(self,item): cur=self.__head pre=None while cur!=None: if cur.item==item: if cur==self.__head: self.__head=cur.next else: pre.next=cur.next break else: pre=cur cur=cur.next def weizhi(self,item): tt=self.__head i=1 while tt != None: if tt.item != item: i=i+1 tt=tt.next else: return i def chazhao(self,item): cur = self.__head while cur != None: if cur.item == item: print("Ture,该节点在链表中的位置为:",end='') return self.weizhi(item) else: cur = cur.next return False def tzzs(self,item): cur= self.__head i=1 while cur!= None: if i!=item: i+=1 cur=cur.next else: return cur.item def xiaozhen(self,item): node=Node(item) if self.is_empty(): self.__head=node else: cur=self.__head i=0 while cur!=None: if cur.item<item: i+=1 cur=cur.next else: return self.renyi(i,item) def duang(self,pos,item): j=0 while j<item: self.shanchu(self.tzzs(pos)) j+=1if __name__=="__main__": SL=tz() print(SL.is_empty()) SL.toubu(4) SL.toubu(3) SL.toubu(2) SL.toubu(1) print(SL.changdu()) SL.weibu(5) SL.weibu(7) SL.weibu(9) SL.weibu(11) SL.weibu(13) SL.bianli() print(SL.changdu()) SL.renyi(5,6) SL.bianli() print(SL.changdu()) SL.shanchu(1) SL.bianli() SL.shanchu(13) SL.bianli() SL.shanchu(6) SL.bianli() print(SL.changdu()) print(SL.chazhao(9)) print(SL.chazhao(15)) print(SL.tzzs(5)) SL.xiaozhen(8) SL.bianli() SL.duang(2,3) SL.bianli()输出结果:True41 2 3 4 5 7 9 11 1391 2 3 4 5 6 7 9 11 13102 3 4 5 6 7 9 11 132 3 4 5 6 7 9 112 3 4 5 7 9 117Ture,该节点在链表中的位置为:6False72 3 4 5 7 8 9 112 7 8 9 11 线性表和顺序链表的关系 灵活运用了线性表中的概念包括头插法尾插法和游标删除等还要考虑在一个线性表中的特殊情况例如在insert插入时的cur.next和cur.next+1之间的关系及 包括 索引从0开始的pos在remove中判断当前节点是不是头节点及怎么判断头节点的方法 if cur.next == self.__head等 在查找的def中查找节点是否存在就要对应一个特殊情况 链表是否为空链表 查找链表是否存在及遍历链表比对数据 查看节点是否存在 并理解了什么叫后继节点在开始设置构造方法进行赋值 如果不知道一个地址则可以设置成功 用cur游标移动遍历元素 遍历每个节点所以 count=0 用count来记录数量
-
ndarray 的数据类型数据类型,即 dtype ,也是一个特殊的对象, 它包含了ndarray需要为某一种类型数据所申明的内存块信息(也成为了元数据,即表示数据的数据)dtype是NumPy能够与琪他系统数据灵活交互的原因。通常,其他系统提供一个硬盘或内存与数据的对应关系,使得利用C或Fortran等底层语言读写数据变得十分方便。名称描述bool_布尔型数据类型(True 或者 False)int_默认的整数类型(类似于 C 语言中的 long,int32 或 int64)intc与 C 的 int 类型一样,一般是 int32 或 int 64intp用于索引的整数类型(类似于 C 的 ssize_t,一般情况下仍然是 int32 或 int64)int8字节(-128 to 127)int16整数(-32768 to 32767)int32整数(-2147483648 to 2147483647)int64整数(-9223372036854775808 to 9223372036854775807)uint8无符号整数(0 to 255)uint16无符号整数(0 to 65535)uint32无符号整数(0 to 4294967295)uint64无符号整数(0 to 18446744073709551615)float_float64 类型的简写float16半精度浮点数,包括:1 个符号位,5 个指数位,10 个尾数位float32单精度浮点数,包括:1 个符号位,8 个指数位,23 个尾数位float64双精度浮点数,包括:1 个符号位,11 个指数位,52 个尾数位complex_complex128 类型的简写,即 128 位复数complex64复数,表示双 32 位浮点数(实数部分和虚数部分)complex128复数,表示双 64 位浮点数(实数部分和虚数部分)使用astype方法来显式的转换数组的数据类型123456arr = np.array([1,2,3,4,5])print(arr.dtype)print(arr)float_arr = arr.astype('float32')#也可以写作 arr.astype(np.float32)print(float_arr.dtype)print(float_arr)int32 [1 2 3 4 5] float32 [1. 2. 3. 4. 5.]注意:将内容为数字的字符串数组转为数字是可以的,当内容是浮点型数字的时候只能转成 float,不能 int,只有是整数的时候才可以转成int用其他数组的dtype来转换数据类型:123456int_arr = np.arange(10)calibers = np.array([.22, .270, .357], dtype=np.float64)print(calibers)arr_last = int_arr.astype(calibers.dtype)print(arr_last.dtype)print(arr_last)[0.22 0.27 0.357] float64 [0. 1. 2. 3. 4. 5. 6. 7. 8. 9.]补充:Python3:numpy的简单使用(ndarray的基本属性以及基本生成数组的方法)声明由于本人学习需要,所以开始学习numpy,这个科学计算工具,本文用于复习当前所学习的内容(当前使用numpy的版本为:1.17.4)1.ndarray的基本的属性2.生成数组的方法(主要测试生成0和生成1的方法:ones和zeros方法)1. 输出当前ndarray的基本属性1234567891011121314151617181920212223# 测试当前Numpy中的narray中的属性# 使用的numpy的版本为:1.17.4import numpy as np default_array = [[1, 2, 3, 4, 5, 6], [1, 2, 3, 4, 5, 6]]np_array = np.array(default_array)print("当前存储后的数据的dtype类型为:{}".format(np_array.dtype)) # int32print("查看这个对象的实际类型:{}".format(type(np_array))) #print("查看这个对象的形状:{}".format(np_array.shape)) # (2,6)print("当前这个对象的字节长度:{}".format(np_array.itemsize)) # 4print("当前这个对象的长度(使用python的len方法):{}".format(len(np_array))) # 2 只迭代了一组数据外层的二维数据print("当前这个对象的长度(使用自己的size方法):{}".format(np_array.size)) # 获取了所有的数据的数量 print(np.array([[1, 2, 3], [1, 2, 3]]).dtype)print(np.array([1.2, 2.2, 3.2]).dtype) # 可以看出当前默认使用的类型为int32# 默认使用的浮点类型为:float64 # 修改和设定当前的使用的初始化类型# print(np.array([[1.1,1.2,1.3]],dtype="int32").dtype)print(np.array([[1.1,1.2,1.3]],dtype=np.int32).dtype)结果:总结:1.创建了二维数据的时候使用原生的python的len方法获取的长度是外层的长度,并不是二维数组实际内容的长度!2.通过np.array(数组)将原来的python中的数组转换为ndarray类型的数据3.每一个ndarray中都会有一个数据类型使用dtype表示,默认使用的整数类型为int32,浮点类型为float644.通过ndarray.size获取当前ndarray中的元素的个数5.通过ndarray.shap获取当前的ndarray的形状6.使用np.array()创建ndarray的时候可以指定当前的ndarray的dtype,其形式可以是字符也可以是np.类型2.使用numpy生成简单的数组(np.zeros(),np.ones(),np.empty(),np.array())123456789101112131415161718192021# 使用numpy中的生成的数组数据的方法import numpy as np # 生成1的操作np_array = np.zeros([2, 2])print("当前生成的数据为:{}".format(np_array))print("输出当前生成的数据的类型为:{}".format(np_array.dtype)) # 说明当前默认产生的数据数据的类型为float64# 现在改变当前的dtype,直接将当前的dtype的数据类型设置为int32np_array.dtype = np.int32print("当前生成的数据为:{}".format(np_array))print("输出当前生成的数据的类型为:{}".format(np_array.dtype)) # 生成1的数据np_array_ones = np.ones([2, 2], dtype=np.int32)print(np_array_ones) # 创建一个未初始化的数据,默认未初始化x = np.empty([3, 2], dtype=int)print(x)结果:总结:1.使用当前的np.zeros(shape)和np.ones(shape)方法生成全是0或者全是1的指定形状的数组2.通过np.empty(shape)生成空的数组3.可以通过ndarray.dtype=dtype方式改变当前的ndarray的类型3.使用生成数组方式二(np.asarray(),np.copy())123456789101112# 从已有的数组中创建数据import numpy as np default_array = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]default_tuple = (1, 2, 3, 4, 5, 6, 7, 8, 9, 10)print(type(default_tuple))copy_array = np.asarray(default_array) # 用于浅拷贝copy_tuple = np.asarray(default_tuple)print("asarray数组后的数据:{}".format(copy_array))print("asarray元组后的数据:{}".format(copy_tuple))deep_copy_array = np.copy(default_array)print("copy数组后的数据:{}".format(deep_copy_array))总结:1.这里使用np.asarray()方法生成的数组与原来的数组有关联,是浅拷贝2.这里的np.copy()方法生成的另外一份备份数据,是深拷贝4.生成指定范围的数组(np.range(),np.random.random(),np.random.randint(),np.linspace())123456789101112131415# 通过固定范围生成数组,使用arange方式生成0 到 9之间的数据,默认生成的数据为当前的为范围的值,这里的步长默认就是1,结果中不包含10,这里是按照指定的步长进行迭代range_array = np.arange(0, 10, dtype=np.int32)print("range_array:{}".format(range_array)) # 通过随机方式生成数组random_array = np.random.random((2, 2))print("使用随机方式生成数组:{}".format(random_array)) # 默认生成的数据为0到1之间的数据 # 2 生成随机的整数random_array_int = np.random.randint(1, 10, (2, 2))print("生成随机整数:{}".format(random_array_int)) # 在指定范围中生成15 个 1到 10之间的数,这是一个随机的数据,是等距离的,当要求的数据超过当前的范围的数据的时候默认就会随机生成一些数据listspace_array = np.linspace(1, 10, 15, dtype=np.int32) # 就是按照一定的等分进行划分为指定个需要的数据,这里的结果中包含10,相当于当前的等差数列一样print("listspace_array:{}".format(listspace_array))结果:总结:1.当前的random方法就是随机生成指定区间的数据,可以指定类型2.range就是相当于当前的python中的range方法,可以指定步长,是一个[a,b)这中数据3.linspace用于在指定的范围中按照指定的方式生成数组,这个是等差数列,如果当前需要的数据大于这个范围就会出现随机生成的情况5.生成等比数列(np.logspace())123# 生成一个等比的数列,这里面的2 表示生成的样本的个数为2 ,起始数据为1,结束数据为4,表示最小为3的1次方到当前的3的4次方equal_ratio_array = np.logspace(1, 4, 2, dtype=np.int32) # 这里的默认的底数为10 表示当前最小为10的一次方,最大为当前的10的4次方print("当前的等比数列的数据为:{}".format(equal_ratio_array))当前的等比数列的数据为:[ 10 10000]总结1.这个等比具有默认的底数为10,第一个表示10的1次方,第二个为生成数的最大次方为10的4次方,生成的数据2表示当前生成的等比数组的长度为22.可以设定当前的底数值,可以指定当前的类型6.总结1.当前的numpy这个模块可以实现创建当前的数组,可以生成指定类型和指定形状的数组2.通过numpy可以模拟需要的数据,产生数的方式很快!
-
[:]和[::]的区别蛮大的,用的好可以节省时间,下面以实例进行分析array([:])12345678910111213>>> import numpy as np>>>>>> x=np.array([1,2,3,4,5,6,7,8,9,10,11,12])>>> print(x[1:5])#打印index为1~5的数组,范围是左闭右开[2 3 4 5]>>> print(x[3:])#打印index=3之后的数组,包含index=3[ 4 5 6 7 8 9 10 11 12]>>> print(x[:9])#打印index=9之前的数组,不包含index=9[1 2 3 4 5 6 7 8 9]>>> print(x[1:-2])#打印index=1到倒数第2个index之间的数组[ 2 3 4 5 6 7 8 9 10]>>> print(x[-9:-2])#打印倒数第9个index和倒数第2个index之间的数组,左开右闭[ 4 5 6 7 8 9 10]array([::])123456789101112>>> print(x[1::3])#以index=1为起始位置,间隔3[ 2 5 8 11]>>> print(x[::3])#默认从index=0开始,间隔3[ 1 4 7 10]>>> print(x[3::])#和[3:]一样[ 4 5 6 7 8 9 10 11 12]>>> print(x[::-1])#反向打印数据,从最后一个index开始,间隔为1[12 11 10 9 8 7 6 5 4 3 2 1]>>> print(x[::-3])#反向打印数据,从最后一个index开始,间隔为3[12 9 6 3]>>> print(x[7:2:-1])#反向打印index=2(不包含)到index=7之间的数据[8 7 6 5 4]也是碰到这方面的问题,没搞明白,干脆试了试就清楚了,应该[:]和[::]还有很多有趣的地方。补充:Numpy.array()详解 、np.array与np.asarray辨析、 np.array和np.ndarry的区别记录一下numpy.array()的详细用法,以及与np.asarray()和np.ndarray()的区别。1. Numpy.array()详解该函数的作用一言蔽之就是用来产生数组。1.1 函数形式123456numpy.array(object, dtype=None, copy=True, order='K', subok=False, ndmin=0)1.2 参数详解object:必选参数,类型为array_like,可以有四种类型:数组,公开数组接口的任何对象,__array__方法返回数组的对象,或任何(嵌套)序列。np.array()的作用就是按照一定要求将object转换为数组。dtype:可选参数,用来表示数组元素的类型。如果没有给出,那么类型将被确定为保持序列中的对象所需的最小类型。注: This argument can only be used to ‘upcast' the array. For downcasting, use the .astype(t) method.copy:可选参数,类型为bool值。如果为true(默认值),则复制对象。否则的话只有在以下三种情况下才会返回副本:(1).if __array__ returns a copy;(2). if obj is a nested sequence;(3). if a copy is needed to satisfy any of the other requirements (dtype, order, etc.)order:{‘K', ‘A', ‘C', ‘F'},optional 。指定阵列的内存布局。该参数我至今还没有遇到过具体用法,这句话的意思就是我不会,故在此省略。subok:可选参数,类型为bool值。如果为True,则子类将被传递,否则返回的数组将被强制为基类数组(默认)。或者说,True:使用object的内部数据类型,False:使用object数组的数据类型。ndmin:可选参数,类型为int型。指定结果数组应具有的最小维数。返回对象out:输出ndarray,满足指定要求的数组对象。1.3 具体用法简单示例12345678910111213141516171819import numpy as np arr01 = np.array([1,2,3])print(arr01) #[1 2 3]print(type(arr01)) #<class 'numpy.ndarray'>print(arr01.dtype) #int32 #Upcastingarr02 = np.array([1.,2.,3.])print(arr02) #[1. 2. 3.]print(arr02.dtype) #float64 #More than one dimension:arr03 = np.array([[1,2],[3,4]])print(arr03)"""[[1 2] [3 4]]"""dtype参数使用示例1234567891011121314151617181920import numpy as np #指定数组元素类型为复数类型DYX= np.array([1,2,3],dtype = complex)print(DYX) #[1.+0.j 2.+0.j 3.+0.j]print(DYX.dtype) #complex128 #由多个元素组成的数据类型:HXH = np.array([(1,2),(3,4)],dtype=[('a','<i4'),('b','<i8')])print(HXH) #[(1, 2) (3, 4)]#下面的输出有点神奇,我也只能记住规律了。print(HXH["a"]) #[1 3]print(HXH["b"]) #[2 4]print(HXH.dtype) #[('a', '<i4'), ('b', '<i8')]print(HXH["a"].dtype) #int32print(HXH["b"].dtype) #int64 TSL = np.array([(1,2,3),(4,5,6)],dtype=[("a","i"),("b","i"),("c","i")])print(TSL["a"]) #[1 4]print(TSL["a"].dtype) #int32上述代码中,numpy的数据类型,可以百度下subok参数使用示例import numpy as np DYX = np.array(np.mat('1 2; 3 4'))#没有显示的写出subok的值,但是默认为Falseprint(DYX)#数组类型print(type(DYX)) #<class 'numpy.ndarray'>"""[[1 2] [3 4]]""" HXH = np.array(np.mat('1 2; 3 4'), subok=True)print(HXH)#矩阵类型print(type(HXH)) #<class 'numpy.matrixlib.defmatrix.matrix'>"""[[1 2] [3 4]]"""前文对subok的描述是这样的:“如果为True,则子类将被传递,否则返回的数组将被强制为基类数组(默认)”。在上文的代码中“np.mat('1 2; 3 4')”,就是子类,是矩阵类型。DYX = np.array(np.mat('1 2; 3 4'))中subok为False,返回的数组类型被强制为基类数组,所以DYX的类型是<class 'numpy.ndarray'>,是数组;HXH = np.array(np.mat('1 2; 3 4'), subok=True)中subok为True,子类被传递,所以HXH的类型是矩阵<class 'numpy.matrixlib.defmatrix.matrix'>。这就是区别所在。ndmin参数使用示例12345678910import numpy as np DYX = np.array([1,2,3],ndmin=0)print(DYX,DYX.shape) #[1 2 3] (3,) HXH = np.array([1,2,3],ndmin=1)print(HXH,HXH.shape) #[1 2 3] (3,) TSL = np.array([1,2,3],ndmin=2)print(TSL,TSL.shape) #[[1 2 3]] (1, 3)其他两个参数copy和order,我至今还没有遇到过,所以暂且不表。谁有介绍这两个参数用法的博客吗?2. Asarray和Array辨析Numpy.asaray的用法不再赘述,主要介绍一下二者的区别。2.1 object对象是普通迭代序列时12345678910111213141516171819202122import numpy as np data = [1,1,1]print(type(data)) #<class 'list'> 列表类型arr_ar = np.array(data)arr_as = np.asarray(data) #输出上没有区别print(arr_ar) #[1 1 1]print(arr_as) #[1 1 1] data[1]=2#改变原序列对arr_ar和arr_as没影响print(arr_ar) #[1 1 1]print(arr_as) #[1 1 1] #此时data是[1, 2, 1]#改变arr_ar和arr_as对原序列没有影响arr_ar[1]=3print(data) #[1, 2, 1]arr_as[1]=3print(data) #[1, 2, 1]可见在参数对象是普通迭代序列时,asarray和array没有区别(在我的理解范围内)。2.2 object对象是ndarray对象时123456789101112131415161718192021222324252627import numpy as np data = np.ones((3,))#print(type(data)) #<class 'numpy.ndarray'> 数组类型arr_ar = np.array(data)arr_as = np.asarray(data) print(arr_ar) #[1. 1. 1.]print(arr_as) #[1. 1. 1.] """这边区别就出来了。修改原始序列后,np.array()产生的数组不变,但是np.asarray()产生的数组发生了变化"""data[1]=2print(arr_ar) #[1. 1. 1.]print(arr_as) #[1. 2. 1.] !!! """这边也有区别,修改array产生的数组,不影响原始序列修改asarray产生的数组,会影响原始序列"""#此时data=[1. 2. 1.]arr_ar[2]=3print(data) #[1. 2. 1.]arr_as[2]=3print(data) #[1. 2. 3.]我们总结一下:相同点:array和asarray都可以将数组转化为ndarray对象。区别:当参数为一般数组时,两个函数结果相同;当参数本身就是ndarray类型时,array会新建一个ndarray对象,作为参数的副本,但是asarray不会新建,而是与参数共享同一个内存。重点就是这个共享内存。3.Numpy.ndarray()这是最近在一个项目里看到的用法,搜索了一下用法,只在stackoverflow看到了一个问题:“What is the difference between ndarray and array in numpy?”。地址如下:https://stackoverflow.com/questions/15879315/what-is-the-difference-between-ndarray-and-array-in-numpynumpy.array只是一个创建ndarray的便利函数;它本身不是一个类。他讲到也可以使用numpy.ndarray创建一个数组,但这不是推荐的方法。 numpy.ndarray() 是一个类,而numpy.array() 是一个创建ndarray的方法/函数。在numpy docs中,如果你想从ndarray类创建一个数组,你可以用引用的2种方式来做:(1).using array(), zeros() or empty() methods: Arrays should be constructed using array, zeros or empty (refer to the See Also section below). The parameters given here refer to a low-level method (ndarray(…)) for instantiating an array.【1-使用array(), zeros()或empty()方法:数组应该使用array, zeros()或empty()构造。这里给出的参数引用用于实例化数组的低级方法(ndarray(…))。】(2).from ndarray class directly: There are two modes of creating an array using new: If buffer is None, then only shape, dtype, and order are used. If buffer is an object exposing the buffer interface, then all keywords are interpreted.【2-来自ndarray类:使用new创建数组有两种模式:如果buffer是None,则只使用shape,dtype和order。 如果buffer是公开buffer接口的对象,则解释所有关键字。】所以说老老实实用numpy.array()吧。
-
pandas.Series.cumprod 官方文档cumprod()累积连乘1234Series.cumprod(axis=None, skipna=True, *args, **kwargs)#实现功能:Return cumulative product over a DataFrame or Series axis.#实现功能:Returns a DataFrame or Series of the same size containing the cumulative product.#return:scalar or Seriescumsum()累积连加pandas.Series.prod官方文档123Series.prod(axis=None, skipna=None, level=None, numeric_only=None, min_count=0, **kwargs)# 实现功能:Return the product of the values for the requested axis.# return:scalar or Series优点没看明白,因为常规情况下,所用的.prod()并非pandas下的函数,而是numpy下的函数。numpy.prod官方文档123numpy.prod(a, axis=None, dtype=None, out=None, keepdims=<class numpy._globals._NoValue>)# 实现功能:Return the product of array elements over a given axis.# return:product_along_axis : ndarray返回给定轴上数组元素的乘积。跟cumprod不同,cumprod是计算当前一个累积乘上前面所有的数据,更多是一个list;prod返回的是给定这个轴上最终一个值。补充:【python初学者】简单易懂的图解:np.cumsum和np.cumprod函数到底在干嘛?1.np.cumsum本人是一名python小白,最近过完了python的基本知识后,在看《利用python进行数据分析》这本书,书中cumsum函数一笔带过留下本小白“懵逼树下你和我”,当然是我自己的问题不是书的问题,经过画图理解后渐渐明白了这个函数到底在干么。1.1np.cumsum-轴的概念首先,在学习cumsum函数之前我们应该先明白什么是轴,以下面代码来进行说明:12arr=np.arange(1,17,1).reshape((2,2,4))arr12345array([[[ 1, 2, 3, 4], [ 5, 6, 7, 8]], [[ 9, 10, 11, 12], [13, 14, 15, 16]]])其实数组的轴(axis)就是数组的维度,上面的代码生成了一个224的数组,所以1、这个数组的0轴为2 ,axis=02、这个数组的1轴为2 ,axis=13、这个数组的2轴为4 ,axis=2该数组如图所示(蓝,橙,黄,绿都是2轴,橙和绿上的“2轴”画图时忘了标注):这里还要补充说一下:红色的数字只是因为我用的iPad画图很不方便所以没改成黑色,忽略就好1.2cumsum(axis=0)cumsum作用:计算轴向元素累加和,返回由中间结果组成的数组这句概念中我认为大家理解起来比较难受的地方应该是轴向元素累加。首先,通过前文对轴概念的理解我们可以知道axis=0代表着最外层的维度也就是0轴(这里可能说法不太正确,主要为了配合上节图片),所以就是0轴的累加计算,我们以前文用到的数组为例(红色虚线表示按照0轴进行累加):step1:沿着0轴进行累加代码:12345arr=np.array([[[ 1, 2, 3, 4], [ 5, 6, 7, 8]], [[ 9, 10, 11, 12], [13, 14, 15, 16]]])arr.cumsum(axis=0)结果为:12345array([[[ 1, 2, 3, 4], [ 5, 6, 7, 8]], [[10, 12, 14, 16], [18, 20, 22, 24]]])1.3cumsum(axis=1)这里我们还是以之前举例的数组为例,沿着1轴进行累加(也就是2 * 2 * 4中的第二个2),这里为了方便讲解我将数组的摆放位置换了一下,不影响哈~step1:红色虚线代表我们现在应该沿着1轴进行累加啦!step2:既然沿着1轴进行累加,我们是不是就应该在1轴内部进行累加呢?所以就应该[1,2,3,4]和[5,6,7,8]进行累加,[9,10,11,12]和[13,14,15,16]进行累加代码结果:1234567arr.cumsum(axis=1)#运行结果array([[[ 1, 2, 3, 4], [ 6, 8, 10, 12]], [[ 9, 10, 11, 12], [22, 24, 26, 28]]])1.4cumsum(axis=2)都已经讲到沿着轴2进行累加了,废话就不多说了直接放图,大家看看有没有做对吧step1:老规矩:红色虚线表示沿着2轴进行累加,所以应该是1,2,3,4进行累加,5,6,7,8进行累加,依次类推step2我们以蓝色这一项为例:第一项:1第二项:1+2=3第三项:1+2+3=6第四项:1+2+3+4=10代码结果:1234567arr.cumsum(axis=2)#运行结果array([[[ 1, 3, 6, 10], [ 5, 11, 18, 26]], [[ 9, 19, 30, 42], [13, 27, 42, 58]]])
-
1、输入一个值,返回其数据类型function type(para) { return Object.prototype.toString.call(para) }2、数组去重function unique1(arr) { return [...new Set(arr)] } function unique2(arr) { var obj = {}; return arr.filter(ele => { if (!obj[ele]) { obj[ele] = true; return true; } }) } function unique3(arr) { var result = []; arr.forEach(ele => { if (result.indexOf(ele) == -1) { result.push(ele) } }) return result; }3、字符串去重String.prototype.unique = function () { var obj = {}, str = '', len = this.length; for (var i = 0; i < len; i++) { if (!obj[this[i]]) { str += this[i]; obj[this[i]] = true; } } return str; } ###### //去除连续的字符串 function uniq(str) { return str.replace(/(\w)\1+/g, '$1') }4、深拷贝 浅拷贝//深克隆(深克隆不考虑函数) function deepClone(obj, result) { var result = result || {}; for (var prop in obj) { if (obj.hasOwnProperty(prop)) { if (typeof obj[prop] == 'object' && obj[prop] !== null) { // 引用值(obj/array)且不为null if (Object.prototype.toString.call(obj[prop]) == '[object Object]') { // 对象 result[prop] = {}; } else { // 数组 result[prop] = []; } deepClone(obj[prop], result[prop]) } else { // 原始值或func result[prop] = obj[prop] } } } return result; } // 深浅克隆是针对引用值 function deepClone(target) { if (typeof (target) !== 'object') { return target; } var result; if (Object.prototype.toString.call(target) == '[object Array]') { // 数组 result = [] } else { // 对象 result = {}; } for (var prop in target) { if (target.hasOwnProperty(prop)) { result[prop] = deepClone(target[prop]) } } return result; } // 无法复制函数 var o1 = jsON.parse(jsON.stringify(obj1));
-
package base;public class demo02 { public static void main(String[] args) { //八大基本数据类型 //整型 int num1 = 10; //最常用 //范围在100多// byte num2 = 200; //short short num3 = 20; //long long num4 = 40L; //Long类型在数字后面加个L表示是long类型 //float 浮点数:小数 float num5 = 12.3F; //float类型加F,否则就报错 double num6 = 3.14159; //字符 char name1 = 'a';// char name2 = 'as'; //字符是一个 //字符串 //String 不是关键字,是一个类 String num7 = "asd"; //布尔值 boolean flag = true; //真 boolean fla = false; //假 }}
-
重构是通过改变现有程序结构而不改变其功能和用途来提高代码的可重用性和可维护性。CodeArts IDE支持重构操作,提供了多种重要的重构类型,如下:重命名提取重构(方法、常数、接口、字段、变量、参数、超类)引入重构 (字段、常量、变量、参数、参数对象、函数变量、功能参数)复制重构移动和更改重构查找和替换重构内联重构 (方法、字段、变量、匿名类、超类、参数)设计模式与重构补全功能运用CodeArts IDE支持的重构操作重构类型支持操作快捷键重命名Rename SymbolShift+F6提取重构Extract Superclass Extract Method Object Extract MethodCtrl+Alt+Shift+MExtract Interface Extract Delegate... Encapsulate Fields 引入重构Introduce Variable Ctrl+Alt+VIntroduce ParameterCtrl+Alt+Shift+PIntroduce Parameter Object Introduce Functional Variable Introduce Functional Parameter Introduce FieldCtrl+Alt+Shift+FIntroduce Constant Ctrl+Alt+C复制重构Copy Class Shift+F5移动和更改重构Push members down Pull members up Move inner class to Upper level Make Static Change Method SignatureCtrl+F6Change Class SignatureCtrl+F6Type Migration 查找和替换重构Replace Inheritance with Delegation Replace Constructor with Factory Method Replace Constructor with Builder 内联重构Inline Variable Inline to Anonymous Class Inline Super Class Inline Parameter Ctrl+Alt+Shift+PInline Method Ctrl+Alt+Shift+LInline Field 其它重构Invert Boolean Use Interface Where Possible Convert Raw Types to GenericsConvert To Instance MethodRemove MiddlemanSafe DeleteAlt+DelMove Static Members
-
代码补全可以有效的提升开发效率、减少拼写错误和输入代码量。CodeArts依赖于codearts.smartassist-java-ls插件实现代码补全功能。代码补全类型主要有:关键字基础补全名字补全类型补全方法补全片段补全缩写补全智能类型匹配补全标签属性补全CodeArts的代码补全具有能使用字段名称的驼峰字母作为关键字母快速搜索的特点。关键字基础补全关键字(Reserved Words)是指在Java、Javascript等计算机语言中有特定含义,用来表示一种数据类型,或者表示程序的结构等。CodeArts支持计算机语言的关键字基础补全。如图所示:输入关键字首字母,代码补全列表可优先推荐关键字public、private、protected。名字补全名字是指用户自定义的变量名、参数名、方法名、类名、接口名、包名等名称。CodeArts可根据上下文场景,推荐当前变量命名的模板。定义类的变量,代码推荐变量命名最优模板。当您定义好方法参数后,输入首字母后,CodeArts可优先在代码列表中推荐参数名称。输入名字首字母,代码补全列表可展示建议的名字。类型补全类型包括基础数据类型(整数类型、字符类型、浮点类型、布尔类型)和引用类型(类、接口类型、数组类型、null类型)。定义的每一个变量都必须声明其数据类型,因其在编译时进行严格的语法检查,如果变量值的数据类型与定义的类型不同,则会报错。因此,CodeArts对数据类型进行补全,便于减少拼写错误,加快变量的定义。如图所示:输入数据类型首字母,代码补全列表可优先推荐。方法补全方法是指定义在类中的具有特定功能的一段独立小程序。CodeArts方法补全时可补全方法所需的元素:方法名、返回值类型、参数表、方法体。CodeArts可根据类中的变量,补全类变量相关方法。类中已定义变量homeBrandMapper,CodeArts搜索推荐关于变量的常用的模板set和get方法。选择setHomeBrandMapper()方法上屏后,自动补全变量的set方法包含方法名、参数表、方法体。在项目主类中,可快速进行main方法声明补全。在类中输入main,选择main() method declaration上屏后,补全主类main方法。片段补全CodeArts为常用的代码片段提供了标准的模板,这些代码片段具有基于源代码语言的各种构造。这包括条件语句和循环、折叠区域和其它构造。 动画演示:缩写补全CodeArts常用缩写补全,可自动补全代码语句及符号。常用缩写:sout、souf、soutm、soutp、soutv打印方法for循环简写foriprsf、psf、psfi、psfs、psvm变量定义语句智能类型匹配补全智能类型匹配代码能够过滤代码建议列表并仅显示适用于当前上下文的类型。在可以确定类型的情况下使用:在赋值语句的右侧部分在变量初始值定义中在return返回语句中在方法调用的参数列表中在对象声明中new关键字之后在链式表达式中默认情况下,CodeArts会在您键入时自动显示代码推荐列表窗口。当您完成语句上屏,希望转换当前代码时,按Ctrl+Shift+Space键可触发CodeArts搜索与当前的代码相关内容,选择可进行转换。实例如下:return返回语句。CodeArts 扫描return语句相关的方法内容,并建议适合当前上下文的返回值。鼠标在return上,操作快捷键Ctrl+Shift+Space,推荐列表展示可转换的代码。标签属性补全CodeArts能够自动补全许多文件类型中标签和属性的名称和值:HTML,包括CSS 类和JSX 中的 HTML 标签的补全。按<可以开始输入标签名称。CodeArts扫描文件显示适合当前上下文的标记名称列表。按Enter键,CodeArts可添加所选的标签。驼峰搜索变量、参数、类、方法均可使用驼峰字母作为关键字母快速搜索,驼峰字母不区分大小写。直接输入SmsHomeBrandMapper的驼峰字母“Shbm”作为关键字;CodeArts搜索项目中的相关类名展示在代码推荐列表,Enter或Tab键可上屏SmsHomeBrandMapper。
-
智能搜索是CodeArts的核心功能之一,依赖于Language Server,能够进行项目级别的搜索,比如整个项目全局搜索、文件搜索、类名搜索以及自定义范围的搜索等,通过快捷键Double Ctrl,或Ctrl + N,或Ctrl + Shift + L可以打开搜索框。智能搜索的类型主要有6大类:AllTypeMemberTextFileCommand各个类型的搜索可以互相切换,搜索内容也随之切换。一、All搜索CodeArts的All搜索可进行全局搜索或自定义范围搜索。1.全局搜索:可以通过名称找到项目中或项目之外的任何内容,可以搜索文件、类、方法、命令等。搜索文本内容包含':'等符号,如图:当前编辑器有打开java文件,搜索面板则会显示当前文件的类、方法等。双击搜索结果项,可在编辑器打开并跳转到相应位置。2.自定义范围的搜索:方法搜索类搜索接口搜索内容搜索当前文件搜索枚举类型搜索注解类型搜索包搜索反射类搜索父类接口搜索子类接口搜索组合搜索 搜索规则:如搜索方法,输入“method:***”或“m:***”即可搜索;同时支持反向搜索,即输入“***:method”或“***:m”(*代表输入的内容)。 2.1 方法搜索 如果想直接搜索方法,可通过“method:***”或“m:***”来进行搜索(*代表输入的方法名),支持反向搜索。 2.2 类搜索 搜索和关键词相关的类,可通过“class:***”或“c:***”来进行搜索(*代表输入的类名),支持反向搜索。 2.3 接口搜索 搜索和关键词相关的接口,可通过“interface:***”或“i:***”来进行搜索(*代表输入的接口名),支持反向搜索。 2.4 内容搜索 搜索和关键词相关的文本内容,可通过“text:***”来进行搜索(*代表输入的文本)。 2.5 当前文件搜索 搜索当前文件的内容,可通过“local:***”或“l:***”来进行搜索(*代表输入的文件名),支持反向搜索。 2.6 枚举类型搜索 搜索和关键词相关的枚举类型,可通过“enum:***”或“e:***”来进行搜索(*代表输入的枚举名称),支持反向搜索。 2.7 注解类型搜索 搜索和关键词相关的注解类型,可通过“annotation:***”或“a:***”来进行搜索(*代表输入的注解名称),支持反向搜索。 2.8 包搜索 搜索和关键词相关的包,可通过“package:***”或“p:***”来进行搜索(*代表输入的包名),支持反向搜索。 2.9 反射类搜索 搜索和关键词相关的反射类,可通过“field:***”或“f:***”来进行搜索(*代表输入的反射类名称),支持反向搜索。 2.10 父类接口搜索 搜索和关键词相关的父类,可通过“super:***”来进行搜索(*代表输入的父类名)。 2.11 子类接口搜索 搜索和关键词相关的子类,可通过“sub:***”来进行搜索(*代表输入的子类名)。 2.12 组合搜索 “或”搜索:OR、|、|| 如搜索类或方法,可通过“class:*** OR method:***”,“class:*** | method:***”,“class:*** || method:***”来进行搜索,支持反向搜索。 “与”搜索:AND、&、&& 如搜索类与方法,可通过“class:*** AND method:***”,“class:*** & method:***”,“class:*** && method:***”来进行搜索,支持反向搜索。 “或”搜索和“与”搜索组合 更多组合搜索,请自行尝试使用。二、Type搜索可搜索类(class)、接口(interface)、枚举类(enum)、注解(annotation)。三、Member搜索可搜索方法(method)和字段(field)。四、Text搜索可对任意文本内容进行搜索。五、File搜索不仅可以搜索类名,还可以搜索其他文件比如properties文件、xml文件,或者范围更大,可以找到对应名称的文件夹。搜索范围:类文件文件夹六、Command搜索在输入框里输入命令关键词,就会展示相应的命令,比如有文件(File)、视图(View)、终端(Terminal)、调试(Debug)、运行(Run)、Git等等,双击可以执行命令,或根据命令快捷键进行相应的操作。
推荐直播
-
华为云码道-玩转OpenClaw,在线养虾2026/03/11 周三 19:00-21:00
刘昱,华为云高级工程师/谈心,华为云技术专家/李海仑,上海圭卓智能科技有限公司CEO
OpenClaw 火爆开发者圈,华为云码道最新推出 Skill ——开发者只需输入一句口令,即可部署一个功能完整的「小龙虾」智能体。直播带你玩转华为云码道,玩转OpenClaw
回顾中 -
华为云码道-AI时代应用开发利器2026/03/18 周三 19:00-20:00
童得力,华为云开发者生态运营总监/姚圣伟,华为云HCDE开发者专家
本次直播由华为专家带你实战应用开发,看华为云码道(CodeArts)代码智能体如何在AI时代让你的创意应用快速落地。更有华为云HCDE开发者专家带你用码道玩转JiuwenClaw,让小艺成为你的AI助理。
回顾中 -
Skill 构建 × 智能创作:基于华为云码道的 AI 内容生产提效方案2026/03/25 周三 19:00-20:00
余伟,华为云软件研发工程师/万邵业(万少),华为云HCDE开发者专家
本次直播带来两大实战:华为云码道 Skill-Creator 手把手搭建专属知识库 Skill;如何用码道提效 OpenClaw 小说文本,打造从大纲到成稿的 AI 原创小说全链路。技术干货 + OPC创作思路,一次讲透!
回顾中
热门标签