• 关于

    数组的常用函数()

    的搜索结果

回答

qsort 功 能: 使用快速排序例程进行排序 用 法: void qsort(void *base, int nelem, int width, int (*fcmp)(const void *,const void *)); 各参数:1 待排序数组首地址 2 数组中待排序元素数量 3 各元素的占用空间大小 4 指向函数的指针,用于确定排序的顺序 程序例: #include <iostream> using namespace std; #include <stdlib.h> #include <string.h> int compare( const void *a, const void *b); char * list[5]= {"cat","car","cab","cap","can"}; int main() pascal 例程 program quicksort; const max = 100000; max = 1000; type tlist = array[1..max] of longint; var data : tlist; i : longint; procedure qsort(var a : tlist); procedure sort(l,r: longint); var i,j,x,y: longint; begin i:=l; j:=r; x:=a[(l+r) div 2]; repeat while a[i]<x do inc(i); while x<a[j] do dec(j); if i<=j then begin y:=a[i];a[i]:=a[j];a[j]:=y; inc(i);dec(j); end; until i>j; if l<j then sort(l,j); if i<r then sort(i,r); end; begin sort(1,max); end; begin write('Creating ',Max,' random numbers between 1 and 500000'); randomize; for i:=1 to max do data:=random(500000); writeln; writeln('Sorting...'); qsort(data); writeln; for i:=1 to max do begin write(data:7); if (i mod 10)=0 then writeln; end; end. c/c++ c函数qsort()和bsearch()的用法 使用qsort()排序 并 用 bsearch()搜索是一个比较常用的组合,使用方便快捷。 qsort 的函数原型是void __cdecl qsort ( void *base, size_t num, size_t width, int (__cdecl *comp)(const void *, const void* ) ) 其中base是排序的一个集合数组,num是这个数组元素的个数,width是一个元素的大小,comp是一个比较函数。 比如:对一个长为1000的数组进行排序时,int a[1000]; 那么base应为a,num应为 1000,width应为 sizeof(int),comp函数随自己的命名。 qsort(a,1000,sizeof(int ),comp); 其中comp函数应写为: int comp(const void *a,const void *b) { return *(int *)a-*(int *)b; } 是对一个二维数组的进行排序: int a[1000][2]; 其中按照a[0]的大小进行一个整体的排序,其中a[1]必须和a[0]一起移动交换。 qsort(a,1000,sizeof(int)*2,comp); int comp(const void *a,const void *b) { return ((int *)a)[0]-((int *)b)[0]; } 对字符串进行一个排序: char a[1000][20]; qsort(a,1000,sizeof(char)*20,comp); int comp(const void *a,const void *b { return strcmp((char *)a,(char *)b); } 对一个结构体进行排序: typedef struct str { char str1[11]; char str2[11]; }str,*stri; str strin[100001]=; int compare(const void *a,const void *b) { return strcmp( ((str*)a)->str2 , ((str*)b)->str2 ); } qsort(strin,total,sizeof(str),compare); 程序例: #include<iostream.h> #include<stdlib.h> #include<string.h> #define N 8 int compare(const void *a,const void *b); void main() { char s[8][10]={"January","February","March","April","May","June","July","September"}; int i; qsort(s,8,sizeof(char)*10,compare); for(i=0;i<N;i++) { for(j=0;j<10;j++) cout<<s[j]; cout<<endl; } } int compare(const void *a,const void *b) { if(strlen((char *)a)!=strlen((char *)b)) return strlen((char *)a)-strlen((char*)b); return (strcmp((char *)a,(char *)b)); }//vc++ 6.0 // VS2008编译通过,具有代表性的例子 #include <stdlib.h> #include <stdio.h> #include <string.h> int compare(const void *arg1,const void *arg2); int main(int argc,char **argv) { int i; argv++; argc--; qsort((void *)argv,(size_t)argc,sizeof(char *),compare); for(i=0;i<argc;++i) { printf("%s ",argv); printf("\n"); } } int compare(const void *arg1,const void *arg2) { return _stricmp(*(char **)arg1,*(char **)arg2); } 在运行输入cmd,在qsort.exe 参数1 参数2 将会排序 下面讲解下Pascal的快排代码 program kuaipai; var save:array[-1..10000000]of longint;//保存数字的数组 n,i:longint; procedure qsort(x,y:longint); var a,b,c,em,d,mid,e,i,j,k,l:longint; begin i:=x;//i代表第一个数字的数组坐标,下面叫“左指针” j:=y;//j代表第二个数字的数组坐标 叫"右指针" mid:=save[(x+y)div 2];//取,这2个数字中间的数组坐标(二分) repeat while save[i]<mid do inc(i); //在中间这个数字的左边,找比中间数大的数字 while save[j]>mid do dec(j);//在中间数右边,找比中间数小的数字 if i<=j//如果左指针在右指针左边 then begin em:=save[i];//交换2个数字的值,这个你会冒泡排序,或者选择排序任意一个,应该明白 save[i]:=save[j]; save[j]:=em; inc(i); dec(j); end; until i>j;//左指针跑到右指针右边了。。。 if i<y then qsort(i,y);//如果左指针,没到界限,那么 从左指针到界限进行上述排序 if j>x then qsort(x,j);//如果右指针没跑到,左界限,那么从右指针到左界限排序 end; begin randomize;//优化程序用的,暂时你不用会 readln(n);//读入,表示有N个数字 for i:=1 to n do//读入这N个数字 read(save[i]); qsort(1,n);//从第一个数字,到最后一个数字排序 for i:=1 to n do//输出 write(save[i],' '); end.

游客886 2019-12-02 01:17:29 0 浏览量 回答数 0

回答

在C++排序中,最常用、最好用的有 冒泡排序(bubble sort),时间复杂度为O(n^2); 鸡尾酒排序(Cocktail sort,双向的冒泡排序),时间复杂度为O(n^2); 快速排序(Quick sort,是对冒泡排序的一种改进),时间复杂度下界为O(nlogn),最坏情况为O(n^2); 插入排序(insertion sort),时间复杂度为O(n^2); 希尔排序(Shell Sort,插入排序的一种,也称缩小增量排序),时间复杂度为O(nlog n) ; 选择排序(selection sort),时间复杂度为O(n^2); 堆排序(Heap sort,选择排序的一种。),时间复杂度为O(nlog n); 归并排序(Merge sort),时间复杂度为O(nlog n); 基数排序(radix sort),时间复杂度为O (nlog(r)m),其中r为所采取的基数,而m为堆数 在C++中有排序函数sort(),包含在<algorithm> 中,参数格式为 sort(a+begin,a+end); 其中begin表示所需要排序的数组a的开头,end则表示所需要排序的数组a的最后一个元素的位置。

青衫无名 2019-12-02 01:18:08 0 浏览量 回答数 0

回答

HashMap 和 HashSet 内部是如何工作的?散列函数(hashing function)是什么? HashMap 不仅是一个常用的数据结构,在面试中也是热门话题。 Q1. HashMap 如何存储数据?A1. 以键/值对(key/value)形式存储。你可以使用键(key)来存、取值。 Q2. HashMap 查询时间的复杂度是怎样的?A2. 是O(n) = O(k * n)。如果 hashCode() 方法能向下面讨论的那样把数据分散到桶(bucket)中,那么平均是O(1)。 Q3. HashMap 内部是如何存储数据的?A3. HashMap 使用后台数组(backing array)作为桶,并使用链表(linked list)存储键/值对。 桶的后台数组:如下所示 hashCode() 返回 1, 45等 1)使用键(key)和值(value)将一个对象放入 map 中时,会隐式调用 hashCode() 方法,返回哈希值(hash code value),比如 123。两个不同的键能够返回一样的哈希值。良好的哈希算法(hashing algorithm)能够将数值分散开。在上面的例子中,我们假设 (“John”,01/01/1956) 的键和 (“Peter”, 01/01/1995) 的键返回相同的哈希值,都是 123。 Java equals vs hashCode 2)当返回一个 hashCode,例如是 123,初始的 HashMap 容量为 10,它如何知道存储到后台数组(backing array)的哪个索引(index)呢?HashMap 内部会调用 hash(int ) 和 indexFor(int h, int length) 方法。这被称为哈希函数(hashing function)。简要解释下这个函数:1234 hashCode() % capacity 123 % 10 = 3456 % 10 = 6 这表示,“hashCode = 123”存储在备份数组的索引3上。容量为 10 的情况下,你可能得到的数字在 0 到 9 之间。一旦 HashMap 达到容量的 75%,也就是哈希因子(hash factor)默认值 0.75,后台数组(backing array)的容量就会加倍,发生重散列(rehashing)为新的 20 的容量重新分配桶。1234 hashCode() % capacity 123 % 20 = 3456 % 20 = 16 上面重散列的取模方法有一个缺陷。如果 hashCode 是负数会怎样?负索引可不是你想要的。因此,一个改进的哈希公式会移出符号位,然后再用取模(即 %)运算符计算剩余部分。12 (123 & 0x7FFFFFFF) % 20 = 3(456 & 0x7FFFFFFF) % 20 = 16 这确保你得到的索引值为正数。如果你查看 Java 8 的 HashMap 源码,它的实现使用以下方法: a). 通过只抽取重要的低位,来防止不良离散值(poorer hashes)。1234567 static int hash(int h) { // This function ensures that hashCodes that differ only by // constant multiples at each bit position have a bounded // number of collisions (approximately 8 at default load factor). h ^= (h >>> 20) ^ (h >>> 12); return h ^ (h >>> 7) ^ (h >>> 4); } b). 根据哈希码(hashCode)和容量(capacity),来决定索引(index)。123 static int indexFor(int h, int length) { return h & (length-1); } 实际的名称值对(name value pairs)作为一个键/值对存储在 LinkedList 中。 如上图所示,键/值对以链表形式存储。两个不同的键可以产生一样的 hashCode,例如123,并存储在同一个 bucket 中,理解这点至关重要。例如,上面例子中的 “John, 01/01/1956” 和 “Peter, 01/01/1995“ 。你如何只检索 “John, 01/01/1956” 呢?此时你的 key 所属类的 equals() 方法会被调用。它遍历 bucket 为 “123” 的 LinkedList 中的每个条目,使用 equals() 方法找到并检索出键为 “John, 01/01/1956” 的条目。这就是在你的类中实现 hashCode() 和 equals() 方法重要性的原因。如果你使用一个现有的包装类,如 Integer 或 String 作为键,它们已经实现了这两个方法。如果你使用自己写的类作为键,如 “John, 01/01/1956” 这样含有名字和出生日期属性的“MyKey”,你有责任正确地实现这些方法。 Q5. 为什么恰当地设置 HashMap 的初始容量(initial capacity)是最佳实践?A5. 这样可以减少重散列的发生。 Q6. HashSet 内部如何存储数据?A6. HashSet 内部使用 HashMap 。它将元素存储为键和值。(译者注:HashSet 把存储的值作为 key) Q7. 为 Object 实现了一个糟糕的 hashcode() 会有什么影响?A7. 不同的对象调用 hashCode() 方法应该返回不同的值。如果不同的对象返回相同的值,会导致更多的键/值对存储在同一个 bucket 中。这会降低 HashMap 和 HashSet 的性能。

wangccsy 2019-12-02 01:48:57 0 浏览量 回答数 0

阿里云试用中心,为您提供0门槛上云实践机会!

0元试用32+款产品,最高免费12个月!拨打95187-1,咨询专业上云建议!

回答

许多Python初学者都会问:我应该学习哪个版本的Python。对于这个问题,我的回答通常是“先选择一个最适合你的Python教程,教程中使用哪个版本的Python,你就用那个版本。等学得差不多了,再来研究不同版本之间的差别”。 许多Python初学者都会问:我应该学习哪个版本的Python。对于这个问题,我的回答通常是“先选择一个最适合你的Python教程,教程中使用哪个版本的Python,你就用那个版本。等学得差不多了,再来研究不同版本之间的差别”。但如果想要用Python开发一个新项目,那么该如何选择Python版本呢?我可以负责任的说,大部分Python库都同时支持Python 2.7.x和3.x版本的,所以不论选择哪个版本都是可以的。但为了在使用Python时避开某些版本中一些常见的陷阱,或需要移植某个Python项目时,依然有必要了解一下Python两个常见版本之间的主要区别。__future__模块Python 3.x引入了一些与Python 2不兼容的关键字和特性,在Python 2中,可以通过内置的__future__模块导入这些新内容。如果你希望在Python 2环境下写的代码也可以在Python 3.x中运行,那么建议使用__future__模块。例如,如果希望在Python 2中拥有Python 3.x的整数除法行为,可以通过下面的语句导入相应的模块。from future import division 下表列出了__future__中其他可导入的特性:特性 可选版本 强制版本 效果nested_scopes 2.1.0b1 2.2 PEP 227:Statically Nested Scopesgenerators 2.2.0a1 2.3 PEP 255:Simple Generatorsdivision 2.2.0a2 3.0 PEP 238:Changing the Division Operatorabsolute_import 2.5.0a1 3.0 PEP 328:Imports: Multi-Line and Absolute/Relativewith_statement 2.5.0a1 2.6 PEP 343:The “with” Statementprint_function 2.6.0a2 3.0 PEP 3105:Make print a functionunicode_literals 2.6.0a2 3.0 PEP 3112:Bytes literals in Python 3000(来源: https://docs.python.org/2/library/future.html)示例:from platform import python_version print函数虽然print语法是Python 3中一个很小的改动,且应该已经广为人知,但依然值得提一下:Python 2中的print语句被Python 3中的print()函数取代,这意味着在Python 3中必须用括号将需要输出的对象括起来。在Python 2中使用额外的括号也是可以的。但反过来在Python 3中想以Python2的形式不带括号调用print函数时,会触发SyntaxError。Python 2print 'Python', python_version() print 'Hello, World!' print('Hello, World!') print "text", ; print 'print more text on the same line' Python 2.7.6 Hello, World! Hello, World! text print more text on the same line Python 3print('Python', python_version()) print('Hello, World!') print("some text,", end="") print(' print more text on the same line') Python 3.4.1 Hello, World! some text, print more text on the same line print 'Hello, World!' File "", line 1 print 'Hello, World!' ^ SyntaxError: invalid syntax 注意:在Python中,带不带括号输出”Hello World”都很正常。但如果在圆括号中同时输出多个对象时,就会创建一个元组,这是因为在Python 2中,print是一个语句,而不是函数调用。print 'Python', python_version() print('a', 'b') print 'a', 'b' Python 2.7.7 ('a', 'b') a b 整数除法由于人们常常会忽视Python 3在整数除法上的改动(写错了也不会触发Syntax Error),所以在移植代码或在Python 2中执行Python 3的代码时,需要特别注意这个改动。所以,我还是会在Python 3的脚本中尝试用float(3)/2或 3/2.0代替3/2,以此来避免代码在Python 2环境下可能导致的错误(或与之相反,在Python 2脚本中用from future import division来使用Python 3的除法)。Python 2print 'Python', python_version() print '3 / 2 =', 3 / 2 print '3 // 2 =', 3 // 2 print '3 / 2.0 =', 3 / 2.0 print '3 // 2.0 =', 3 // 2.0 Python 2.7.6 3 / 2 = 1 3 // 2 = 1 3 / 2.0 = 1.5 3 // 2.0 = 1.0 Python 3print('Python', python_version()) print('3 / 2 =', 3 / 2) print('3 // 2 =', 3 // 2) print('3 / 2.0 =', 3 / 2.0) print('3 // 2.0 =', 3 // 2.0) Python 3.4.1 3 / 2 = 1.5 3 // 2 = 1 3 / 2.0 = 1.5 3 // 2.0 = 1.0 UnicodePython 2有基于ASCII的str()类型,其可通过单独的unicode()函数转成unicode类型,但没有byte类型。而在Python 3中,终于有了Unicode(utf-8)字符串,以及两个字节类:bytes和bytearrays。Python 2print 'Python', python_version() Python 2.7.6 print type(unicode('this is like a python3 str type')) print type(b'byte type does not exist') print 'they are really' + b' the same' they are really the same print type(bytearray(b'bytearray oddly does exist though')) Python 3print('Python', python_version()) print('strings are now utf-8 u03BCnicou0394é!') Python 3.4.1 strings are now utf-8 μnicoΔé! print('Python', python_version(), end="") print(' has', type(b' bytes for storing data')) Python 3.4.1 has print('and Python', python_version(), end="") print(' also has', type(bytearray(b'bytearrays'))) and Python 3.4.1 also has 'note that we cannot add a string' + b'bytes for data' TypeError Traceback (most recent call last) in () ----> 1 'note that we cannot add a string' + b'bytes for data' TypeError: Can't convert 'bytes' object to str implicitly xrange在Python 2.x中,经常会用xrange()创建一个可迭代对象,通常出现在“for循环”或“列表/集合/字典推导式”中。这种行为与生成器非常相似(如”惰性求值“),但这里的xrange-iterable无尽的,意味着可能在这个xrange上无限迭代。由于xrange的“惰性求知“特性,如果只需迭代一次(如for循环中),range()通常比xrange()快一些。不过不建议在多次迭代中使用range(),因为range()每次都会在内存中重新生成一个列表。在Python 3中,range()的实现方式与xrange()函数相同,所以就不存在专用的xrange()(在Python 3中使用xrange()会触发NameError)。import timeit n = 10000 def test_range(n): return for i in range(n): pass def test_xrange(n): for i in xrange(n): pass Python 2print 'Python', python_version() print 'ntiming range()' %timeit test_range(n) print 'nntiming xrange()' %timeit test_xrange(n) Python 2.7.6 timing range() 1000 loops, best of 3: 433 µs per loop timing xrange() 1000 loops, best of 3: 350 µs per loop Python 3print('Python', python_version()) print('ntiming range()') %timeit test_range(n) Python 3.4.1 timing range() 1000 loops, best of 3: 520 µs per loop print(xrange(10)) NameError Traceback (most recent call last) in () ----> 1 print(xrange(10)) NameError: name 'xrange' is not defined Python 3中的range对象中的__contains__方法另一个值得一提的是,在Python 3.x中,range有了一个新的__contains__方法。__contains__方法可以有效的加快Python 3.x中整数和布尔型的“查找”速度。x = 10000000 def val_in_range(x, val): return val in range(x) def val_in_xrange(x, val): return val in xrange(x) print('Python', python_version()) assert(val_in_range(x, x/2) == True) assert(val_in_range(x, x//2) == True) %timeit val_in_range(x, x/2) %timeit val_in_range(x, x//2) Python 3.4.1 1 loops, best of 3: 742 ms per loop 1000000 loops, best of 3: 1.19 µs per loop 根据上面的timeit的结果,查找整数比查找浮点数要快大约6万倍。但由于Python 2.x中的range或xrange没有__contains__方法,所以在Python 2中的整数和浮点数的查找速度差别不大。print 'Python', python_version() assert(val_in_xrange(x, x/2.0) == True) assert(val_in_xrange(x, x/2) == True) assert(val_in_range(x, x/2) == True) assert(val_in_range(x, x//2) == True) %timeit val_in_xrange(x, x/2.0) %timeit val_in_xrange(x, x/2) %timeit val_in_range(x, x/2.0) %timeit val_in_range(x, x/2) Python 2.7.7 1 loops, best of 3: 285 ms per loop 1 loops, best of 3: 179 ms per loop 1 loops, best of 3: 658 ms per loop 1 loops, best of 3: 556 ms per loop 下面的代码证明了Python 2.x中没有__contain__方法:print('Python', python_version()) range.__contains__ Python 3.4.1 print('Python', python_version()) range.__contains__ Python 2.7.7 AttributeError Traceback (most recent call last) in () 1 print 'Python', python_version() ----> 2 range.__contains__ AttributeError: 'builtin_function_or_method' object has no attribute '__contains__' print('Python', python_version()) xrange.__contains__ Python 2.7.7 AttributeError Traceback (most recent call last) in () 1 print 'Python', python_version() ----> 2 xrange.__contains__ AttributeError: type object 'xrange' has no attribute '__contains__' 关于Python 2中xrange()与Python 3中range()之间的速度差异的一点说明:有读者指出了Python 3中的range()和Python 2中xrange()执行速度有差异。由于这两者的实现方式相同,因此理论上执行速度应该也是相同的。这里的速度差别仅仅是因为Python 3的总体速度就比Python 2慢。def test_while(): i = 0 while i < 20000: i += 1 return print('Python', python_version()) %timeit test_while() Python 3.4.1 %timeit test_while() 100 loops, best of 3: 2.68 ms per loop print 'Python', python_version() %timeit test_while() Python 2.7.6 1000 loops, best of 3: 1.72 ms per loop 触发异常Python 2支持新旧两种异常触发语法,而Python 3只接受带括号的的语法(不然会触发SyntaxError):Python 2print 'Python', python_version()Python 2.7.6 raise IOError, "file error" IOError Traceback (most recent call last) in ()----> 1 raise IOError, "file error" IOError: file error raise IOError("file error") IOError Traceback (most recent call last) in ()----> 1 raise IOError("file error") IOError: file errorPython 3print('Python', python_version())Python 3.4.1raise IOError, "file error"File "", line 1raise IOError, "file error"^SyntaxError: invalid syntaxThe proper way to raise an exception in Python 3:print('Python', python_version())raise IOError("file error")Python 3.4.1 OSError Traceback (most recent call last) in ()1 print('Python', python_version())----> 2 raise IOError("file error") OSError: file error异常处理Python 3中的异常处理也发生了一点变化。在Python 3中必须使用“as”关键字。Python 2print 'Python', python_version()try: let_us_cause_a_NameError except NameError, err: print err, '--> our error message' Python 2.7.6name 'let_us_cause_a_NameError' is not defined --> our error messagePython 3print('Python', python_version())try: let_us_cause_a_NameError except NameError as err: print(err, '--> our error message') Python 3.4.1name 'let_us_cause_a_NameError' is not defined --> our error messagenext()函数和.next()方法由于会经常用到next()(.next())函数(方法),所以还要提到另一个语法改动(实现方面也做了改动):在Python 2.7.5中,函数形式和方法形式都可以使用,而在Python 3中,只能使用next()函数(试图调用.next()方法会触发AttributeError)。Python 2print 'Python', python_version()my_generator = (letter for letter in 'abcdefg')next(my_generator)my_generator.next()Python 2.7.6'b'Python 3print('Python', python_version())my_generator = (letter for letter in 'abcdefg')next(my_generator)Python 3.4.1'a' my_generator.next() AttributeError Traceback (most recent call last) in ()----> 1 my_generator.next() AttributeError: 'generator' object has no attribute 'next'For循环变量与全局命名空间泄漏好消息是:在Python 3.x中,for循环中的变量不再会泄漏到全局命名空间中了!这是Python 3.x中做的一个改动,在“What’s New In Python 3.0”中有如下描述:“列表推导不再支持[… for var in item1, item2, …]这样的语法,使用[… for var in (item1, item2, …)]代替。还要注意列表推导有不同的语义:现在列表推导更接近list()构造器中的生成器表达式这样的语法糖,特别要注意的是,循环控制变量不会再泄漏到循环周围的空间中了。”Python 2print 'Python', python_version() i = 1print 'before: i =', i print 'comprehension: ', [i for i in range(5)] print 'after: i =', iPython 2.7.6before: i = 1comprehension: [0, 1, 2, 3, 4]after: i = 4Python 3print('Python', python_version()) i = 1print('before: i =', i) print('comprehension:', [i for i in range(5)]) print('after: i =', i)Python 3.4.1before: i = 1comprehension: [0, 1, 2, 3, 4]after: i = 1比较无序类型Python 3中另一个优秀的改动是,如果我们试图比较无序类型,会触发一个TypeError。Python 2print 'Python', python_version()print "[1, 2] > 'foo' = ", [1, 2] > 'foo'print "(1, 2) > 'foo' = ", (1, 2) > 'foo'print "[1, 2] > (1, 2) = ", [1, 2] > (1, 2)Python 2.7.6[1, 2] > 'foo' = False(1, 2) > 'foo' = True[1, 2] > (1, 2) = FalsePython 3print('Python', python_version())print("[1, 2] > 'foo' = ", [1, 2] > 'foo')print("(1, 2) > 'foo' = ", (1, 2) > 'foo')print("[1, 2] > (1, 2) = ", [1, 2] > (1, 2)) Python 3.4.1 TypeError Traceback (most recent call last) in ()1 print('Python', python_version())----> 2 print("[1, 2] > 'foo' = ", [1, 2] > 'foo')3 print("(1, 2) > 'foo' = ", (1, 2) > 'foo')4 print("[1, 2] > (1, 2) = ", [1, 2] > (1, 2))TypeError: unorderable types: list() > str()通过input()解析用户的输入幸运的是,Python 3改进了input()函数,这样该函数就会总是将用户的输入存储为str对象。在Python 2中,为了避免读取非字符串类型会发生的一些危险行为,不得不使用raw_input()代替input()。Python 2Python 2.7.6[GCC 4.0.1 (Apple Inc. build 5493)] on darwinType "help", "copyright", "credits" or "license" for more information. my_input = input('enter a number: ') enter a number: 123 type(my_input) my_input = raw_input('enter a number: ') enter a number: 123 type(my_input) Python 3Python 3.4.1[GCC 4.2.1 (Apple Inc. build 5577)] on darwinType "help", "copyright", "credits" or "license" for more information. my_input = input('enter a number: ') enter a number: 123 type(my_input) 返回可迭代对象,而不是列表在xrange一节中可以看到,某些函数和方法在Python中返回的是可迭代对象,而不像在Python 2中返回列表。由于通常对这些对象只遍历一次,所以这种方式会节省很多内存。然而,如果通过生成器来多次迭代这些对象,效率就不高了。此时我们的确需要列表对象,可以通过list()函数简单的将可迭代对象转成列表。Python 2print 'Python', python_version() print range(3)print type(range(3))Python 2.7.6[0, 1, 2]Python 3print('Python', python_version())print(range(3))print(type(range(3)))print(list(range(3)))Python 3.4.1range(0, 3)[0, 1, 2]下面列出了Python 3中其他不再返回列表的常用函数和方法:zip()map()filter()字典的.key()方法字典的.value()方法字典的.item()方法 __future__模块 [回到目录] Python 3.x引入了一些与Python 2不兼容的关键字和特性,在Python 2中,可以通过内置的__future__模块导入这些新内容。如果你希望在Python 2环境下写的代码也可以在Python 3.x中运行,那么建议使用__future__模块。例如,如果希望在Python 2中拥有Python 3.x的整数除法行为,可以通过下面的语句导入相应的模块。 ? 1 from future import division 下表列出了__future__中其他可导入的特性: 特性 可选版本 强制版本 效果 nested_scopes 2.1.0b1 2.2 PEP 227:Statically Nested Scopes generators 2.2.0a1 2.3 PEP 255:Simple Generators division 2.2.0a2 3.0 PEP 238:Changing the Division Operator absolute_import 2.5.0a1 3.0 PEP 328:Imports: Multi-Line and Absolute/Relative with_statement 2.5.0a1 2.6 PEP 343:The “with” Statement print_function 2.6.0a2 3.0 PEP 3105:Make print a function unicode_literals 2.6.0a2 3.0 PEP 3112:Bytes literals in Python 3000 (来源: https://docs.python.org/2/library/future.html) 示例: ? 1 from platform import python_version print函数 [回到目录] 虽然print语法是Python 3中一个很小的改动,且应该已经广为人知,但依然值得提一下:Python 2中的print语句被Python 3中的print()函数取代,这意味着在Python 3中必须用括号将需要输出的对象括起来。 在Python 2中使用额外的括号也是可以的。但反过来在Python 3中想以Python2的形式不带括号调用print函数时,会触发SyntaxError。 Python 2 ? 1234 print 'Python', python_version()print 'Hello, World!'print('Hello, World!')print "text", ; print 'print more text on the same line' ? 1234 Python 2.7.6Hello, World!Hello, World!text print more text on the same line Python 3 ? 12345 print('Python', python_version())print('Hello, World!') print("some text,", end="") print(' print more text on the same line') ? 123 Python 3.4.1Hello, World!some text, print more text on the same line ? 1 print 'Hello, World!' ? File "", line 1print 'Hello, World!'^SyntaxError: invalid syntax 注意: 在Python中,带不带括号输出”Hello World”都很正常。但如果在圆括号中同时输出多个对象时,就会创建一个元组,这是因为在Python 2中,print是一个语句,而不是函数调用。 ? 123 print 'Python', python_version()print('a', 'b')print 'a', 'b' Python 2.7.7('a', 'b')a b 整数除法 [回到目录] 由于人们常常会忽视Python 3在整数除法上的改动(写错了也不会触发Syntax Error),所以在移植代码或在Python 2中执行Python 3的代码时,需要特别注意这个改动。 所以,我还是会在Python 3的脚本中尝试用float(3)/2或 3/2.0代替3/2,以此来避免代码在Python 2环境下可能导致的错误(或与之相反,在Python 2脚本中用from future import division来使用Python 3的除法)。 Python 2 ? 12345 print 'Python', python_version()print '3 / 2 =', 3 / 2print '3 // 2 =', 3 // 2print '3 / 2.0 =', 3 / 2.0print '3 // 2.0 =', 3 // 2.0 Python 2.7.63 / 2 = 13 // 2 = 13 / 2.0 = 1.53 // 2.0 = 1.0 Python 3 ? 12345 print('Python', python_version())print('3 / 2 =', 3 / 2)print('3 // 2 =', 3 // 2)print('3 / 2.0 =', 3 / 2.0)print('3 // 2.0 =', 3 // 2.0) Python 3.4.13 / 2 = 1.53 // 2 = 13 / 2.0 = 1.53 // 2.0 = 1.0 Unicode [回到目录] Python 2有基于ASCII的str()类型,其可通过单独的unicode()函数转成unicode类型,但没有byte类型。 而在Python 3中,终于有了Unicode(utf-8)字符串,以及两个字节类:bytes和bytearrays。 Python 2 ? 1 print 'Python', python_version() Python 2.7.6 ? 1 print type(unicode('this is like a python3 str type')) ? 1 print type(b'byte type does not exist') ? 1 print 'they are really' + b' the same' they are really the same ? 1 print type(bytearray(b'bytearray oddly does exist though')) Python 3 ? 12 print('Python', python_version())print('strings are now utf-8 u03BCnicou0394é!') Python 3.4.1strings are now utf-8 μnicoΔé! ? 12 print('Python', python_version(), end="")print(' has', type(b' bytes for storing data')) Python 3.4.1 has ? 12 print('and Python', python_version(), end="")print(' also has', type(bytearray(b'bytearrays'))) and Python 3.4.1 also has ? 1 'note that we cannot add a string' + b'bytes for data' TypeError Traceback (most recent call last) in ()----> 1 'note that we cannot add a string' + b'bytes for data' TypeError: Can't convert 'bytes' object to str implicitly xrange [回到目录] 在Python 2.x中,经常会用xrange()创建一个可迭代对象,通常出现在“for循环”或“列表/集合/字典推导式”中。 这种行为与生成器非常相似(如”惰性求值“),但这里的xrange-iterable无尽的,意味着可能在这个xrange上无限迭代。 由于xrange的“惰性求知“特性,如果只需迭代一次(如for循环中),range()通常比xrange()快一些。不过不建议在多次迭代中使用range(),因为range()每次都会在内存中重新生成一个列表。 在Python 3中,range()的实现方式与xrange()函数相同,所以就不存在专用的xrange()(在Python 3中使用xrange()会触发NameError)。 ? 12345678910 import timeit n = 10000def test_range(n): return for i in range(n): pass def test_xrange(n): for i in xrange(n): pass Python 2 ? 1234567 print 'Python', python_version() print 'ntiming range()'%timeit test_range(n) print 'nntiming xrange()'%timeit test_xrange(n) Python 2.7.6 timing range()1000 loops, best of 3: 433 µs per loop timing xrange()1000 loops, best of 3: 350 µs per loop Python 3 ? 1234 print('Python', python_version()) print('ntiming range()')%timeit test_range(n) Python 3.4.1 timing range()1000 loops, best of 3: 520 µs per loop ? 1 print(xrange(10)) NameError Traceback (most recent call last)in ()----> 1 print(xrange(10)) NameError: name 'xrange' is not defined Python 3中的range对象中的__contains__方法 另一个值得一提的是,在Python 3.x中,range有了一个新的__contains__方法。__contains__方法可以有效的加快Python 3.x中整数和布尔型的“查找”速度。 ? 123456789101112 x = 10000000def val_in_range(x, val): return val in range(x) def val_in_xrange(x, val): return val in xrange(x) print('Python', python_version())assert(val_in_range(x, x/2) == True)assert(val_in_range(x, x//2) == True)%timeit val_in_range(x, x/2)%timeit val_in_range(x, x//2) Python 3.4.11 loops, best of 3: 742 ms per loop1000000 loops, best of 3: 1.19 µs per loop 根据上面的timeit的结果,查找整数比查找浮点数要快大约6万倍。但由于Python 2.x中的range或xrange没有__contains__方法,所以在Python 2中的整数和浮点数的查找速度差别不大。 ? 12345678910 print 'Python', python_version() assert(val_in_xrange(x, x/2.0) == True)assert(val_in_xrange(x, x/2) == True)assert(val_in_range(x, x/2) == True)assert(val_in_range(x, x//2) == True)%timeit val_in_xrange(x, x/2.0)%timeit val_in_xrange(x, x/2)%timeit val_in_range(x, x/2.0)%timeit val_in_range(x, x/2) Python 2.7.71 loops, best of 3: 285 ms per loop1 loops, best of 3: 179 ms per loop1 loops, best of 3: 658 ms per loop1 loops, best of 3: 556 ms per loop 下面的代码证明了Python 2.x中没有__contain__方法: ? 12 print('Python', python_version())range.__contains__ Python 3.4.1 ? 12 print('Python', python_version())range.__contains__ Python 2.7.7 AttributeError Traceback (most recent call last) in ()1 print 'Python', python_version()----> 2 range.__contains__ AttributeError: 'builtin_function_or_method' object has no attribute '__contains__' ? 12 print('Python', python_version())xrange.__contains__ Python 2.7.7 AttributeError Traceback (most recent call last)in ()1 print 'Python', python_version()----> 2 xrange.__contains__ AttributeError: type object 'xrange' has no attribute '__contains__' 关于Python 2中xrange()与Python 3中range()之间的速度差异的一点说明: 有读者指出了Python 3中的range()和Python 2中xrange()执行速度有差异。由于这两者的实现方式相同,因此理论上执行速度应该也是相同的。这里的速度差别仅仅是因为Python 3的总体速度就比Python 2慢。 ? 12345 def test_while(): i = 0 while i < 20000: i += 1 return ? 12 print('Python', python_version())%timeit test_while() Python 3.4.1%timeit test_while()100 loops, best of 3: 2.68 ms per loop ? 12 print 'Python', python_version()%timeit test_while() Python 2.7.61000 loops, best of 3: 1.72 ms per loop 触发异常 [回到目录] Python 2支持新旧两种异常触发语法,而Python 3只接受带括号的的语法(不然会触发SyntaxError): Python 2 ? 1 print 'Python', python_version() Python 2.7.6 ? 1 raise IOError, "file error" IOError Traceback (most recent call last) in ()----> 1 raise IOError, "file error" IOError: file error ? 1 raise IOError("file error") IOError Traceback (most recent call last) in ()----> 1 raise IOError("file error") IOError: file error Python 3 ? 1 print('Python', python_version()) Python 3.4.1 ? 1 raise IOError, "file error" File "", line 1raise IOError, "file error"^SyntaxError: invalid syntaxThe proper way to raise an exception in Python 3: ? 12 print('Python', python_version())raise IOError("file error") Python 3.4.1 OSError Traceback (most recent call last) in ()1 print('Python', python_version())----> 2 raise IOError("file error") OSError: file error 异常处理 [回到目录] Python 3中的异常处理也发生了一点变化。在Python 3中必须使用“as”关键字。 Python 2 ? 12345 print 'Python', python_version()try: let_us_cause_a_NameErrorexcept NameError, err: print err, '--> our error message' Python 2.7.6name 'let_us_cause_a_NameError' is not defined --> our error message Python 3 ? 12345 print('Python', python_version())try: let_us_cause_a_NameErrorexcept NameError as err: print(err, '--> our error message') Python 3.4.1name 'let_us_cause_a_NameError' is not defined --> our error message next()函数和.next()方法 [回到目录] 由于会经常用到next()(.next())函数(方法),所以还要提到另一个语法改动(实现方面也做了改动):在Python 2.7.5中,函数形式和方法形式都可以使用,而在Python 3中,只能使用next()函数(试图调用.next()方法会触发AttributeError)。 Python 2 ? 1234 print 'Python', python_version()my_generator = (letter for letter in 'abcdefg')next(my_generator)my_generator.next() Python 2.7.6'b' Python 3 ? 123 print('Python', python_version())my_generator = (letter for letter in 'abcdefg')next(my_generator) Python 3.4.1'a' ? 1 my_generator.next() AttributeError Traceback (most recent call last) in ()----> 1 my_generator.next() AttributeError: 'generator' object has no attribute 'next' For循环变量与全局命名空间泄漏 [回到目录] 好消息是:在Python 3.x中,for循环中的变量不再会泄漏到全局命名空间中了! 这是Python 3.x中做的一个改动,在“What's New In Python 3.0”中有如下描述: “列表推导不再支持[... for var in item1, item2, ...]这样的语法,使用[... for var in (item1, item2, ...)]代替。还要注意列表推导有不同的语义:现在列表推导更接近list()构造器中的生成器表达式这样的语法糖,特别要注意的是,循环控制变量不会再泄漏到循环周围的空间中了。” Python 2 ? 12345678 print 'Python', python_version() i = 1print 'before: i =', i print 'comprehension: ', [i for i in range(5)] print 'after: i =', i Python 2.7.6before: i = 1comprehension: [0, 1, 2, 3, 4]after: i = 4 Python 3 ? 12345678 print('Python', python_version()) i = 1print('before: i =', i) print('comprehension:', [i for i in range(5)]) print('after: i =', i) Python 3.4.1before: i = 1comprehension: [0, 1, 2, 3, 4]after: i = 1 比较无序类型 [回到目录] Python 3中另一个优秀的改动是,如果我们试图比较无序类型,会触发一个TypeError。 Python 2 ? 1234 print 'Python', python_version()print "[1, 2] > 'foo' = ", [1, 2] > 'foo'print "(1, 2) > 'foo' = ", (1, 2) > 'foo'print "[1, 2] > (1, 2) = ", [1, 2] > (1, 2) Python 2.7.6[1, 2] > 'foo' = False(1, 2) > 'foo' = True[1, 2] > (1, 2) = False Python 3 ? 1234 print('Python', python_version())print("[1, 2] > 'foo' = ", [1, 2] > 'foo')print("(1, 2) > 'foo' = ", (1, 2) > 'foo')print("[1, 2] > (1, 2) = ", [1, 2] > (1, 2)) Python 3.4.1 TypeError Traceback (most recent call last) in ()1 print('Python', python_version())----> 2 print("[1, 2] > 'foo' = ", [1, 2] > 'foo')3 print("(1, 2) > 'foo' = ", (1, 2) > 'foo')4 print("[1, 2] > (1, 2) = ", [1, 2] > (1, 2))TypeError: unorderable types: list() > str() 通过input()解析用户的输入 [回到目录] 幸运的是,Python 3改进了input()函数,这样该函数就会总是将用户的输入存储为str对象。在Python 2中,为了避免读取非字符串类型会发生的一些危险行为,不得不使用raw_input()代替input()。 Python 2 ? 1234567891011121314151617 Python 2.7.6[GCC 4.0.1 (Apple Inc. build 5493)] on darwinType "help", "copyright", "credits" or "license" for more information. my_input = input('enter a number: ') enter a number: 123 type(my_input) my_input = raw_input('enter a number: ') enter a number: 123 type(my_input) Python 3 ? 12345678 Python 3.4.1[GCC 4.2.1 (Apple Inc. build 5577)] on darwinType "help", "copyright", "credits" or "license" for more information. my_input = input('enter a number: ') enter a number: 123 type(my_input) 返回可迭代对象,而不是列表 [回到目录] 在xrange一节中可以看到,某些函数和方法在Python中返回的是可迭代对象,而不像在Python 2中返回列表。 由于通常对这些对象只遍历一次,所以这种方式会节省很多内存。然而,如果通过生成器来多次迭代这些对象,效率就不高了。 此时我们的确需要列表对象,可以通过list()函数简单的将可迭代对象转成列表。 Python 2 ? 1234 print 'Python', python_version() print range(3)print type(range(3)) Python 2.7.6[0, 1, 2] Python 3 ? 1234 print('Python', python_version())print(range(3))print(type(range(3)))print(list(range(3))) Python 3.4.1range(0, 3)[0, 1, 2] 下面列出了Python 3中其他不再返回列表的常用函数和方法:•zip()•map()•filter()•字典的.key()方法•字典的.value()方法•字典的.item()方法 更多关于Python 2和Python 3的文章 [回到目录] 下面列出了其他一些可以进一步了解Python 2和Python 3的优秀文章, //迁移到 Python 3•Should I use Python 2 or Python 3 for my development activity?•What's New In Python 3.0•Porting to Python 3•Porting Python 2 Code to Python 3•How keep Python 3 moving forward // 对Python 3的褒与贬•10 awesome features of Python that you can't use because you refuse to upgrade to Python 3•关于你不想知道的所有Python3 unicode特性•Python 3 正在毁灭 Python•Python 3 能振兴 Python•Python 3 is fine

xuning715 2019-12-02 01:10:35 0 浏览量 回答数 0

回答

流处理,听起来很高大上啊,其实就是分块读取。有这么一些情况,有一个很大的几个G的文件,没办法一次处理,那么就分批次处理,一次处理1百万行,接着处理下1百万行,慢慢地总是能处理完的。 使用类似迭代器的方式 data=pd.read_csv(file, chunksize=1000000)for sub_df in data: print('do something in sub_df here') 1234索引 Series和DataFrame都是有索引的,索引的好处是快速定位,在涉及到两个Series或DataFrame时可以根据索引自动对齐,比如日期自动对齐,这样可以省去很多事。 缺失值 pd.isnull(obj)obj.isnull()12将字典转成数据框,并赋予列名,索引 DataFrame(data, columns=['col1','col2','col3'...], index = ['i1','i2','i3'...]) 12查看列名 DataFrame.columns 查看索引 DataFrame.index 重建索引 obj.reindex(['a','b','c','d','e'...], fill_value=0] 按给出的索引顺序重新排序,而不是替换索引。如果索引没有值,就用0填充 就地修改索引 data.index=data.index.map(str.upper)12345列顺序重排(也是重建索引) DataFrame.reindex[columns=['col1','col2','col3'...])` 也可以同时重建index和columns DataFrame.reindex[index=['a','b','c'...],columns=['col1','col2','col3'...])12345重建索引的快捷键 DataFrame.ix[['a','b','c'...],['col1','col2','col3'...]]1重命名轴索引 data.rename(index=str.title,columns=str.upper) 修改某个索引和列名,可以通过传入字典 data.rename(index={'old_index':'new_index'}, columns={'old_col':'new_col'}) 12345查看某一列 DataFrame['state'] 或 DataFrame.state1查看某一行 需要用到索引 DataFrame.ix['index_name']1添加或删除一列 DataFrame['new_col_name'] = 'char_or_number' 删除行 DataFrame.drop(['index1','index2'...]) 删除列 DataFrame.drop(['col1','col2'...],axis=1) 或 del DataFrame['col1']1234567DataFrame选择子集 类型 说明obj[val] 选择一列或多列obj.ix[val] 选择一行或多行obj.ix[:,val] 选择一列或多列obj.ix[val1,val2] 同时选择行和列reindx 对行和列重新索引icol,irow 根据整数位置选取单列或单行get_value,set_value 根据行标签和列标签选择单个值针对series obj[['a','b','c'...]]obj['b':'e']=512针对dataframe 选择多列 dataframe[['col1','col2'...]] 选择多行 dataframe[m:n] 条件筛选 dataframe[dataframe['col3'>5]] 选择子集 dataframe.ix[0:3,0:5]1234567891011dataframe和series的运算 会根据 index 和 columns 自动对齐然后进行运算,很方便啊 方法 说明add 加法sub 减法div 除法mul 乘法 没有数据的地方用0填充空值 df1.add(df2,fill_value=0) dataframe 与 series 的运算 dataframe - series 规则是: -------- v 指定轴方向 dataframe.sub(series,axis=0)规则是:-------- --- | | | | ----->| | | | | | | | | | | | -------- ---12345678910111213141516171819202122apply函数 f=lambda x:x.max()-x.min() 默认对每一列应用 dataframe.apply(f) 如果需要对每一行分组应用 dataframe.apply(f,axis=1)1234567排序和排名 默认根据index排序,axis = 1 则根据columns排序 dataframe.sort_index(axis=0, ascending=False) 根据值排序 dataframe.sort_index(by=['col1','col2'...]) 排名,给出的是rank值 series.rank(ascending=False) 如果出现重复值,则取平均秩次 在行或列上面的排名 dataframe.rank(axis=0)12345678910111213描述性统计 方法 说明count 计数describe 给出各列的常用统计量min,max 最大最小值argmin,argmax 最大最小值的索引位置(整数)idxmin,idxmax 最大最小值的索引值quantile 计算样本分位数sum,mean 对列求和,均值mediam 中位数mad 根据平均值计算平均绝对离差var,std 方差,标准差skew 偏度(三阶矩)Kurt 峰度(四阶矩)cumsum 累积和Cummins,cummax 累计组大致和累计最小值cumprod 累计积diff 一阶差分pct_change 计算百分数变化唯一值,值计数,成员资格 obj.unique()obj.value_count()obj.isin(['b','c'])123处理缺失值 过滤缺失值 只要有缺失值就丢弃这一行 dataframe.dropna() 要求全部为缺失才丢弃这一行 dataframe.dropna(how='all') 根据列来判断 dataframe.dropna(how='all',axis=1) 填充缺失值 1.用0填充 df.fillna(0) 2.不同的列用不同的值填充 df.fillna({1:0.5, 3:-1}) 3.用均值填充 df.fillna(df.mean()) 此时axis参数同前面, 123456789101112131415161718192021将列转成行索引 df.set_index(['col1','col2'...])1数据清洗,重塑 合并数据集 取 df1,df2 都有的部分,丢弃没有的 默认是inner的连接方式 pd.merge(df1,df2, how='inner') 如果df1,df2的连接字段名不同,则需要特别指定 pd.merge(df1,df2,left_on='l_key',right_on='r_key') 其他的连接方式有 left,right, outer等。 如果dataframe是多重索引,根据多个键进行合并 pd.merge(left, right, on=['key1','key2'],how = 'outer') 合并后如果有重复的列名,需要添加后缀 pd.merge(left, right, on='key1', suffixes=('_left','_right'))1234567891011121314索引上的合并 针对dataframe中的连接键不是列名,而是索引名的情况。 pd.merge(left, right, left_on = 'col_key', right_index=True) 即左边的key是列名,右边的key是index。 多重索引 pd.merge(left, right, left_on=['key1','key2'], right_index=True)123456dataframe的join方法 实现按索引合并。 其实这个join方法和数据库的join函数是以一样的理解 left.join(right, how='outer') 一次合并多个数据框 left.join([right1,right2],how='outer')123456轴向连接(更常用) 连接:concatenation 绑定:binding 堆叠:stacking列上的连接 np.concatenation([df1,df2],axis=1) #np包pd.concat([df1,df2], axis=1) #pd包 和R语言中的 cbind 是一样的 如果axis=0,则和 rbind 是一样的 索引对齐,没有的就为空 join='inner' 得到交集 pd.concat([df1,df2], axis=1, join='innner') keys 参数,还没看明白 ignore_index=True,如果只是简单的合并拼接而不考虑索引问题。 pd.concat([df1,df2],ignore_index=True)123456789101112131415合并重复数据 针对可能有索引全部或者部分重叠的两个数据集 填充因为合并时索引赵成的缺失值 where函数 where即if-else函数 np.where(isnull(a),b,a)12combine_first方法 如果a中值为空,就用b中的值填补 a[:-2].combine_first(b[2:]) combine_first函数即对数据打补丁,用df2的数据填充df1中的缺失值 df1.combine_first(df2)12345重塑层次化索引 stact:将数据转为长格式,即列旋转为行 unstack:转为宽格式,即将行旋转为列result=data.stack()result.unstack()12长格式转为宽格式 pivoted = data.pivot('date','item','value') 前两个参数分别是行和列的索引名,最后一个参数则是用来填充dataframe的数据列的列名。如果忽略最后一个参数,得到的dataframe会带有层次化的列。 123透视表 table = df.pivot_table(values=["Price","Quantity"], index=["Manager","Rep"], aggfunc=[np.sum,np.mean], margins=True)) values:需要对哪些字段应用函数 index:透视表的行索引(row) columns:透视表的列索引(column) aggfunc:应用什么函数 fill_value:空值填充 margins:添加汇总项 然后可以对透视表进行筛选 table.query('Manager == ["Debra Henley"]')table.query('Status == ["pending","won"]')123456789101112131415移除重复数据 判断是否重复 data.duplicated()` 移除重复数据 data.drop_duplicated() 对指定列判断是否存在重复值,然后删除重复数据 data.drop_duplicated(['key1'])123456789交叉表 是一种用于计算分组频率的特殊透视表. 注意,只对离散型的,分类型的,字符型的有用,连续型数据是不能计算频率这种东西的。 pd.crosstab(df.col1, df.col2, margins=True)1类似vlookup函数 利用函数或映射进行数据转换 1.首先定义一个字典 meat_to_animal={ 'bacon':'pig', 'pulled pork':'pig', 'honey ham':'cow' } 2.对某一列应用一个函数,或者字典,顺便根据这一列的结果创建新列 data['new_col']=data['food'].map(str.lower).map(meat_to_animal)123456789替换值 data.replace(-999,np.na) 多个值的替换 data.replace([-999,-1000],np.na) 对应替换 data.replace([-999,-1000],[np.na,0]) 对应替换也可以传入一个字典 data.replace({-999:np.na,-1000:0})123456789离散化 定义分割点 简单分割(等宽分箱) s=pd.Series(range(100))pd.cut(s, bins=10, labels=range(10)) bins=[20,40,60,80,100] 切割 cats = pd.cut(series,bins) 查看标签 cats.labels 查看水平(因子) cats.levels 区间计数 pd.value_count(cats) 自定义分区的标签 group_names=['youth','youngAdult','MiddleAge','Senior']pd.cut(ages,bins,labels=group_names)1234567891011121314151617181920212223分位数分割 data=np.random.randn(1000)pd.qcut(data,4) #四分位数 自定义分位数,包含端点 pd.qcut(data,[0,0.3,0.5,0.9,1])12345异常值 查看各个统计量 data.describe() 对某一列 col=data[3]col[np.abs(col)>3] 选出全部含有“超过3或-3的值的行 data[(np.abs(data)>3).any(1)] 异常值替换 data[np.abs(data)>3]=np.sign(data)*312345678910111213抽样 随机抽取k行 df.take(np.random.permutation(len(df))[:k]) 随机抽取k行,但是k可能大于df的行数 可以理解为过抽样了 df.take(np.random.randint(0,len(df),size=k))1234567数据摊平处理 相当于将类别属性转成因子类型,比如是否有车,这个字段有3个不同的值,有,没有,过段时间买,那么将会被编码成3个字段,有车,没车,过段时间买车,每个字段用0-1二值填充变成数值型。 对摊平的数据列增加前缀 dummies = pd.get_dummies(df['key'],prefix='key') 将摊平产生的数据列拼接回去 df[['data1']].join(dummies)12345字符串操作 拆分 strings.split(',') 根据正则表达式切分 re.split('s+',strings) 连接 'a'+'b'+'c'...或者'+'.join(series) 判断是否存在 's' in strings`strings.find('s') 计数 strings.count(',') 替换 strings.replace('old','new') 去除空白字符 s.strip()12345678910111213141516171819202122232425正则表达式 正则表达式需要先编译匹配模式,然后才去匹配查找,这样能节省大量的CPU时间。 re.complie:编译 findall:匹配所有 search:只返回第一个匹配项的起始和结束地址 match:值匹配字符串的首部 sub:匹配替换,如果找到就替换 原始字符串 strings = 'sdf@153.com,dste@qq.com,sor@gmail.com' 编译匹配模式,IGNORECASE可以在使用的时候对大小写不敏感 pattern = r'[A-Z0-9._%+-]+@[A-Z0-9.-]+\.[A-Z]{2,4}'regex = re.compile(pattern,flags=re.IGNORECASE) 匹配所有 regex.findall(strings) 使用search m = regex.search(strings) #获取匹配的地址strings[m.start():m.end()] 匹配替换 regex.sub('new_string', strings)12345678910111213141516根据模式再切分 将模式切分,也就是将匹配到的进一步切分,通过pattern中的括号实现. pattern = r'([A-Z0-9._%+-]+)@([A-Z0-9.-]+)\.([A-Z]{2,4})'regex = re.compile(pattern)regex.findall(strings) 如果使用match m=regex.match(string)m.groups() 效果是这样的 suzyu123@163.com --> [(suzyu123, 163, com)] 获取 list-tuple 其中的某一列 matches.get(i)12345678910111213分组聚合,计算 group_by技术 根据多个索引分组,然后计算均值 means = df['data1'].groupby([df['index1'],df['index2']).mean() 展开成透视表格式 means.unstack()12345分组后价将片段做成一个字典 pieces = dict(list(df.groupby('index1'))) pieces['b']123groupby默认是对列(axis=0)分组,也可以在行(axis=1)上分组 语法糖,groupby的快捷函数 df.groupby('index1')['col_names']df.groupby('index1')[['col_names']] 是下面代码的语法糖 df['col_names'].groupby(df['index1']) df.groupby(['index1','index2'])['col_names'].mean()1234567通过字典或series进行分组 people = DataFrame(np.random.randn(5, 5), columns=['a', 'b', 'c', 'd', 'e'], index=['Joe', 'Steve', 'Wes', 'Jim','Travis']) 选择部分设为na people.ix[2:3,['b','c']]=np.na mapping = {'a': 'red', 'b': 'red', 'c': 'blue', 'd': 'blue', 'e': 'red', 'f' : 'orange'} people.groupby(mapping,axis=1).sum()1234567891011通过函数进行分组 根据索引的长度进行分组 people.groupby(len).sum()12数据聚合 使用自定义函数 对所有的数据列使用自定义函数 df.groupby('index1').agg(myfunc) 使用系统函数 df.groupby('index1')['data1']describe()12345根据列分组应用多个函数 分组 grouped = df.groupby(['col1','col2']) 选择多列,对每一列应用多个函数 grouped['data1','data2'...].agg(['mean','std','myfunc'])12345对不同列使用不同的函数 grouped = df.groupby(['col1','col2']) 传入一个字典,对不同的列使用不同的函数 不同的列可以应用不同数量的函数 grouped.agg({'data1':['min','max','mean','std'], 'data2':'sum'}) 123456分组计算后重命名列名 grouped = df.groupby(['col1','col2']) grouped.agg({'data1':[('min','max','mean','std'),('d_min','d_max','d_mean','d_std')], 'data2':'sum'}) 1234返回的聚合数据不要索引 df.groupby(['sex','smoker'], as_index=False).mean()1分组计算结果添加前缀 对计算后的列名添加前缀 df.groupby('index1').mean().add_prefix('mean_')12将分组计算后的值替换到原数据框 将函数应用到各分组,再将分组计算的结果代换原数据框的值 也可以使用自定义函数 df.groupby(['index1','index2'...]).transform(np.mean)123更一般化的apply函数 df.groupby(['col1','col2'...]).apply(myfunc) df.groupby(['col1','col2'...]).apply(['min','max','mean','std'])123禁用分组键 分组键会跟原始对象的索引共同构成结果对象中的层次化索引 df.groupby('smoker', group_keys=False).apply(mean)1分组索引转成df的列 某些情况下,groupby的as_index=False参数并没有什么用,得到的还是一个series,这种情况一般是尽管分组了,但是计算需要涉及几列,最后得到的还是series,series的index是层次化索引。这里将series转成dataframe,series的层次化索引转成dataframe的列。 def fmean(df): """需要用两列才能计算最后的结果""" skus=len(df['sku'].unique()) sums=df['salecount'].sum() return sums/skus 尽管禁用分组键,得到的还是series salemean=data.groupby(by=['season','syear','smonth'],as_index=False).apply(fmean) 将series转成dataframe,顺便设置索引 sub_df = pd.DataFrame(salemean.index.tolist(),columns=salemean.index.names,index=salemean.index) 将groupby的结果和sub_df合并 sub_df['salemean']=salemean12345678910111213桶分析与分位数 对数据切分段,然后对每一分段应用函数 frame = DataFrame({'col1':np.random.randn(1000), 'col2':np.random.randn(1000)}) 数据分段,创建分段用的因子 返回每一元素是属于哪一分割区间 factor = pd.cut(frame.col1, 4) 分组计算,然后转成数据框形式 grouped = frame.col2.groupby(factor)grouped.apply(myfunc).unstack()12345678910用分组的均值填充缺失值 自定义函数 fill_mean= lambda x:x.fillna(x.mean()) 分组填充 df.groupby(group_key).apply(fill_mean)12345分组后不同的数据替换不同的值 定义字典 fill_value = {'east':0.5, 'west':-1} 定义函数 fill_func = lambda x:x.fillna(fill_value(x.name)) 分组填充 df.groupby(['index1','index2'...]).apply(fill_func)12345678sql操作 有时候觉得pandas很方便,但是有时候却很麻烦,不如SQL方便。因此pandas中也有一些例子,用pandas实现SQL的功能,简单的就不说了,下面说些复杂点的操作。 之所以说这个复杂的语句,是因为不想将这些数据操作分写在不同的语句中,而是从头到尾连续编码实现一个功能。 SQL复杂操作用到的主要函数是assign,简单说其实和join的功能是一样的,根据df1,df2的索引值来将df2拼接到df1上。 两个函数是query,也听方便的。 有一批销量数据,筛选出那些有2个月以上的销量产品的数据,说白了就是剔除那些新上市产品的数据 方法是先统计每个产品的数据量,然后选出那些数据量>2的产品,再在数据表中选择这些产品 sku smonth a 1 a 2 a 3 a 4 b 5 b 6 b 7 b 8 c 9 c 10 按sku分组,统计smonth的次数,拼接到salecount中,然后查询cnt>2的 salecount.assign(cnt=salecount.groupby(['sku'])['smonth'].count()).query('cnt>2')

xuning715 2019-12-02 01:10:39 0 浏览量 回答数 0

回答

Python 正则表达式正则表达式是一个特殊的字符序列,它能帮助你方便的检查一个字符串是否与某种模式匹配。Python 自1.5版本起增加了re 模块,它提供 Perl 风格的正则表达式模式。re 模块使 Python 语言拥有全部的正则表达式功能。compile 函数根据一个模式字符串和可选的标志参数生成一个正则表达式对象。该对象拥有一系列方法用于正则表达式匹配和替换。re 模块也提供了与这些方法功能完全一致的函数,这些函数使用一个模式字符串做为它们的第一个参数。本章节主要介绍Python中常用的正则表达式处理函数。re.match函数re.match 尝试从字符串的起始位置匹配一个模式,如果不是起始位置匹配成功的话,match()就返回none。函数语法:re.match(pattern, string, flags=0)函数参数说明:参数 描述pattern 匹配的正则表达式string 要匹配的字符串。flags 标志位,用于控制正则表达式的匹配方式,如:是否区分大小写,多行匹配等等。参见:正则表达式修饰符 - 可选标志匹配成功re.match方法返回一个匹配的对象,否则返回None。我们可以使用group(num) 或 groups() 匹配对象函数来获取匹配表达式。匹配对象方法 描述group(num=0) 匹配的整个表达式的字符串,group() 可以一次输入多个组号,在这种情况下它将返回一个包含那些组所对应值的元组。groups() 返回一个包含所有小组字符串的元组,从 1 到 所含的小组号。实例 !/usr/bin/python -- coding: UTF-8 -- import reprint(re.match('www', 'www.runoob.com').span()) # 在起始位置匹配print(re.match('com', 'www.runoob.com')) # 不在起始位置匹配以上实例运行输出结果为:(0, 3)None实例 !/usr/bin/python import re line = "Cats are smarter than dogs" matchObj = re.match( r'(.) are (.?) .*', line, re.M|re.I) if matchObj: print "matchObj.group() : ", matchObj.group() print "matchObj.group(1) : ", matchObj.group(1) print "matchObj.group(2) : ", matchObj.group(2)else: print "No match!!"以上实例执行结果如下:matchObj.group() : Cats are smarter than dogsmatchObj.group(1) : CatsmatchObj.group(2) : smarterre.search方法re.search 扫描整个字符串并返回第一个成功的匹配。函数语法:re.search(pattern, string, flags=0)函数参数说明:参数 描述pattern 匹配的正则表达式string 要匹配的字符串。flags 标志位,用于控制正则表达式的匹配方式,如:是否区分大小写,多行匹配等等。匹配成功re.search方法返回一个匹配的对象,否则返回None。我们可以使用group(num) 或 groups() 匹配对象函数来获取匹配表达式。匹配对象方法 描述group(num=0) 匹配的整个表达式的字符串,group() 可以一次输入多个组号,在这种情况下它将返回一个包含那些组所对应值的元组。groups() 返回一个包含所有小组字符串的元组,从 1 到 所含的小组号。实例 !/usr/bin/python -- coding: UTF-8 -- import reprint(re.search('www', 'www.runoob.com').span()) # 在起始位置匹配print(re.search('com', 'www.runoob.com').span()) # 不在起始位置匹配以上实例运行输出结果为:(0, 3)(11, 14)实例 !/usr/bin/python import re line = "Cats are smarter than dogs"; searchObj = re.search( r'(.) are (.?) .*', line, re.M|re.I) if searchObj: print "searchObj.group() : ", searchObj.group() print "searchObj.group(1) : ", searchObj.group(1) print "searchObj.group(2) : ", searchObj.group(2)else: print "Nothing found!!"以上实例执行结果如下:searchObj.group() : Cats are smarter than dogssearchObj.group(1) : CatssearchObj.group(2) : smarterre.match与re.search的区别re.match只匹配字符串的开始,如果字符串开始不符合正则表达式,则匹配失败,函数返回None;而re.search匹配整个字符串,直到找到一个匹配。实例 !/usr/bin/python import re line = "Cats are smarter than dogs"; matchObj = re.match( r'dogs', line, re.M|re.I)if matchObj: print "match --> matchObj.group() : ", matchObj.group()else: print "No match!!" matchObj = re.search( r'dogs', line, re.M|re.I)if matchObj: print "search --> matchObj.group() : ", matchObj.group()else: print "No match!!"以上实例运行结果如下:No match!!search --> matchObj.group() : dogs检索和替换Python 的 re 模块提供了re.sub用于替换字符串中的匹配项。语法:re.sub(pattern, repl, string, count=0, flags=0)参数:pattern : 正则中的模式字符串。repl : 替换的字符串,也可为一个函数。string : 要被查找替换的原始字符串。count : 模式匹配后替换的最大次数,默认 0 表示替换所有的匹配。实例 !/usr/bin/python -- coding: UTF-8 -- import re phone = "2004-959-559 # 这是一个国外电话号码" 删除字符串中的 Python注释 num = re.sub(r'#.*$', "", phone)print "电话号码是: ", num 删除非数字(-)的字符串 num = re.sub(r'D', "", phone)print "电话号码是 : ", num以上实例执行结果如下:电话号码是: 2004-959-559 电话号码是 : 2004959559repl 参数是一个函数以下实例中将字符串中的匹配的数字乘以 2:实例 !/usr/bin/python -- coding: UTF-8 -- import re 将匹配的数字乘以 2 def double(matched): value = int(matched.group('value')) return str(value * 2) s = 'A23G4HFD567'print(re.sub('(?Pd+)', double, s))执行输出结果为:A46G8HFD1134re.compile 函数compile 函数用于编译正则表达式,生成一个正则表达式( Pattern )对象,供 match() 和 search() 这两个函数使用。语法格式为:re.compile(pattern[, flags])参数:pattern : 一个字符串形式的正则表达式flags : 可选,表示匹配模式,比如忽略大小写,多行模式等,具体参数为:re.I 忽略大小写re.L 表示特殊字符集 w, W, b, B, s, S 依赖于当前环境re.M 多行模式re.S 即为 . 并且包括换行符在内的任意字符(. 不包括换行符)re.U 表示特殊字符集 w, W, b, B, d, D, s, S 依赖于 Unicode 字符属性数据库re.X 为了增加可读性,忽略空格和 # 后面的注释实例实例 import repattern = re.compile(r'd+') # 用于匹配至少一个数字m = pattern.match('one12twothree34four') # 查找头部,没有匹配print m None m = pattern.match('one12twothree34four', 2, 10) # 从'e'的位置开始匹配,没有匹配print m None m = pattern.match('one12twothree34four', 3, 10) # 从'1'的位置开始匹配,正好匹配print m # 返回一个 Match 对象 <_sre.SRE_Match object at 0x10a42aac0> m.group(0) # 可省略 0 '12' m.start(0) # 可省略 0 3 m.end(0) # 可省略 0 5 m.span(0) # 可省略 0 (3, 5)在上面,当匹配成功时返回一个 Match 对象,其中:group([group1, …]) 方法用于获得一个或多个分组匹配的字符串,当要获得整个匹配的子串时,可直接使用 group() 或 group(0);start([group]) 方法用于获取分组匹配的子串在整个字符串中的起始位置(子串第一个字符的索引),参数默认值为 0;end([group]) 方法用于获取分组匹配的子串在整个字符串中的结束位置(子串最后一个字符的索引+1),参数默认值为 0;span([group]) 方法返回 (start(group), end(group))。再看看一个例子:实例 import repattern = re.compile(r'([a-z]+) ([a-z]+)', re.I) # re.I 表示忽略大小写m = pattern.match('Hello World Wide Web')print m # 匹配成功,返回一个 Match 对象 <_sre.SRE_Match object at 0x10bea83e8> m.group(0) # 返回匹配成功的整个子串 'Hello World' m.span(0) # 返回匹配成功的整个子串的索引 (0, 11) m.group(1) # 返回第一个分组匹配成功的子串 'Hello' m.span(1) # 返回第一个分组匹配成功的子串的索引 (0, 5) m.group(2) # 返回第二个分组匹配成功的子串 'World' m.span(2) # 返回第二个分组匹配成功的子串 (6, 11) m.groups() # 等价于 (m.group(1), m.group(2), ...) ('Hello', 'World') m.group(3) # 不存在第三个分组 Traceback (most recent call last): File "", line 1, in IndexError: no such groupfindall在字符串中找到正则表达式所匹配的所有子串,并返回一个列表,如果没有找到匹配的,则返回空列表。注意: match 和 search 是匹配一次 findall 匹配所有。语法格式为:findall(string[, pos[, endpos]])参数:string : 待匹配的字符串。pos : 可选参数,指定字符串的起始位置,默认为 0。endpos : 可选参数,指定字符串的结束位置,默认为字符串的长度。查找字符串中的所有数字:实例 -- coding:UTF8 -- import re pattern = re.compile(r'd+') # 查找数字result1 = pattern.findall('runoob 123 google 456')result2 = pattern.findall('run88oob123google456', 0, 10) print(result1)print(result2)输出结果:['123', '456']['88', '12']re.finditer和 findall 类似,在字符串中找到正则表达式所匹配的所有子串,并把它们作为一个迭代器返回。re.finditer(pattern, string, flags=0)参数:参数 描述pattern 匹配的正则表达式string 要匹配的字符串。flags 标志位,用于控制正则表达式的匹配方式,如:是否区分大小写,多行匹配等等。参见:正则表达式修饰符 - 可选标志实例 -- coding: UTF-8 -- import re it = re.finditer(r"d+","12a32bc43jf3") for match in it: print (match.group() ) 输出结果:12 32 43 3re.splitsplit 方法按照能够匹配的子串将字符串分割后返回列表,它的使用形式如下:re.split(pattern, string[, maxsplit=0, flags=0])参数:参数 描述pattern 匹配的正则表达式string 要匹配的字符串。maxsplit 分隔次数,maxsplit=1 分隔一次,默认为 0,不限制次数。flags 标志位,用于控制正则表达式的匹配方式,如:是否区分大小写,多行匹配等等。参见:正则表达式修饰符 - 可选标志实例 import rere.split('W+', 'runoob, runoob, runoob.')['runoob', 'runoob', 'runoob', '']re.split('(W+)', ' runoob, runoob, runoob.') ['', ' ', 'runoob', ', ', 'runoob', ', ', 'runoob', '.', ''] re.split('W+', ' runoob, runoob, runoob.', 1) ['', 'runoob, runoob, runoob.'] re.split('a*', 'hello world') # 对于一个找不到匹配的字符串而言,split 不会对其作出分割 ['hello world']正则表达式对象re.RegexObjectre.compile() 返回 RegexObject 对象。re.MatchObjectgroup() 返回被 RE 匹配的字符串。start() 返回匹配开始的位置end() 返回匹配结束的位置span() 返回一个元组包含匹配 (开始,结束) 的位置正则表达式修饰符 - 可选标志正则表达式可以包含一些可选标志修饰符来控制匹配的模式。修饰符被指定为一个可选的标志。多个标志可以通过按位 OR(|) 它们来指定。如 re.I | re.M 被设置成 I 和 M 标志:修饰符 描述re.I 使匹配对大小写不敏感re.L 做本地化识别(locale-aware)匹配re.M 多行匹配,影响 ^ 和 $re.S 使 . 匹配包括换行在内的所有字符re.U 根据Unicode字符集解析字符。这个标志影响 w, W, b, B.re.X 该标志通过给予你更灵活的格式以便你将正则表达式写得更易于理解。正则表达式模式模式字符串使用特殊的语法来表示一个正则表达式:字母和数字表示他们自身。一个正则表达式模式中的字母和数字匹配同样的字符串。多数字母和数字前加一个反斜杠时会拥有不同的含义。标点符号只有被转义时才匹配自身,否则它们表示特殊的含义。反斜杠本身需要使用反斜杠转义。由于正则表达式通常都包含反斜杠,所以你最好使用原始字符串来表示它们。模式元素(如 r't',等价于 '\t')匹配相应的特殊字符。下表列出了正则表达式模式语法中的特殊元素。如果你使用模式的同时提供了可选的标志参数,某些模式元素的含义会改变。模式 描述^ 匹配字符串的开头$ 匹配字符串的末尾。. 匹配任意字符,除了换行符,当re.DOTALL标记被指定时,则可以匹配包括换行符的任意字符。[...] 用来表示一组字符,单独列出:[amk] 匹配 'a','m'或'k'1 不在[]中的字符:2 匹配除了a,b,c之外的字符。re* 匹配0个或多个的表达式。re+ 匹配1个或多个的表达式。re? 匹配0个或1个由前面的正则表达式定义的片段,非贪婪方式re{ n} 精确匹配 n 个前面表达式。例如, o{2} 不能匹配 "Bob" 中的 "o",但是能匹配 "food" 中的两个 o。re{ n,} 匹配 n 个前面表达式。例如, o{2,} 不能匹配"Bob"中的"o",但能匹配 "foooood"中的所有 o。"o{1,}" 等价于 "o+"。"o{0,}" 则等价于 "o*"。re{ n, m} 匹配 n 到 m 次由前面的正则表达式定义的片段,贪婪方式a| b 匹配a或b(re) 匹配括号内的表达式,也表示一个组(?imx) 正则表达式包含三种可选标志:i, m, 或 x 。只影响括号中的区域。(?-imx) 正则表达式关闭 i, m, 或 x 可选标志。只影响括号中的区域。(?: re) 类似 (...), 但是不表示一个组(?imx: re) 在括号中使用i, m, 或 x 可选标志(?-imx: re) 在括号中不使用i, m, 或 x 可选标志(?#...) 注释.(?= re) 前向肯定界定符。如果所含正则表达式,以 ... 表示,在当前位置成功匹配时成功,否则失败。但一旦所含表达式已经尝试,匹配引擎根本没有提高;模式的剩余部分还要尝试界定符的右边。(?! re) 前向否定界定符。与肯定界定符相反;当所含表达式不能在字符串当前位置匹配时成功(?> re) 匹配的独立模式,省去回溯。w 匹配字母数字及下划线W 匹配非字母数字及下划线s 匹配任意空白字符,等价于 [tnrf].S 匹配任意非空字符d 匹配任意数字,等价于 [0-9].D 匹配任意非数字A 匹配字符串开始Z 匹配字符串结束,如果是存在换行,只匹配到换行前的结束字符串。z 匹配字符串结束G 匹配最后匹配完成的位置。b 匹配一个单词边界,也就是指单词和空格间的位置。例如, 'erb' 可以匹配"never" 中的 'er',但不能匹配 "verb" 中的 'er'。B 匹配非单词边界。'erB' 能匹配 "verb" 中的 'er',但不能匹配 "never" 中的 'er'。n, t, 等. 匹配一个换行符。匹配一个制表符。等1...9 匹配第n个分组的内容。10 匹配第n个分组的内容,如果它经匹配。否则指的是八进制字符码的表达式。正则表达式实例字符匹配实例 描述python 匹配 "python".字符类实例 描述[Pp]ython 匹配 "Python" 或 "python"rub[ye] 匹配 "ruby" 或 "rube"[aeiou] 匹配中括号内的任意一个字母[0-9] 匹配任何数字。类似于 [0123456789][a-z] 匹配任何小写字母[A-Z] 匹配任何大写字母[a-zA-Z0-9] 匹配任何字母及数字3 除了aeiou字母以外的所有字符4 匹配除了数字外的字符特殊字符类实例 描述. 匹配除 "n" 之外的任何单个字符。要匹配包括 'n' 在内的任何字符,请使用象 '[.n]' 的模式。d 匹配一个数字字符。等价于 [0-9]。D 匹配一个非数字字符。等价于 4。s 匹配任何空白字符,包括空格、制表符、换页符等等。等价于 [ fnrtv]。S 匹配任何非空白字符。等价于 5。w 匹配包括下划线的任何单词字符。等价于'[A-Za-z0-9_]'。W 匹配任何非单词字符。等价于 '6'。 Python 面向对象 Python CGI编程 1 篇笔记 jim 264*7522@qq.com正则表达式实例: !/usr/bin/python import reline = "Cats are smarter than dogs"matchObj = re.match( r'(.) are (.?) .*', line, re.M|re.I)if matchObj: print "matchObj.group() : ", matchObj.group() print "matchObj.group(1) : ", matchObj.group(1) print "matchObj.group(2) : ", matchObj.group(2) else: print "No match!!" 正则表达式:r'(.) are (.?) .*'解析:首先,这是一个字符串,前面的一个 r 表示字符串为非转义的原始字符串,让编译器忽略反斜杠,也就是忽略转义字符。但是这个字符串里没有反斜杠,所以这个 r 可有可无。 (.) 第一个匹配分组,. 代表匹配除换行符之外的所有字符。 (.?) 第二个匹配分组,.? 后面多个问号,代表非贪婪模式,也就是说只匹配符合条件的最少字符 后面的一个 .* 没有括号包围,所以不是分组,匹配效果和第一个一样,但是不计入匹配结果中。matchObj.group() 等同于 matchObj.group(0),表示匹配到的完整文本字符matchObj.group(1) 得到第一组匹配结果,也就是(.*)匹配到的matchObj.group(2) 得到第二组匹配结果,也就是(.*?)匹配到的因为只有匹配结果中只有两组,所以如果填 3 时会报错。 ... ↩ abc ↩ aeiou ↩ 0-9 ↩ fnrtv ↩ A-Za-z0-9_ ↩

xuning715 2019-12-02 01:10:40 0 浏览量 回答数 0

回答

迭代法  迭代法也称辗转法,是一种不断用变量的旧值递推新值的过程,跟迭代法相对应的是直接法(或者称为一次解法),即一次性解决问题。迭代法又分为精确迭代和近似迭代。“二分法”和“牛顿迭代法”属于近似迭代法。   迭代算法是用计算机解决问题的一种基本方法。它利用计算机运算速度快、适合做重复性操作的特点,让计算机对一组指令(或一定步骤)进行重复执行,在每次执行这组指令(或这些步骤)时,都从变量的原值推出它的一个新值。   利用迭代算法解决问题,需要做好以下三个方面的工作:   一、确定迭代变量。在可以用迭代算法解决的问题中,至少存在一个直接或间接地不断由旧值递推出新值的变量,这个变量就是迭代变量。   二、建立迭代关系式。所谓迭代关系式,指如何从变量的前一个值推出其下一个值的公式(或关系)。迭代关系式的建立是解决迭代问题的关键,通常可以使用递推或倒推的方法来完成。   三、对迭代过程进行控制。在什么时候结束迭代过程。这是编写迭代程序必须考虑的问题。不能让迭代过程无休止地重复执行下去。迭代过程的控制通常可分为两种情况:一种是所需的迭代次数是个确定的值,可以计算出来;另一种是所需的迭代次数无法确定。对于前一种情况,可以构建一个固定次数的循环来实现对迭代过程的控制;对于后一种情况,需要进一步分析出用来结束迭代过程的条件。   例 1 : 一个饲养场引进一只刚出生的新品种兔子,这种兔子从出生的下一个月开始,每月新生一只兔子,新生的兔子也如此繁殖。如果所有的兔子都不死去,问到第 12 个月时,该饲养场共有兔子多少只。   分析: 这是一个典型的递推问题。我们不妨假设第 1 个月时兔子的只数为 u 1 ,第 2 个月时兔子的只数为 u 2 ,第 3 个月时兔子的只数为 u 3 ,……根据题意,“这种兔子从出生的下一个月开始,每月新生一只兔子”,则有   u 1 = 1 , u 2 = u 1 + u 1 × 1 = 2 , u 3 = u 2 + u 2 × 1 = 4 ,……   根据这个规律,可以归纳出下面的递推公式:   u n = u n - 1 × 2 (n ≥ 2)   对应 u n 和 u n - 1 ,定义两个迭代变量 y 和 x ,可将上面的递推公式转换成如下迭代关系:   y=x*2   x=y   让计算机对这个迭代关系重复执行 11 次,就可以算出第 12 个月时的兔子数。参考程序如下:   cls   x=1   for i=2 to 12   y=x*2   x=y   next i   print y   end   例 2 : 阿米巴用简单分裂的方式繁殖,它每分裂一次要用 3 分钟。将若干个阿米巴放在一个盛满营养参液的容器内, 45 分钟后容器内充满了阿米巴。已知容器最多可以装阿米巴 220,220个。试问,开始的时候往容器内放了多少个阿米巴。请编程序算出。   分析: 根据题意,阿米巴每 3 分钟分裂一次,那么从开始的时候将阿米巴放入容器里面,到 45 分钟后充满容器,需要分裂 45/3=15 次。而“容器最多可以装阿米巴2^ 20 个”,即阿米巴分裂 15 次以后得到的个数是 2^20 。题目要求我们计算分裂之前的阿米巴数,不妨使用倒推的方法,从第 15 次分裂之后的 2^20 个,倒推出第 15 次分裂之前(即第 14 次分裂之后)的个数,再进一步倒推出第 13 次分裂之后、第 12 次分裂之后、……第 1 次分裂之前的个数。   设第 1 次分裂之前的个数为 x 0 、第 1 次分裂之后的个数为 x 1 、第 2 次分裂之后的个数为 x 2 、……第 15 次分裂之后的个数为 x 15 ,则有   x 14 =x 15 /2 、 x 13 =x 14 /2 、…… x n-1 =x n /2 (n ≥ 1)   因为第 15 次分裂之后的个数 x 15 是已知的,如果定义迭代变量为 x ,则可以将上面的倒推公式转换成如下的迭代公式:   x=x/2 ( x 的初值为第 15 次分裂之后的个数 2^20 )   让这个迭代公式重复执行 15 次,就可以倒推出第 1 次分裂之前的阿米巴个数。因为所需的迭代次数是个确定的值,我们可以使用一个固定次数的循环来实现对迭代过程的控制。参考程序如下:   cls   x=2^20   for i=1 to 15   x=x/2   next i   print x   end   ps:java中幂的算法是Math.pow(2, 20);返回double,稍微注意一下   例 3 : 验证谷角猜想。日本数学家谷角静夫在研究自然数时发现了一个奇怪现象:对于任意一个自然数 n ,若 n 为偶数,则将其除以 2 ;若 n 为奇数,则将其乘以 3 ,然后再加 1 。如此经过有限次运算后,总可以得到自然数 1 。人们把谷角静夫的这一发现叫做“谷角猜想”。   要求:编写一个程序,由键盘输入一个自然数 n ,把 n 经过有限次运算后,最终变成自然数 1 的全过程打印出来。   分析: 定义迭代变量为 n ,按照谷角猜想的内容,可以得到两种情况下的迭代关系式:当 n 为偶数时, n=n/2 ;当 n 为奇数时, n=n*3+1 。用 QBASIC 语言把它描述出来就是:   if n 为偶数 then   n=n/2   else   n=n*3+1   end if   这就是需要计算机重复执行的迭代过程。这个迭代过程需要重复执行多少次,才能使迭代变量 n 最终变成自然数 1 ,这是我们无法计算出来的。因此,还需进一步确定用来结束迭代过程的条件。仔细分析题目要求,不难看出,对任意给定的一个自然数 n ,只要经过有限次运算后,能够得到自然数 1 ,就已经完成了验证工作。因此,用来结束迭代过程的条件可以定义为: n=1 。参考程序如下:   cls   input "Please input n=";n   do until n=1   if n mod 2=0 then   rem 如果 n 为偶数,则调用迭代公式 n=n/2   n=n/2   print "—";n;   else   n=n*3+1   print "—";n;   end if   loop   end   迭代法   迭代法是用于求方程或方程组近似根的一种常用的算法设计方法。设方程为f(x)=0,用某种数学方法导出等价的形式x=g(x),然后按以下步骤执行:   (1) 选一个方程的近似根,赋给变量x0;   (2) 将x0的值保存于变量x1,然后计算g(x1),并将结果存于变量x0;   (3) 当x0与x1的差的绝对值还小于指定的精度要求时,重复步骤(2)的计算。   若方程有根,并且用上述方法计算出来的近似根序列收敛,则按上述方法求得的x0就认为是方程的根。上述算法用C程序的形式表示为:   【算法】迭代法求方程的根   { x0=初始近似根;   do {   x1=x0;   x0=g(x1); /*按特定的方程计算新的近似根*/   } while ( fabs(x0-x1)>Epsilon);   printf(“方程的近似根是%f\n”,x0);   }   迭代算法也常用于求方程组的根,令   X=(x0,x1,…,xn-1)   设方程组为:   xi=gi(X) (I=0,1,…,n-1)   则求方程组根的迭代算法可描述如下:   【算法】迭代法求方程组的根   { for (i=0;i   x=初始近似根;   do {   for (i=0;i   y=x;   for (i=0;i   x=gi(X);   for (delta=0.0,i=0;i   if (fabs(y-x)>delta) delta=fabs(y-x);   } while (delta>Epsilon);   for (i=0;i   printf(“变量x[%d]的近似根是 %f”,I,x);   printf(“\n”);   }   具体使用迭代法求根时应注意以下两种可能发生的情况:   (1) 如果方程无解,算法求出的近似根序列就不会收敛,迭代过程会变成死循环,因此在使用迭代算法前应先考察方程是否有解,并在程序中对迭代的次数给予限制;   (2) 方程虽然有解,但迭代公式选择不当,或迭代的初始近似根选择不合理,也会导致迭代失败。   递归   递归是设计和描述算法的一种有力的工具,由于它在复杂算法的描述中被经常采用,为此在进一步介绍其他算法设计方法之前先讨论它。   能采用递归描述的算法通常有这样的特征:为求解规模为N的问题,设法将它分解成规模较小的问题,然后从这些小问题的解方便地构造出大问题的解,并且这些规模较小的问题也能采用同样的分解和综合方法,分解成规模更小的问题,并从这些更小问题的解构造出规模较大问题的解。特别地,当规模N=1时,能直接得解。   【问题】 编写计算斐波那契(Fibonacci)数列的第n项函数fib(n)。   斐波那契数列为:0、1、1、2、3、……,即:   fib(0)=0;   fib(1)=1;   fib(n)=fib(n-1)+fib(n-2) (当n>1时)。   写成递归函数有:   int fib(int n)   { if (n==0) return 0;   if (n==1) return 1;   if (n>1) return fib(n-1)+fib(n-2);   }   递归算法的执行过程分递推和回归两个阶段。在递推阶段,把较复杂的问题(规模为n)的求解推到比原问题简单一些的问题(规模小于n)的求解。例如上例中,求解fib(n),把它推到求解fib(n-1)和fib(n-2)。也就是说,为计算fib(n),必须先计算fib(n-1)和fib(n- 2),而计算fib(n-1)和fib(n-2),又必须先计算fib(n-3)和fib(n-4)。依次类推,直至计算fib(1)和fib(0),分别能立即得到结果1和0。在递推阶段,必须要有终止递归的情况。例如在函数fib中,当n为1和0的情况。   在回归阶段,当获得最简单情况的解后,逐级返回,依次得到稍复杂问题的解,例如得到fib(1)和fib(0)后,返回得到fib(2)的结果,……,在得到了fib(n-1)和fib(n-2)的结果后,返回得到fib(n)的结果。   在编写递归函数时要注意,函数中的局部变量和参数知识局限于当前调用层,当递推进入“简单问题”层时,原来层次上的参数和局部变量便被隐蔽起来。在一系列“简单问题”层,它们各有自己的参数和局部变量。   由于递归引起一系列的函数调用,并且可能会有一系列的重复计算,递归算法的执行效率相对较低。当某个递归算法能较方便地转换成递推算法时,通常按递推算法编写程序。例如上例计算斐波那契数列的第n项的函数fib(n)应采用递推算法,即从斐波那契数列的前两项出发,逐次由前两项计算出下一项,直至计算出要求的第n项。   【问题】 组合问题   问题描述:找出从自然数1、2、……、n中任取r个数的所有组合。例如n=5,r=3的所有组合为: (1)5、4、3 (2)5、4、2 (3)5、4、1   (4)5、3、2 (5)5、3、1 (6)5、2、1   (7)4、3、2 (8)4、3、1 (9)4、2、1   (10)3、2、1   分析所列的10个组合,可以采用这样的递归思想来考虑求组合函数的算法。设函数为void comb(int m,int k)为找出从自然数1、2、……、m中任取k个数的所有组合。当组合的第一个数字选定时,其后的数字是从余下的m-1个数中取k-1数的组合。这就将求m 个数中取k个数的组合问题转化成求m-1个数中取k-1个数的组合问题。设函数引入工作数组a[ ]存放求出的组合的数字,约定函数将确定的k个数字组合的第一个数字放在a[k]中,当一个组合求出后,才将a[ ]中的一个组合输出。第一个数可以是m、m-1、……、k,函数将确定组合的第一个数字放入数组后,有两种可能的选择,因还未去顶组合的其余元素,继续递归去确定;或因已确定了组合的全部元素,输出这个组合。细节见以下程序中的函数comb。   【程序】   # include   # define MAXN 100   int a[MAXN];   void comb(int m,int k)   { int i,j;   for (i=m;i>=k;i--)   { a[k]=i;   if (k>1)   comb(i-1,k-1);   else   { for (j=a[0];j>0;j--)   printf(“%4d”,a[j]);   printf(“\n”);   }   }   }   void main()   { a[0]=3;   comb(5,3);   }   【问题】 背包问题   问题描述:有不同价值、不同重量的物品n件,求从这n件物品中选取一部分物品的选择方案,使选中物品的总重量不超过指定的限制重量,但选中物品的价值之和最大。   设n 件物品的重量分别为w0、w1、…、wn-1,物品的价值分别为v0、v1、…、vn-1。采用递归寻找物品的选择方案。设前面已有了多种选择的方案,并保留了其中总价值最大的方案于数组option[ ],该方案的总价值存于变量maxv。当前正在考察新方案,其物品选择情况保存于数组cop[ ]。假定当前方案已考虑了前i-1件物品,现在要考虑第i件物品;当前方案已包含的物品的重量之和为tw;至此,若其余物品都选择是可能的话,本方案能达到的总价值的期望值为tv。算法引入tv是当一旦当前方案的总价值的期望值也小于前面方案的总价值maxv时,继续考察当前方案变成无意义的工作,应终止当前方案,立即去考察下一个方案。因为当方案的总价值不比maxv大时,该方案不会被再考察,这同时保证函数后找到的方案一定会比前面的方案更好。   对于第i件物品的选择考虑有两种可能:   (1) 考虑物品i被选择,这种可能性仅当包含它不会超过方案总重量限制时才是可行的。选中后,继续递归去考虑其余物品的选择。   (2) 考虑物品i不被选择,这种可能性仅当不包含物品i也有可能会找到价值更大的方案的情况。   按以上思想写出递归算法如下:   try(物品i,当前选择已达到的重量和,本方案可能达到的总价值tv)   { /*考虑物品i包含在当前方案中的可能性*/   if(包含物品i是可以接受的)   { 将物品i包含在当前方案中;   if (i   try(i+1,tw+物品i的重量,tv);   else   /*又一个完整方案,因为它比前面的方案好,以它作为最佳方案*/   以当前方案作为临时最佳方案保存;   恢复物品i不包含状态;   }   /*考虑物品i不包含在当前方案中的可能性*/   if (不包含物品i仅是可男考虑的)   if (i   try(i+1,tw,tv-物品i的价值);   else   /*又一个完整方案,因它比前面的方案好,以它作为最佳方案*/   以当前方案作为临时最佳方案保存;   }

沉默术士 2019-12-02 01:25:10 0 浏览量 回答数 0

回答

Python常见数据结构整理 Python中常见的数据结构可以统称为容器(container)。序列(如列表和元组)、映射(如字典)以及集合(set)是三类主要的容器。 一、序列(列表、元组和字符串) 序列中的每个元素都有自己的编号。Python中有6种内建的序列。其中列表和元组是最常见的类型。其他包括字符串、Unicode字符串、buffer对象和xrange对象。下面重点介绍下列表、元组和字符串。 1、列表 列表是可变的,这是它区别于字符串和元组的最重要的特点,一句话概括即:列表可以修改,而字符串和元组不能。 (1)、创建 通过下面的方式即可创建一个列表: 1 2 3 4 list1=['hello','world'] print list1 list2=[1,2,3] print list2 输出: ['hello', 'world'] [1, 2, 3] 可以看到,这中创建方式非常类似于javascript中的数组。 (2)、list函数 通过list函数(其实list是一种类型而不是函数)对字符串创建列表非常有效: 1 2 list3=list("hello") print list3 输出: ['h', 'e', 'l', 'l', 'o'] 2、元组 元组与列表一样,也是一种序列,唯一不同的是元组不能被修改(字符串其实也有这种特点)。 (1)、创建 1 2 3 4 5 6 t1=1,2,3 t2="jeffreyzhao","cnblogs" t3=(1,2,3,4) t4=() t5=(1,) print t1,t2,t3,t4,t5 输出: (1, 2, 3) ('jeffreyzhao', 'cnblogs') (1, 2, 3, 4) () (1,) 从上面我们可以分析得出: a、逗号分隔一些值,元组自动创建完成; b、元组大部分时候是通过圆括号括起来的; c、空元组可以用没有包含内容的圆括号来表示; d、只含一个值的元组,必须加个逗号(,); (2)、tuple函数 tuple函数和序列的list函数几乎一样:以一个序列(注意是序列)作为参数并把它转换为元组。如果参数就算元组,那么该参数就会原样返回: 1 2 3 4 5 6 7 8 t1=tuple([1,2,3]) t2=tuple("jeff") t3=tuple((1,2,3)) print t1 print t2 print t3 t4=tuple(123) print t45 输出: (1, 2, 3) ('j', 'e', 'f', 'f') (1, 2, 3) Traceback (most recent call last): File "F:\Python\test.py", line 7, in <module> t4=tuple(123) TypeError: 'int' object is not iterable 3、字符串 (1)创建 1 2 3 4 5 str1='Hello world' print str1 print str1[0] for c in str1: print c 输出: Hello world H H e l l o w o r l d (2)格式化 字符串格式化使用字符串格式化操作符即百分号%来实现。 1 2 str1='Hello,%s' % 'world.' print str1 格式化操作符的右操作数可以是任何东西,如果是元组或者映射类型(如字典),那么字符串格式化将会有所不同。 1 2 3 4 5 6 strs=('Hello','world') #元组 str1='%s,%s' % strs print str1 d={'h':'Hello','w':'World'} #字典 str1='%(h)s,%(w)s' % d print str1 输出: Hello,world Hello,World 注意:如果需要转换的元组作为转换表达式的一部分存在,那么必须将它用圆括号括起来: 1 2 str1='%s,%s' % 'Hello','world' print str1 输出: Traceback (most recent call last): File "F:\Python\test.py", line 2, in <module> str1='%s,%s' % 'Hello','world' TypeError: not enough arguments for format string 如果需要输出%这个特殊字符,毫无疑问,我们会想到转义,但是Python中正确的处理方式如下: 1 2 str1='%s%%' % 100 print str1 输出:100% 对数字进行格式化处理,通常需要控制输出的宽度和精度: 1 2 3 4 5 6 7 from math import pi str1='%.2f' % pi #精度2 print str1 str1='%10f' % pi #字段宽10 print str1 str1='%10.2f' % pi #字段宽10,精度2 print str1 输出: 3.14 3.141593 3.14 字符串格式化还包含很多其他丰富的转换类型,可参考官方文档。 Python中在string模块还提供另外一种格式化值的方法:模板字符串。它的工作方式类似于很多UNIX Shell里的变量替换,如下所示: 1 2 3 4 from string import Template str1=Template('$x,$y!') str1=str1.substitute(x='Hello',y='world') print str1 输出: Hello,world! 如果替换字段是单词的一部分,那么参数名称就必须用括号括起来,从而准确指明结尾: 1 2 3 4 from string import Template str1=Template('Hello,w${x}d!') str1=str1.substitute(x='orl') print str1 输出: Hello,world! 如要输出符,可以使用$输出: 1 2 3 4 from string import Template str1=Template('$x$$') str1=str1.substitute(x='100') print str1 输出:100$ 除了关键字参数之外,模板字符串还可以使用字典变量提供键值对进行格式化: 1 2 3 4 5 from string import Template d={'h':'Hello','w':'world'} str1=Template('$h,$w!') str1=str1.substitute(d) print str1 输出: Hello,world! 除了格式化之外,Python字符串还内置了很多实用方法,可参考官方文档,这里不再列举。 4、通用序列操作(方法) 从列表、元组以及字符串可以“抽象”出序列的一些公共通用方法(不是你想像中的CRUD),这些操作包括:索引(indexing)、分片(sliceing)、加(adding)、乘(multiplying)以及检查某个元素是否属于序列的成员。除此之外,还有计算序列长度、最大最小元素等内置函数。 (1)索引 1 2 3 4 5 6 str1='Hello' nums=[1,2,3,4] t1=(123,234,345) print str1[0] print nums[1] print t1[2] 输出 H 2 345 索引从0(从左向右)开始,所有序列可通过这种方式进行索引。神奇的是,索引可以从最后一个位置(从右向左)开始,编号是-1: 1 2 3 4 5 6 str1='Hello' nums=[1,2,3,4] t1=(123,234,345) print str1[-1] print nums[-2] print t1[-3] 输出: o 3 123 (2)分片 分片操作用来访问一定范围内的元素。分片通过冒号相隔的两个索引来实现: 1 2 3 4 5 6 7 8 nums=range(10) print nums print nums[1:5] print nums[6:10] print nums[1:] print nums[-3:-1] print nums[-3:] #包括序列结尾的元素,置空最后一个索引 print nums[:] #复制整个序列 输出: [0, 1, 2, 3, 4, 5, 6, 7, 8, 9] [1, 2, 3, 4] [6, 7, 8, 9] [1, 2, 3, 4, 5, 6, 7, 8, 9] [7, 8] [7, 8, 9] 不同的步长,有不同的输出: 1 2 3 4 5 6 7 8 nums=range(10) print nums print nums[0:10] #默认步长为1 等价于nums[1:5:1] print nums[0:10:2] #步长为2 print nums[0:10:3] #步长为3 ##print nums[0:10:0] #步长为0 print nums[0:10:-2] #步长为-2 输出: [0, 1, 2, 3, 4, 5, 6, 7, 8, 9] [0, 1, 2, 3, 4, 5, 6, 7, 8, 9] [0, 2, 4, 6, 8] [0, 3, 6, 9] [] (3)序列相加 1 2 3 4 5 6 7 str1='Hello' str2=' world' print str1+str2 num1=[1,2,3] num2=[2,3,4] print num1+num2 print str1+num1 输出: Hello world [1, 2, 3, 2, 3, 4] Traceback (most recent call last): File "F:\Python\test.py", line 7, in <module> print str1+num1 TypeError: cannot concatenate 'str' and 'list' objects (4)乘法 1 2 3 4 5 6 print [None]*10 str1='Hello' print str1*2 num1=[1,2] print num1*2 print str1*num1 输出: [None, None, None, None, None, None, None, None, None, None] HelloHello [1, 2, 1, 2] Traceback (most recent call last): File "F:\Python\test.py", line 5, in <module> print str1*num1 TypeError: can't multiply sequence by non-int of type 'list' (5)成员资格 in运算符会用来检查一个对象是否为某个序列(或者其他类型)的成员(即元素): 1 2 3 4 5 str1='Hello' print 'h' in str1 print 'H' in str1 num1=[1,2] print 1 in num1 输出: False True True (6)长度、最大最小值 通过内建函数len、max和min可以返回序列中所包含元素的数量、最大和最小元素。 1 2 3 4 5 6 7 8 str1='Hello' print len(str1) print max(str1) print min(str1) num1=[1,2,1,4,123] print len(num1) print max(num1) print min(num1) 输出: 5 o H 5 123 1 二、映射(字典) 映射中的每个元素都有一个名字,如你所知,这个名字专业的名称叫键。字典(也叫散列表)是Python中唯一内建的映射类型。 1、键类型 字典的键可以是数字、字符串或者是元组,键必须唯一。在Python中,数字、字符串和元组都被设计成不可变类型,而常见的列表以及集合(set)都是可变的,所以列表和集合不能作为字典的键。键可以为任何不可变类型,这正是Python中的字典最强大的地方。 1 2 3 4 5 6 7 8 list1=["hello,world"] set1=set([123]) d={} d[1]=1 print d d[list1]="Hello world." d[set1]=123 print d 输出: {1: 1} Traceback (most recent call last): File "F:\Python\test.py", line 6, in <module> d[list1]="Hello world." TypeError: unhashable type: 'list' 2、自动添加 即使键在字典中并不存在,也可以为它分配一个值,这样字典就会建立新的项。 3、成员资格 表达式item in d(d为字典)查找的是键(containskey),而不是值(containsvalue)。 Python字典强大之处还包括内置了很多常用操作方法,可参考官方文档,这里不再列举。 思考:根据我们使用强类型语言的经验,比如C#和Java,我们肯定会问Python中的字典是线程安全的吗。 三、集合 集合(Set)在Python 2.3引入,通常使用较新版Python可直接创建,如下所示: strs=set(['jeff','wong','cnblogs']) nums=set(range(10)) 看上去,集合就是由序列(或者其他可迭代的对象)构建的。集合的几个重要特点和方法如下: 1、副本是被忽略的 集合主要用于检查成员资格,因此副本是被忽略的,如下示例所示,输出的集合内容是一样的。 1 2 3 4 5 set1=set([0,1,2,3,0,1,2,3,4,5]) print set1 set2=set([0,1,2,3,4,5]) print set2 输出如下: set([0, 1, 2, 3, 4, 5]) set([0, 1, 2, 3, 4, 5]) 2、集合元素的顺序是随意的 这一点和字典非常像,可以简单理解集合为没有value的字典。 1 2 strs=set(['jeff','wong','cnblogs']) print strs 输出如下: set(['wong', 'cnblogs', 'jeff'])

琴瑟 2019-12-02 01:22:27 0 浏览量 回答数 0

回答

迭代算法是用计算机解决问题的一种基本方法。它利用计算机运算速度快、适合做重复性操作的特点,让计算机对一组指令(或一定步骤)进行重复执行,在每次执行这组指令(或这些步骤)时,都从变量的原值推出它的一个新值。 利用迭代算法解决问题,需要做好以下三个方面的工作: 一、确定迭代变量。在可以用迭代算法解决的问题中,至少存在一个直接或间接地不断由旧值递推出新值的变量,这个变量就是迭代变量。 二、建立迭代关系式。所谓迭代关系式,指如何从变量的前一个值推出其下一个值的公式(或关系)。迭代关系式的建立是解决迭代问题的关键,通常可以使用递推或倒推的方法来完成。 三、对迭代过程进行控制。在什么时候结束迭代过程。这是编写迭代程序必须考虑的问题。不能让迭代过程无休止地重复执行下去。迭代过程的控制通常可分为两种情况:一种是所需的迭代次数是个确定的值,可以计算出来;另一种是所需的迭代次数无法确定。对于前一种情况,可以构建一个固定次数的循环来实现对迭代过程的控制;对于后一种情况,需要进一步分析出用来结束迭代过程的条件。 例 1 : 一个饲养场引进一只刚出生的新品种兔子,这种兔子从出生的下一个月开始,每月新生一只兔子,新生的兔子也如此繁殖。如果所有的兔子都不死去,问到第 12 个月时,该饲养场共有兔子多少只。 分析: 这是一个典型的递推问题。我们不妨假设第 1 个月时兔子的只数为 u 1 ,第 2 个月时兔子的只数为 u 2 ,第 3 个月时兔子的只数为 u 3 ,……根据题意,“这种兔子从出生的下一个月开始,每月新生一只兔子”,则有 u 1 = 1 , u 2 = u 1 + u 1 × 1 = 2 , u 3 = u 2 + u 2 × 1 = 4 ,…… 根据这个规律,可以归纳出下面的递推公式: u n = u n - 1 × 2 (n ≥ 2) 对应 u n 和 u n - 1 ,定义两个迭代变量 y 和 x ,可将上面的递推公式转换成如下迭代关系: y=x*2 x=y 让计算机对这个迭代关系重复执行 11 次,就可以算出第 12 个月时的兔子数。参考程序如下: cls x=1 for i=2 to 12 y=x*2 x=y next i print y end 例 2 : 阿米巴用简单分裂的方式繁殖,它每分裂一次要用 3 分钟。将若干个阿米巴放在一个盛满营养参液的容器内, 45 分钟后容器内充满了阿米巴。已知容器最多可以装阿米巴 2 20 个。试问,开始的时候往容器内放了多少个阿米巴。请编程序算出。 分析: 根据题意,阿米巴每 3 分钟分裂一次,那么从开始的时候将阿米巴放入容器里面,到 45 分钟后充满容器,需要分裂 45/3=15 次。而“容器最多可以装阿米巴 2 20 个”,即阿米巴分裂 15 次以后得到的个数是 2 20 。题目要求我们计算分裂之前的阿米巴数,不妨使用倒推的方法,从第 15 次分裂之后的 2 20 个,倒推出第 15 次分裂之前(即第 14 次分裂之后)的个数,再进一步倒推出第 13 次分裂之后、第 12 次分裂之后、……第 1 次分裂之前的个数。 设第 1 次分裂之前的个数为 x 0 、第 1 次分裂之后的个数为 x 1 、第 2 次分裂之后的个数为 x 2 、……第 15 次分裂之后的个数为 x 15 ,则有 x 14 =x 15 /2 、 x 13 =x 14 /2 、…… x n-1 =x n /2 (n ≥ 1) 因为第 15 次分裂之后的个数 x 15 是已知的,如果定义迭代变量为 x ,则可以将上面的倒推公式转换成如下的迭代公式: x=x/2 ( x 的初值为第 15 次分裂之后的个数 2 20 ) 让这个迭代公式重复执行 15 次,就可以倒推出第 1 次分裂之前的阿米巴个数。因为所需的迭代次数是个确定的值,我们可以使用一个固定次数的循环来实现对迭代过程的控制。参考程序如下: cls x=2^20 for i=1 to 15 x=x/2 next i print x end 例 3 : 验证谷角猜想。日本数学家谷角静夫在研究自然数时发现了一个奇怪现象:对于任意一个自然数 n ,若 n 为偶数,则将其除以 2 ;若 n 为奇数,则将其乘以 3 ,然后再加 1 。如此经过有限次运算后,总可以得到自然数 1 。人们把谷角静夫的这一发现叫做“谷角猜想”。 要求:编写一个程序,由键盘输入一个自然数 n ,把 n 经过有限次运算后,最终变成自然数 1 的全过程打印出来。 分析: 定义迭代变量为 n ,按照谷角猜想的内容,可以得到两种情况下的迭代关系式:当 n 为偶数时, n=n/2 ;当 n 为奇数时, n=n*3+1 。用 QBASIC 语言把它描述出来就是: if n 为偶数 then n=n/2 else n=n*3+1 end if 这就是需要计算机重复执行的迭代过程。这个迭代过程需要重复执行多少次,才能使迭代变量 n 最终变成自然数 1 ,这是我们无法计算出来的。因此,还需进一步确定用来结束迭代过程的条件。仔细分析题目要求,不难看出,对任意给定的一个自然数 n ,只要经过有限次运算后,能够得到自然数 1 ,就已经完成了验证工作。因此,用来结束迭代过程的条件可以定义为: n=1 。参考程序如下: cls input "Please input n=";n do until n=1 if n mod 2=0 then rem 如果 n 为偶数,则调用迭代公式 n=n/2 n=n/2 print "—";n; else n=n*3+1 print "—";n; end if loop end 迭代法 迭代法是用于求方程或方程组近似根的一种常用的算法设计方法。设方程为f(x)=0,用某种数学方法导出等价的形式x=g(x),然后按以下步骤执行: (1) 选一个方程的近似根,赋给变量x0; (2) 将x0的值保存于变量x1,然后计算g(x1),并将结果存于变量x0; (3) 当x0与x1的差的绝对值还小于指定的精度要求时,重复步骤(2)的计算。 若方程有根,并且用上述方法计算出来的近似根序列收敛,则按上述方法求得的x0就认为是方程的根。上述算法用C程序的形式表示为: 【算法】迭代法求方程的根 { x0=初始近似根; do { x1=x0; x0=g(x1); /*按特定的方程计算新的近似根*/ } while ( fabs(x0-x1)>Epsilon); printf(“方程的近似根是%f\n”,x0); } 迭代算法也常用于求方程组的根,令 X=(x0,x1,…,xn-1) 设方程组为: xi=gi(X) (I=0,1,…,n-1) 则求方程组根的迭代算法可描述如下: 【算法】迭代法求方程组的根 { for (i=0;i x=初始近似根; do { for (i=0;i y=x; for (i=0;i x=gi(X); for (delta=0.0,i=0;i if (fabs(y-x)>delta) delta=fabs(y-x); } while (delta>Epsilon); for (i=0;i printf(“变量x[%d]的近似根是 %f”,I,x); printf(“\n”); } 具体使用迭代法求根时应注意以下两种可能发生的情况: (1) 如果方程无解,算法求出的近似根序列就不会收敛,迭代过程会变成死循环,因此在使用迭代算法前应先考察方程是否有解,并在程序中对迭代的次数给予限制; (2) 方程虽然有解,但迭代公式选择不当,或迭代的初始近似根选择不合理,也会导致迭代失败。 递归 递归是设计和描述算法的一种有力的工具,由于它在复杂算法的描述中被经常采用,为此在进一步介绍其他算法设计方法之前先讨论它。 能采用递归描述的算法通常有这样的特征:为求解规模为N的问题,设法将它分解成规模较小的问题,然后从这些小问题的解方便地构造出大问题的解,并且这些规模较小的问题也能采用同样的分解和综合方法,分解成规模更小的问题,并从这些更小问题的解构造出规模较大问题的解。特别地,当规模N=1时,能直接得解。 【问题】 编写计算斐波那契(Fibonacci)数列的第n项函数fib(n)。 斐波那契数列为:0、1、1、2、3、……,即: fib(0)=0; fib(1)=1; fib(n)=fib(n-1)+fib(n-2) (当n>1时)。 写成递归函数有: int fib(int n) { if (n==0) return 0; if (n==1) return 1; if (n>1) return fib(n-1)+fib(n-2); } 递归算法的执行过程分递推和回归两个阶段。在递推阶段,把较复杂的问题(规模为n)的求解推到比原问题简单一些的问题(规模小于n)的求解。例如上例中,求解fib(n),把它推到求解fib(n-1)和fib(n-2)。也就是说,为计算fib(n),必须先计算fib(n-1)和fib(n- 2),而计算fib(n-1)和fib(n-2),又必须先计算fib(n-3)和fib(n-4)。依次类推,直至计算fib(1)和fib(0),分别能立即得到结果1和0。在递推阶段,必须要有终止递归的情况。例如在函数fib中,当n为1和0的情况。 在回归阶段,当获得最简单情况的解后,逐级返回,依次得到稍复杂问题的解,例如得到fib(1)和fib(0)后,返回得到fib(2)的结果,……,在得到了fib(n-1)和fib(n-2)的结果后,返回得到fib(n)的结果。 在编写递归函数时要注意,函数中的局部变量和参数知识局限于当前调用层,当递推进入“简单问题”层时,原来层次上的参数和局部变量便被隐蔽起来。在一系列“简单问题”层,它们各有自己的参数和局部变量。 由于递归引起一系列的函数调用,并且可能会有一系列的重复计算,递归算法的执行效率相对较低。当某个递归算法能较方便地转换成递推算法时,通常按递推算法编写程序。例如上例计算斐波那契数列的第n项的函数fib(n)应采用递推算法,即从斐波那契数列的前两项出发,逐次由前两项计算出下一项,直至计算出要求的第n项。 【问题】 组合问题 问题描述:找出从自然数1、2、……、n中任取r个数的所有组合。例如n=5,r=3的所有组合为: (1)5、4、3 (2)5、4、2 (3)5、4、1 (4)5、3、2 (5)5、3、1 (6)5、2、1 (7)4、3、2 (8)4、3、1 (9)4、2、1 (10)3、2、1 分析所列的10个组合,可以采用这样的递归思想来考虑求组合函数的算法。设函数为void comb(int m,int k)为找出从自然数1、2、……、m中任取k个数的所有组合。当组合的第一个数字选定时,其后的数字是从余下的m-1个数中取k-1数的组合。这就将求m 个数中取k个数的组合问题转化成求m-1个数中取k-1个数的组合问题。设函数引入工作数组a[ ]存放求出的组合的数字,约定函数将确定的k个数字组合的第一个数字放在a[k]中,当一个组合求出后,才将a[ ]中的一个组合输出。第一个数可以是m、m-1、……、k,函数将确定组合的第一个数字放入数组后,有两种可能的选择,因还未去顶组合的其余元素,继续递归去确定;或因已确定了组合的全部元素,输出这个组合。细节见以下程序中的函数comb。 【程序】 # include # define MAXN 100 int a[MAXN]; void comb(int m,int k) { int i,j; for (i=m;i>=k;i--) { a[k]=i; if (k>1) comb(i-1,k-1); else { for (j=a[0];j>0;j--) printf(“%4d”,a[j]); printf(“\n”); } } } void main() { a[0]=3; comb(5,3); } 【问题】 背包问题 问题描述:有不同价值、不同重量的物品n件,求从这n件物品中选取一部分物品的选择方案,使选中物品的总重量不超过指定的限制重量,但选中物品的价值之和最大。 设n 件物品的重量分别为w0、w1、…、wn-1,物品的价值分别为v0、v1、…、vn-1。采用递归寻找物品的选择方案。设前面已有了多种选择的方案,并保留了其中总价值最大的方案于数组option[ ],该方案的总价值存于变量maxv。当前正在考察新方案,其物品选择情况保存于数组cop[ ]。假定当前方案已考虑了前i-1件物品,现在要考虑第i件物品;当前方案已包含的物品的重量之和为tw;至此,若其余物品都选择是可能的话,本方案能达到的总价值的期望值为tv。算法引入tv是当一旦当前方案的总价值的期望值也小于前面方案的总价值maxv时,继续考察当前方案变成无意义的工作,应终止当前方案,立即去考察下一个方案。因为当方案的总价值不比maxv大时,该方案不会被再考察,这同时保证函数后找到的方案一定会比前面的方案更好。 对于第i件物品的选择考虑有两种可能: (1) 考虑物品i被选择,这种可能性仅当包含它不会超过方案总重量限制时才是可行的。选中后,继续递归去考虑其余物品的选择。 (2) 考虑物品i不被选择,这种可能性仅当不包含物品i也有可能会找到价值更大的方案的情况。 按以上思想写出递归算法如下: try(物品i,当前选择已达到的重量和,本方案可能达到的总价值tv) { /*考虑物品i包含在当前方案中的可能性*/ if(包含物品i是可以接受的) { 将物品i包含在当前方案中; if (i try(i+1,tw+物品i的重量,tv); else /*又一个完整方案,因为它比前面的方案好,以它作为最佳方案*/ 以当前方案作为临时最佳方案保存; 恢复物品i不包含状态; } /*考虑物品i不包含在当前方案中的可能性*/ if (不包含物品i仅是可男考虑的) if (i try(i+1,tw,tv-物品i的价值); else /*又一个完整方案,因它比前面的方案好,以它作为最佳方案*/ 以当前方案作为临时最佳方案保存; } 为了理解上述算法,特举以下实例。设有4件物品,它们的重量和价值见表: 物品 0 1 2 3 重量 5 3 2 1 价值 4 4 3 1 并设限制重量为7。则按以上算法,下图表示找解过程。由图知,一旦找到一个解,算法就进一步找更好的佳。如能判定某个查找分支不会找到更好的解,算法不会在该分支继续查找,而是立即终止该分支,并去考察下一个分支。 按上述算法编写函数和程序如下: 【程序】 # include # define N 100 double limitW,totV,maxV; int option[N],cop[N]; struct { double weight; double value; }a[N]; int n; void find(int i,double tw,double tv) { int k; /*考虑物品i包含在当前方案中的可能性*/ if (tw+a.weight<=limitW) { cop=1; if (i else { for (k=0;k option[k]=cop[k]; maxv=tv; } cop=0; } /*考虑物品i不包含在当前方案中的可能性*/ if (tv-a.value>maxV) if (i else { for (k=0;k option[k]=cop[k]; maxv=tv-a.value; } } void main() { int k; double w,v; printf(“输入物品种数\n”); scanf((“%d”,&n); printf(“输入各物品的重量和价值\n”); for (totv=0.0,k=0;k { scanf(“%1f%1f”,&w,&v); a[k].weight=w; a[k].value=v; totV+=V; } printf(“输入限制重量\n”); scanf(“%1f”,&limitV); maxv=0.0; for (k=0;k find(0,0.0,totV); for (k=0;k if (option[k]) printf(“%4d”,k+1); printf(“\n总价值为%.2f\n”,maxv); } 作为对比,下面以同样的解题思想,考虑非递归的程序解。为了提高找解速度,程序不是简单地逐一生成所有候选解,而是从每个物品对候选解的影响来形成值得进一步考虑的候选解,一个候选解是通过依次考察每个物品形成的。对物品i的考察有这样几种情况:当该物品被包含在候选解中依旧满足解的总重量的限制,该物品被包含在候选解中是应该继续考虑的;反之,该物品不应该包括在当前正在形成的候选解中。同样地,仅当物品不被包括在候选解中,还是有可能找到比目前临时最佳解更好的候选解时,才去考虑该物品不被包括在候选解中;反之,该物品不包括在当前候选解中的方案也不应继续考虑。对于任一值得继续考虑的方案,程序就去进一步考虑下一个物品。 【程序】 # include # define N 100 double limitW; int cop[N]; struct ele { double weight; double value; } a[N]; int k,n; struct { int ; double tw; double tv; }twv[N]; void next(int i,double tw,double tv) { twv.=1; twv.tw=tw; twv.tv=tv; } double find(struct ele *a,int n) { int i,k,f; double maxv,tw,tv,totv; maxv=0; for (totv=0.0,k=0;k totv+=a[k].value; next(0,0.0,totv); i=0; While (i>=0) { f=twv.; tw=twv.tw; tv=twv.tv; switch(f) { case 1: twv.++; if (tw+a.weight<=limitW) if (i { next(i+1,tw+a.weight,tv); i++; } else { maxv=tv; for (k=0;k cop[k]=twv[k].!=0; } break; case 0: i--; break; default: twv.=0; if (tv-a.value>maxv) if (i { next(i+1,tw,tv-a.value); i++; } else { maxv=tv-a.value; for (k=0;k cop[k]=twv[k].!=0; } break; } } return maxv; } void main() { double maxv; printf(“输入物品种数\n”); scanf((“%d”,&n); printf(“输入限制重量\n”); scanf(“%1f”,&limitW); printf(“输入各物品的重量和价值\n”); for (k=0;k scanf(“%1f%1f”,&a[k].weight,&a[k].value); maxv=find(a,n); printf(“\n选中的物品为\n”); for (k=0;k if (option[k]) printf(“%4d”,k+1); printf(“\n总价值为%.2f\n”,maxv); } 递归的基本概念和特点 程序调用自身的编程技巧称为递归( recursion)。 一个过程或函数在其定义或说明中又直接或间接调用自身的一种方法,它通常把一个大型复杂的问题层层转化为一个与原问题相似的规模较小的问题来求解,递归策略只需少量的程序就可描述出解题过程所需要的多次重复计算,大大地减少了程序的代码量。递归的能力在于用有限的语句来定义对象的无限集合。用递归思想写出的程序往往十分简洁易懂。 一般来说,递归需要有边界条件、递归前进段和递归返回段。当边界条件不满足时,递归前进;当边界条件满足时,递归返回。 注意: (1) 递归就是在过程或函数里调用自身; (2) 在使用递增归策略时,必须有一个明确的递归结束条件,称为递归出口。

马铭芳 2019-12-02 01:24:44 0 浏览量 回答数 0

回答

  迭代算法是用计算机解决问题的一种基本方法。它利用计算机运算速度快、适合做重复性操作的特点,让计算机对一组指令(或一定步骤)进行重复执行,在每次执行这组指令(或这些步骤)时,都从变量的原值推出它的一个新值。   利用迭代算法解决问题,需要做好以下三个方面的工作:   一、确定迭代变量。在可以用迭代算法解决的问题中,至少存在一个直接或间接地不断由旧值递推出新值的变量,这个变量就是迭代变量。   二、建立迭代关系式。所谓迭代关系式,指如何从变量的前一个值推出其下一个值的公式(或关系)。迭代关系式的建立是解决迭代问题的关键,通常可以使用递推或倒推的方法来完成。   三、对迭代过程进行控制。在什么时候结束迭代过程。这是编写迭代程序必须考虑的问题。不能让迭代过程无休止地重复执行下去。迭代过程的控制通常可分为两种情况:一种是所需的迭代次数是个确定的值,可以计算出来;另一种是所需的迭代次数无法确定。对于前一种情况,可以构建一个固定次数的循环来实现对迭代过程的控制;对于后一种情况,需要进一步分析出用来结束迭代过程的条件。   例 1 : 一个饲养场引进一只刚出生的新品种兔子,这种兔子从出生的下一个月开始,每月新生一只兔子,新生的兔子也如此繁殖。如果所有的兔子都不死去,问到第 12 个月时,该饲养场共有兔子多少只。   分析: 这是一个典型的递推问题。我们不妨假设第 1 个月时兔子的只数为 u 1 ,第 2 个月时兔子的只数为 u 2 ,第 3 个月时兔子的只数为 u 3 ,……根据题意,“这种兔子从出生的下一个月开始,每月新生一只兔子”,则有   u 1 = 1 , u 2 = u 1 + u 1 × 1 = 2 , u 3 = u 2 + u 2 × 1 = 4 ,……   根据这个规律,可以归纳出下面的递推公式:   u n = u n - 1 × 2 (n ≥ 2)   对应 u n 和 u n - 1 ,定义两个迭代变量 y 和 x ,可将上面的递推公式转换成如下迭代关系:   y=x*2   x=y   让计算机对这个迭代关系重复执行 11 次,就可以算出第 12 个月时的兔子数。参考程序如下:   cls   x=1   for i=2 to 12   y=x*2   x=y   next i   print y   end   例 2 : 阿米巴用简单分裂的方式繁殖,它每分裂一次要用 3 分钟。将若干个阿米巴放在一个盛满营养参液的容器内, 45 分钟后容器内充满了阿米巴。已知容器最多可以装阿米巴 2 20 个。试问,开始的时候往容器内放了多少个阿米巴。请编程序算出。   分析: 根据题意,阿米巴每 3 分钟分裂一次,那么从开始的时候将阿米巴放入容器里面,到 45 分钟后充满容器,需要分裂 45/3=15 次。而“容器最多可以装阿米巴 2 20 个”,即阿米巴分裂 15 次以后得到的个数是 2 20 。题目要求我们计算分裂之前的阿米巴数,不妨使用倒推的方法,从第 15 次分裂之后的 2 20 个,倒推出第 15 次分裂之前(即第 14 次分裂之后)的个数,再进一步倒推出第 13 次分裂之后、第 12 次分裂之后、……第 1 次分裂之前的个数。   设第 1 次分裂之前的个数为 x 0 、第 1 次分裂之后的个数为 x 1 、第 2 次分裂之后的个数为 x 2 、……第 15 次分裂之后的个数为 x 15 ,则有   x 14 =x 15 /2 、 x 13 =x 14 /2 、…… x n-1 =x n /2 (n ≥ 1)   因为第 15 次分裂之后的个数 x 15 是已知的,如果定义迭代变量为 x ,则可以将上面的倒推公式转换成如下的迭代公式:   x=x/2 ( x 的初值为第 15 次分裂之后的个数 2 20 )   让这个迭代公式重复执行 15 次,就可以倒推出第 1 次分裂之前的阿米巴个数。因为所需的迭代次数是个确定的值,我们可以使用一个固定次数的循环来实现对迭代过程的控制。参考程序如下:   cls   x=2^20   for i=1 to 15   x=x/2   next i   print x   end   例 3 : 验证谷角猜想。日本数学家谷角静夫在研究自然数时发现了一个奇怪现象:对于任意一个自然数 n ,若 n 为偶数,则将其除以 2 ;若 n 为奇数,则将其乘以 3 ,然后再加 1 。如此经过有限次运算后,总可以得到自然数 1 。人们把谷角静夫的这一发现叫做“谷角猜想”。   要求:编写一个程序,由键盘输入一个自然数 n ,把 n 经过有限次运算后,最终变成自然数 1 的全过程打印出来。   分析: 定义迭代变量为 n ,按照谷角猜想的内容,可以得到两种情况下的迭代关系式:当 n 为偶数时, n=n/2 ;当 n 为奇数时, n=n*3+1 。用 QBASIC 语言把它描述出来就是:   if n 为偶数 then   n=n/2   else   n=n*3+1   end if   这就是需要计算机重复执行的迭代过程。这个迭代过程需要重复执行多少次,才能使迭代变量 n 最终变成自然数 1 ,这是我们无法计算出来的。因此,还需进一步确定用来结束迭代过程的条件。仔细分析题目要求,不难看出,对任意给定的一个自然数 n ,只要经过有限次运算后,能够得到自然数 1 ,就已经完成了验证工作。因此,用来结束迭代过程的条件可以定义为: n=1 。参考程序如下:   cls   input "Please input n=";n   do until n=1   if n mod 2=0 then   rem 如果 n 为偶数,则调用迭代公式 n=n/2   n=n/2   print "—";n;   else   n=n*3+1   print "—";n;   end if   loop   end   迭代法   迭代法是用于求方程或方程组近似根的一种常用的算法设计方法。设方程为f(x)=0,用某种数学方法导出等价的形式x=g(x),然后按以下步骤执行:   (1) 选一个方程的近似根,赋给变量x0;   (2) 将x0的值保存于变量x1,然后计算g(x1),并将结果存于变量x0;   (3) 当x0与x1的差的绝对值还小于指定的精度要求时,重复步骤(2)的计算。   若方程有根,并且用上述方法计算出来的近似根序列收敛,则按上述方法求得的x0就认为是方程的根。上述算法用C程序的形式表示为:   【算法】迭代法求方程的根   { x0=初始近似根;   do {   x1=x0;   x0=g(x1); /*按特定的方程计算新的近似根*/   } while ( fabs(x0-x1)>Epsilon);   printf(“方程的近似根是%f\n”,x0);   }   迭代算法也常用于求方程组的根,令   X=(x0,x1,…,xn-1)   设方程组为:   xi=gi(X) (I=0,1,…,n-1)   则求方程组根的迭代算法可描述如下:   【算法】迭代法求方程组的根   { for (i=0;i   x=初始近似根;   do {   for (i=0;i   y=x;   for (i=0;i   x=gi(X);   for (delta=0.0,i=0;i   if (fabs(y-x)>delta) delta=fabs(y-x);   } while (delta>Epsilon);   for (i=0;i   printf(“变量x[%d]的近似根是 %f”,I,x);   printf(“\n”);   }   具体使用迭代法求根时应注意以下两种可能发生的情况:   (1) 如果方程无解,算法求出的近似根序列就不会收敛,迭代过程会变成死循环,因此在使用迭代算法前应先考察方程是否有解,并在程序中对迭代的次数给予限制;   (2) 方程虽然有解,但迭代公式选择不当,或迭代的初始近似根选择不合理,也会导致迭代失败。   递归   递归是设计和描述算法的一种有力的工具,由于它在复杂算法的描述中被经常采用,为此在进一步介绍其他算法设计方法之前先讨论它。   能采用递归描述的算法通常有这样的特征:为求解规模为N的问题,设法将它分解成规模较小的问题,然后从这些小问题的解方便地构造出大问题的解,并且这些规模较小的问题也能采用同样的分解和综合方法,分解成规模更小的问题,并从这些更小问题的解构造出规模较大问题的解。特别地,当规模N=1时,能直接得解。   【问题】 编写计算斐波那契(Fibonacci)数列的第n项函数fib(n)。   斐波那契数列为:0、1、1、2、3、……,即:   fib(0)=0;   fib(1)=1;   fib(n)=fib(n-1)+fib(n-2) (当n>1时)。   写成递归函数有:   int fib(int n)   { if (n==0) return 0;   if (n==1) return 1;   if (n>1) return fib(n-1)+fib(n-2);   }   递归算法的执行过程分递推和回归两个阶段。在递推阶段,把较复杂的问题(规模为n)的求解推到比原问题简单一些的问题(规模小于n)的求解。例如上例中,求解fib(n),把它推到求解fib(n-1)和fib(n-2)。也就是说,为计算fib(n),必须先计算fib(n-1)和fib(n- 2),而计算fib(n-1)和fib(n-2),又必须先计算fib(n-3)和fib(n-4)。依次类推,直至计算fib(1)和fib(0),分别能立即得到结果1和0。在递推阶段,必须要有终止递归的情况。例如在函数fib中,当n为1和0的情况。   在回归阶段,当获得最简单情况的解后,逐级返回,依次得到稍复杂问题的解,例如得到fib(1)和fib(0)后,返回得到fib(2)的结果,……,在得到了fib(n-1)和fib(n-2)的结果后,返回得到fib(n)的结果。   在编写递归函数时要注意,函数中的局部变量和参数知识局限于当前调用层,当递推进入“简单问题”层时,原来层次上的参数和局部变量便被隐蔽起来。在一系列“简单问题”层,它们各有自己的参数和局部变量。   由于递归引起一系列的函数调用,并且可能会有一系列的重复计算,递归算法的执行效率相对较低。当某个递归算法能较方便地转换成递推算法时,通常按递推算法编写程序。例如上例计算斐波那契数列的第n项的函数fib(n)应采用递推算法,即从斐波那契数列的前两项出发,逐次由前两项计算出下一项,直至计算出要求的第n项。   【问题】 组合问题   问题描述:找出从自然数1、2、……、n中任取r个数的所有组合。例如n=5,r=3的所有组合为: (1)5、4、3 (2)5、4、2 (3)5、4、1   (4)5、3、2 (5)5、3、1 (6)5、2、1   (7)4、3、2 (8)4、3、1 (9)4、2、1   (10)3、2、1   分析所列的10个组合,可以采用这样的递归思想来考虑求组合函数的算法。设函数为void comb(int m,int k)为找出从自然数1、2、……、m中任取k个数的所有组合。当组合的第一个数字选定时,其后的数字是从余下的m-1个数中取k-1数的组合。这就将求m 个数中取k个数的组合问题转化成求m-1个数中取k-1个数的组合问题。设函数引入工作数组a[ ]存放求出的组合的数字,约定函数将确定的k个数字组合的第一个数字放在a[k]中,当一个组合求出后,才将a[ ]中的一个组合输出。第一个数可以是m、m-1、……、k,函数将确定组合的第一个数字放入数组后,有两种可能的选择,因还未去顶组合的其余元素,继续递归去确定;或因已确定了组合的全部元素,输出这个组合。细节见以下程序中的函数comb。   【程序】   # include   # define MAXN 100   int a[MAXN];   void comb(int m,int k)   { int i,j;   for (i=m;i>=k;i--)   { a[k]=i;   if (k>1)   comb(i-1,k-1);   else   { for (j=a[0];j>0;j--)   printf(“%4d”,a[j]);   printf(“\n”);   }   }   }   void main()   { a[0]=3;   comb(5,3);   }   【问题】 背包问题   问题描述:有不同价值、不同重量的物品n件,求从这n件物品中选取一部分物品的选择方案,使选中物品的总重量不超过指定的限制重量,但选中物品的价值之和最大。   设n 件物品的重量分别为w0、w1、…、wn-1,物品的价值分别为v0、v1、…、vn-1。采用递归寻找物品的选择方案。设前面已有了多种选择的方案,并保留了其中总价值最大的方案于数组option[ ],该方案的总价值存于变量maxv。当前正在考察新方案,其物品选择情况保存于数组cop[ ]。假定当前方案已考虑了前i-1件物品,现在要考虑第i件物品;当前方案已包含的物品的重量之和为tw;至此,若其余物品都选择是可能的话,本方案能达到的总价值的期望值为tv。算法引入tv是当一旦当前方案的总价值的期望值也小于前面方案的总价值maxv时,继续考察当前方案变成无意义的工作,应终止当前方案,立即去考察下一个方案。因为当方案的总价值不比maxv大时,该方案不会被再考察,这同时保证函数后找到的方案一定会比前面的方案更好。   对于第i件物品的选择考虑有两种可能:   (1) 考虑物品i被选择,这种可能性仅当包含它不会超过方案总重量限制时才是可行的。选中后,继续递归去考虑其余物品的选择。   (2) 考虑物品i不被选择,这种可能性仅当不包含物品i也有可能会找到价值更大的方案的情况。   按以上思想写出递归算法如下:   try(物品i,当前选择已达到的重量和,本方案可能达到的总价值tv)   { /*考虑物品i包含在当前方案中的可能性*/   if(包含物品i是可以接受的)   { 将物品i包含在当前方案中;   if (i   try(i+1,tw+物品i的重量,tv);   else   /*又一个完整方案,因为它比前面的方案好,以它作为最佳方案*/   以当前方案作为临时最佳方案保存;   恢复物品i不包含状态;   }   /*考虑物品i不包含在当前方案中的可能性*/   if (不包含物品i仅是可男考虑的)   if (i   try(i+1,tw,tv-物品i的价值);   else   /*又一个完整方案,因它比前面的方案好,以它作为最佳方案*/   以当前方案作为临时最佳方案保存;   }   为了理解上述算法,特举以下实例。设有4件物品,它们的重量和价值见表:   物品 0 1 2 3   重量 5 3 2 1   价值 4 4 3 1   并设限制重量为7。则按以上算法,下图表示找解过程。由图知,一旦找到一个解,算法就进一步找更好的佳。如能判定某个查找分支不会找到更好的解,算法不会在该分支继续查找,而是立即终止该分支,并去考察下一个分支。   按上述算法编写函数和程序如下:   【程序】   # include   # define N 100   double limitW,totV,maxV;   int option[N],cop[N];   struct { double weight;   double value;   }a[N];   int n;   void find(int i,double tw,double tv)   { int k;   /*考虑物品i包含在当前方案中的可能性*/   if (tw+a.weight<=limitW)   { cop=1;   if (i   else   { for (k=0;k   option[k]=cop[k];   maxv=tv;   }   cop=0;   }   /*考虑物品i不包含在当前方案中的可能性*/   if (tv-a.value>maxV)   if (i   else   { for (k=0;k   option[k]=cop[k];   maxv=tv-a.value;   }   }   void main()   { int k;   double w,v;   printf(“输入物品种数\n”);   scanf((“%d”,&n);   printf(“输入各物品的重量和价值\n”);   for (totv=0.0,k=0;k   { scanf(“%1f%1f”,&w,&v);   a[k].weight=w;   a[k].value=v;   totV+=V;   }   printf(“输入限制重量\n”);   scanf(“%1f”,&limitV);   maxv=0.0;   for (k=0;k find(0,0.0,totV);   for (k=0;k   if (option[k]) printf(“%4d”,k+1);   printf(“\n总价值为%.2f\n”,maxv);   }   作为对比,下面以同样的解题思想,考虑非递归的程序解。为了提高找解速度,程序不是简单地逐一生成所有候选解,而是从每个物品对候选解的影响来形成值得进一步考虑的候选解,一个候选解是通过依次考察每个物品形成的。对物品i的考察有这样几种情况:当该物品被包含在候选解中依旧满足解的总重量的限制,该物品被包含在候选解中是应该继续考虑的;反之,该物品不应该包括在当前正在形成的候选解中。同样地,仅当物品不被包括在候选解中,还是有可能找到比目前临时最佳解更好的候选解时,才去考虑该物品不被包括在候选解中;反之,该物品不包括在当前候选解中的方案也不应继续考虑。对于任一值得继续考虑的方案,程序就去进一步考虑下一个物品。   【程序】   # include   # define N 100   double limitW;   int cop[N];   struct ele { double weight;   double value;   } a[N];   int k,n;   struct { int ;   double tw;   double tv;   }twv[N];   void next(int i,double tw,double tv)   { twv.=1;   twv.tw=tw;   twv.tv=tv;   }   double find(struct ele *a,int n)   { int i,k,f;   double maxv,tw,tv,totv;   maxv=0;   for (totv=0.0,k=0;k   totv+=a[k].value;   next(0,0.0,totv);   i=0;   While (i>=0)   { f=twv.;   tw=twv.tw;   tv=twv.tv;   switch(f)   { case 1: twv.++;   if (tw+a.weight<=limitW)   if (i   { next(i+1,tw+a.weight,tv);   i++;   }   else   { maxv=tv;   for (k=0;k   cop[k]=twv[k].!=0;   }   break;   case 0: i--;   break;   default: twv.=0;   if (tv-a.value>maxv)   if (i   { next(i+1,tw,tv-a.value);   i++;   }   else   { maxv=tv-a.value;   for (k=0;k   cop[k]=twv[k].!=0;   }   break;   }   }   return maxv;   }   void main()   { double maxv;   printf(“输入物品种数\n”);   scanf((“%d”,&n);   printf(“输入限制重量\n”);   scanf(“%1f”,&limitW);   printf(“输入各物品的重量和价值\n”);   for (k=0;k   scanf(“%1f%1f”,&a[k].weight,&a[k].value);   maxv=find(a,n);   printf(“\n选中的物品为\n”);   for (k=0;k   if (option[k]) printf(“%4d”,k+1);   printf(“\n总价值为%.2f\n”,maxv);   }   递归的基本概念和特点   程序调用自身的编程技巧称为递归( recursion)。   一个过程或函数在其定义或说明中又直接或间接调用自身的一种方法,它通常把一个大型复杂的问题层层转化为一个与原问题相似的规模较小的问题来求解,递归策略只需少量的程序就可描述出解题过程所需要的多次重复计算,大大地减少了程序的代码量。递归的能力在于用有限的语句来定义对象的无限集合。用递归思想写出的程序往往十分简洁易懂。   一般来说,递归需要有边界条件、递归前进段和递归返回段。当边界条件不满足时,递归前进;当边界条件满足时,递归返回。   注意:   (1) 递归就是在过程或函数里调用自身;   (2) 在使用递增归策略时,必须有一个明确的递归结束条件,称为递归出口。

小哇 2019-12-02 01:25:19 0 浏览量 回答数 0

问题

MaxCompute用户指南:MapReduce:Java SDK:原生SDK概述

行者武松 2019-12-01 22:04:29 1179 浏览量 回答数 0

回答

迭代法也称辗转法,是一种不断用变量的旧值递推新值的过程,跟迭代法相对应的是直接法,即一次性解决问题。迭代法又分为精确迭代和近似迭代。“二分法”和“牛顿迭代法”属于近似迭代法。迭代算法是用计算机解决问题的一种基本方法。它利用计算机运算速度快、适合做重复性操作的特点,让计算机对一组指令(或一定步骤)进行重复执行,在每次执行这组指令(或这些步骤)时,都从变量的原值推出它的一个新值。 迭代是数值分析中通过从一个初始估计出发寻找一系列近似解来解决问题(一般是解方程或者方程组)的过程,为实现这一过程所使用的方法统称为迭代法(Iterative Method)。 一般可以做如下定义:对于给定的线性方程组x=Bx+f(这里的x、B、f同为矩阵,任意线性方程组都可以变换成此形式),用公式x(k+1)=Bx(k)+f(括号中为上标,代表迭代k次得到的x,初始时k=0)逐步带入求近似解的方法称为迭代法(或称一阶定常迭代法)。如果k趋向无穷大时limx(k)存在,记为x*,称此迭代法收敛。显然x*就是此方程组的解,否则称为迭代法发散。 跟迭代法相对应的是直接法(或者称为一次解法),即一次性的快速解决问题,例如通过开方解决方程x +3= 4。一般如果可能,直接解法总是优先考虑的。但当遇到复杂问题时,特别是在未知量很多,方程为非线性时,我们无法找到直接解法(例如五次以及更高次的代数方程没有解析解,参见阿贝耳定理),这时候或许可以通过迭代法寻求方程(组)的近似解。 最常见的迭代法是牛顿法。其他还包括最速下降法、共轭迭代法、变尺度迭代法、最小二乘法、线性规划、非线性规划、单纯型法、惩罚函数法、斜率投影法、遗传算法、模拟退火等等。 利用迭代算法解决问题,需要做好以下三个方面的工作: 确定迭代变量 在可以用迭代算法解决的问题中,至少存在一个直接或间接地不断由旧值递推出新值的变量,这个变量就是迭代变量。 建立迭代关系式 所谓迭代关系式,指如何从变量的前一个值推出其下一个值的公式(或关系)。迭代关系式的建立是解决迭代问题的关键,通常可以顺推或倒推的方法来完成。 对迭代过程进行控制 在 什么时候结束迭代过程。这是编写迭代程序必须考虑的问题。不能让迭代过程无休止地重复执行下去。迭代过程的控制通常可分为两种情况:一种是所需的迭代次数 是个确定的值,可以计算出来;另一种是所需的迭代次数无法确定。对于前一种情况,可以构建一个固定次数的循环来实现对迭代过程的控制;对于后一种情况,需 要进一步分析出用来结束迭代过程的条件。 举例 例 1 :一个饲养场引进一只刚出生的新品种兔子,这种兔子从出生的下一个月开始,每月新生一只兔子,新生的兔子也如此繁殖。如果所有的兔子都不死去,问到第 12 个月时,该饲养场共有兔子多少只。 分析:这是一个典型的递推问题。我们不妨假设第 1 个月时兔子的只数为 u 1 ,第 2 个月时兔子的只数为 u 2 ,第 3 个月时兔子的只数为 u 3 ,……根据题意,“这种兔子从出生的下一个月开始,每月新生一只兔子”,则有 u 1 = 1 , u 2 = u 1 + u 1 × 1 = 2 , u 3 = u 2 + u 2 × 1 = 4 ,…… 根据这个规律,可以归纳出下面的递推公式: u n = u(n - 1)× 2 (n ≥ 2) 对应 u n 和 u(n - 1),定义两个迭代变量 y 和 x ,可将上面的递推公式转换成如下迭代关系: y=x*2 x=y 让计算机对这个迭代关系重复执行 11 次,就可以算出第 12 个月时的兔子数。参考程序如下: cls x=1 for i=2 to 12 y=x*2 x=y next i print y end 例 2 :阿米巴用简单分裂的方式繁殖,它每分裂一次要用 3 分钟。将若干个阿米巴放在一个盛满营养参液的容器内, 45 分钟后容器内充满了阿米巴。已知容器最多可以装阿米巴 220,220个。试问,开始的时候往容器内放了多少个阿米巴。请编程序算出。 分析:根据题意,阿米巴每 3 分钟分裂一次,那么从开始的时候将阿米巴放入容器里面,到 45 分钟后充满容器,需要分裂 45/3=15 次。而“容器最多可以装阿米巴2^ 20 个”,即阿米巴分裂 15 次以后得到的个数是 2^20。题目要求我们计算分裂之前的阿米巴数,不妨使用倒推的方法,从第 15 次分裂之后的 2^20 个,倒推出第 15 次分裂之前(即第 14 次分裂之后)的个数,再进一步倒推出第 13 次分裂之后、第 12 次分裂之后、……第 1 次分裂之前的个数。 设第 1 次分裂之前的个数为 x 0 、第 1 次分裂之后的个数为 x 1 、第 2 次分裂之后的个数为 x 2 、……第 15 次分裂之后的个数为 x 15 ,则有 x 14 =x 15 /2 、 x 13 =x 14 /2 、…… x n-1 =x n /2 (n ≥ 1) 因为第 15 次分裂之后的个数 x 15 是已知的,如果定义迭代变量为 x ,则可以将上面的倒推公式转换成如下的迭代公式: x=x/2 (x 的初值为第 15 次分裂之后的个数 2^20) 让这个迭代公式重复执行 15 次,就可以倒推出第 1 次分裂之前的阿米巴个数。因为所需的迭代次数是个确定的值,我们可以使用一个固定次数的循环来实现对迭代过程的控制。参考程序如下: cls x=2^20 for i=1 to 15 x=x/2 next i print x end ps:java中幂的算法是Math.pow(2,20);返回double,稍微注意一下 例 3 :验证谷角猜想。日本数学家谷角静夫在研究自然数时发现了一个奇怪现象:对于任意一个自然数 n ,若 n 为偶数,则将其除以 2 ;若 n 为奇数,则将其乘以 3 ,然后再加 1。如此经过有限次运算后,总可以得到自然数 1。人们把谷角静夫的这一发现叫做“谷角猜想”。 要求:编写一个程序,由键盘输入一个自然数 n ,把 n 经过有限次运算后,最终变成自然数 1 的全过程打印出来。 分析:定义迭代变量为 n ,按照谷角猜想的内容,可以得到两种情况下的迭代关系式:当 n 为偶数时, n=n/2 ;当 n 为奇数时, n=n*3+1。用 QBASIC 语言把它描述出来就是: if n 为偶数 then n=n/2 else n=n*3+1 end if 这就是需要计算机重复执行的迭代过程。这个迭代过程需要重复执行多少次,才能使迭代变量 n 最终变成自然数 1 ,这是我们无法计算出来的。因此,还需进一步确定用来结束迭代过程的条件。仔细分析题目要求,不难看出,对任意给定的一个自然数 n ,只要经过有限次运算后,能够得到自然数 1 ,就已经完成了验证工作。因此,用来结束迭代过程的条件可以定义为:n=1。参考程序如下: cls input "Please input n=";n do until n=1 if n mod 2=0 then rem 如果 n 为偶数,则调用迭代公式 n=n/2 n=n/2 print "—";n; else n=n*3+1 print "—";n; end if loop end 迭代法开平方: #include<stdio.h> #include<math.h> void main() { double a,x0,x1; printf("Input a:\n"); scanf("%lf",&a);//为什么在VC6.0中不能写成“scanf("%f",&a);”。 if(a<0) printf("Error!\n"); else { x0=a/2; x1=(x0+a/x0)/2; do { x0=x1; x1=(x0+a/x0)/2; }while(fabs(x0-x1)>=1e-6); } printf("Result:\n"); printf("sqrt(%g)=%g\n",a,x1); } 求平方根的迭代公式:x1=1/2*(x0+a/x0)。 算法:1.先自定一个初值x0,作为a的平方根值,在我们的程序中取a/2作为a的初值;利用迭代公式求出一个x1。此值与真正的a的平方根值相比,误差很大。 ⒉把新求得的x1代入x0中,准备用此新的x0再去求出一个新的x1. ⒊利用迭代公式再求出一个新的x1的值,也就是用新的x0又求出一个新的平方根值x1,此值将更趋近于真正的平方根值。 ⒋比较前后两次求得的平方根值x0和x1,如果它们的差值小于我们指定的值,即达到我们要求的精度,则认为x1就是a的平方根值,去执行步骤5;否则执行步骤2,即循环进行迭代。 迭代法是用于求方程或方程组近似根的一种常用的算法设计方法。设方程为f(x)=0,用某种数学方法导出等价的形式x=g(x),然后按以下步骤执行: ⑴ 选一个方程的近似根,赋给变量x0; ⑵ 将x0的值保存于变量x1,然后计算g(x1),并将结果存于变量x0; ⑶ 当x0与x1的差的绝对值还小于指定的精度要求时,重复步骤⑵的计算。 若方程有根,并且用上述方法计算出来的近似根序列收敛,则按上述方法求得的x0就认为是方程的根。上述算法用C程序的形式表示为: 【算法】迭代法求方程的根 { x0=初始近似根; do { x1=x0; x0=g(x1); /*按特定的方程计算新的近似根*/ } while (fabs(x0-x1)>Epsilon); printf(“方程的近似根是%f\n”,x0); } 迭代算法也常用于求方程组的根,令 X=(x0,x1,…,xn-1) 设方程组为: xi=gi(X) (I=0,1,…,n-1) 则求方程组根的迭代算法可描述如下: 【算法】迭代法求方程组的根 { for (i=0;i x=初始近似根; do { for (i=0;i y=x; for (i=0;i x=gi(X); for (delta=0.0,i=0;i if (fabs(y-x)>delta) delta=fabs(y-x); } while (delta>Epsilon); for (i=0;i printf(“变量x[%d]的近似根是 %f”,I,x); printf(“\n”); } 具体使用迭代法求根时应注意以下两种可能发生的情况: ⑴ 如果方程无解,算法求出的近似根序列就不会收敛,迭代过程会变成死循环,因此在使用迭代算法前应先考察方程是否有解,并在程序中对迭代的次数给予限制; ⑵ 方程虽然有解,但迭代公式选择不当,或迭代的初始近似根选择不合理,也会导致迭代失败。 递归 递归是设计和描述算法的一种有力的工具,由于它在复杂算法的描述中被经常采用,为此在进一步介绍其他算法设计方法之前先讨论它。 能采用递归描述的算法通常有这样的特征:为求解规模为N的问题,设法将它分解成规模较小的问题,然后从这些小问题的解方便地构造出大问题的解,并且这些规模较小的问题也能采用同样的分解和综合方法,分解成规模更小的问题,并从这些更小问题的解构造出规模较大问题的解。特别地,当规模N=1时,能直接得解。 【问题】 编写计算斐波那契(Fibonacci)数列的第n项函数fib(n)。 斐波那契数列为:0、1、1、2、3、……,即: fib(0)=0; fib⑴=1; fib(n)=fib(n-1)+fib(n-2) (当n>1时)。 写成递归函数有: int fib(int n) { if (n==0) return 0; if (n==1) return 1; if (n>1) return fib(n-1)+fib(n-2); } 递归算法的执行过程分递推和回归两个阶段。在递推阶段,把较复杂的问题(规模为n)的求解推到比原问题简单一些的问 题(规模小于n)的求解。例如上例中,求解fib(n),把它推到求解fib(n-1)和fib(n-2)。也就是说,为计算fib(n),必须先计算 fib(n-1)和fib(n- 2),而计算fib(n-1)和fib(n-2),又必须先计算fib(n-3)和fib(n-4)。依次类推,直至计算fib⑴和fib(0),分别能 立即得到结果1和0。在递推阶段,必须要有终止递归的情况。例如在函数fib中,当n为1和0的情况。 在回归阶段,当获得最简单情况的解后,逐级返回,依次得到稍复杂问题的解,例如得到fib⑴和fib(0)后,返回得到fib⑵的结果,……,在得到了fib(n-1)和fib(n-2)的结果后,返回得到fib(n)的结果。 在编写递归函数时要注意,函数中的局部变量和参数知识局限于当前调用层,当递推进入“简单问题”层时,原来层次上的参数和局部变量便被隐蔽起来。在一系列“简单问题”层,它们各有自己的参数和局部变量。 由于递归引起一系列的函数调用,并且可能会有一系列的重复计算,递归算法的执行效率相对较低。当某个递归算法能较方便地转换成递推算法时,通常按递推算法编写程序。例如上例计算斐波那契数列的第n项的函数fib(n)应采用递推算法,即从斐波那契数列的前两项出发,逐次由前两项计算出下一项,直至计算出要求的第n项。 【问题】 组合问题 问题描述:找出从自然数1、2、……、n中任取r个数的所有组合。例如n=5,r=3的所有组合为:⑴5、4、3 ⑵5、4、2 ⑶5、4、1 ⑷5、3、2 ⑸5、3、1 ⑹5、2、1 ⑺4、3、2 ⑻4、3、1 ⑼4、2、1 ⑽3、2、1 分析所列的10个组合,可以采用这样的递归思想来考虑求组合函数的算法。设函数为void comb(int m,int k)为找出从自然数1、2、……、m中任取k个数的所有组合。当组合的第一个数字选定时,其后的数字是从余下的m-1个数中取k-1数的组合。这就将求m 个数中取k个数的组合问题转化成求m-1个数中取k-1个数的组合问题。设函数引入工作数组a[ ]存放求出的组合的数字,约定函数将确定的k个数字组合的第一个数字放在a[k]中,当一个组合求出后,才将a[ ]中的一个组合输出。第一个数可以是m、m-1、……、k,函数将确定组合的第一个数字放入数组后,有两种可能的选择,因还未去顶组合的其余元素,继续递 归去确定;或因已确定了组合的全部元素,输出这个组合。细节见以下程序中的函数comb。 【程序】 # include # define MAXN 100 int a[MAXN]; void comb(int m,int k) { int i,j; for (i=m;i>=k;i--) { a[k]=i; if (k>1) comb(i-1,k-1); else { for (j=a[0];j>0;j--) printf(“%4d”,a[j]); printf(“\n”); } } } void main() { a[0]=3; comb(5,3); } 【问题】 背包问题 问题描述:有不同价值、不同重量的物品n件,求从这n件物品中选取一部分物品的选择方案,使选中物品的总重量不超过指定的限制重量,但选中物品的价值之和最大。 设n 件物品的重量分别为w0、w1、…、wn-1,物品的价值分别为v0、v1、…、vn-1。采用递归寻找物品的选择方案。设前面已有了多种选择的方案,并 保留了其中总价值最大的方案于数组option[ ],该方案的总价值存于变量maxv。当前正在考察新方案,其物品选择情况保存于数组cop[ ]。假定当前方案已考虑了前i-1件物品,现在要考虑第i件物品;当前方案已包含的物品的重量之和为tw;至此,若其余物品都选择是可能的话,本方案能达 到的总价值的期望值为tv。算法引入tv是当一旦当前方案的总价值的期望值也小于前面方案的总价值maxv时,继续考察当前方案变成无意义的工作,应终止 当前方案,立即去考察下一个方案。因为当方案的总价值不比maxv大时,该方案不会被再考察,这同时保证函数后找到的方案一定会比前面的方案更好。 对于第i件物品的选择考虑有两种可能: ⑴ 考虑物品i被选择,这种可能性仅当包含它不会超过方案总重量限制时才是可行的。选中后,继续递归去考虑其余物品的选择。 ⑵ 考虑物品i不被选择,这种可能性仅当不包含物品i也有可能会找到价值更大的方案的情况。 按以上思想写出递归算法如下: try(物品i,当前选择已达到的重量和,本方案可能达到的总价值tv) { /*考虑物品i包含在当前方案中的可能性*/ if(包含物品i是可以接受的) { 将物品i包含在当前方案中; if (i try(i+1,tw+物品i的重量,tv); else /*又一个完整方案,因为它比前面的方案好,以它作为最佳方案*/ 以当前方案作为临时最佳方案保存; 恢复物品i不包含状态; } /*考虑物品i不包含在当前方案中的可能性*/ if (不包含物品i仅是可男考虑的) if (i try(i+1,tw,tv-物品i的价值); else /*又一个完整方案,因它比前面的方案好,以它作为最佳方案*/ 以当前方案作为临时最佳方案保存; } 为了理解上述算法,特举以下实例。设有4件物品,它们的重量和价值见表: 物品 0 1 2 3 重量 5 3 2 1 价值 4 4 3 1 并设限制重量为7。则按以上算法,下图表示找解过程。由图知,一旦找到一个解,算法就进一步找更好的佳。如能判定某个查找分支不会找到更好的解,算法不会在该分支继续查找,而是立即终止该分支,并去考察下一个分支。 按上述算法编写函数和程序如下: 【程序】 # include # define N 100 double limitW,totV,maxV; int option[N],cop[N]; struct { double weight; double value; }a[N]; int n; void find(int i,double tw,double tv) { int k; /*考虑物品i包含在当前方案中的可能性*/ if (tw+a.weight<=limitW) { cop=1; if (i else { for (k=0;k option[k]=cop[k]; maxv=tv; } cop=0; } /*考虑物品i不包含在当前方案中的可能性*/ if (tv-a.value>maxV) if (i else { for (k=0;k option[k]=cop[k]; maxv=tv-a.value; } } void main() { int k; double w,v; printf(“输入物品种数\n”); scanf((“%d”,&n); printf(“输入各物品的重量和价值\n”); for (totv=0.0,k=0;k { scanf(“%1f%1f”,&w,&v); a[k].weight=w; a[k].value=v; totV+=V; } printf(“输入限制重量\n”); scanf(“%1f”,&limitV); maxv=0.0; for (k=0;k find(0,0.0,totV); for (k=0;k if (option[k]) printf(“%4d”,k+1); printf(“\n总价值为%.2f\n”,maxv); } 作为对比,下面以同样的解题思想,考虑非递归的程序解。为了提高找解速度,程序不是简单地逐一生成所有候选解,而是 从每个物品对候选解的影响来形成值得进一步考虑的候选解,一个候选解是通过依次考察每个物品形成的。对物品i的考察有这样几种情况:当该物品被包含在候选 解中依旧满足解的总重量的限制,该物品被包含在候选解中是应该继续考虑的;反之,该物品不应该包括在当前正在形成的候选解中。同样地,仅当物品不被包括在 候选解中,还是有可能找到比目前临时最佳解更好的候选解时,才去考虑该物品不被包括在候选解中;反之,该物品不包括在当前候选解中的方案也不应继续考虑。 对于任一值得继续考虑的方案,程序就去进一步考虑下一个物品。 【程序】 # include # define N 100 double limitW; int cop[N]; struct ele { double weight; double value; } a[N]; int k,n; struct { int ; double tw; double tv; }twv[N]; void next(int i,double tw,double tv) { twv.=1; twv tw=tw; twv tv=tv; } double find(struct ele *a,int n) { int i,k,f; double maxv,tw,tv,totv; maxv=0; for (totv=0.0,k=0;k totv+=a[k].value; next(0,0.0,totv); i=0; While (i>=0) { f=twv.; tw=twv tw; tv=twv tv; switch(f) { case 1: twv.++; if (tw+a.weight<=limitW) if (i { next(i+1,tw+a.weight,tv); i++; } else { maxv=tv; for (k=0;k cop[k]=twv[k].!=0; } break; case 0: i--; break; default: twv.=0; if (tv-a.value>maxv) if (i { next(i+1,tw,tv-a.value); i++; } else { maxv=tv-a.value; for (k=0;k cop[k]=twv[k].!=0; } break; } } return maxv; } void main() { double maxv; printf(“输入物品种数\n”); scanf((“%d”,&n); printf(“输入限制重量\n”); scanf(“%1f”,&limitW); printf(“输入各物品的重量和价值\n”); for (k=0;k scanf(“%1f%1f”,&a[k].weight,&a[k].value); maxv=find(a,n); printf(“\n选中的物品为\n”); for (k=0;k if (option[k]) printf(“%4d”,k+1); printf(“\n总价值为%.2f\n”,maxv); }

云篆 2019-12-02 01:25:10 0 浏览量 回答数 0

回答

学习Python的人都知道数组是最常用的的数据类型,为了保证程序的正确性,需要调试程序。 因此,需要在程序中控制台中打印数组的全部元素,如果数组的容量较小,例如 只含有10个元素,采用print命令或print函数可以答应出数组中的每个元素; 如果数组的容量过大,只能打印出数组的部分元素,打印结果只包含开始部分元素和结尾部分元素,中间元素省略。省略的部分不利于程序的调试; 因此,为了方便调试程序,需要将数组中的元素全部打印出来。 少量元素情况 ? 1234 打印数组中的元素 import numpy as npa = np.array(6)print a 程序结果为: ? 1 [0 1 2 3 4 5] 大量元素情况 可以采用 set_printoptions(threshold='nan') ? 1234 import numpy as npnp.set_printoptions(threshold=np.NaN)print np.arange(100)print np.arange(100).reshape(10, 10) 结果为: [ 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99] [[ 0 1 2 3 4 5 6 7 8 9] [10 11 12 13 14 15 16 17 18 19] [20 21 22 23 24 25 26 27 28 29] [30 31 32 33 34 35 36 37 38 39] [40 41 42 43 44 45 46 47 48 49] [50 51 52 53 54 55 56 57 58 59] [60 61 62 63 64 65 66 67 68 69] [70 71 72 73 74 75 76 77 78 79] [80 81 82 83 84 85 86 87 88 89] [90 91 92 93 94 95 96 97 98 99]] 当array里面的存放的数据维度过大时,在控制台会出现不能将array完全输出的情况,中间部分的结果会用省略号打印出来。这时就需要用到numpy里面的set_printoptions()方法 我们来看一下 set_printoptions 方法的简单说明 ? 12345678 set_printoptions(precision=None, threshold=None, edgeitems=None, linewidth=None, suppress=None, nanstr=None, infstr=None, formatter=None) precision:输出结果保留精度的位数 threshold:array数量的个数在小于threshold的时候不会被折叠 edgeitems:在array已经被折叠后,开头和结尾都会显示edgeitems个数 formatter:这个很有意思,像python3里面str.format(),就是可以对你的输出进行自定义的格式化 举例: precision: ? 123 np.set_printoptions(precision=4)print(np.array([1.23456789])) [ 1.2346] # 最后进位了 threshold: ? 123456 np.set_printoptions(threshold=10)print(np.arange(1, 11, 1)) # np.arange(1, 11, 1)生成出来是[1-10],10个数 [ 1 2 3 4 5 6 7 8 9 10] np.set_printoptions(threshold=9)print(np.arange(1, 11, 1)) [ 1 2 3 ..., 8 9 10] edgeitems: ? 123456 np.set_printoptions(threshold=5)print(np.arange(1, 11, 1)) [ 1 2 3 ..., 8 9 10] np.set_printoptions(threshold=5, edgeitems=4)print(np.arange(1, 11, 1)) [ 1 2 3 4 ..., 7 8 9 10] formatter ? 123 np.set_printoptions(formatter={'all': lambda x: 'int: ' + str(-x)})print(np.arange(1, 5, 1)) [int: -1 int: -2 int: -3 int: -4] 这个formatter是一个可调用的字典,'all'是其中一个key,表示里面的x可以包含所有type,还有其他key,具体可以在源码里面查看最后如果只想在代码中的某一部分使用自定义的printoptions,那么可以通过再次调用np.set_printoptions()这个方法来进行reset sublime也是一款常用的文本编辑器,当我们在对python文件进行编辑的时候,我们可以在首选项里进行配置来快速运行python文件; 方法/步骤我们用sublime打开一个python文件,我们要怎么运行它呢,如图所示;如何在sublime上运行python我们点击"首选项/浏览包",点开之后出来一个目录图形窗口,我们找到python文件夹,如果没有我们新建一个,如图所示;如何在sublime上运行python如何在sublime上运行python在python文件夹里,我们再建一个文件命名为 Python.sublime-commands,如图所示;如何在sublime上运行python然后我们写入如下内容,path那一行要换成自己的python安装路径;{"cmd":["python.exe", "-u", "$file"],"path":"D:python3.6","file_regex": "^[ ]File "(...?)", line ([0-9]*)","selector": "source.python"}如何在sublime上运行python修改好之后我们点击保存,我们点击"Tools/Bulid System/Python",如图所示;如何在sublime上运行python然后我们回到之前的python文件,我们点击"Tools/Bulid"(快捷键是Ctrl+B,后面就按快捷键了),可以看到运行的结果,如图所示;如何在sublime上运行python如何在sublime上运行python我们再打开一个有错误的python文件,运行看一下效果,如图所示;如何在sublime上运行python

xuning715 2019-12-02 01:10:43 0 浏览量 回答数 0

问题

最佳实践 -SQL Server -解析SQL Server 2012常用的分析函数

李沃晟 2019-12-01 21:40:07 551 浏览量 回答数 0

回答

一、接口的默认方法 Java 8允许我们给接口添加一个非抽象的方法实现,只需要使用 default关键字即可,这个特征又叫做扩展方法,示例如下: interface Formula { double calculate(int a); default double sqrt(int a) { return Math.sqrt(a); } } Formula接口在拥有calculate方法之外同时还定义了sqrt方法,实现了Formula接口的子类只需要实现一个calculate方法,默认方法sqrt将在子类上可以直接使用。 Formula formula = new Formula() { @Override public double calculate(int a) { return sqrt(a * 100); } }; formula.calculate(100); // 100.0 formula.sqrt(16); // 4.0 文中的formula被实现为一个匿名类的实例,该代码非常容易理解,6行代码实现了计算 sqrt(a * 100)。在下一节中,我们将会看到实现单方法接口的更简单的做法。 译者注: 在Java中只有单继承,如果要让一个类赋予新的特性,通常是使用接口来实现,在C++中支持多继承,允许一个子类同时具有多个父类的接口与功能,在其他语言中,让一个类同时具有其他的可复用代码的方法叫做mixin。新的Java 8 的这个特新在编译器实现的角度上来说更加接近Scala的trait。 在C#中也有名为扩展方法的概念,允许给已存在的类型扩展方法,和Java 8的这个在语义上有差别。 二、Lambda 表达式 首先看看在老版本的Java中是如何排列字符串的: List<String> names = Arrays.asList("peter", "anna", "mike", "xenia"); Collections.sort(names, new Comparator<String>() { @Override public int compare(String a, String b) { return b.compareTo(a); } }); 只需要给静态方法 Collections.sort 传入一个List对象以及一个比较器来按指定顺序排列。通常做法都是创建一个匿名的比较器对象然后将其传递给sort方法。 在Java 8 中你就没必要使用这种传统的匿名对象的方式了,Java 8提供了更简洁的语法,lambda表达式: Collections.sort(names, (String a, String b) -> { return b.compareTo(a); }); 看到了吧,代码变得更段且更具有可读性,但是实际上还可以写得更短: Collections.sort(names, (String a, String b) -> b.compareTo(a)); 对于函数体只有一行代码的,你可以去掉大括号{}以及return关键字,但是你还可以写得更短点: Collections.sort(names, (a, b) -> b.compareTo(a)); Java编译器可以自动推导出参数类型,所以你可以不用再写一次类型。接下来我们看看lambda表达式还能作出什么更方便的东西来 三、函数式接口 Lambda表达式是如何在java的类型系统中表示的呢?每一个lambda表达式都对应一个类型,通常是接口类型。而“函数式接口”是指仅仅只包含一个抽象方法的接口,每一个该类型的lambda表达式都会被匹配到这个抽象方法。因为 默认方法 不算抽象方法,所以你也可以给你的函数式接口添加默认方法。 我们可以将lambda表达式当作任意只包含一个抽象方法的接口类型,确保你的接口一定达到这个要求,你只需要给你的接口添加 @FunctionalInterface 注解,编译器如果发现你标注了这个注解的接口有多于一个抽象方法的时候会报错的。 @FunctionalInterface interface Converter<F, T> { T convert(F from); } Converter<String, Integer> converter = (from) -> Integer.valueOf(from); Integer converted = converter.convert("123"); System.out.println(converted); // 123 需要注意如果@FunctionalInterface如果没有指定,上面的代码也是对的。 译者注 将lambda表达式映射到一个单方法的接口上,这种做法在Java 8之前就有别的语言实现,比如Rhino JavaScript解释器,如果一个函数参数接收一个单方法的接口而你传递的是一个function,Rhino 解释器会自动做一个单接口的实例到function的适配器,典型的应用场景有 org.w3c.dom.events.EventTarget 的addEventListener 第二个参数 EventListener。 四、方法与构造函数引用 前一节中的代码还可以通过静态方法引用来表示: Converter<String, Integer> converter = Integer::valueOf; Integer converted = converter.convert("123"); System.out.println(converted); // 123 Java 8 允许你使用 :: 关键字来传递方法或者构造函数引用,上面的代码展示了如何引用一个静态方法,我们也可以引用一个对象的方法: converter = something::startsWith; String converted = converter.convert("Java"); System.out.println(converted); // "J" 接下来看看构造函数是如何使用::关键字来引用的,首先我们定义一个包含多个构造函数的简单类: class Person { String firstName; String lastName; Person() {} Person(String firstName, String lastName) { this.firstName = firstName; this.lastName = lastName; } } 接下来我们指定一个用来创建Person对象的对象工厂接口: interface PersonFactory<P extends Person> { P create(String firstName, String lastName); } 这里我们使用构造函数引用来将他们关联起来,而不是实现一个完整的工厂: PersonFactory<Person> personFactory = Person::new; Person person = personFactory.create("Peter", "Parker"); 我们只需要使用 Person::new 来获取Person类构造函数的引用,Java编译器会自动根据PersonFactory.create方法的签名来选择合适的构造函数。 五、Lambda 作用域 在lambda表达式中访问外层作用域和老版本的匿名对象中的方式很相似。你可以直接访问标记了final的外层局部变量,或者实例的字段以及静态变量。 六、访问局部变量 我们可以直接在lambda表达式中访问外层的局部变量: final int num = 1; Converter<Integer, String> stringConverter = (from) -> String.valueOf(from + num); stringConverter.convert(2); // 3 但是和匿名对象不同的是,这里的变量num可以不用声明为final,该代码同样正确: int num = 1; Converter<Integer, String> stringConverter = (from) -> String.valueOf(from + num); stringConverter.convert(2); // 3 不过这里的num必须不可被后面的代码修改(即隐性的具有final的语义),例如下面的就无法编译: int num = 1; Converter<Integer, String> stringConverter = (from) -> String.valueOf(from + num); num = 3; 在lambda表达式中试图修改num同样是不允许的。 七、访问对象字段与静态变量 和本地变量不同的是,lambda内部对于实例的字段以及静态变量是即可读又可写。该行为和匿名对象是一致的: class Lambda4 { static int outerStaticNum; int outerNum; void testScopes() { Converter<Integer, String> stringConverter1 = (from) -> { outerNum = 23; return String.valueOf(from); }; Converter<Integer, String> stringConverter2 = (from) -> { outerStaticNum = 72; return String.valueOf(from); }; } } 八、访问接口的默认方法 还记得第一节中的formula例子么,接口Formula定义了一个默认方法sqrt可以直接被formula的实例包括匿名对象访问到,但是在lambda表达式中这个是不行的。 Lambda表达式中是无法访问到默认方法的,以下代码将无法编译: Formula formula = (a) -> sqrt( a * 100); Built-in Functional Interfaces JDK 1.8 API包含了很多内建的函数式接口,在老Java中常用到的比如Comparator或者Runnable接口,这些接口都增加了@FunctionalInterface注解以便能用在lambda上。 Java 8 API同样还提供了很多全新的函数式接口来让工作更加方便,有一些接口是来自Google Guava库里的,即便你对这些很熟悉了,还是有必要看看这些是如何扩展到lambda上使用的。 Predicate接口 Predicate 接口只有一个参数,返回boolean类型。该接口包含多种默认方法来将Predicate组合成其他复杂的逻辑(比如:与,或,非): Predicate<String> predicate = (s) -> s.length() > 0; predicate.test("foo"); // true predicate.negate().test("foo"); // false Predicate<Boolean> nonNull = Objects::nonNull; Predicate<Boolean> isNull = Objects::isNull; Predicate<String> isEmpty = String::isEmpty; Predicate<String> isNotEmpty = isEmpty.negate(); Function 接口 Function 接口有一个参数并且返回一个结果,并附带了一些可以和其他函数组合的默认方法(compose, andThen): Function<String, Integer> toInteger = Integer::valueOf; Function<String, String> backToString = toInteger.andThen(String::valueOf); backToString.apply("123"); // "123" Supplier 接口 Supplier 接口返回一个任意范型的值,和Function接口不同的是该接口没有任何参数 Supplier personSupplier = Person::new; personSupplier.get(); // new Person Consumer 接口 Consumer 接口表示执行在单个参数上的操作。 Consumer greeter = (p) -> System.out.println("Hello, " + p.firstName); greeter.accept(new Person("Luke", "Skywalker")); Comparator 接口 Comparator 是老Java中的经典接口, Java 8在此之上添加了多种默认方法: Comparator comparator = (p1, p2) -> p1.firstName.compareTo(p2.firstName); Person p1 = new Person("John", "Doe"); Person p2 = new Person("Alice", "Wonderland"); comparator.compare(p1, p2); // > 0 comparator.reversed().compare(p1, p2); // < 0 Optional 接口 Optional 不是函数是接口,这是个用来防止NullPointerException异常的辅助类型,这是下一届中将要用到的重要概念,现在先简单的看看这个接口能干什么: Optional 被定义为一个简单的容器,其值可能是null或者不是null。在Java 8之前一般某个函数应该返回非空对象但是偶尔却可能返回了null,而在Java 8中,不推荐你返回null而是返回Optional。 Optional optional = Optional.of("bam"); optional.isPresent(); // true optional.get(); // "bam" optional.orElse("fallback"); // "bam" optional.ifPresent((s) -> System.out.println(s.charAt(0))); // "b" Stream 接口 java.util.Stream 表示能应用在一组元素上一次执行的操作序列。Stream 操作分为中间操作或者最终操作两种,最终操作返回一特定类型的计算结果,而中间操作返回Stream本身,这样你就可以将多个操作依次串起来。Stream 的创建需要指定一个数据源,比如 java.util.Collection的子类,List或者Set, Map不支持。Stream的操作可以串行执行或者并行执行。 首先看看Stream是怎么用,首先创建实例代码的用到的数据List: List stringCollection = new ArrayList<>(); stringCollection.add("ddd2"); stringCollection.add("aaa2"); stringCollection.add("bbb1"); stringCollection.add("aaa1"); stringCollection.add("bbb3"); stringCollection.add("ccc"); stringCollection.add("bbb2"); stringCollection.add("ddd1"); Java 8扩展了集合类,可以通过 Collection.stream() 或者 Collection.parallelStream() 来创建一个Stream。下面几节将详细解释常用的Stream操作: Filter 过滤 过滤通过一个predicate接口来过滤并只保留符合条件的元素,该操作属于中间操作,所以我们可以在过滤后的结果来应用其他Stream操作(比如forEach)。forEach需要一个函数来对过滤后的元素依次执行。forEach是一个最终操作,所以我们不能在forEach之后来执行其他Stream操作。 stringCollection .stream() .filter((s) -> s.startsWith("a")) .forEach(System.out::println); // "aaa2", "aaa1" Sort 排序 排序是一个中间操作,返回的是排序好后的Stream。如果你不指定一个自定义的Comparator则会使用默认排序。 stringCollection .stream() .sorted() .filter((s) -> s.startsWith("a")) .forEach(System.out::println); // "aaa1", "aaa2" 需要注意的是,排序只创建了一个排列好后的Stream,而不会影响原有的数据源,排序之后原数据stringCollection是不会被修改的。 System.out.println(stringCollection); // ddd2, aaa2, bbb1, aaa1, bbb3, ccc, bbb2, ddd1 Map 映射 中间操作map会将元素根据指定的Function接口来依次将元素转成另外的对象,下面的示例展示了将字符串转换为大写字符串。你也可以通过map来讲对象转换成其他类型,map返回的Stream类型是根据你map传递进去的函数的返回值决定的。 stringCollection .stream() .map(String::toUpperCase) .sorted((a, b) -> b.compareTo(a)) .forEach(System.out::println); // "DDD2", "DDD1", "CCC", "BBB3", "BBB2", "AAA2", "AAA1" Match 匹配 Stream提供了多种匹配操作,允许检测指定的Predicate是否匹配整个Stream。所有的匹配操作都是最终操作,并返回一个boolean类型的值。 boolean anyStartsWithA = stringCollection .stream() .anyMatch((s) -> s.startsWith("a")); System.out.println(anyStartsWithA); // true boolean allStartsWithA = stringCollection .stream() .allMatch((s) -> s.startsWith("a")); System.out.println(allStartsWithA); // false boolean noneStartsWithZ = stringCollection .stream() .noneMatch((s) -> s.startsWith("z")); System.out.println(noneStartsWithZ); // true Count 计数 计数是一个最终操作,返回Stream中元素的个数,返回值类型是long。 long startsWithB = stringCollection .stream() .filter((s) -> s.startsWith("b")) .count(); System.out.println(startsWithB); // 3 Reduce 规约 这是一个最终操作,允许通过指定的函数来讲stream中的多个元素规约为一个元素,规越后的结果是通过Optional接口表示的: Optional reduced = stringCollection .stream() .sorted() .reduce((s1, s2) -> s1 + "#" + s2); reduced.ifPresent(System.out::println); // "aaa1#aaa2#bbb1#bbb2#bbb3#ccc#ddd1#ddd2" 并行Streams 前面提到过Stream有串行和并行两种,串行Stream上的操作是在一个线程中依次完成,而并行Stream则是在多个线程上同时执行。 下面的例子展示了是如何通过并行Stream来提升性能: 首先我们创建一个没有重复元素的大表 int max = 1000000; List values = new ArrayList<>(max); for (int i = 0; i < max; i++) { UUID uuid = UUID.randomUUID(); values.add(uuid.toString()); } 然后我们计算一下排序这个Stream要耗时多久, 串行排序: long t0 = System.nanoTime(); long count = values.stream().sorted().count(); System.out.println(count); long t1 = System.nanoTime(); long millis = TimeUnit.NANOSECONDS.toMillis(t1 - t0); System.out.println(String.format("sequential sort took: %d ms", millis)); // 串行耗时: 899 ms 并行排序: long t0 = System.nanoTime(); long count = values.parallelStream().sorted().count(); System.out.println(count); long t1 = System.nanoTime(); long millis = TimeUnit.NANOSECONDS.toMillis(t1 - t0); System.out.println(String.format("parallel sort took: %d ms", millis)); // 并行排序耗时: 472 ms 上面两个代码几乎是一样的,但是并行版的快了50%之多,唯一需要做的改动就是将stream()改为parallelStream()。 Map 前面提到过,Map类型不支持stream,不过Map提供了一些新的有用的方法来处理一些日常任务。 Map<Integer, String> map = new HashMap<>(); for (int i = 0; i < 10; i++) { map.putIfAbsent(i, "val" + i); } map.forEach((id, val) -> System.out.println(val)); 以上代码很容易理解, putIfAbsent 不需要我们做额外的存在性检查,而forEach则接收一个Consumer接口来对map里的每一个键值对进行操作。 下面的例子展示了map上的其他有用的函数: map.computeIfPresent(3, (num, val) -> val + num); map.get(3); // val33 map.computeIfPresent(9, (num, val) -> null); map.containsKey(9); // false map.computeIfAbsent(23, num -> "val" + num); map.containsKey(23); // true map.computeIfAbsent(3, num -> "bam"); map.get(3); // val33 接下来展示如何在Map里删除一个键值全都匹配的项 map.remove(3, "val3"); map.get(3); // val33 map.remove(3, "val33"); map.get(3); // null 另外一个有用的方法 map.getOrDefault(42, "not found"); // not found 对Map的元素做合并也变得很容易了: map.merge(9, "val9", (value, newValue) -> value.concat(newValue)); map.get(9); // val9 map.merge(9, "concat", (value, newValue) -> value.concat(newValue)); map.get(9); // val9concat Merge做的事情是如果键名不存在则插入,否则则对原键对应的值做合并操作并重新插入到map中。 九、Date API Java 8 在包java.time下包含了一组全新的时间日期API。新的日期API和开源的Joda-Time库差不多,但又不完全一样,下面的例子展示了这组新API里最重要的一些部分: Clock 时钟 Clock类提供了访问当前日期和时间的方法,Clock是时区敏感的,可以用来取代 System.currentTimeMillis() 来获取当前的微秒数。某一个特定的时间点也可以使用Instant类来表示,Instant类也可以用来创建老的java.util.Date对象。 Clock clock = Clock.systemDefaultZone(); long millis = clock.millis(); Instant instant = clock.instant(); Date legacyDate = Date.from(instant); // legacy java.util.Date Timezones 时区 在新API中时区使用ZoneId来表示。时区可以很方便的使用静态方法of来获取到。 时区定义了到UTS时间的时间差,在Instant时间点对象到本地日期对象之间转换的时候是极其重要的。 System.out.println(ZoneId.getAvailableZoneIds()); // prints all available timezone ids ZoneId zone1 = ZoneId.of("Europe/Berlin"); ZoneId zone2 = ZoneId.of("Brazil/East"); System.out.println(zone1.getRules()); System.out.println(zone2.getRules()); // ZoneRules[currentStandardOffset=+01:00] // ZoneRules[currentStandardOffset=-03:00] LocalTime 本地时间 LocalTime 定义了一个没有时区信息的时间,例如 晚上10点,或者 17:30:15。下面的例子使用前面代码创建的时区创建了两个本地时间。之后比较时间并以小时和分钟为单位计算两个时间的时间差: LocalTime now1 = LocalTime.now(zone1); LocalTime now2 = LocalTime.now(zone2); System.out.println(now1.isBefore(now2)); // false long hoursBetween = ChronoUnit.HOURS.between(now1, now2); long minutesBetween = ChronoUnit.MINUTES.between(now1, now2); System.out.println(hoursBetween); // -3 System.out.println(minutesBetween); // -239 LocalTime 提供了多种工厂方法来简化对象的创建,包括解析时间字符串。 LocalTime late = LocalTime.of(23, 59, 59); System.out.println(late); // 23:59:59 DateTimeFormatter germanFormatter = DateTimeFormatter .ofLocalizedTime(FormatStyle.SHORT) .withLocale(Locale.GERMAN); LocalTime leetTime = LocalTime.parse("13:37", germanFormatter); System.out.println(leetTime); // 13:37 LocalDate 本地日期 LocalDate 表示了一个确切的日期,比如 2014-03-11。该对象值是不可变的,用起来和LocalTime基本一致。下面的例子展示了如何给Date对象加减天/月/年。另外要注意的是这些对象是不可变的,操作返回的总是一个新实例。 LocalDate today = LocalDate.now(); LocalDate tomorrow = today.plus(1, ChronoUnit.DAYS); LocalDate yesterday = tomorrow.minusDays(2); LocalDate independenceDay = LocalDate.of(2014, Month.JULY, 4); DayOfWeek dayOfWeek = independenceDay.getDayOfWeek(); System.out.println(dayOfWeek); // FRIDAY 从字符串解析一个LocalDate类型和解析LocalTime一样简单: DateTimeFormatter germanFormatter = DateTimeFormatter .ofLocalizedDate(FormatStyle.MEDIUM) .withLocale(Locale.GERMAN); LocalDate xmas = LocalDate.parse("24.12.2014", germanFormatter); System.out.println(xmas); // 2014-12-24 LocalDateTime 本地日期时间 LocalDateTime 同时表示了时间和日期,相当于前两节内容合并到一个对象上了。LocalDateTime和LocalTime还有LocalDate一样,都是不可变的。LocalDateTime提供了一些能访问具体字段的方法。 LocalDateTime sylvester = LocalDateTime.of(2014, Month.DECEMBER, 31, 23, 59, 59); DayOfWeek dayOfWeek = sylvester.getDayOfWeek(); System.out.println(dayOfWeek); // WEDNESDAY Month month = sylvester.getMonth(); System.out.println(month); // DECEMBER long minuteOfDay = sylvester.getLong(ChronoField.MINUTE_OF_DAY); System.out.println(minuteOfDay); // 1439 只要附加上时区信息,就可以将其转换为一个时间点Instant对象,Instant时间点对象可以很容易的转换为老式的java.util.Date。 Instant instant = sylvester .atZone(ZoneId.systemDefault()) .toInstant(); Date legacyDate = Date.from(instant); System.out.println(legacyDate); // Wed Dec 31 23:59:59 CET 2014 格式化LocalDateTime和格式化时间和日期一样的,除了使用预定义好的格式外,我们也可以自己定义格式: DateTimeFormatter formatter = DateTimeFormatter .ofPattern("MMM dd, yyyy - HH:mm"); LocalDateTime parsed = LocalDateTime.parse("Nov 03, 2014 - 07:13", formatter); String string = formatter.format(parsed); System.out.println(string); // Nov 03, 2014 - 07:13 和java.text.NumberFormat不一样的是新版的DateTimeFormatter是不可变的,所以它是线程安全的。 关于时间日期格式的详细信息:http://download.java.net/jdk8/docs/api/java/time/format/DateTimeFormatter.html 十、Annotation 注解 在Java 8中支持多重注解了,先看个例子来理解一下是什么意思。 首先定义一个包装类Hints注解用来放置一组具体的Hint注解: @interface Hints { Hint[] value(); } @Repeatable(Hints.class) @interface Hint { String value(); } Java 8允许我们把同一个类型的注解使用多次,只需要给该注解标注一下@Repeatable即可。 例 1: 使用包装类当容器来存多个注解(老方法) @Hints({@Hint("hint1"), @Hint("hint2")}) class Person {} 例 2:使用多重注解(新方法) @Hint("hint1") @Hint("hint2") class Person {} 第二个例子里java编译器会隐性的帮你定义好@Hints注解,了解这一点有助于你用反射来获取这些信息: Hint hint = Person.class.getAnnotation(Hint.class); System.out.println(hint); // null Hints hints1 = Person.class.getAnnotation(Hints.class); System.out.println(hints1.value().length); // 2 Hint[] hints2 = Person.class.getAnnotationsByType(Hint.class); System.out.println(hints2.length); // 2 即便我们没有在Person类上定义@Hints注解,我们还是可以通过 getAnnotation(Hints.class) 来获取 @Hints注解,更加方便的方法是使用 getAnnotationsByType 可以直接获取到所有的@Hint注解。 另外Java 8的注解还增加到两种新的target上了: @Target({ElementType.TYPE_PARAMETER, ElementType.TYPE_USE}) @interface MyAnnotation {}

日你dady哟 2019-12-02 03:08:13 0 浏览量 回答数 0

回答

一、算法工程师简介 (通常是月薪15k以上,年薪18万以上,只是一个概数,具体薪资可以到招聘网站如拉钩,猎聘网上看看) 算法工程师目前是一个高端也是相对紧缺的职位; 算法工程师包括 音/视频算法工程师(通常统称为语音/视频/图形开发工程师)、图像处理算法工程师、计算机视觉算法工程师、通信基带算法工程师、信号算法工程师、射频/通信算法工程师、自然语言算法工程师、数据挖掘算法工程师、搜索算法工程师、控制算法工程师(云台算法工程师,飞控算法工程师,机器人控制算法)、导航算法工程师( @之介 感谢补充)、其他【其他一切需要复杂算法的行业】 专业要求:计算机、电子、通信、数学等相关专业; 学历要求:本科及其以上的学历,大多数是硕士学历及其以上; 语言要求:英语要求是熟练,基本上能阅读国外专业书刊,做这一行经常要读论文; 必须掌握计算机相关知识,熟练使用仿真工具MATLAB等,必须会一门编程语言。 算法工程师的技能树(不同方向差异较大,此处仅供参考) 1 机器学习 2 大数据处理:熟悉至少一个分布式计算框架Hadoop/Spark/Storm/ map-reduce/MPI 3 数据挖掘 4 扎实的数学功底 5 至少熟悉C/C++或者Java,熟悉至少一门编程语言例如java/python/R 加分项:具有较为丰富的项目实践经验(不是水论文的哪种) 二、算法工程师大致分类与技术要求 (一)图像算法/计算机视觉工程师类 包括 图像算法工程师,图像处理工程师,音/视频处理算法工程师,计算机视觉工程师 要求 l 专业:计算机、数学、统计学相关专业; l 技术领域:机器学习,模式识别 l 技术要求: (1) 精通DirectX HLSL和OpenGL GLSL等shader语言,熟悉常见图像处理算法GPU实现及优化; (2) 语言:精通C/C++; (3) 工具:Matlab数学软件,CUDA运算平台,VTK图像图形开源软件【医学领域:ITK,医学图像处理软件包】 (4) 熟悉OpenCV/OpenGL/Caffe等常用开源库; (5) 有人脸识别,行人检测,视频分析,三维建模,动态跟踪,车识别,目标检测跟踪识别经历的人优先考虑; (6) 熟悉基于GPU的算法设计与优化和并行优化经验者优先; (7) 【音/视频领域】熟悉H.264等视频编解码标准和FFMPEG,熟悉rtmp等流媒体传输协议,熟悉视频和音频解码算法,研究各种多媒体文件格式,GPU加速; 应用领域: (1) 互联网:如美颜app (2) 医学领域:如临床医学图像 (3) 汽车领域 (4) 人工智能 相关术语: (1) OCR:OCR (Optical Character Recognition,光学字符识别)是指电子设备(例如扫描仪或数码相机)检查纸上打印的字符,通过检测暗、亮的模式确定其形状,然后用字符识别方法将形状翻译成计算机文字的过程 (2) Matlab:商业数学软件; (3) CUDA: (Compute Unified Device Architecture),是显卡厂商NVIDIA推出的运算平台(由ISA和GPU构成)。 CUDA™是一种由NVIDIA推出的通用并行计算架构,该架构使GPU能够解决复杂的计算问题 (4) OpenCL: OpenCL是一个为异构平台编写程序的框架,此异构平台可由CPU,GPU或其他类型的处理器组成。 (5) OpenCV:开源计算机视觉库;OpenGL:开源图形库;Caffe:是一个清晰,可读性高,快速的深度学习框架。 (6) CNN:(深度学习)卷积神经网络(Convolutional Neural Network)CNN主要用来识别位移、缩放及其他形式扭曲不变性的二维图形。 (7) 开源库:指的是计算机行业中对所有人开发的代码库,所有人均可以使用并改进代码算法。 (二)机器学习工程师 包括 机器学习工程师 要求 l 专业:计算机、数学、统计学相关专业; l 技术领域:人工智能,机器学习 l 技术要求: (1) 熟悉Hadoop/Hive以及Map-Reduce计算模式,熟悉Spark、Shark等尤佳; (2) 大数据挖掘; (3) 高性能、高并发的机器学习、数据挖掘方法及架构的研发; 应用领域: (1)人工智能,比如各类仿真、拟人应用,如机器人 (2)医疗用于各类拟合预测 (3)金融高频交易 (4)互联网数据挖掘、关联推荐 (5)无人汽车,无人机 相关术语: (1) Map-Reduce:MapReduce是一种编程模型,用于大规模数据集(大于1TB)的并行运算。概念"Map(映射)"和"Reduce(归约)",是它们的主要思想,都是从函数式编程语言里借来的,还有从矢量编程语言里借来的特性。 (三)自然语言处理工程师 包括 自然语言处理工程师 要求 l 专业:计算机相关专业; l 技术领域:文本数据库 l 技术要求: (1) 熟悉中文分词标注、文本分类、语言模型、实体识别、知识图谱抽取和推理、问答系统设计、深度问答等NLP 相关算法; (2) 应用NLP、机器学习等技术解决海量UGC的文本相关性; (3) 分词、词性分析、实体识别、新词发现、语义关联等NLP基础性研究与开发; (4) 人工智能,分布式处理Hadoop; (5) 数据结构和算法; 应用领域: 口语输入、书面语输入 、语言分析和理解、语言生成、口语输出技术、话语分析与对话、文献自动处理、多语问题的计算机处理、多模态的计算机处理、信息传输与信息存储 、自然语言处理中的数学方法、语言资源、自然语言处理系统的评测。 相关术语: (2) NLP:人工智能的自然语言处理,NLP (Natural Language Processing) 是人工智能(AI)的一个子领域。NLP涉及领域很多,最令我感兴趣的是“中文自动分词”(Chinese word segmentation):结婚的和尚未结婚的【计算机中却有可能理解为结婚的“和尚“】 (四)射频/通信/信号算法工程师类 包括 3G/4G无线通信算法工程师, 通信基带算法工程师,DSP开发工程师(数字信号处理),射频通信工程师,信号算法工程师 要求 l 专业:计算机、通信相关专业; l 技术领域:2G、3G、4G,BlueTooth(蓝牙),WLAN,无线移动通信, 网络通信基带信号处理 l 技术要求: (1) 了解2G,3G,4G,BlueTooth,WLAN等无线通信相关知识,熟悉现有的通信系统和标准协议,熟悉常用的无线测试设备; (2) 信号处理技术,通信算法; (3) 熟悉同步、均衡、信道译码等算法的基本原理; (4) 【射频部分】熟悉射频前端芯片,扎实的射频微波理论和测试经验,熟练使用射频电路仿真工具(如ADS或MW或Ansoft);熟练使用cadence、altium designer PCB电路设计软件; (5) 有扎实的数学基础,如复变函数、随机过程、数值计算、矩阵论、离散数学 应用领域: 通信 VR【用于快速传输视频图像,例如乐客灵境VR公司招募的通信工程师(数据编码、流数据)】 物联网,车联网 导航,军事,卫星,雷达 相关术语: (1) 基带信号:指的是没有经过调制(进行频谱搬移和变换)的原始电信号。 (2) 基带通信(又称基带传输):指传输基带信号。进行基带传输的系统称为基带传输系统。传输介质的整个信道被一个基带信号占用.基带传输不需要调制解调器,设备化费小,具有速率高和误码率低等优点,.适合短距离的数据传输,传输距离在100米内,在音频市话、计算机网络通信中被广泛采用。如从计算机到监视器、打印机等外设的信号就是基带传输的。大多数的局域网使用基带传输,如以太网、令牌环网。 (3) 射频:射频(RF)是Radio Frequency的缩写,表示可以辐射到空间的电磁频率(电磁波),频率范围从300KHz~300GHz之间(因为其较高的频率使其具有远距离传输能力)。射频简称RF射频就是射频电流,它是一种高频交流变化电磁波的简称。每秒变化小于1000次的交流电称为低频电流,大于10000次的称为高频电流,而射频就是这样一种高频电流。高频(大于10K);射频(300K-300G)是高频的较高频段;微波频段(300M-300G)又是射频的较高频段。【有线电视就是用射频传输方式】 (4) DSP:数字信号处理,也指数字信号处理芯片 (五)数据挖掘算法工程师类 包括 推荐算法工程师,数据挖掘算法工程师 要求 l 专业:计算机、通信、应用数学、金融数学、模式识别、人工智能; l 技术领域:机器学习,数据挖掘 l 技术要求: (1) 熟悉常用机器学习和数据挖掘算法,包括但不限于决策树、Kmeans、SVM、线性回归、逻辑回归以及神经网络等算法; (2) 熟练使用SQL、Matlab、Python等工具优先; (3) 对Hadoop、Spark、Storm等大规模数据存储与运算平台有实践经验【均为分布式计算框架】 (4) 数学基础要好,如高数,统计学,数据结构 l 加分项:数据挖掘建模大赛; 应用领域 (1) 个性化推荐 (2) 广告投放 (3) 大数据分析 相关术语 Map-Reduce:MapReduce是一种编程模型,用于大规模数据集(大于1TB)的并行运算。概念"Map(映射)"和"Reduce(归约)",是它们的主要思想,都是从函数式编程语言里借来的,还有从矢量编程语言里借来的特性。 (六)搜索算法工程师 要求 l 技术领域:自然语言 l 技术要求: (1) 数据结构,海量数据处理、高性能计算、大规模分布式系统开发 (2) hadoop、lucene (3) 精通Lucene/Solr/Elastic Search等技术,并有二次开发经验 (4) 精通Lucene/Solr/Elastic Search等技术,并有二次开发经验; (5) 精通倒排索引、全文检索、分词、排序等相关技术; (6) 熟悉Java,熟悉Spring、MyBatis、Netty等主流框架; (7) 优秀的数据库设计和优化能力,精通MySQL数据库应用 ; (8) 了解推荐引擎和数据挖掘和机器学习的理论知识,有大型搜索应用的开发经验者优先。 (七)控制算法工程师类 包括了云台控制算法,飞控控制算法,机器人控制算法 要求 l 专业:计算机,电子信息工程,航天航空,自动化 l 技术要求: (1) 精通自动控制原理(如PID)、现代控制理论,精通组合导航原理,姿态融合算法,电机驱动,电机驱动 (2) 卡尔曼滤波,熟悉状态空间分析法对控制系统进行数学模型建模、分析调试; l 加分项:有电子设计大赛,机器人比赛,robocon等比赛经验,有硬件设计的基础; 应用领域 (1)医疗/工业机械设备 (2)工业机器人 (3)机器人 (4)无人机飞控、云台控制等 (八)导航算法工程师 要求 l 专业:计算机,电子信息工程,航天航空,自动化 l 技术要求(以公司职位JD为例) 公司一(1)精通惯性导航、激光导航、雷达导航等工作原理; (2)精通组合导航算法设计、精通卡尔曼滤波算法、精通路径规划算法; (3)具备导航方案设计和实现的工程经验; (4)熟悉C/C++语言、熟悉至少一种嵌入式系统开发、熟悉Matlab工具; 公司二(1)熟悉基于视觉信息的SLAM、定位、导航算法,有1年以上相关的科研或项目经历; (2)熟悉惯性导航算法,熟悉IMU与视觉信息的融合; 应用领域 无人机、机器人等。

小哇 2019-12-02 01:21:12 0 浏览量 回答数 0

回答

一、算法工程师简介 (通常是月薪15k以上,年薪18万以上,只是一个概数,具体薪资可以到招聘网站如拉钩,猎聘网上看看) 算法工程师目前是一个高端也是相对紧缺的职位; 算法工程师包括 音/视频算法工程师(通常统称为语音/视频/图形开发工程师)、图像处理算法工程师、计算机视觉算法工程师、通信基带算法工程师、信号算法工程师、射频/通信算法工程师、自然语言算法工程师、数据挖掘算法工程师、搜索算法工程师、控制算法工程师(云台算法工程师,飞控算法工程师,机器人控制算法)、导航算法工程师( @之介 感谢补充)、其他【其他一切需要复杂算法的行业】 专业要求:计算机、电子、通信、数学等相关专业; 学历要求:本科及其以上的学历,大多数是硕士学历及其以上; 语言要求:英语要求是熟练,基本上能阅读国外专业书刊,做这一行经常要读论文; 必须掌握计算机相关知识,熟练使用仿真工具MATLAB等,必须会一门编程语言。 算法工程师的技能树(不同方向差异较大,此处仅供参考) 1 机器学习 2 大数据处理:熟悉至少一个分布式计算框架Hadoop/Spark/Storm/ map-reduce/MPI 3 数据挖掘 4 扎实的数学功底 5 至少熟悉C/C++或者Java,熟悉至少一门编程语言例如java/python/R 加分项:具有较为丰富的项目实践经验(不是水论文的哪种) 二、算法工程师大致分类与技术要求 (一)图像算法/计算机视觉工程师类 包括 图像算法工程师,图像处理工程师,音/视频处理算法工程师,计算机视觉工程师 要求 l 专业:计算机、数学、统计学相关专业; l 技术领域:机器学习,模式识别 l 技术要求: (1) 精通DirectX HLSL和OpenGL GLSL等shader语言,熟悉常见图像处理算法GPU实现及优化; (2) 语言:精通C/C++; (3) 工具:Matlab数学软件,CUDA运算平台,VTK图像图形开源软件【医学领域:ITK,医学图像处理软件包】 (4) 熟悉OpenCV/OpenGL/Caffe等常用开源库; (5) 有人脸识别,行人检测,视频分析,三维建模,动态跟踪,车识别,目标检测跟踪识别经历的人优先考虑; (6) 熟悉基于GPU的算法设计与优化和并行优化经验者优先; (7) 【音/视频领域】熟悉H.264等视频编解码标准和FFMPEG,熟悉rtmp等流媒体传输协议,熟悉视频和音频解码算法,研究各种多媒体文件格式,GPU加速; 应用领域: (1) 互联网:如美颜app (2) 医学领域:如临床医学图像 (3) 汽车领域 (4) 人工智能 相关术语: (1) OCR:OCR (Optical Character Recognition,光学字符识别)是指电子设备(例如扫描仪或数码相机)检查纸上打印的字符,通过检测暗、亮的模式确定其形状,然后用字符识别方法将形状翻译成计算机文字的过程 (2) Matlab:商业数学软件; (3) CUDA: (Compute Unified Device Architecture),是显卡厂商NVIDIA推出的运算平台(由ISA和GPU构成)。 CUDA™是一种由NVIDIA推出的通用并行计算架构,该架构使GPU能够解决复杂的计算问题 (4) OpenCL: OpenCL是一个为异构平台编写程序的框架,此异构平台可由CPU,GPU或其他类型的处理器组成。 (5) OpenCV:开源计算机视觉库;OpenGL:开源图形库;Caffe:是一个清晰,可读性高,快速的深度学习框架。 (6) CNN:(深度学习)卷积神经网络(Convolutional Neural Network)CNN主要用来识别位移、缩放及其他形式扭曲不变性的二维图形。 (7) 开源库:指的是计算机行业中对所有人开发的代码库,所有人均可以使用并改进代码算法。 (二)机器学习工程师 包括 机器学习工程师 要求 l 专业:计算机、数学、统计学相关专业; l 技术领域:人工智能,机器学习 l 技术要求: (1) 熟悉Hadoop/Hive以及Map-Reduce计算模式,熟悉Spark、Shark等尤佳; (2) 大数据挖掘; (3) 高性能、高并发的机器学习、数据挖掘方法及架构的研发; 应用领域: (1)人工智能,比如各类仿真、拟人应用,如机器人 (2)医疗用于各类拟合预测 (3)金融高频交易 (4)互联网数据挖掘、关联推荐 (5)无人汽车,无人机 相关术语: (1) Map-Reduce:MapReduce是一种编程模型,用于大规模数据集(大于1TB)的并行运算。概念"Map(映射)"和"Reduce(归约)",是它们的主要思想,都是从函数式编程语言里借来的,还有从矢量编程语言里借来的特性。 (三)自然语言处理工程师 包括 自然语言处理工程师 要求 l 专业:计算机相关专业; l 技术领域:文本数据库 l 技术要求: (1) 熟悉中文分词标注、文本分类、语言模型、实体识别、知识图谱抽取和推理、问答系统设计、深度问答等NLP 相关算法; (2) 应用NLP、机器学习等技术解决海量UGC的文本相关性; (3) 分词、词性分析、实体识别、新词发现、语义关联等NLP基础性研究与开发; (4) 人工智能,分布式处理Hadoop; (5) 数据结构和算法; 应用领域: 口语输入、书面语输入 、语言分析和理解、语言生成、口语输出技术、话语分析与对话、文献自动处理、多语问题的计算机处理、多模态的计算机处理、信息传输与信息存储 、自然语言处理中的数学方法、语言资源、自然语言处理系统的评测。 相关术语: (2) NLP:人工智能的自然语言处理,NLP (Natural Language Processing) 是人工智能(AI)的一个子领域。NLP涉及领域很多,最令我感兴趣的是“中文自动分词”(Chinese word segmentation):结婚的和尚未结婚的【计算机中却有可能理解为结婚的“和尚“】 (四)射频/通信/信号算法工程师类 包括 3G/4G无线通信算法工程师, 通信基带算法工程师,DSP开发工程师(数字信号处理),射频通信工程师,信号算法工程师 要求 l 专业:计算机、通信相关专业; l 技术领域:2G、3G、4G,BlueTooth(蓝牙),WLAN,无线移动通信, 网络通信基带信号处理 l 技术要求: (1) 了解2G,3G,4G,BlueTooth,WLAN等无线通信相关知识,熟悉现有的通信系统和标准协议,熟悉常用的无线测试设备; (2) 信号处理技术,通信算法; (3) 熟悉同步、均衡、信道译码等算法的基本原理; (4) 【射频部分】熟悉射频前端芯片,扎实的射频微波理论和测试经验,熟练使用射频电路仿真工具(如ADS或MW或Ansoft);熟练使用cadence、altium designer PCB电路设计软件; (5) 有扎实的数学基础,如复变函数、随机过程、数值计算、矩阵论、离散数学 应用领域: 通信 VR【用于快速传输视频图像,例如乐客灵境VR公司招募的通信工程师(数据编码、流数据)】 物联网,车联网 导航,军事,卫星,雷达 相关术语: (1) 基带信号:指的是没有经过调制(进行频谱搬移和变换)的原始电信号。 (2) 基带通信(又称基带传输):指传输基带信号。进行基带传输的系统称为基带传输系统。传输介质的整个信道被一个基带信号占用.基带传输不需要调制解调器,设备化费小,具有速率高和误码率低等优点,.适合短距离的数据传输,传输距离在100米内,在音频市话、计算机网络通信中被广泛采用。如从计算机到监视器、打印机等外设的信号就是基带传输的。大多数的局域网使用基带传输,如以太网、令牌环网。 (3) 射频:射频(RF)是Radio Frequency的缩写,表示可以辐射到空间的电磁频率(电磁波),频率范围从300KHz~300GHz之间(因为其较高的频率使其具有远距离传输能力)。射频简称RF射频就是射频电流,它是一种高频交流变化电磁波的简称。每秒变化小于1000次的交流电称为低频电流,大于10000次的称为高频电流,而射频就是这样一种高频电流。高频(大于10K);射频(300K-300G)是高频的较高频段;微波频段(300M-300G)又是射频的较高频段。【有线电视就是用射频传输方式】 (4) DSP:数字信号处理,也指数字信号处理芯片 (五)数据挖掘算法工程师类 包括 推荐算法工程师,数据挖掘算法工程师 要求 l 专业:计算机、通信、应用数学、金融数学、模式识别、人工智能; l 技术领域:机器学习,数据挖掘 l 技术要求: (1) 熟悉常用机器学习和数据挖掘算法,包括但不限于决策树、Kmeans、SVM、线性回归、逻辑回归以及神经网络等算法; (2) 熟练使用SQL、Matlab、Python等工具优先; (3) 对Hadoop、Spark、Storm等大规模数据存储与运算平台有实践经验【均为分布式计算框架】 (4) 数学基础要好,如高数,统计学,数据结构 l 加分项:数据挖掘建模大赛; 应用领域 (1) 个性化推荐 (2) 广告投放 (3) 大数据分析 相关术语 Map-Reduce:MapReduce是一种编程模型,用于大规模数据集(大于1TB)的并行运算。概念"Map(映射)"和"Reduce(归约)",是它们的主要思想,都是从函数式编程语言里借来的,还有从矢量编程语言里借来的特性。 (六)搜索算法工程师 要求 l 技术领域:自然语言 l 技术要求: (1) 数据结构,海量数据处理、高性能计算、大规模分布式系统开发 (2) hadoop、lucene (3) 精通Lucene/Solr/Elastic Search等技术,并有二次开发经验 (4) 精通Lucene/Solr/Elastic Search等技术,并有二次开发经验; (5) 精通倒排索引、全文检索、分词、排序等相关技术; (6) 熟悉Java,熟悉Spring、MyBatis、Netty等主流框架; (7) 优秀的数据库设计和优化能力,精通MySQL数据库应用 ; (8) 了解推荐引擎和数据挖掘和机器学习的理论知识,有大型搜索应用的开发经验者优先。 (七)控制算法工程师类 包括了云台控制算法,飞控控制算法,机器人控制算法 要求 l 专业:计算机,电子信息工程,航天航空,自动化 l 技术要求: (1) 精通自动控制原理(如PID)、现代控制理论,精通组合导航原理,姿态融合算法,电机驱动,电机驱动 (2) 卡尔曼滤波,熟悉状态空间分析法对控制系统进行数学模型建模、分析调试; l 加分项:有电子设计大赛,机器人比赛,robocon等比赛经验,有硬件设计的基础; 应用领域 (1)医疗/工业机械设备 (2)工业机器人 (3)机器人 (4)无人机飞控、云台控制等 (八)导航算法工程师 要求 l 专业:计算机,电子信息工程,航天航空,自动化 l 技术要求(以公司职位JD为例) 公司一(1)精通惯性导航、激光导航、雷达导航等工作原理; (2)精通组合导航算法设计、精通卡尔曼滤波算法、精通路径规划算法; (3)具备导航方案设计和实现的工程经验; (4)熟悉C/C++语言、熟悉至少一种嵌入式系统开发、熟悉Matlab工具; 公司二(1)熟悉基于视觉信息的SLAM、定位、导航算法,有1年以上相关的科研或项目经历; (2)熟悉惯性导航算法,熟悉IMU与视觉信息的融合; 应用领域 无人机、机器人等。

琴瑟 2019-12-02 01:21:11 0 浏览量 回答数 0

回答

Arraylist和Vector是采用数组方式存储数据,此数组元素数大于实际存储的数据以便增加插入元素,都允许直接序号索引元素,但是插入数据要涉及到数组元素移动等内存操作,所以插入数据慢,查找有下标,所以查询数据快,Vector由于使用了synchronized方法-线程安全,所以性能上比ArrayList要差,LinkedList使用双向链表实现存储,按序号索引数据需要进行向前或向后遍历,但是插入数据时只需要记录本项前后项即可,插入数据较快。线性表,链表,哈希表是常用的数据结构,在进行java开发时,JDK已经为我们提供了一系列相应的类实现基本的数据结构,这些结构均在java.util包中,collection├List│├LinkedList│├ArrayList│└Vector│ └Stack└SetMap├Hashtable├HashMap└WeakHashMapCollection接口Collection是最基本的集合接口,一个Collection代表一组Object,即Collection的元素(elements),一些Collection允许相同的元素而另一些不行。一些能排序而另一些不行。Java SDK不提供直接继承自Collection的类,Java SDK提供的类都是继承自Collection的“子接口”如List和Set。所有实现Collection接口的类都必须提供两个标准的构造函数:无参数的构造函数用于创建一个空的Collection,有一个Collection参数的构造函数用于创建一个新的Collection,这个新的Collection与传入的Collection有相同的元素。后一个构造函数允许用户复制一个Collection。如何遍历Collection中的每一个元素?不论Collection的实际类型如何,它都支持一个iterator()的方法,该方法返回一个迭代子,使用该迭代子即可逐一访问Collection中每一个元素。典型的用法如下:    Iterator it = collection.iterator(); // 获得一个迭代子    while(it.hasNext()) {      Object obj = it.next(); // 得到下一个元素    }  由Collection接口派生的两个接口是List和Set。List接口  List是有序的Collection,使用此接口能够精确的控制每个元素插入的位置。用户能够使用索引(元素在List中的位置,类似于数组下标)来访问List中的元素,这类似于Java的数组。和下面要提到的Set不同,List允许有相同的元素。  除了具有Collection接口必备的iterator()方法外,List还提供一个listIterator()方法,返回一个ListIterator接口,和标准的Iterator接口相比,ListIterator多了一些add()之类的方法,允许添加,删除,设定元素,还能向前或向后遍历。  实现List接口的常用类有LinkedList,ArrayList,Vector和Stack。ArrayList类  ArrayList实现了可变大小的数组。它允许所有元素,包括null。ArrayList没有同步。size,isEmpty,get,set方法运行时间为常数。但是add方法开销为分摊的常数,添加n个元素需要O(n)的时间。其他的方法运行时间为线性。  每个ArrayList实例都有一个容量(Capacity),即用于存储元素的数组的大小。这个容量可随着不断添加新元素而自动增加,但是增长算法并没有定义。当需要插入大量元素时,在插入前可以调用ensureCapacity方法来增加ArrayList的容量以提高插入效率。  和LinkedList一样,ArrayList也是非同步的(unsynchronized)。Vector类  Vector非常类似ArrayList,但是Vector是同步的。由Vector创建的Iterator,虽然和ArrayList创建的Iterator是同一接口,但是,因为Vector是同步的,当一个Iterator被创建而且正在被使用,另一个线程改变了Vector的状态(例如,添加或删除了一些元素),这时调用Iterator的方法时将抛出ConcurrentModificationException,因此必须捕获该异常。Stack 类  Stack继承自Vector,实现一个后进先出的堆栈。Stack提供5个额外的方法使得Vector得以被当作堆栈使用。基本的push和pop方法,还有peek方法得到栈顶的元素,empty方法测试堆栈是否为空,search方法检测一个元素在堆栈中的位置。Stack刚创建后是空栈。Map接口  请注意,Map没有继承Collection接口,Map提供key到value的映射。一个Map中不能包含相同的key,每个key只能映射一个value。Map接口提供3种集合的视图,Map的内容可以被当作一组key集合,一组value集合,或者一组key-value映射。Hashtable类  Hashtable继承Map接口,实现一个key-value映射的哈希表。任何非空(non-null)的对象都可作为key或者value。  添加数据使用put(key, value),取出数据使用get(key),这两个基本操作的时间开销为常数。Hashtable通过initial capacity和load factor两个参数调整性能。通常缺省的load factor 0.75较好地实现了时间和空间的均衡。增大load factor可以节省空间但相应的查找时间将增大,这会影响像get和put这样的操作。使用Hashtable的简单示例如下,将1,2,3放到Hashtable中,他们的key分别是”one”,”two”,”three”:    Hashtable numbers = new Hashtable();    numbers.put(“one”, new Integer(1));    numbers.put(“two”, new Integer(2));    numbers.put(“three”, new Integer(3));  要取出一个数,比如2,用相应的key:    Integer n = (Integer)numbers.get(“two”);    System.out.println(“two = ” + n);  由于作为key的对象将通过计算其散列函数来确定与之对应的value的位置,因此任何作为key的对象都必须实现hashCode和equals方法。hashCode和equals方法继承自根类Object,如果你用自定义的类当作key的话,要相当小心,按照散列函数的定义,如果两个对象相同,即obj1.equals(obj2)=true,则它们的hashCode必须相同,但如果两个对象不同,则它们的hashCode不一定不同,如果两个不同对象的hashCode相同,这种现象称为冲突,冲突会导致操作哈希表的时间开销增大,所以尽量定义好的hashCode()方法,能加快哈希表的操作。  如果相同的对象有不同的hashCode,对哈希表的操作会出现意想不到的结果(期待的get方法返回null),要避免这种问题,只需要牢记一条:要同时复写equals方法和hashCode方法,而不要只写其中一个。  Hashtable是同步的。HashMap类  HashMap和Hashtable类似,不同之处在于HashMap是非同步的,并且允许null,即null value和null key。,但是将HashMap视为Collection时(values()方法可返回Collection),其迭代子操作时间开销和HashMap的容量成比例。因此,如果迭代操作的性能相当重要的话,不要将HashMap的初始化容量设得过高,或者load factor过低。WeakHashMap类  WeakHashMap是一种改进的HashMap,它对key实行“弱引用”,如果一个key不再被外部所引用,那么该key可以被GC回收。总结  如果涉及到堆栈,队列等操作,应该考虑用List,对于需要快速插入,删除元素,应该使用LinkedList,如果需要快速随机访问元素,应该使用ArrayList。  如果程序在单线程环境中,或者访问仅仅在一个线程中进行,考虑非同步的类,其效率较高,如果多个线程可能同时操作一个类,应该使用同步的类。  要特别注意对哈希表的操作,作为key的对象要正确复写equals和hashCode方法。  尽量返回接口而非实际的类型,如返回List而非ArrayList,这样如果以后需要将ArrayList换成LinkedList时,客户端代码不用改变。这就是针对抽象编程。同步性Vector是同步的。这个类中的一些方法保证了Vector中的对象是线程安全的。而ArrayList则是异步的,因此ArrayList中的对象并不是线程安全的。因为同步的要求会影响执行的效率,所以如果你不需要线程安全的集合那么使用ArrayList是一个很好的选择,这样可以避免由于同步带来的不必要的性能开销。数据增长从内部实现机制来讲ArrayList和Vector都是使用数组(Array)来控制集合中的对象。当你向这两种类型中增加元素的时候,如果元素的数目超出了内部数组目前的长度它们都需要扩展内部数组的长度,Vector缺省情况下自动增长原来一倍的数组长度,ArrayList是原来的50%,所以最后你获得的这个集合所占的空间总是比你实际需要的要大。所以如果你要在集合中保存大量的数据那么使用Vector有一些优势,因为你可以通过设置集合的初始化大小来避免不必要的资源开销。使用模式在ArrayList和Vector中,从一个指定的位置(通过索引)查找数据或是在集合的末尾增加、移除一个元素所花费的时间是一样的,这个时间我们用O(1)表示。但是,如果在集合的其他位置增加或移除元素那么花费的时间会呈线形增长:O(n-i),其中n代表集合中元素的个数,i代表元素增加或移除元素的索引位置。为什么会这样呢?以为在进行上述操作的时候集合中第i和第i个元素之后的所有元素都要执行位移的操作。这一切意味着什么呢?这意味着,你只是查找特定位置的元素或只在集合的末端增加、移除元素,那么使用Vector或ArrayList都可以。如果是其他操作,你最好选择其他的集合操作类。比如,LinkList集合类在增加或移除集合中任何位置的元素所花费的时间都是一样的?O(1),但它在索引一个元素的使用缺比较慢-O(i),其中i是索引的位置.使用ArrayList也很容易,因为你可以简单的使用索引来代替创建iterator对象的操作。LinkList也会为每个插入的元素创建对象,所有你要明白它也会带来额外的开销。最后,在《Practical Java》一书中Peter Haggar建议使用一个简单的数组(Array)来代替Vector或ArrayList。尤其是对于执行效率要求高的程序更应如此。因为使用数组(Array)避免了同步、额外的方法调用和不必要的重新分配空间的操作。

wangccsy 2019-12-02 01:48:37 0 浏览量 回答数 0

回答

Android平台进行数据存储的五大方式,分别如下: 1 使用SharedPreferences存储数据 2 文件存储数据 3 SQLite数据库存储数据 4 使用ContentProvider存储数据 5 网络存储数据 下面详细讲解这五种方式的特点 第一种: 使用SharedPreferences存储数据 适用范围:保存少量的数据,且这些数据的格式非常简单:字符串型、基本类型的值。比如应用程序的各种配置信息(如是否打开音效、是否使用震动效果、小游戏的玩家积分等),解锁口 令密码等 核心原理:保存基于XML文件存储的key-value键值对数据,通常用来存储一些简单的配置信息。通过DDMS的File Explorer面板,展开文件浏览树,很明显SharedPreferences数据总是存储在/data/data/<package name>/shared_prefs目录下。SharedPreferences对象本身只能获取数据而不支持存储和修改,存储修改是通过SharedPreferences.edit()获取的内部接口Editor对象实现。 SharedPreferences本身是一 个接口,程序无法直接创建SharedPreferences实例,只能通过Context提供的getSharedPreferences(String name, int mode)方法来获取SharedPreferences实例,该方法中name表示要操作的xml文件名,第二个参数具体如下: Context.MODE_PRIVATE: 指定该SharedPreferences数据只能被本应用程序读、写。 Context.MODE_WORLD_READABLE: 指定该SharedPreferences数据能被其他应用程序读,但不能写。 Context.MODE_WORLD_WRITEABLE: 指定该SharedPreferences数据能被其他应用程序读,写 Editor有如下主要重要方法: SharedPreferences.Editor clear():清空SharedPreferences里所有数据 SharedPreferences.Editor putXxx(String key , xxx value): 向SharedPreferences存入指定key对应的数据,其中xxx 可以是boolean,float,int等各种基本类型据 SharedPreferences.Editor remove(): 删除SharedPreferences中指定key对应的数据项 boolean commit(): 当Editor编辑完成后,使用该方法提交修改 实际案例:运行界面如下 这里只提供了两个按钮和一个输入文本框,布局简单,故在此不给出界面布局文件了,程序核心代码如下: 、class ViewOcl implements View.OnClickListener{ @Override public void onClick(View v) { switch(v.getId()){ case R.id.btnSet: //步骤1:获取输入值 String code = txtCode.getText().toString().trim(); //步骤2-1:创建一个SharedPreferences.Editor接口对象,lock表示要写入的XML文件名,MODE_WORLD_WRITEABLE写操作 SharedPreferences.Editor editor = getSharedPreferences("lock", MODE_WORLD_WRITEABLE).edit(); //步骤2-2:将获取过来的值放入文件 editor.putString("code", code); //步骤3:提交 editor.commit(); Toast.makeText(getApplicationContext(), "口令设置成功", Toast.LENGTH_LONG).show(); break; case R.id.btnGet: //步骤1:创建一个SharedPreferences接口对象 SharedPreferences read = getSharedPreferences("lock", MODE_WORLD_READABLE); //步骤2:获取文件中的值 String value = read.getString("code", ""); Toast.makeText(getApplicationContext(), "口令为:"+value, Toast.LENGTH_LONG).show(); break; } } } 、读写其他应用的SharedPreferences: 步骤如下 1、在创建SharedPreferences时,指定MODE_WORLD_READABLE模式,表明该SharedPreferences数据可以被其他程序读取 2、创建其他应用程序对应的Context: Context pvCount = createPackageContext("com.tony.app", Context.CONTEXT_IGNORE_SECURITY);这里的com.tony.app就是其他程序的包名 3、使用其他程序的Context获取对应的SharedPreferences SharedPreferences read = pvCount.getSharedPreferences("lock", Context.MODE_WORLD_READABLE); 4、如果是写入数据,使用Editor接口即可,所有其他操作均和前面一致。 SharedPreferences对象与SQLite数据库相比,免去了创建数据库,创建表,写SQL语句等诸多操作,相对而言更加方便,简洁。但是SharedPreferences也有其自身缺陷,比如其职能存储boolean,int,float,long和String五种简单的数据类型,比如其无法进行条件查询等。所以不论SharedPreferences的数据存储操作是如何简单,它也只能是存储方式的一种补充,而无法完全替代如SQLite数据库这样的其他数据存储方式。 第二种: 文件存储数据 核心原理: Context提供了两个方法来打开数据文件里的文件IO流 FileInputStream openFileInput(String name); FileOutputStream(String name , int mode),这两个方法第一个参数 用于指定文件名,第二个参数指定打开文件的模式。具体有以下值可选: MODE_PRIVATE:为默认操作模式,代表该文件是私有数据,只能被应用本身访问,在该模式下,写入的内容会覆盖原文件的内容,如果想把新写入的内容追加到原文件中。可 以使用Context.MODE_APPEND MODE_APPEND:模式会检查文件是否存在,存在就往文件追加内容,否则就创建新文件。 MODE_WORLD_READABLE:表示当前文件可以被其他应用读取; MODE_WORLD_WRITEABLE:表示当前文件可以被其他应用写入。 除此之外,Context还提供了如下几个重要的方法: getDir(String name , int mode):在应用程序的数据文件夹下获取或者创建name对应的子目录 File getFilesDir():获取该应用程序的数据文件夹得绝对路径 String[] fileList():返回该应用数据文件夹的全部文件 public String read() { try { FileInputStream inStream = this.openFileInput("message.txt"); byte[] buffer = new byte[1024]; int hasRead = 0; StringBuilder sb = new StringBuilder(); while ((hasRead = inStream.read(buffer)) != -1) { sb.append(new String(buffer, 0, hasRead)); } inStream.close(); return sb.toString(); } catch (Exception e) { e.printStackTrace(); } return null; } public void write(String msg){ // 步骤1:获取输入值 if(msg == null) return; try { // 步骤2:创建一个FileOutputStream对象,MODE_APPEND追加模式 FileOutputStream fos = openFileOutput("message.txt", MODE_APPEND); // 步骤3:将获取过来的值放入文件 fos.write(msg.getBytes()); // 步骤4:关闭数据流 fos.close(); } catch (Exception e) { e.printStackTrace(); } } openFileOutput()方法的第一参数用于指定文件名称,不能包含路径分隔符“/” ,如果文件不存在,Android 会自动创建它。创建的文件保存在/data/data//files目录,如: /data/data/cn.tony.app/files/message.txt, 下面讲解某些特殊文件读写需要注意的地方: 读写sdcard上的文件 其中读写步骤按如下进行: 1、调用Environment的getExternalStorageState()方法判断手机上是否插了sd卡,且应用程序具有读写SD卡的权限,如下代码将返回true Environment.getExternalStorageState().equals(Environment.MEDIA_MOUNTED) 2、调用Environment.getExternalStorageDirectory()方法来获取外部存储器,也就是SD卡的目录,或者使用"/mnt/sdcard/"目录 3、使用IO流操作SD卡上的文件 注意点:手机应该已插入SD卡,对于模拟器而言,可通过mksdcard命令来创建虚拟存储卡 必须在AndroidManifest.xml上配置读写SD卡的权限 // 文件写操作函数 private void write(String content) { if (Environment.getExternalStorageState().equals( Environment.MEDIA_MOUNTED)) { // 如果sdcard存在 File file = new File(Environment.getExternalStorageDirectory() .toString() + File.separator + DIR + File.separator + FILENAME); // 定义File类对象 if (!file.getParentFile().exists()) { // 父文件夹不存在 file.getParentFile().mkdirs(); // 创建文件夹 } PrintStream out = null; // 打印流对象用于输出 try { out = new PrintStream(new FileOutputStream(file, true)); // 追加文件 out.println(content); } catch (Exception e) { e.printStackTrace(); } finally { if (out != null) { out.close(); // 关闭打印流 } } } else { // SDCard不存在,使用Toast提示用户 Toast.makeText(this, "保存失败,SD卡不存在!", Toast.LENGTH_LONG).show(); } } // 文件读操作函数 private String read() { if (Environment.getExternalStorageState().equals( Environment.MEDIA_MOUNTED)) { // 如果sdcard存在 File file = new File(Environment.getExternalStorageDirectory() .toString() + File.separator + DIR + File.separator + FILENAME); // 定义File类对象 if (!file.getParentFile().exists()) { // 父文件夹不存在 file.getParentFile().mkdirs(); // 创建文件夹 } Scanner scan = null; // 扫描输入 StringBuilder sb = new StringBuilder(); try { scan = new Scanner(new FileInputStream(file)); // 实例化Scanner while (scan.hasNext()) { // 循环读取 sb.append(scan.next() + "\n"); // 设置文本 } return sb.toString(); } catch (Exception e) { e.printStackTrace(); } finally { if (scan != null) { scan.close(); // 关闭打印流 } } } else { // SDCard不存在,使用Toast提示用户 Toast.makeText(this, "读取失败,SD卡不存在!", Toast.LENGTH_LONG).show(); } return null; } 复制代码 第三种:SQLite存储数据 SQLite是轻量级嵌入式数据库引擎,它支持 SQL 语言,并且只利用很少的内存就有很好的性能。现在的主流移动设备像Android、iPhone等都使用SQLite作为复杂数据的存储引擎,在我们为移动设备开发应用程序时,也许就要使用到SQLite来存储我们大量的数据,所以我们就需要掌握移动设备上的SQLite开发技巧 SQLiteDatabase类为我们提供了很多种方法,上面的代码中基本上囊括了大部分的数据库操作;对于添加、更新和删除来说,我们都可以使用 1 db.executeSQL(String sql); 2 db.executeSQL(String sql, Object[] bindArgs);//sql语句中使用占位符,然后第二个参数是实际的参数集 除了统一的形式之外,他们还有各自的操作方法: 1 db.insert(String table, String nullColumnHack, ContentValues values); 2 db.update(String table, Contentvalues values, String whereClause, String whereArgs); 3 db.delete(String table, String whereClause, String whereArgs);以上三个方法的第一个参数都是表示要操作的表名;insert中的第二个参数表示如果插入的数据每一列都为空的话,需要指定此行中某一列的名称,系统将此列设置为NULL,不至于出现错误;insert中的第三个参数是ContentValues类型的变量,是键值对组成的Map,key代表列名,value代表该列要插入的值;update的第二个参数也很类似,只不过它是更新该字段key为最新的value值,第三个参数whereClause表示WHERE表达式,比如“age > ? and age < ?”等,最后的whereArgs参数是占位符的实际参数值;delete方法的参数也是一样 下面给出demo 数据的添加 1.使用insert方法 复制代码1 ContentValues cv = new ContentValues();//实例化一个ContentValues用来装载待插入的数据2 cv.put("title","you are beautiful");//添加title3 cv.put("weather","sun"); //添加weather4 cv.put("context","xxxx"); //添加context5 String publish = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss")6 .format(new Date());7 cv.put("publish ",publish); //添加publish8 db.insert("diary",null,cv);//执行插入操作复制代码2.使用execSQL方式来实现 String sql = "insert into user(username,password) values ('Jack Johnson','iLovePopMuisc');//插入操作的SQL语句db.execSQL(sql);//执行SQL语句数据的删除 同样有2种方式可以实现 String whereClause = "username=?";//删除的条件String[] whereArgs = {"Jack Johnson"};//删除的条件参数db.delete("user",whereClause,whereArgs);//执行删除使用execSQL方式的实现 String sql = "delete from user where username='Jack Johnson'";//删除操作的SQL语句db.execSQL(sql);//执行删除操作数据修改 同上,仍是2种方式 ContentValues cv = new ContentValues();//实例化ContentValuescv.put("password","iHatePopMusic");//添加要更改的字段及内容String whereClause = "username=?";//修改条件String[] whereArgs = {"Jack Johnson"};//修改条件的参数db.update("user",cv,whereClause,whereArgs);//执行修改使用execSQL方式的实现 String sql = "update user set password = 'iHatePopMusic' where username='Jack Johnson'";//修改的SQL语句db.execSQL(sql);//执行修改数据查询 下面来说说查询操作。查询操作相对于上面的几种操作要复杂些,因为我们经常要面对着各种各样的查询条件,所以系统也考虑到这种复杂性,为我们提供了较为丰富的查询形式: 1 db.rawQuery(String sql, String[] selectionArgs); 2 db.query(String table, String[] columns, String selection, String[] selectionArgs, String groupBy, String having, String orderBy); 3 db.query(String table, String[] columns, String selection, String[] selectionArgs, String groupBy, String having, String orderBy, String limit); 4 db.query(String distinct, String table, String[] columns, String selection, String[] selectionArgs, String groupBy, String having, String orderBy, String limit); 上面几种都是常用的查询方法,第一种最为简单,将所有的SQL语句都组织到一个字符串中,使用占位符代替实际参数,selectionArgs就是占位符实际参数集; 各参数说明: table:表名称colums:表示要查询的列所有名称集selection:表示WHERE之后的条件语句,可以使用占位符selectionArgs:条件语句的参数数组groupBy:指定分组的列名having:指定分组条件,配合groupBy使用orderBy:y指定排序的列名limit:指定分页参数distinct:指定“true”或“false”表示要不要过滤重复值Cursor:返回值,相当于结果集ResultSet最后,他们同时返回一个Cursor对象,代表数据集的游标,有点类似于JavaSE中的ResultSet。下面是Cursor对象的常用方法: 复制代码 1 c.move(int offset); //以当前位置为参考,移动到指定行 2 c.moveToFirst(); //移动到第一行 3 c.moveToLast(); //移动到最后一行 4 c.moveToPosition(int position); //移动到指定行 5 c.moveToPrevious(); //移动到前一行 6 c.moveToNext(); //移动到下一行 7 c.isFirst(); //是否指向第一条 8 c.isLast(); //是否指向最后一条 9 c.isBeforeFirst(); //是否指向第一条之前 10 c.isAfterLast(); //是否指向最后一条之后 11 c.isNull(int columnIndex); //指定列是否为空(列基数为0) 12 c.isClosed(); //游标是否已关闭 13 c.getCount(); //总数据项数 14 c.getPosition(); //返回当前游标所指向的行数 15 c.getColumnIndex(String columnName);//返回某列名对应的列索引值 16 c.getString(int columnIndex); //返回当前行指定列的值 复制代码实现代码 复制代码String[] params = {12345,123456};Cursor cursor = db.query("user",columns,"ID=?",params,null,null,null);//查询并获得游标if(cursor.moveToFirst()){//判断游标是否为空 for(int i=0;i<cursor.getCount();i++){ cursor.move(i);//移动到指定记录 String username = cursor.getString(cursor.getColumnIndex("username"); String password = cursor.getString(cursor.getColumnIndex("password")); } }复制代码通过rawQuery实现的带参数查询 复制代码Cursor result=db.rawQuery("SELECT ID, name, inventory FROM mytable");//Cursor c = db.rawQuery("s name, inventory FROM mytable where ID=?",new Stirng[]{"123456"}); result.moveToFirst(); while (!result.isAfterLast()) { int id=result.getInt(0); String name=result.getString(1); int inventory=result.getInt(2); // do something useful with these result.moveToNext(); } result.close();复制代码 在上面的代码示例中,已经用到了这几个常用方法中的一些,关于更多的信息,大家可以参考官方文档中的说明。 最后当我们完成了对数据库的操作后,记得调用SQLiteDatabase的close()方法释放数据库连接,否则容易出现SQLiteException。 上面就是SQLite的基本应用,但在实际开发中,为了能够更好的管理和维护数据库,我们会封装一个继承自SQLiteOpenHelper类的数据库操作类,然后以这个类为基础,再封装我们的业务逻辑方法。 这里直接使用案例讲解:下面是案例demo的界面 SQLiteOpenHelper类介绍 SQLiteOpenHelper是SQLiteDatabase的一个帮助类,用来管理数据库的创建和版本的更新。一般是建立一个类继承它,并实现它的onCreate和onUpgrade方法。 方法名 方法描述SQLiteOpenHelper(Context context,String name,SQLiteDatabase.CursorFactory factory,int version) 构造方法,其中 context 程序上下文环境 即:XXXActivity.this; name :数据库名字; factory:游标工厂,默认为null,即为使用默认工厂; version 数据库版本号 onCreate(SQLiteDatabase db) 创建数据库时调用onUpgrade(SQLiteDatabase db,int oldVersion , int newVersion) 版本更新时调用getReadableDatabase() 创建或打开一个只读数据库getWritableDatabase() 创建或打开一个读写数据库首先创建数据库类 复制代码 1 import android.content.Context; 2 import android.database.sqlite.SQLiteDatabase; 3 import android.database.sqlite.SQLiteDatabase.CursorFactory; 4 import android.database.sqlite.SQLiteOpenHelper; 5 6 public class SqliteDBHelper extends SQLiteOpenHelper { 7 8 // 步骤1:设置常数参量 9 private static final String DATABASE_NAME = "diary_db";10 private static final int VERSION = 1;11 private static final String TABLE_NAME = "diary";12 13 // 步骤2:重载构造方法14 public SqliteDBHelper(Context context) {15 super(context, DATABASE_NAME, null, VERSION);16 }17 18 /*19 * 参数介绍:context 程序上下文环境 即:XXXActivity.this 20 * name 数据库名字 21 * factory 接收数据,一般情况为null22 * version 数据库版本号23 */24 public SqliteDBHelper(Context context, String name, CursorFactory factory,25 int version) {26 super(context, name, factory, version);27 }28 //数据库第一次被创建时,onCreate()会被调用29 @Override30 public void onCreate(SQLiteDatabase db) {31 // 步骤3:数据库表的创建32 String strSQL = "create table "33 + TABLE_NAME34 + "(tid integer primary key autoincrement,title varchar(20),weather varchar(10),context text,publish date)";35 //步骤4:使用参数db,创建对象36 db.execSQL(strSQL);37 }38 //数据库版本变化时,会调用onUpgrade()39 @Override40 public void onUpgrade(SQLiteDatabase arg0, int arg1, int arg2) {41 42 }43 }复制代码正如上面所述,数据库第一次创建时onCreate方法会被调用,我们可以执行创建表的语句,当系统发现版本变化之后,会调用onUpgrade方法,我们可以执行修改表结构等语句。 我们需要一个Dao,来封装我们所有的业务方法,代码如下: 复制代码 1 import android.content.Context; 2 import android.database.Cursor; 3 import android.database.sqlite.SQLiteDatabase; 4 5 import com.chinasoft.dbhelper.SqliteDBHelper; 6 7 public class DiaryDao { 8 9 private SqliteDBHelper sqliteDBHelper;10 private SQLiteDatabase db;11 12 // 重写构造方法13 public DiaryDao(Context context) {14 this.sqliteDBHelper = new SqliteDBHelper(context);15 db = sqliteDBHelper.getWritableDatabase();16 }17 18 // 读操作19 public String execQuery(final String strSQL) {20 try {21 System.out.println("strSQL>" + strSQL);22 // Cursor相当于JDBC中的ResultSet23 Cursor cursor = db.rawQuery(strSQL, null);24 // 始终让cursor指向数据库表的第1行记录25 cursor.moveToFirst();26 // 定义一个StringBuffer的对象,用于动态拼接字符串27 StringBuffer sb = new StringBuffer();28 // 循环游标,如果不是最后一项记录29 while (!cursor.isAfterLast()) {30 sb.append(cursor.getInt(0) + "/" + cursor.getString(1) + "/"31 + cursor.getString(2) + "/" + cursor.getString(3) + "/"32 + cursor.getString(4)+"#");33 //cursor游标移动34 cursor.moveToNext();35 }36 db.close();37 return sb.deleteCharAt(sb.length()-1).toString();38 } catch (RuntimeException e) {39 e.printStackTrace();40 return null;41 }42 43 }44 45 // 写操作46 public boolean execOther(final String strSQL) {47 db.beginTransaction(); //开始事务48 try {49 System.out.println("strSQL" + strSQL);50 db.execSQL(strSQL);51 db.setTransactionSuccessful(); //设置事务成功完成 52 db.close();53 return true;54 } catch (RuntimeException e) {55 e.printStackTrace();56 return false;57 }finally { 58 db.endTransaction(); //结束事务 59 } 60 61 }62 }复制代码我们在Dao构造方法中实例化sqliteDBHelper并获取一个SQLiteDatabase对象,作为整个应用的数据库实例;在增删改信息时,我们采用了事务处理,确保数据完整性;最后要注意释放数据库资源db.close(),这一个步骤在我们整个应用关闭时执行,这个环节容易被忘记,所以朋友们要注意。 我们获取数据库实例时使用了getWritableDatabase()方法,也许朋友们会有疑问,在getWritableDatabase()和getReadableDatabase()中,你为什么选择前者作为整个应用的数据库实例呢?在这里我想和大家着重分析一下这一点。 我们来看一下SQLiteOpenHelper中的getReadableDatabase()方法: 复制代码 1 public synchronized SQLiteDatabase getReadableDatabase() { 2 if (mDatabase != null && mDatabase.isOpen()) { 3 // 如果发现mDatabase不为空并且已经打开则直接返回 4 return mDatabase; 5 } 6 7 if (mIsInitializing) { 8 // 如果正在初始化则抛出异常 9 throw new IllegalStateException("getReadableDatabase called recursively"); 10 } 11 12 // 开始实例化数据库mDatabase 13 14 try { 15 // 注意这里是调用了getWritableDatabase()方法 16 return getWritableDatabase(); 17 } catch (SQLiteException e) { 18 if (mName == null) 19 throw e; // Can't open a temp database read-only! 20 Log.e(TAG, "Couldn't open " + mName + " for writing (will try read-only):", e); 21 } 22 23 // 如果无法以可读写模式打开数据库 则以只读方式打开 24 25 SQLiteDatabase db = null; 26 try { 27 mIsInitializing = true; 28 String path = mContext.getDatabasePath(mName).getPath();// 获取数据库路径 29 // 以只读方式打开数据库 30 db = SQLiteDatabase.openDatabase(path, mFactory, SQLiteDatabase.OPEN_READONLY); 31 if (db.getVersion() != mNewVersion) { 32 throw new SQLiteException("Can't upgrade read-only database from version " + db.getVersion() + " to " 33 + mNewVersion + ": " + path); 34 } 35 36 onOpen(db); 37 Log.w(TAG, "Opened " + mName + " in read-only mode"); 38 mDatabase = db;// 为mDatabase指定新打开的数据库 39 return mDatabase;// 返回打开的数据库 40 } finally { 41 mIsInitializing = false; 42 if (db != null && db != mDatabase) 43 db.close(); 44 } 45 }复制代码在getReadableDatabase()方法中,首先判断是否已存在数据库实例并且是打开状态,如果是,则直接返回该实例,否则试图获取一个可读写模式的数据库实例,如果遇到磁盘空间已满等情况获取失败的话,再以只读模式打开数据库,获取数据库实例并返回,然后为mDatabase赋值为最新打开的数据库实例。既然有可能调用到getWritableDatabase()方法,我们就要看一下了: 复制代码public synchronized SQLiteDatabase getWritableDatabase() { if (mDatabase != null && mDatabase.isOpen() && !mDatabase.isReadOnly()) { // 如果mDatabase不为空已打开并且不是只读模式 则返回该实例 return mDatabase; } if (mIsInitializing) { throw new IllegalStateException("getWritableDatabase called recursively"); } // If we have a read-only database open, someone could be using it // (though they shouldn't), which would cause a lock to be held on // the file, and our attempts to open the database read-write would // fail waiting for the file lock. To prevent that, we acquire the // lock on the read-only database, which shuts out other users. boolean success = false; SQLiteDatabase db = null; // 如果mDatabase不为空则加锁 阻止其他的操作 if (mDatabase != null) mDatabase.lock(); try { mIsInitializing = true; if (mName == null) { db = SQLiteDatabase.create(null); } else { // 打开或创建数据库 db = mContext.openOrCreateDatabase(mName, 0, mFactory); } // 获取数据库版本(如果刚创建的数据库,版本为0) int version = db.getVersion(); // 比较版本(我们代码中的版本mNewVersion为1) if (version != mNewVersion) { db.beginTransaction();// 开始事务 try { if (version == 0) { // 执行我们的onCreate方法 onCreate(db); } else { // 如果我们应用升级了mNewVersion为2,而原版本为1则执行onUpgrade方法 onUpgrade(db, version, mNewVersion); } db.setVersion(mNewVersion);// 设置最新版本 db.setTransactionSuccessful();// 设置事务成功 } finally { db.endTransaction();// 结束事务 } } onOpen(db); success = true; return db;// 返回可读写模式的数据库实例 } finally { mIsInitializing = false; if (success) { // 打开成功 if (mDatabase != null) { // 如果mDatabase有值则先关闭 try { mDatabase.close(); } catch (Exception e) { } mDatabase.unlock();// 解锁 } mDatabase = db;// 赋值给mDatabase } else { // 打开失败的情况:解锁、关闭 if (mDatabase != null) mDatabase.unlock(); if (db != null) db.close(); } } }复制代码大家可以看到,几个关键步骤是,首先判断mDatabase如果不为空已打开并不是只读模式则直接返回,否则如果mDatabase不为空则加锁,然后开始打开或创建数据库,比较版本,根据版本号来调用相应的方法,为数据库设置新版本号,最后释放旧的不为空的mDatabase并解锁,把新打开的数据库实例赋予mDatabase,并返回最新实例。 看完上面的过程之后,大家或许就清楚了许多,如果不是在遇到磁盘空间已满等情况,getReadableDatabase()一般都会返回和getWritableDatabase()一样的数据库实例,所以我们在DBManager构造方法中使用getWritableDatabase()获取整个应用所使用的数据库实例是可行的。当然如果你真的担心这种情况会发生,那么你可以先用getWritableDatabase()获取数据实例,如果遇到异常,再试图用getReadableDatabase()获取实例,当然这个时候你获取的实例只能读不能写了 最后,让我们看一下如何使用这些数据操作方法来显示数据,界面核心逻辑代码: 复制代码public class SQLiteActivity extends Activity { public DiaryDao diaryDao; //因为getWritableDatabase内部调用了mContext.openOrCreateDatabase(mName, 0, mFactory); //所以要确保context已初始化,我们可以把实例化Dao的步骤放在Activity的onCreate里 @Override protected void onCreate(Bundle savedInstanceState) { diaryDao = new DiaryDao(SQLiteActivity.this); initDatabase(); } class ViewOcl implements View.OnClickListener { @Override public void onClick(View v) { String strSQL; boolean flag; String message; switch (v.getId()) { case R.id.btnAdd: String title = txtTitle.getText().toString().trim(); String weather = txtWeather.getText().toString().trim();; String context = txtContext.getText().toString().trim();; String publish = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss") .format(new Date()); // 动态组件SQL语句 strSQL = "insert into diary values(null,'" + title + "','" + weather + "','" + context + "','" + publish + "')"; flag = diaryDao.execOther(strSQL); //返回信息 message = flag?"添加成功":"添加失败"; Toast.makeText(getApplicationContext(), message, Toast.LENGTH_LONG).show(); break; case R.id.btnDelete: strSQL = "delete from diary where tid = 1"; flag = diaryDao.execOther(strSQL); //返回信息 message = flag?"删除成功":"删除失败"; Toast.makeText(getApplicationContext(), message, Toast.LENGTH_LONG).show(); break; case R.id.btnQuery: strSQL = "select * from diary order by publish desc"; String data = diaryDao.execQuery(strSQL); Toast.makeText(getApplicationContext(), data, Toast.LENGTH_LONG).show(); break; case R.id.btnUpdate: strSQL = "update diary set title = '测试标题1-1' where tid = 1"; flag = diaryDao.execOther(strSQL); //返回信息 message = flag?"更新成功":"更新失败"; Toast.makeText(getApplicationContext(), message, Toast.LENGTH_LONG).show(); break; } } } private void initDatabase() { // 创建数据库对象 SqliteDBHelper sqliteDBHelper = new SqliteDBHelper(SQLiteActivity.this); sqliteDBHelper.getWritableDatabase(); System.out.println("数据库创建成功"); } }复制代码 Android sqlite3数据库管理工具 Android SDK的tools目录下提供了一个sqlite3.exe工具,这是一个简单的sqlite数据库管理工具。开发者可以方便的使用其对sqlite数据库进行命令行的操作。 程序运行生成的.db文件一般位于"/data/data/项目名(包括所处包名)/databases/.db",因此要对数据库文件进行操作需要先找到数据库文件: 1、进入shell 命令 adb shell2、找到数据库文件 cd data/data ls --列出所有项目 cd project_name --进入所需项目名 cd databases ls --列出现寸的数据库文件 3、进入数据库 sqlite3 test_db --进入所需数据库 会出现类似如下字样: SQLite version 3.6.22Enter ".help" for instructionsEnter SQL statements terminated with a ";"sqlite>至此,可对数据库进行sql操作。 4、sqlite常用命令 .databases --产看当前数据库.tables --查看当前数据库中的表.help --sqlite3帮助.schema --各个表的生成语句 原文地址https://www.cnblogs.com/ITtangtang/p/3920916.html

auto_answer 2019-12-02 01:50:21 0 浏览量 回答数 0

回答

排序算法是一种基本并且常用的算法。由于实际工作中处理的数量巨大,所以排序算法对算法本身的速度要求很高。 而一般我们所谓的算法的性能主要是指算法的复杂度,一般用O方法来表示。在后面我将给出详细的说明。 对于排序的算法我想先做一点简单的介绍,也是给这篇文章理一个提纲。 我将按照算法的复杂度,从简单到难来分析算法。 第一部分是简单排序算法,后面你将看到他们的共同点是算法复杂度为O(N*N)(因为没有使用word,所以无法打出上标和下标)。 第二部分是高级排序算法,复杂度为O(Log2(N))。这里我们只介绍一种算法。另外还有几种算法因为涉及树与堆的概念,所以这里不于讨论。 第三部分类似动脑筋。这里的两种算法并不是最好的(甚至有最慢的),但是算法本身比较奇特,值得参考(编程的角度)。同时也可以让我们从另外的角度来认识这个问题。 第四部分是我送给大家的一个餐后的甜点——一个基于模板的通用快速排序。由于是模板函数可以对任何数据类型排序(抱歉,里面使用了一些论坛专家的呢称)。 现在,让我们开始吧: 一、简单排序算法 由于程序比较简单,所以没有加什么注释。所有的程序都给出了完整的运行代码,并在我的VC环境 下运行通过。因为没有涉及MFC和WINDOWS的内容,所以在BORLAND C++的平台上应该也不会有什么 问题的。在代码的后面给出了运行过程示意,希望对理解有帮助。 1.冒泡法: 这是最原始,也是众所周知的最慢的算法了。他的名字的由来因为它的工作看来象是冒泡: #include <iostream.h> void BubbleSort(int* pData,int Count) { int iTemp; for(int i=1;i<Count;i++) { for(int j=Count-1;j>=i;j--) { if(pData[j]<pData[j-1]) { iTemp = pData[j-1]; pData[j-1] = pData[j]; pData[j] = iTemp; } } } } void main() { int data[] = {10,9,8,7,6,5,4}; BubbleSort(data,7); for (int i=0;i<7;i++) cout<<data[i]<<" "; cout<<"\n"; } 倒序(最糟情况) 第一轮:10,9,8,7->10,9,7,8->10,7,9,8->7,10,9,8(交换3次) 第二轮:7,10,9,8->7,10,8,9->7,8,10,9(交换2次) 第一轮:7,8,10,9->7,8,9,10(交换1次) 循环次数:6次 交换次数:6次 其他: 第一轮:8,10,7,9->8,10,7,9->8,7,10,9->7,8,10,9(交换2次) 第二轮:7,8,10,9->7,8,10,9->7,8,10,9(交换0次) 第一轮:7,8,10,9->7,8,9,10(交换1次) 循环次数:6次 交换次数:3次 上面我们给出了程序段,现在我们分析它:这里,影响我们算法性能的主要部分是循环和交换, 显然,次数越多,性能就越差。从上面的程序我们可以看出循环的次数是固定的,为1+2+...+n-1。 写成公式就是1/2*(n-1)*n。 现在注意,我们给出O方法的定义: 若存在一常量K和起点n0,使当n>=n0时,有f(n)<=K*g(n),则f(n) = O(g(n))。(呵呵,不要说没 学好数学呀,对于编程数学是非常重要的。。。) 现在我们来看1/2*(n-1)*n,当K=1/2,n0=1,g(n)=n*n时,1/2*(n-1)*n<=1/2*n*n=K*g(n)。所以f(n) =O(g(n))=O(n*n)。所以我们程序循环的复杂度为O(n*n)。 再看交换。从程序后面所跟的表可以看到,两种情况的循环相同,交换不同。其实交换本身同数据源的有序程度有极大的关系,当数据处于倒序的情况时,交换次数同循环一样(每次循环判断都会交换),复杂度为O(n*n)。当数据为正序,将不会有交换。复杂度为O(0)。乱序时处于中间状态。正是由于这样的原因,我们通常都是通过循环次数来对比算法。 2.交换法: 交换法的程序最清晰简单,每次用当前的元素一一的同其后的元素比较并交换。 #include <iostream.h> void ExchangeSort(int* pData,int Count) { int iTemp; for(int i=0;i<Count-1;i++) { for(int j=i+1;j<Count;j++) { if(pData[j]<pData[i]) { iTemp = pData[i]; pData[i] = pData[j]; pData[j] = iTemp; } } } } void main() { int data[] = {10,9,8,7,6,5,4}; ExchangeSort(data,7); for (int i=0;i<7;i++) cout<<data[i]<<" "; cout<<"\n"; } 倒序(最糟情况) 第一轮:10,9,8,7->9,10,8,7->8,10,9,7->7,10,9,8(交换3次) 第二轮:7,10,9,8->7,9,10,8->7,8,10,9(交换2次) 第一轮:7,8,10,9->7,8,9,10(交换1次) 循环次数:6次 交换次数:6次 其他: 第一轮:8,10,7,9->8,10,7,9->7,10,8,9->7,10,8,9(交换1次) 第二轮:7,10,8,9->7,8,10,9->7,8,10,9(交换1次) 第一轮:7,8,10,9->7,8,9,10(交换1次) 循环次数:6次 交换次数:3次 从运行的表格来看,交换几乎和冒泡一样糟。事实确实如此。循环次数和冒泡一样也是1/2*(n-1)*n,所以算法的复杂度仍然是O(n*n)。由于我们无法给出所有的情况,所以只能直接告诉大家他们在交换上面也是一样的糟糕(在某些情况下稍好,在某些情况下稍差)。 3.选择法: 现在我们终于可以看到一点希望:选择法,这种方法提高了一点性能(某些情况下)这种方法类似我们人为的排序习惯:从数据中选择最小的同第一个值交换,在从省下的部分中选择最小的与第二个交换,这样往复下去。 #include <iostream.h> void SelectSort(int* pData,int Count) { int iTemp; int iPos; for(int i=0;i<Count-1;i++) { iTemp = pData[i]; iPos = i; for(int j=i+1;j<Count;j++) { if(pData[j]<iTemp) { iTemp = pData[j]; iPos = j; } } pData[iPos] = pData[i]; pData[i] = iTemp; } } void main() { int data[] = {10,9,8,7,6,5,4}; SelectSort(data,7); for (int i=0;i<7;i++) cout<<data[i]<<" "; cout<<"\n"; } 倒序(最糟情况) 第一轮:10,9,8,7->(iTemp=9)10,9,8,7->(iTemp=8)10,9,8,7->(iTemp=7)7,9,8,10(交换1次) 第二轮:7,9,8,10->7,9,8,10(iTemp=8)->(iTemp=8)7,8,9,10(交换1次) 第一轮:7,8,9,10->(iTemp=9)7,8,9,10(交换0次) 循环次数:6次 交换次数:2次 其他: 第一轮:8,10,7,9->(iTemp=8)8,10,7,9->(iTemp=7)8,10,7,9->(iTemp=7)7,10,8,9(交换1次) 第二轮:7,10,8,9->(iTemp=8)7,10,8,9->(iTemp=8)7,8,10,9(交换1次) 第一轮:7,8,10,9->(iTemp=9)7,8,9,10(交换1次) 循环次数:6次 交换次数:3次 遗憾的是算法需要的循环次数依然是1/2*(n-1)*n。所以算法复杂度为O(n*n)。 我们来看他的交换。由于每次外层循环只产生一次交换(只有一个最小值)。所以f(n)<=n 所以我们有f(n)=O(n)。所以,在数据较乱的时候,可以减少一定的交换次数。 4.插入法: 插入法较为复杂,它的基本工作原理是抽出牌,在前面的牌中寻找相应的位置插入,然后继续下一张 #include <iostream.h> void InsertSort(int* pData,int Count) { int iTemp; int iPos; for(int i=1;i<Count;i++) { iTemp = pData[i]; iPos = i-1; while((iPos>=0) && (iTemp<pData[iPos])) { pData[iPos+1] = pData[iPos]; iPos--; } pData[iPos+1] = iTemp; } } void main() { int data[] = {10,9,8,7,6,5,4}; InsertSort(data,7); for (int i=0;i<7;i++) cout<<data[i]<<" "; cout<<"\n"; } 倒序(最糟情况) 第一轮:10,9,8,7->9,10,8,7(交换1次)(循环1次) 第二轮:9,10,8,7->8,9,10,7(交换1次)(循环2次) 第一轮:8,9,10,7->7,8,9,10(交换1次)(循环3次) 循环次数:6次 交换次数:3次 其他: 第一轮:8,10,7,9->8,10,7,9(交换0次)(循环1次) 第二轮:8,10,7,9->7,8,10,9(交换1次)(循环2次) 第一轮:7,8,10,9->7,8,9,10(交换1次)(循环1次) 循环次数:4次 交换次数:2次 上面结尾的行为分析事实上造成了一种假象,让我们认为这种算法是简单算法中最好的,其实不是, 因为其循环次数虽然并不固定,我们仍可以使用O方法。从上面的结果可以看出,循环的次数f(n)<= 1/2*n*(n-1)<=1/2*n*n。所以其复杂度仍为O(n*n)(这里说明一下,其实如果不是为了展示这些简单 排序的不同,交换次数仍然可以这样推导)。现在看交换,从外观上看,交换次数是O(n)(推导类似 选择法),但我们每次要进行与内层循环相同次数的‘=’操作。正常的一次交换我们需要三次‘=’ 而这里显然多了一些,所以我们浪费了时间。 最终,我个人认为,在简单排序算法中,选择法是最好的。 二、高级排序算法: 高级排序算法中我们将只介绍这一种,同时也是目前我所知道(我看过的资料中)的最快的。 它的工作看起来仍然象一个二叉树。首先我们选择一个中间值middle程序中我们使用数组中间值,然后 把比它小的放在左边,大的放在右边(具体的实现是从两边找,找到一对后交换)。然后对两边分别使 用这个过程(最容易的方法——递归)。 1.快速排序: #include <iostream.h> void run(int* pData,int left,int right) { int i,j; int middle,iTemp; i = left; j = right; middle = pData[(left+right)/2]; //求中间值 do{ while((pData[i]<middle) && (i<right))//从左扫描大于中值的数 i++; while((pData[j]>middle) && (j>left))//从右扫描大于中值的数 j--; if(i<=j)//找到了一对值 { //交换 iTemp = pData[i]; pData[i] = pData[j]; pData[j] = iTemp; i++; j--; } }while(i<=j);//如果两边扫描的下标交错,就停止(完成一次) //当左边部分有值(left<j),递归左半边 if(left<j) run(pData,left,j); //当右边部分有值(right>i),递归右半边 if(right>i) run(pData,i,right); } void QuickSort(int* pData,int Count) { run(pData,0,Count-1); } void main() { int data[] = {10,9,8,7,6,5,4}; QuickSort(data,7); for (int i=0;i<7;i++) cout<<data[i]<<" "; cout<<"\n"; } 这里我没有给出行为的分析,因为这个很简单,我们直接来分析算法:首先我们考虑最理想的情况 1.数组的大小是2的幂,这样分下去始终可以被2整除。假设为2的k次方,即k=log2(n)。 2.每次我们选择的值刚好是中间值,这样,数组才可以被等分。 第一层递归,循环n次,第二层循环2*(n/2)...... 所以共有n+2(n/2)+4(n/4)+...+n*(n/n) = n+n+n+...+n=k*n=log2(n)*n 所以算法复杂度为O(log2(n)*n) 其他的情况只会比这种情况差,最差的情况是每次选择到的middle都是最小值或最大值,那么他将变 成交换法(由于使用了递归,情况更糟)。但是你认为这种情况发生的几率有多大。。呵呵,你完全 不必担心这个问题。实践证明,大多数的情况,快速排序总是最好的。 如果你担心这个问题,你可以使用堆排序,这是一种稳定的O(log2(n)*n)算法,但是通常情况下速度要慢于快速排序(因为要重组堆)。 三、其他排序 1.双向冒泡: 通常的冒泡是单向的,而这里是双向的,也就是说还要进行反向的工作。 代码看起来复杂,仔细理一下就明白了,是一个来回震荡的方式。 写这段代码的作者认为这样可以在冒泡的基础上减少一些交换(我不这么认为,也许我错了)。 反正我认为这是一段有趣的代码,值得一看。 #include <iostream.h> void Bubble2Sort(int* pData,int Count) { int iTemp; int left = 1; int right =Count -1; int t; do { //正向的部分 for(int i=right;i>=left;i--) { if(pData[i]<pData[i-1]) { iTemp = pData[i]; pData[i] = pData[i-1]; pData[i-1] = iTemp; t = i; } } left = t+1; //反向的部分 for(i=left;i<right+1;i++) { if(pData[i]<pData[i-1]) { iTemp = pData[i]; pData[i] = pData[i-1]; pData[i-1] = iTemp; t = i; } } right = t-1; }while(left<=right); } void main() { int data[] = {10,9,8,7,6,5,4}; Bubble2Sort(data,7); for (int i=0;i<7;i++) cout<<data[i]<<" "; cout<<"\n"; } 2.SHELL排序 这个排序非常复杂,看了程序就知道了。 首先需要一个递减的步长,这里我们使用的是9、5、3、1(最后的步长必须是1)。 工作原理是首先对相隔9-1个元素的所有内容排序,然后再使用同样的方法对相隔5-1个元素的排序 以次类推。 #include <iostream.h> void ShellSort(int* pData,int Count) { int step[4]; step[0] = 9; step[1] = 5; step[2] = 3; step[3] = 1; int iTemp; int k,s,w; for(int i=0;i<4;i++) { k = step[i]; s = -k; for(int j=k;j<Count;j++) { iTemp = pData[j]; w = j-k;//求上step个元素的下标 if(s ==0) { s = -k; s++; pData[s] = iTemp; } while((iTemp<pData[w]) && (w>=0) && (w<=Count)) { pData[w+k] = pData[w]; w = w-k; } pData[w+k] = iTemp; } } } void main() { int data[] = {10,9,8,7,6,5,4,3,2,1,-10,-1}; ShellSort(data,12); for (int i=0;i<12;i++) cout<<data[i]<<" "; cout<<"\n"; } 呵呵,程序看起来有些头疼。不过也不是很难,把s==0的块去掉就轻松多了,这里是避免使用0 步长造成程序异常而写的代码。这个代码我认为很值得一看。 这个算法的得名是因为其发明者的名字D.L.SHELL。依照参考资料上的说法:“由于复杂的数学原因 避免使用2的幂次步长,它能降低算法效率。”另外算法的复杂度为n的1.2次幂。同样因为非常复杂并 “超出本书讨论范围”的原因(我也不知道过程),我们只有结果了。 四、基于模板的通用排序: 这个程序我想就没有分析的必要了,大家看一下就可以了。不明白可以在论坛上问。 MyData.h文件 /////////////////////////////////////////////////////// class CMyData { public: CMyData(int Index,char* strData); CMyData(); virtual ~CMyData(); int m_iIndex; int GetDataSize(){ return m_iDataSize; }; const char* GetData(){ return m_strDatamember; }; //这里重载了操作符: CMyData& operator =(CMyData &SrcData); bool operator <(CMyData& data ); bool operator >(CMyData& data ); private: char* m_strDatamember; int m_iDataSize; }; //////////////////////////////////////////////////////// MyData.cpp文件 //////////////////////////////////////////////////////// CMyData::CMyData(): m_iIndex(0), m_iDataSize(0), m_strDatamember(NULL) { } CMyData::~CMyData() { if(m_strDatamember != NULL) delete[] m_strDatamember; m_strDatamember = NULL; } CMyData::CMyData(int Index,char* strData): m_iIndex(Index), m_iDataSize(0), m_strDatamember(NULL) { m_iDataSize = strlen(strData); m_strDatamember = new char[m_iDataSize+1]; strcpy(m_strDatamember,strData); } CMyData& CMyData::operator =(CMyData &SrcData) { m_iIndex = SrcData.m_iIndex; m_iDataSize = SrcData.GetDataSize(); m_strDatamember = new char[m_iDataSize+1]; strcpy(m_strDatamember,SrcData.GetData()); return *this; } bool CMyData::operator <(CMyData& data ) { return m_iIndex<data.m_iIndex; } bool CMyData::operator >(CMyData& data ) { return m_iIndex>data.m_iIndex; } /////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////// //主程序部分 #include <iostream.h> #include "MyData.h" template <class T> void run(T* pData,int left,int right) { int i,j; T middle,iTemp; i = left; j = right; //下面的比较都调用我们重载的操作符函数 middle = pData[(left+right)/2]; //求中间值 do{ while((pData[i]<middle) && (i<right))//从左扫描大于中值的数 i++; while((pData[j]>middle) && (j>left))//从右扫描大于中值的数 j--; if(i<=j)//找到了一对值 { //交换 iTemp = pData[i]; pData[i] = pData[j]; pData[j] = iTemp; i++; j--; } }while(i<=j);//如果两边扫描的下标交错,就停止(完成一次) //当左边部分有值(left<j),递归左半边 if(left<j) run(pData,left,j); //当右边部分有值(right>i),递归右半边 if(right>i) run(pData,i,right); } template <class T> void QuickSort(T* pData,int Count) { run(pData,0,Count-1); } void main() { CMyData data[] = { CMyData(8,"xulion"), CMyData(7,"sanzoo"), CMyData(6,"wangjun"), CMyData(5,"VCKBASE"), CMyData(4,"jacky2000"), CMyData(3,"cwally"), CMyData(2,"VCUSER"), CMyData(1,"isdong") }; QuickSort(data,8); for (int i=0;i<8;i++) cout<<data[i].m_iIndex<<" "<<data[i].GetData()<<"\n"; cout<<"\n"; }

沉默术士 2019-12-02 01:19:06 0 浏览量 回答数 0

回答

字符串的操作 字符串的连接操作 符号: +格式:str1 + str2例如:str1 = 'I Love' str2 = 'You!' print(str1 + str2) >>> 'I Love You!' 返回值:str字符串的复制操作 符号: *格式:str * num例如:str = 'abc' num = 3 print(str1 * num) >>> 'abcabcabc' 返回值:str字符串的索引操作 符号: []格式:str[index]例如:str = 'abcde' print(str[2]) >>> 'c' 返回值:str字符串的切片操作 符号: [::]格式:str[index:index:step]例如:str = 'abcdefg' print(str[:]) >>> 'abcdefg' #取字符串所有内容,开头的0可以忽略 print(str[:3]) >>> 'abc' #取出字符串索引0-3的内容(顾头不顾尾,不包含索引3的对象) print(str[:5:2]) >>> 'ace' #取出字符串索引0-5的内容,每2个索引取一次 print(str[3:]) >>> 'defg' #取出字符串索引3开始到结尾的内容 print(str[-2:]) >>> 'fg' #取出字符串倒数第2开始到结尾的内容 返回值:str 内置方法 连接 格式:x.__add__(y)等同于x+y返回值:str 包含 格式:x.__contains__(y)返回值:bool相等 格式:x.__eq__(y)等同于x==y返回值:bool大于等于 格式:x.__ge__(y)等同于x>=y返回值:bool访问属性方法(文件操作时用) 格式:x.__getattribute__(self,name)返回值:tuple获取键值对(字典操作时用) 格式:x.__getitem__(key)返回值:键的值 大于 格式:x.__gt__(y)等同于x>y返回值:bool哈希值 格式:x.__hash__()返回值:哈希值,int类型迭代器 格式:x.__iter__()返回值:迭代器小于等于 格式:x.__le__(y)等同于x<=y返回值:bool长度 格式:x.__len__()等同于len(x)返回值:int类型小于 格式:x.__lt__()返回值:布尔类型复制 格式:x.__mul__(y)等同于x*y返回值:str注::此处的y必须是int类型 不等于 格式:x.__ne__(y)等同于x!=y返回值:bool右->左 复制 格式:x.__rmul__(y)等同于y*x返回值:str注:此处的y必须是int类型 常用方法 首字母大写,后面的小写 格式:x.capitalize() #开头第一个单词首字母大写,后面的所有字符串全部小写 例如:x = = 'i am A 好 boy' print(x.capitalize()) >>> 'I am a 好 boy' 返回值:str全部字符小写 格式:x.casefold() #字符串中所有单词的所有字母全部小写 例如:x = = 'i am A 好 boy' print(x.casefold()) >>> 'i am a 好 boy' 返回值:str居中,两边默认以空格填充 格式:x.center() #定义字符串的长度,不足长度时,两边以指定字符串进行填充 例如:x = 'abc' print(x.center(20,'*')) >>> '********abc*********' 返回值:str计数(默认全文计数) 格式:x.count(str,index1,index2) #指定开始和结束范围来统计某字符串的个数 例如:x = 'sffefwsf' print(x.count('sf'),0,8) >>> 2 返回值:int类型编码 格式:x.encode() #指定字符串的编码格式 例如:x.encode(encoding='utf-8')#转换为utf-8格式返回值:bytes以什么为结尾 格式:x.endswith(str,index1,index2) #指定字符串的开始和结束范围,判断所选区域是否是以指定字符串结尾 例如:x = 'adfd' print(x.endswith('fd',2,3)) >>> True 返回值:bool把t转换为空格 格式:x.expandtabs() #默认开头到\t为8个字节,不足以空格填充 例如:x = 'i amt At 好 boy' print(x.expandtabs()) >>> 'i am A 好 boy' 返回值:str查找 格式:x.find(str,index1,index2) #指定开始和结束的范围,查找指定区域内是否由指定的字符串(只返回查找到第一个的索引值) 例如:x = 'asdfdsfsafsaf' print(x.find('df',1,8)) >>> 2 返回值:int类型注:如果find未查找到,将返回为-1 格式化 格式:x.format(*args) #字符串的格式化可以有参数,可以无参数,可以是索引值参数,也可以是关键字参数 无参数 例如:s1 = 'I {} {} {}' print(s1.format('love','you','!')) >>> 'I love you !' 索引参数 例如:s1 = 'I {0} {1} {0} {1}' print(s1.format('love','you')) >>> 'I love you love you' 注:使用索引参数时,只能按顺序,从索引0开始 关键字参数 例如:s1 = 'I {m} {n}' print(s1.format(m = 'love',n = 'you')) >>> 'I love you' 格式限定符 填充常和对齐一起使用: ^ < > 分别是居中,左对齐和右对齐,后面带宽度 : 后面带填充的字符,只能是一个字符,默认是空格 例如: 无(位置)参数:s1 = 'I love you {}' print(s1.format(', very much !')) 'I love you , very much !' 默认居中方法:s1 = 'I love you {:^18}' print(s1.format(', very much !')) 'I love you , very much ! ' 指定字符居中:s1 = 'I love you {:*^18}' print(s1.format(', very much !')) 'I love you , very much !*' 指定字符左对齐:s1 = 'I love you {:*<18}' print(s1.format(', very much !')) 'I love you , very much !*' 指定字符右对齐:s1 = 'I love you {:*>18}' print(s1.format(', very much !')) 'I love you *, very much !' 精度与类型f 例如:s1 = '圆周率大概是{}' print(s1.format(3.1415926)) >>> '圆周率大概是3.1415926' s1 = '圆周率大概是{:.2f}'print(s1.format(3.1415926)) >>> '圆周率大概是3.14' s1 = '圆周率大概是{:.2f}'print(s1.format(3.1415926)) >>> '圆周率大概是3.1416' 注:精度一般和浮点一起使用,取值时使用四舍五入法 进制操作 主要的进制为b、d、o、x,分别是二、十、八、十六进制 例如: 十进制:s1 = 'The pen values {} yuan!'s1 = 'The pen values {:d} yuan!'print(s1.format(17)) >>> 'The pen values 17 yuan!' 二进制:s1 = 'The pen values {:b} yuan!'print(s1.format(17)) >>> 'The pen values 10001 yuan!' 八进制:s1 = 'The pen values {:o} yuan!'print(s1.format(17)) >>> 'The pen values 21 yuan!' 十六进:s1 = 'The pen values {:x} yuan!'print(s1.format(17)) >>> 'The pen values 11 yuan!' 金融字符,千分位 即, 例如:s1 = 'The phone is {}$ !' print(s1.format(10000000)) >>> 'The phone is 10000000$ !' s1 = 'The phone is {:,}$ !'print(s1.format(10000000)) >>> 'The phone is 10,000,000$ !' 返回值:str 查找 格式:x.index(str,index1,index2) #用法和find一样 返回值:int注::当index未查找到,程序将报错 非符号字符串 格式:x.isalnum() #字符串中可以有大小写,可以有数字,但不可以有符号 返回值:bool纯字母的字符串 格式:x.isalpha() #字符串中可以有大小写,但不可以有数字和符号 返回值:bool纯数字的字符串 格式:x.isdecimal() #字符串中只可以数字 返回值:bool纯数字的字符串 格式:x.isdigit() #字符串中只可以数字 返回值:bool开头字母的字符串 格式:x.isidentifier() #字符串以字母开头,可以是大小写,后面可以有数字,但数字不能开头 返回值:bool全部小写 格式:x.islower() #字符串全部是小写,也可以有数字,数字可以开头,但不能全数字 返回值:bool包含中文数字 格式:x.isnumeric() #字符串中可以有数字,也可以有中文大小写数字 返回值:bool可打印 格式:x.isprintable() #打印为空,则为假 返回值:bool空格 格式:x.isspace() #字符串中只能是空格 返回值:bool标题 格式:x.istitle() #字符串中每个单词首字母大写 返回值:bool全部大写 格式:x.isupper() #字符串中可以有数字,数字可以开头,但必须有大写字母 返回值:bool拼接 格式:x.join(str) #把字符串以指定字符串进行相连 例如:x = 'abcde' print('_'.join(x)) >>> 'a_b_c_d_e' 返回值:str左对齐 格式:x.ljust(str) #字符串左对齐,需要指定长度,不足长度时可以用指定字符串进行填充(默认以空格填充),当指定长度小于字符串长度,将会左对齐 例如:x = 'abcde' print(x.ljust(12,'*')) >>> 'abcde*******' 返回值:str全部小写 格式:x.lower() #字符串中所有单词,所有字母全部小写 #与casefold()功能一样 返回值:str去除左边空格 格式:x.lstrip() #去除对字符串左边的指定字符串,字符串中间和结尾的指定字符串不做处理,默认去除的是空格 例如:x = 'aaafdfdfaaadfdsaaa' print(x.lstrip('a')) >>> 'fdfdfaaadfdsaaa' 返回值:str字符串进行分段 格式:x.partition(str) #把字符串以从左到右第一个指定字符串为元素进行分段,以元组形式展现 例如:x = 'acbadfsadfsdfsd' print(x.partition('sa')) >>> ('acbadf', 'sa', 'dfsdfsd') 返回值:tuple字符串替换 格式:x.replace(old,new,count) #把字符串中指定的字符串替换为新字符串,默认全部替换,也可以指定替换次数,如果次数超过存在的个数,将全部替换 例如:x = 'acbadfsadfsdfsd' print(x.replace('df','A',7)) >>> 'acbaAsaAsAsd' 返回值:str右→左 查找 格式:x.rfind(str,index1,index2) #功能同find()一样 返回值:int右→左 查找 格式:x.rindex(str,index1,index2) #功能同index()一样 返回值:int右对齐 格式:x.rjust(str) #字符串右对齐,需要指定长度,不足长度时可以用指定字符串进行填充(默认以空格填充),当指定长度小于字符串长度,将会右对齐 例如:x = 'adc' print(x.rjust(6,'%')) >>> '%%%adc' 返回值:str右→左 字符串分段 格式:x.rpartition(str) #把字符串以从右到左第一个指定字符串进行分段,以元组形式展示 例如:x = 'abccbacbd' print(x.rpartition('cb')) >>> ('abccba', 'cb', 'd') 返回值:tuple字符串切片 格式:x.rsplit(str) #把字符串以指定字符串进行切片,并以列表的形式展现 例如:x = 'abccbacbd' print(x.rsplit('cb')) >>> ['abc', 'a', 'd'] 返回值:list去除右边空格 格式:x.rstrip(str) #去除字符串最右边的指定字符串,左边和中间的指定字符串不做处理,默认去除的是空格 例如:x = 'aaafdfdfaaadfdsaaa' print(x.rstrip('a')) >>> 'aaafdfdfaaadfds' 返回值:str切片 格式:x.split() #把字符串以指定字符串进行切片,并以列表的形式展现 #功能同rsplit()一样 返回值:list换行符分段 格式:x.splitlines() #把字符串以换行符进行切片,并以列表的形式展现 返回值:list以什么为开头 格式:x.startswith(str,index1,index2) #指定字符串的开始和结束范围,判断指定区域是否是以指定字符串开头 例如:x = 'adafsdaf' print(x.startswith('da',1,8)) >>> True 返回值:bool去除两边空格 格式:x.strip(str) #去除字符串两边的指定字符串,中间的指定字符串不做处理,默认去除的是空格 例如:x = 'aaadfsaaafdsfaaa' print(x.strip()) >>> 'dfsaaafdsf' 返回值:str大小写互转 格式:x.swapcase() #字符串中所有的字母进行大小写相互转换 例如:x = 'aBcdE' print(x.swapcase()) >>> 'AbCDe' 返回值:str标题化 格式:x.title() #对字符串中所有单词首字母大写,单词中间的大写全部转换为小写 例如:x = 'asd faDSdsf sdf' print(x.title()) >>> 'Asd Fadsdsf Sdf' 返回值:str全部变大写 格式:x.upper() #字符串中所有字母全部转换为大写 例如:x = 'DFdgDdfdg' print(x.upper()) >>> 'DFDGDDFDG' 返回值:str左对齐,零填充 格式:x.zfill() #字符串左对齐,指定字符串长度,不足部分以0填充 例如:x = 'afd' print(x.zfill(5)) >>> '00afd' 返回值:str映射函数 x.maketrans(*args) #把两个字符串进行一一对应,两个字符串长度必须一致 格式:str.maketrans(str1,str2) bytearray.maketrans(str1,str2) bytes.maketrans(str1,str2) 返回值:dict映射函数 x.translate(*args) #把maketrans()得到的映射表应用出来 格式:x.translate(str.maketrans(str1,str2))返回值:str

xuning715 2019-12-02 01:10:18 0 浏览量 回答数 0

回答

1.1。Numba的约5分钟指南 Numba是Python的即时编译器,它最适用于使用NumPy数组和函数以及循环的代码。使用Numba的最常用方法是通过其装饰器集合,可以应用于您的函数来指示Numba编译它们。当调用Numba修饰函数时,它被编译为机器代码“及时”执行,并且您的全部或部分代码随后可以以本机机器代码速度运行! 开箱即用的Numba使用以下方法: 操作系统:Windows(32位和64位),OSX和Linux(32位和64位) 架构:x86,x86_64,ppc64le。在armv7l,armv8l(aarch64)上进行实验。 GPU:Nvidia CUDA。AMD ROC的实验。 CPython的 NumPy 1.10 - 最新 1.1.1。我怎么得到它? Numba可作为畅达包为 蟒蛇Python发布: $ conda install numba Numba还有pip可供选择: $ pip install numba Numba也可以 从源代码编译,虽然我们不建议首次使用Numba用户。 Numba通常用作核心包,因此其依赖性保持在绝对最小值,但是,可以按如下方式安装额外的包以提供其他功能: scipy- 支持编译numpy.linalg功能。 colorama - 支持回溯/错误消息中的颜色突出显示。 pyyaml - 通过YAML配置文件启用Numba配置。 icc_rt - 允许使用Intel SVML(高性能短矢量数学库,仅限x86_64)。安装说明在 性能提示中。 1.1.2。Numba会为我的代码工作吗? 这取决于你的代码是什么样的,如果你的代码是以数字为导向的(做了很多数学运算),经常使用NumPy和/或有很多循环,那么Numba通常是一个不错的选择。在这些例子中,我们将应用最基本的Numba的JIT装饰器,@jit试图加速一些函数来演示哪些有效,哪些无效。 Numba在代码看起来像这样: from numba import jit import numpy as np x = np.arange(100).reshape(10, 10) @jit(nopython=True) # Set "nopython" mode for best performance def go_fast(a): # Function is compiled to machine code when called the first time trace = 0 for i in range(a.shape[0]): # Numba likes loops trace += np.tanh(a[i, i]) # Numba likes NumPy functions return a + trace # Numba likes NumPy broadcasting print(go_fast(x)) 对于看起来像这样的代码,如果有的话,它将无法正常工作: from numba import jit import pandas as pd x = {'a': [1, 2, 3], 'b': [20, 30, 40]} @jit def use_pandas(a): # Function will not benefit from Numba jit df = pd.DataFrame.from_dict(a) # Numba doesn't know about pd.DataFrame df += 1 # Numba doesn't understand what this is return df.cov() # or this! print(use_pandas(x)) 请注意,Numba不理解Pandas,因此Numba只是通过解释器运行此代码,但增加了Numba内部开销的成本! 1.1.3。什么是nopython模式? Numba @jit装饰器从根本上以两种编译模式运行, nopython模式和object模式。在go_fast上面的例子中, nopython=True在@jit装饰器中设置,这是指示Numba在nopython模式下操作。nopython编译模式的行为本质上是编译装饰函数,以便它完全运行而不需要Python解释器的参与。这是使用Numba jit装饰器的推荐和最佳实践方式,因为它可以带来最佳性能。 如果编译nopython模式失败,Numba可以编译使用 ,如果没有设置,这是装饰器的 后退模式(如上例所示)。在这种模式下,Numba将识别它可以编译的循环并将它们编译成在机器代码中运行的函数,并且它将运行解释器中的其余代码。为获得最佳性能,请避免使用此模式objectmode@jitnopython=Trueuse_pandas 1.1.4。如何衡量Numba的表现? 首先,回想一下,Numba必须为执行函数的机器代码版本之前给出的参数类型编译函数,这需要时间。但是,一旦编译完成,Numba会为所呈现的特定类型的参数缓存函数的机器代码版本。如果再次使用相同的类型调用它,它可以重用缓存的版本而不必再次编译。 测量性能时,一个非常常见的错误是不考虑上述行为,并使用一个简单的计时器来计算一次,该计时器包括在执行时编译函数所花费的时间。 例如: from numba import jit import numpy as np import time x = np.arange(100).reshape(10, 10) @jit(nopython=True) def go_fast(a): # Function is compiled and runs in machine code trace = 0 for i in range(a.shape[0]): trace += np.tanh(a[i, i]) return a + trace DO NOT REPORT THIS... COMPILATION TIME IS INCLUDED IN THE EXECUTION TIME! start = time.time() go_fast(x) end = time.time() print("Elapsed (with compilation) = %s" % (end - start)) NOW THE FUNCTION IS COMPILED, RE-TIME IT EXECUTING FROM CACHE start = time.time() go_fast(x) end = time.time() print("Elapsed (after compilation) = %s" % (end - start)) 这,例如打印: Elapsed (with compilation) = 0.33030009269714355 Elapsed (after compilation) = 6.67572021484375e-06 衡量Numba JIT对您的代码的影响的一个好方法是使用timeit模块函数来执行时间,这些函数测量多次执行迭代,因此可以在第一次执行时适应编译时间。 作为旁注,如果编译时间成为问题,Numba JIT支持 编译函数的磁盘缓存,并且还具有Ahead-Of-Time编译模式。 1.1.5。它有多快? 假设Numba可以在nopython模式下运行,或者至少编译一些循环,它将针对您的特定CPU进行编译。加速因应用而异,但可以是一到两个数量级。Numba有一个 性能指南,涵盖了获得额外性能的常用选项。 1.1.6。Numba如何运作? Numba读取装饰函数的Python字节码,并将其与有关函数输入参数类型的信息相结合。它分析并优化您的代码,最后使用LLVM编译器库生成函数的机器代码版本,根据您的CPU功能量身定制。每次调用函数时都会使用此编译版本。 1.1.7。其他感兴趣的东西: Numba有相当多的装饰,我们看到@jit和@njit,但也有: @vectorize- 生成NumPy ufunc(ufunc支持所有方法)。文件在这里。 @guvectorize- 产生NumPy广义ufuncs。 文件在这里。 @stencil - 将函数声明为类似模板操作的内核。 文件在这里。 @jitclass - 对于jit感知类。文件在这里。 @cfunc - 声明一个函数用作本机回调(从C / C ++等调用)。文件在这里。 @overload- 注册您自己的函数实现,以便在nopython模式下使用,例如@overload(scipy.special.j0)。 文件在这里。 一些装饰者提供额外选项: parallel = True- 启用功能的 自动并行化。 fastmath = True- 为该功能启用快速数学行为。 ctypes / cffi / cython互操作性: cffi- 模式支持调用CFFI函数nopython。 ctypes- 模式支持调用ctypes包装函数nopython。。 Cython导出的函数是可调用的。 1.1.7.1。GPU目标: Numba可以针对Nvidia CUDA和(实验性)AMD ROC GPU。您可以使用纯Python编写内核,让Numba处理计算和数据移动(或明确地执行此操作)。单击关于CUDA或ROC的 Numba文档 。 示例:接下来我们写一段简单的代码,来计算一下执行时间: 示例1:不使用numba的: import time def num(): arr = [] for i in range(10000000): arr.append(i) stime = time.time() num() etime = time.time() - stime print(arr) print('用时:{}秒'.format(etime)) 示例输出时间: 用时:1.4500024318695068秒 示例2:使用numba @jit import time from numba import jit @jit def num(): arr = [] for i in range(10000000): arr.append(i) stime = time.time() num() etime = time.time() - stime print(arr) print('用时:{}秒'.format(etime)) 示例输出: 用时:0.5530002117156982秒 结论: 上述两个示例代码,一个使用了numba,另一个没有使用numba;可以看出使用numba @jit装饰后,时间明显快了很多倍。 这只是一个简单示例;对于复杂计算提高速度更明显。

天枢2020 2020-03-13 18:38:04 0 浏览量 回答数 0

回答

PHP面试干货 1、进程和线程 进程和线程都是由操作系统所体会的程序运行的基本单元,系统利用该基本单元实现系统对应用的并发性。进程和线程的区别在于: 简而言之,一个程序至少有一个进程,一个进程至少有一个线程. 线程的划分尺度小于进程,使得多线程程序的并发性高。 另外,进程在执行过程中拥有独立的内存单元,而多个线程共享内存,从而极大地提高了程序的运行效率。 线程在执行过程中与进程还是有区别的。每个独立的线程有一个程序运行的入口、顺序执行序列和程序的出口。但是线程不能够独立执行,必须依存在应用程序中,由应用程序提供多个线程执行控制。 从逻辑角度来看,多线程的意义在于一个应用程序中,有多个执行部分可以同时执行。但操作系统并没有将多个线程看做多个独立的应用,来实现进程的调度和管理以及资源分配。这就是进程和线程的重要区别。 2、apache默认使用进程管理还是线程管理?如何判断并设置最大连接数? 一个进程可以开多个线程 默认是进程管理 默认有一个主进程 Linux: ps -aux | grep httpd | more 一个子进程代表一个用户的连接 Conf/extra/httpd-mpm.conf 多路功能模块 http -l 查询当前apache处于什么模式下 3、单例模式 单例模式需求:只能实例化产生一个对象 如何实现: 私有化构造函数 禁止克隆对象 提供一个访问这个实例的公共的静态方法(通常为getInstance方法),从而返回唯一对象 需要一个保存类的静态属性 class demo { private static $MyObject; //保存对象的静态属性 private function __construct(){ //私有化构造函数 } private function __clone(){ //禁止克隆 } public static function getInstance(){ if(! (self::$MyObject instanceof self)){ self::$MyObject = new self; } return self::$MyObject; } } 4、安装完Apache后,在http.conf中配置加载PHP文件以Apache模块的方式安装PHP,在文件http.conf中首先要用语句LoadModule php5_module "e:/php/php5apache2.dll"动态装载PHP模块,然后再用语句AddType application/x-httpd-php .php 使得Apache把所有扩展名为PHP的文件都作为PHP脚本处理 5、debug_backtrace()函数能返回脚本里的任意行中调用的函数的名称。该函数同时还经常被用在调试中,用来判断错误是如何发生的 function one($str1, $str2) { two("Glenn", "Quagmire"); } function two($str1, $str2) { three("Cleveland", "Brown"); } function three($str1, $str2) { print_r(debug_backtrace()); } one("Peter", "Griffin"); Array ( [0] => Array ( [file] => D:\www\test\result.php [line] => 9 [function] => three [args] => Array ( [0] => Cleveland [1] => Brown ) ) [1] => Array ( [file] => D:\www\test\result.php [line] => 5 [function] => two [args] => Array ( [0] => Glenn [1] => Quagmire ) ) [2] => Array ( [file] => D:\www\test\result.php [line] => 16 [function] => one [args] => Array ( [0] => Peter [1] => Griffin ) ) ) 6、输出用户的IP地址,并且判断用户的IP地址是否在192.168.1.100 — 192.168.1.150之间 echo $ip=getenv('REMOTE_ADDR'); $ip=str_replace('.','',$ip); if($ip<1921681150 && $ip>1921681100) { echo 'ip在192.168.1.100—–192.168.1.150之间'; } else { echo 'ip不在192.168.1.100—–192.168.1.150之间'; } 7、请将2维数组按照name的长度进行重新排序,按照顺序将id赋值 $tarray = array( array('id' => 0, 'name' => '123'), array('id' => 0, 'name' => '1234'), array('id' => 0, 'name' => '1235'), array('id' => 0, 'name' => '12356'), array('id' => 0, 'name' => '123abc') ); foreach($tarray as $key=>$val) { $c[]=$val['name']; } function aa($a,$b) { if(strlen($a)==strlen($b)) return 0; return strlen($a)>strlen($b)?-1:1; } usort($c,'aa'); $len=count($c); for($i=0;$i<$len;$i++) { $t[$i]['id']=$i+1; $t[$i]['name']=$c[$i]; } print_r($t); 8、表单数据提交方式POST和GET的区别,URL地址传递的数据最大长度是多少? POST方式提交数据用户不可见,是数据更安全,最大长度不受限制,而GET方式传值在URL地址可以看到,相对不安全,对大长度是2048字节。 9、SESSION和COOKIE的作用和区别,SESSION信息的存储方式,如何进行遍历 SESSION和COOKIE都能够使值在页面之间进行传递,SESSION存储在服务器端,数据更安全,COOKIE保存在客户端,用户使用手段可以进行修改,SESSION依赖于COOKIE进行传递的。Session遍历使用$_SESSION[]取值,cookie遍历使用$_COOKIE[]取值。 10、什么是数据库索引,主键索引,唯一索引的区别,索引的缺点是什么 索引用来快速地寻找那些具有特定值的记录。 主键索引和唯一索引的区别:主键是一种唯一性索引,但它必须指定为“PRIMARY KEY”,每个表只能有一个主键。唯一索引索引列的所有值都只能出现一次,即必须唯一。 索引的缺点: 1、创建索引和维护索引要耗费时间,这种时间随着数据量的增加而增加。 2、索引需要占用物理空间,除了数据表占数据空间之外,每一个索引还要占一定的物理空间,如果要建立聚簇索引,需要的空间就会更大。 3、当对表中的数据进行增加、删除、修改的时候,索引也要动态的维护,这样就降低了数据的维护速度。 11、数据库设计时,常遇到的性能瓶颈有哪些,常有的解决方案 瓶颈主要有: 1、磁盘搜索 优化方法是:将数据分布在多个磁盘上 2、磁盘读/写 优化方法是:从多个磁盘并行读写。 3、CPU周期 优化方法:扩充内存 4、内存带宽 12、include和require区别 include引入文件的时候,如果碰到错误,会给出提示,并继续运行下边的代码。 require引入文件的时候,如果碰到错误,会给出提示,并停止运行下边的代码。 13、文件上传时设计到点 和文件上传有关的php.ini配置选项(File Uploads): file_uploads=On/Off:文件是否允许上传 upload_max_filesize上传文件时,单个文件的最大大小 post_max_size:提交表单时,整个post表单的最大大小 max_file_uploads =20上传文件的个数 内存占用,脚本最大执行时间也间接影响到文件的上传 14、header常见状态 //200 正常状态 header('HTTP/1.1 200 OK'); // 301 永久重定向,记得在后面要加重定向地址 Location:$url header('HTTP/1.1 301 Moved Permanently'); // 重定向,其实就是302 暂时重定向 header('Location: http://www.maiyoule.com/'); // 设置页面304 没有修改 header('HTTP/1.1 304 Not Modified'); // 显示登录框, header('HTTP/1.1 401 Unauthorized'); header('WWW-Authenticate: Basic realm="登录信息"'); echo '显示的信息!'; // 403 禁止访问 header('HTTP/1.1 403 Forbidden'); // 404 错误 header('HTTP/1.1 404 Not Found'); // 500 服务器错误 header('HTTP/1.1 500 Internal Server Error'); // 3秒后重定向指定地址(也就是刷新到新页面与 <meta http-equiv="refresh" content="10;http://www.maiyoule.com/ /> 相同) header('Refresh: 3; url=http://www.maiyoule.com/'); echo '10后跳转到http://www.maiyoule.com'; // 重写 X-Powered-By 值 header('X-Powered-By: PHP/5.3.0'); header('X-Powered-By: Brain/0.6b'); //设置上下文语言 header('Content-language: en'); // 设置页面最后修改时间(多用于防缓存) $time = time() - 60; //建议使用filetime函数来设置页面缓存时间 header('Last-Modified: '.gmdate('D, d M Y H:i:s', $time).' GMT'); // 设置内容长度 header('Content-Length: 39344'); // 设置头文件类型,可以用于流文件或者文件下载 header('Content-Type: application/octet-stream'); header('Content-Disposition: attachment; filename="example.zip"'); header('Content-Transfer-Encoding: binary'); readfile('example.zip');//读取文件到客户端 //禁用页面缓存 header('Cache-Control: no-cache, no-store, max-age=0, must-revalidate'); header('Expires: Mon, 26 Jul 1997 05:00:00 GMT'); header('Pragma: no-cache'); //设置页面头信息 header('Content-Type: text/html; charset=iso-8859-1'); header('Content-Type: text/html; charset=utf-8'); header('Content-Type: text/plain'); header('Content-Type: image/jpeg'); header('Content-Type: application/zip'); header('Content-Type: application/pdf'); header('Content-Type: audio/mpeg'); header('Content-Type: application/x-shockwave-flash'); //.... 至于Content-Type 的值 可以去查查 w3c 的文档库,那里很丰富 15、ORM和ActiveRecord ORM:object relation mapping,即对象关系映射,简单的说就是对象模型和关系模型的一种映射。为什么要有这么一个映射?很简单,因为现在的开发语言基本都是oop的,但是传统的数据库却是关系型的。为了可以靠贴近面向对象开发,我们想要像操作对象一样操作数据库。还可以隔离底层数据库层,我们不需要关心我们使用的是mysql还是其他的关系型数据库 ActiveRecord也属于ORM层,由Rails最早提出,遵循标准的ORM模型:表映射到记录,记录映射到对象,字段映射到对象属性。配合遵循的命名和配置惯例,能够很大程度的快速实现模型的操作,而且简洁易懂。 ActiveRecord的主要思想是: 1. 每一个数据库表对应创建一个类,类的每一个对象实例对应于数据库中表的一行记录;通常表的每个字段在类中都有相应的Field; 2. ActiveRecord同时负责把自己持久化,在ActiveRecord中封装了对数据库的访问,即CURD;; 3. ActiveRecord是一种领域模型(Domain Model),封装了部分业务逻辑; ActiveRecord比较适用于: 1. 业务逻辑比较简单,当你的类基本上和数据库中的表一一对应时, ActiveRecord是非常方便的,即你的业务逻辑大多数是对单表操作; 2. 当发生跨表的操作时, 往往会配合使用事务脚本(Transaction Script),把跨表事务提升到事务脚本中; 3. ActiveRecord最大优点是简单, 直观。 一个类就包括了数据访问和业务逻辑. 如果配合代码生成器使用就更方便了; 这些优点使ActiveRecord特别适合WEB快速开发。 16、斐波那契方法,也就是1 1 2 3 5 8 ……,这里给出两种方法,大家可以对比下,看看哪种快,以及为什么 function fibonacci($n){ if($n == 0){ return 0; } if($n == 1){ return 1; } return fibonacci($n-1)+fibonacci($n-2); } function fibonacci($n){ for($i=0; $i<$n; $i++){ $r[] = $i<2 ? 1 : $r[$i-1]+$r[$i-2]; } return $r[--$i]; } 17、约瑟夫环,也就是常见的数猴子,n只猴子围成一圈,每只猴子下面标了编号,从1开始数起,数到m那么第m只猴子便退出,依次类推,每数到m,那么那个位置的猴子退出,那么最后剩下的猴子下的编号是啥。 function yuesefu($n,$m) { $r=0; for($i=2; $i<=$n; $i++) { $r=($r+$m)%$i; } return $r+1; } 18、冒泡排序,大致是临近的数字两两进行比较,按照从小到大或者从大到小的顺序进行交换,这样一趟过去后,最大或最小的数字被交换到了最后一位,然后再从头开始进行两两比较交换,直到倒数第二位时结束 function bubbleSort($arr){ for($i=0, $len=count($arr); $i<$len; $i++){ for($j=0; $j<$len; $j++){ if($arr[$i]<$arr[$j]){ $tmp = $arr[$j]; $arr[$j] = $arr[$i]; $arr[$i] = $tmp; } } } return $arr; } 19、快速排序,也就是找出一个元素(理论上可以随便找一个)作为基准,然后对数组进行分区操作,使基准左边元素的值都不大于基准值,基准右边的元素值 都不小于基准值,如此作为基准的元素调整到排序后的正确位置。递归快速排序,将其他n-1个元素也调整到排序后的正确位置。最后每个元素都是在排序后的正 确位置,排序完成。所以快速排序算法的核心算法是分区操作,即如何调整基准的位置以及调整返回基准的最终位置以便分治递归。 function quickSort($arr){ $len = count($arr); if($len <=1){ return $arr; } $key = $arr[0]; $leftArr = $rightArr= array(); for($i=1; $i<$len; $i++){ if($arr[$i] <= $key){ $leftArr[] = $arr[$i]; } else{ $rightArr[] = $arr[$i]; } } $leftArr = quickSort($leftArr); $rightArr = quickSort($rightArr); return array_merge($leftArr, array($key), $rightArr); } 20、(递归的)列出目录下所有文件及目录,这里也有两种方法 function listDir($path){ $res = dir($path); while($file = $res->read()){ if($file == '.' || $file == '..'){ continue; } if(is_dir($path . '/' .$file)){ echo $path . '/' .$file . "\r\n"; listDir($path . '/' .$file); } else{ echo $path . '/' .$file . "\r\n"; } } $res->close(); } function listDir($path){ if(is_dir($path)){ if(FALSE !== ($res = opendir($path))){ while(FALSE !== ($file = readdir($res))){ if($file == '.' || $file == '..'){ continue; } $subPath = $path . '/' . $file; if(is_dir($subPath)){ echo $subPath . "\r\n"; listDir($subPath); } else{ echo $subPath . "\r\n"; } } } } } 21、找出相对的目录,比如/a/b/c/d/e.php相对于/a/b/13/34/c.php是/c/d/ function ralativePath($a, $b){ $a = explode('/', dirname($a)); $b = explode('/', dirname($b)); $c = '/'; foreach ($a as $k=> $v){ if($v != $b[$k]){ $c .= $v . '/'; } } echo $c; } 22、快速找出url中php后缀 function get_ext($url){ $data = parse_url($url); return pathinfo($data['path'], PATHINFO_EXTENSION); } 23、正则题,使用正则抓取网页,以网页meta为utf8为准,若是抓取的网页编码为big5之类的,需要转化为utf8再收录 function preg_meta($meta){ $replacement = "\\1utf8\\6\\7"; $pattern = '#(<meta\s+http-equiv=(\'|"|)Content-Type(\'|"|)\s+content=(\'|"|)text/html; charset=)(\w+)(\'|"|)(>)#i'; return preg_replace($pattern, $replacement, $meta); } echo preg_meta("<meta http-equiv=Content-Type content='text/html; charset=big5'><META http-equiv=\"Content-Type\" content='text/html; charset=big5'>"); 24、不用php的反转函数倒序输出字符串,如abc,反序输出cba function revstring($str){ for($i=strlen($str)-1; $i>=0; $i--){ echo $str{$i}; } } revstring('abc'); 25、常见端口 TCP 21端口:FTP 文件传输服务 SSH 22端口:SSH连接linux服务器,通过SSH连接可以远程管理Linux等设备 TCP 23端口:TELNET 终端仿真服务 TCP 25端口:SMTP 简单邮件传输服务 UDP 53端口:DNS 域名解析服务 TCP 80端口:HTTP 超文本传输服务 TCP 110端口:POP3 “邮局协议版本3”使用的端口 TCP 443端口:HTTPS 加密的超文本传输服务 TCP 1521端口:Oracle数据库服务 TCP 1863端口:MSN Messenger的文件传输功能所使用的端口 TCP 3389端口:Microsoft RDP 微软远程桌面使用的端口 TCP 5631端口:Symantec pcAnywhere 远程控制数据传输时使用的端口 UDP 5632端口:Symantec pcAnywhere 主控端扫描被控端时使用的端口 TCP 5000端口:MS SQL Server使用的端口 UDP 8000端口:腾讯QQ 26、linux常用的命令 top linux进程实时监控 ps 在Linux中是查看进程的命令。ps查看正处于Running的进程 mv 为文件或目录改名或将文件由一个目录移入另一个目录中。 find 查找文件 df 可显示所有文件系统对i节点和磁盘块的使用情况。 cat 打印文件类容 chmod 变更文件或目录的权限 chgrp 文件或目录的权限的掌控以拥有者及所诉群组来管理。可以使用chgrp指令取变更文件与目录所属群组 grep 是一种强大的文本搜索工具,它能使用正则表达式搜索文本,并把匹 配的行打印出来。 wc 为统计指定文件中的字节数、字数、行数,并将统计结果显示输出 27、对于大流量的网站,您采用什么样的方法来解决访问量问题 首先,确认服务器硬件是否足够支持当前的流量 其次,优化数据库访问。 第三,禁止外部的盗链。 第四,控制大文件的下载。 第五,使用不同主机分流主要流量 第六,使用流量分析统计软件 28、$_SERVER常用的字段 $_SERVER['PHP_SELF'] #当前正在执行脚本的文件名 $_SERVER['SERVER_NAME'] #当前运行脚本所在服务器主机的名称 $_SERVER['REQUEST_METHOD'] #访问页面时的请求方法。例如:“GET”、“HEAD”,“POST”,“PUT” $_SERVER['QUERY_STRING'] #查询(query)的字符串 $_SERVER['HTTP_HOST'] #当前请求的 Host: 头部的内容 $_SERVER['HTTP_REFERER'] #链接到当前页面的前一页面的 URL 地址 $_SERVER['REMOTE_ADDR'] #正在浏览当前页面用户的 IP 地址 $_SERVER['REMOTE_HOST'] #正在浏览当前页面用户的主机名 $_SERVER['SCRIPT_FILENAME'] #当前执行脚本的绝对路径名 $_SERVER['SCRIPT_NAME'] #包含当前脚本的路径。这在页面需要指向自己时非常有用 $_SERVER['REQUEST_URI'] #访问此页面所需的 URI。例如,“/index.html” 29、安装php扩展 进入扩展的目录 phpize命令得到configure文件 ./configure --with-php-config=/usr/local/php/bin/php-config make & make install 在php.ini中加入扩展名称.so 重启web服务器(nginx/apache) 30、php-fpm与nginx PHP-FPM也是一个第三方的FastCGI进程管理器,它是作为PHP的一个补丁来开发的,在安装的时候也需要和PHP源码一起编译,也就是说PHP-FPM被编译到PHP内核中,因此在处理性能方面更加优秀;同时它在处理高并发方面也比spawn-fcgi引擎好很多,因此,推荐Nginx+PHP/PHP-FPM这个组合对PHP进行解析。 FastCGI 的主要优点是把动态语言和HTTP Server分离开来,所以Nginx与PHP/PHP-FPM经常被部署在不同的服务器上,以分担前端Nginx服务器的压力,使Nginx专一处理静态请求和转发动态请求,而PHP/PHP-FPM服务器专一解析PHP动态请求 #fastcgi FastCGI是一个可伸缩地、高速地在HTTP server和动态脚本语言间通信的接口。多数流行的HTTP server都支持FastCGI,包括Apache、Nginx和lighttpd等,同时,FastCGI也被许多脚本语言所支持,其中就有PHP。 FastCGI是从CGI发展改进而来的。传统CGI接口方式的主要缺点是性能很差,因为每次HTTP服务器遇到动态程序时都需要重新启动脚本解析器来执行解析,然后结果被返回给HTTP服务器。这在处理高并发访问时,几乎是不可用的。另外传统的CGI接口方式安全性也很差,现在已经很少被使用了。 FastCGI接口方式采用C/S结构,可以将HTTP服务器和脚本解析服务器分开,同时在脚本解析服务器上启动一个或者多个脚本解析守护进程。当HTTP服务器每次遇到动态程序时,可以将其直接交付给FastCGI进程来执行,然后将得到的结果返回给浏览器。这种方式可以让HTTP服务器专一地处理静态请求或者将动态脚本服务器的结果返回给客户端,这在很大程度上提高了整个应用系统的性能。 Nginx+FastCGI运行原理 Nginx不支持对外部程序的直接调用或者解析,所有的外部程序(包括PHP)必须通过FastCGI接口来调用。FastCGI接口在Linux下是socket,(这个socket可以是文件socket,也可以是ip socket)。为了调用CGI程序,还需要一个FastCGI的wrapper(wrapper可以理解为用于启动另一个程序的程序),这个wrapper绑定在某个固定socket上,如端口或者文件socket。当Nginx将CGI请求发送给这个socket的时候,通过FastCGI接口,wrapper接纳到请求,然后派生出一个新的线程,这个线程调用解释器或者外部程序处理脚本并读取返回数据;接着,wrapper再将返回的数据通过FastCGI接口,沿着固定的socket传递给Nginx;最后,Nginx将返回的数据发送给客户端,这就是Nginx+FastCGI的整个运作过程。 31、ajax全称“Asynchronous Javascript And XML”(异步JavaScript和XML)

小川游鱼 2019-12-02 01:41:29 0 浏览量 回答数 0

回答

PHP面试干货 1、进程和线程 进程和线程都是由操作系统所体会的程序运行的基本单元,系统利用该基本单元实现系统对应用的并发性。进程和线程的区别在于: 简而言之,一个程序至少有一个进程,一个进程至少有一个线程. 线程的划分尺度小于进程,使得多线程程序的并发性高。 另外,进程在执行过程中拥有独立的内存单元,而多个线程共享内存,从而极大地提高了程序的运行效率。 线程在执行过程中与进程还是有区别的。每个独立的线程有一个程序运行的入口、顺序执行序列和程序的出口。但是线程不能够独立执行,必须依存在应用程序中,由应用程序提供多个线程执行控制。 从逻辑角度来看,多线程的意义在于一个应用程序中,有多个执行部分可以同时执行。但操作系统并没有将多个线程看做多个独立的应用,来实现进程的调度和管理以及资源分配。这就是进程和线程的重要区别。 2、apache默认使用进程管理还是线程管理?如何判断并设置最大连接数? 一个进程可以开多个线程 默认是进程管理 默认有一个主进程 Linux: ps -aux | grep httpd | more 一个子进程代表一个用户的连接 Conf/extra/httpd-mpm.conf 多路功能模块 http -l 查询当前apache处于什么模式下 3、单例模式 单例模式需求:只能实例化产生一个对象 如何实现: 私有化构造函数 禁止克隆对象 提供一个访问这个实例的公共的静态方法(通常为getInstance方法),从而返回唯一对象 需要一个保存类的静态属性 class demo { private static $MyObject; //保存对象的静态属性 private function __construct(){ //私有化构造函数 } private function __clone(){ //禁止克隆 } public static function getInstance(){ if(! (self::$MyObject instanceof self)){ self::$MyObject = new self; } return self::$MyObject; } } 4、安装完Apache后,在http.conf中配置加载PHP文件以Apache模块的方式安装PHP,在文件http.conf中首先要用语句LoadModule php5_module "e:/php/php5apache2.dll"动态装载PHP模块,然后再用语句AddType application/x-httpd-php .php 使得Apache把所有扩展名为PHP的文件都作为PHP脚本处理 5、debug_backtrace()函数能返回脚本里的任意行中调用的函数的名称。该函数同时还经常被用在调试中,用来判断错误是如何发生的 function one($str1, $str2) { two("Glenn", "Quagmire"); } function two($str1, $str2) { three("Cleveland", "Brown"); } function three($str1, $str2) { print_r(debug_backtrace()); } one("Peter", "Griffin"); Array ( [0] => Array ( [file] => D:\www\test\result.php [line] => 9 [function] => three [args] => Array ( [0] => Cleveland [1] => Brown ) ) [1] => Array ( [file] => D:\www\test\result.php [line] => 5 [function] => two [args] => Array ( [0] => Glenn [1] => Quagmire ) ) [2] => Array ( [file] => D:\www\test\result.php [line] => 16 [function] => one [args] => Array ( [0] => Peter [1] => Griffin ) ) ) 6、输出用户的IP地址,并且判断用户的IP地址是否在192.168.1.100 — 192.168.1.150之间 echo $ip=getenv('REMOTE_ADDR'); $ip=str_replace('.','',$ip); if($ip<1921681150 && $ip>1921681100) { echo 'ip在192.168.1.100—–192.168.1.150之间'; } else { echo 'ip不在192.168.1.100—–192.168.1.150之间'; } 7、请将2维数组按照name的长度进行重新排序,按照顺序将id赋值 $tarray = array( array('id' => 0, 'name' => '123'), array('id' => 0, 'name' => '1234'), array('id' => 0, 'name' => '1235'), array('id' => 0, 'name' => '12356'), array('id' => 0, 'name' => '123abc') ); foreach($tarray as $key=>$val) { $c[]=$val['name']; } function aa($a,$b) { if(strlen($a)==strlen($b)) return 0; return strlen($a)>strlen($b)?-1:1; } usort($c,'aa'); $len=count($c); for($i=0;$i<$len;$i++) { $t[$i]['id']=$i+1; $t[$i]['name']=$c[$i]; } print_r($t); 8、表单数据提交方式POST和GET的区别,URL地址传递的数据最大长度是多少? POST方式提交数据用户不可见,是数据更安全,最大长度不受限制,而GET方式传值在URL地址可以看到,相对不安全,对大长度是2048字节。 9、SESSION和COOKIE的作用和区别,SESSION信息的存储方式,如何进行遍历 SESSION和COOKIE都能够使值在页面之间进行传递,SESSION存储在服务器端,数据更安全,COOKIE保存在客户端,用户使用手段可以进行修改,SESSION依赖于COOKIE进行传递的。Session遍历使用$_SESSION[]取值,cookie遍历使用$_COOKIE[]取值。 10、什么是数据库索引,主键索引,唯一索引的区别,索引的缺点是什么 索引用来快速地寻找那些具有特定值的记录。 主键索引和唯一索引的区别:主键是一种唯一性索引,但它必须指定为“PRIMARY KEY”,每个表只能有一个主键。唯一索引索引列的所有值都只能出现一次,即必须唯一。 索引的缺点: 1、创建索引和维护索引要耗费时间,这种时间随着数据量的增加而增加。 2、索引需要占用物理空间,除了数据表占数据空间之外,每一个索引还要占一定的物理空间,如果要建立聚簇索引,需要的空间就会更大。 3、当对表中的数据进行增加、删除、修改的时候,索引也要动态的维护,这样就降低了数据的维护速度。 11、数据库设计时,常遇到的性能瓶颈有哪些,常有的解决方案 瓶颈主要有: 1、磁盘搜索 优化方法是:将数据分布在多个磁盘上 2、磁盘读/写 优化方法是:从多个磁盘并行读写。 3、CPU周期 优化方法:扩充内存 4、内存带宽 12、include和require区别 include引入文件的时候,如果碰到错误,会给出提示,并继续运行下边的代码。 require引入文件的时候,如果碰到错误,会给出提示,并停止运行下边的代码。 13、文件上传时设计到点 和文件上传有关的php.ini配置选项(File Uploads): file_uploads=On/Off:文件是否允许上传 upload_max_filesize上传文件时,单个文件的最大大小 post_max_size:提交表单时,整个post表单的最大大小 max_file_uploads =20上传文件的个数 内存占用,脚本最大执行时间也间接影响到文件的上传 14、header常见状态 //200 正常状态 header('HTTP/1.1 200 OK'); // 301 永久重定向,记得在后面要加重定向地址 Location:$url header('HTTP/1.1 301 Moved Permanently'); // 重定向,其实就是302 暂时重定向 header('Location: http://www.maiyoule.com/'); // 设置页面304 没有修改 header('HTTP/1.1 304 Not Modified'); // 显示登录框, header('HTTP/1.1 401 Unauthorized'); header('WWW-Authenticate: Basic realm="登录信息"'); echo '显示的信息!'; // 403 禁止访问 header('HTTP/1.1 403 Forbidden'); // 404 错误 header('HTTP/1.1 404 Not Found'); // 500 服务器错误 header('HTTP/1.1 500 Internal Server Error'); // 3秒后重定向指定地址(也就是刷新到新页面与 <meta http-equiv="refresh" content="10;http://www.maiyoule.com/ /> 相同) header('Refresh: 3; url=http://www.maiyoule.com/'); echo '10后跳转到http://www.maiyoule.com'; // 重写 X-Powered-By 值 header('X-Powered-By: PHP/5.3.0'); header('X-Powered-By: Brain/0.6b'); //设置上下文语言 header('Content-language: en'); // 设置页面最后修改时间(多用于防缓存) $time = time() - 60; //建议使用filetime函数来设置页面缓存时间 header('Last-Modified: '.gmdate('D, d M Y H:i:s', $time).' GMT'); // 设置内容长度 header('Content-Length: 39344'); // 设置头文件类型,可以用于流文件或者文件下载 header('Content-Type: application/octet-stream'); header('Content-Disposition: attachment; filename="example.zip"'); header('Content-Transfer-Encoding: binary'); readfile('example.zip');//读取文件到客户端 //禁用页面缓存 header('Cache-Control: no-cache, no-store, max-age=0, must-revalidate'); header('Expires: Mon, 26 Jul 1997 05:00:00 GMT'); header('Pragma: no-cache'); //设置页面头信息 header('Content-Type: text/html; charset=iso-8859-1'); header('Content-Type: text/html; charset=utf-8'); header('Content-Type: text/plain'); header('Content-Type: image/jpeg'); header('Content-Type: application/zip'); header('Content-Type: application/pdf'); header('Content-Type: audio/mpeg'); header('Content-Type: application/x-shockwave-flash'); //.... 至于Content-Type 的值 可以去查查 w3c 的文档库,那里很丰富 15、ORM和ActiveRecord ORM:object relation mapping,即对象关系映射,简单的说就是对象模型和关系模型的一种映射。为什么要有这么一个映射?很简单,因为现在的开发语言基本都是oop的,但是传统的数据库却是关系型的。为了可以靠贴近面向对象开发,我们想要像操作对象一样操作数据库。还可以隔离底层数据库层,我们不需要关心我们使用的是mysql还是其他的关系型数据库 ActiveRecord也属于ORM层,由Rails最早提出,遵循标准的ORM模型:表映射到记录,记录映射到对象,字段映射到对象属性。配合遵循的命名和配置惯例,能够很大程度的快速实现模型的操作,而且简洁易懂。 ActiveRecord的主要思想是: 1. 每一个数据库表对应创建一个类,类的每一个对象实例对应于数据库中表的一行记录;通常表的每个字段在类中都有相应的Field; 2. ActiveRecord同时负责把自己持久化,在ActiveRecord中封装了对数据库的访问,即CURD;; 3. ActiveRecord是一种领域模型(Domain Model),封装了部分业务逻辑; ActiveRecord比较适用于: 1. 业务逻辑比较简单,当你的类基本上和数据库中的表一一对应时, ActiveRecord是非常方便的,即你的业务逻辑大多数是对单表操作; 2. 当发生跨表的操作时, 往往会配合使用事务脚本(Transaction Script),把跨表事务提升到事务脚本中; 3. ActiveRecord最大优点是简单, 直观。 一个类就包括了数据访问和业务逻辑. 如果配合代码生成器使用就更方便了; 这些优点使ActiveRecord特别适合WEB快速开发。 16、斐波那契方法,也就是1 1 2 3 5 8 ……,这里给出两种方法,大家可以对比下,看看哪种快,以及为什么 function fibonacci($n){ if($n == 0){ return 0; } if($n == 1){ return 1; } return fibonacci($n-1)+fibonacci($n-2); } function fibonacci($n){ for($i=0; $i<$n; $i++){ $r[] = $i<2 ? 1 : $r[$i-1]+$r[$i-2]; } return $r[--$i]; } 17、约瑟夫环,也就是常见的数猴子,n只猴子围成一圈,每只猴子下面标了编号,从1开始数起,数到m那么第m只猴子便退出,依次类推,每数到m,那么那个位置的猴子退出,那么最后剩下的猴子下的编号是啥。 function yuesefu($n,$m) { $r=0; for($i=2; $i<=$n; $i++) { $r=($r+$m)%$i; } return $r+1; } 18、冒泡排序,大致是临近的数字两两进行比较,按照从小到大或者从大到小的顺序进行交换,这样一趟过去后,最大或最小的数字被交换到了最后一位,然后再从头开始进行两两比较交换,直到倒数第二位时结束 function bubbleSort($arr){ for($i=0, $len=count($arr); $i<$len; $i++){ for($j=0; $j<$len; $j++){ if($arr[$i]<$arr[$j]){ $tmp = $arr[$j]; $arr[$j] = $arr[$i]; $arr[$i] = $tmp; } } } return $arr; } 19、快速排序,也就是找出一个元素(理论上可以随便找一个)作为基准,然后对数组进行分区操作,使基准左边元素的值都不大于基准值,基准右边的元素值 都不小于基准值,如此作为基准的元素调整到排序后的正确位置。递归快速排序,将其他n-1个元素也调整到排序后的正确位置。最后每个元素都是在排序后的正 确位置,排序完成。所以快速排序算法的核心算法是分区操作,即如何调整基准的位置以及调整返回基准的最终位置以便分治递归。 function quickSort($arr){ $len = count($arr); if($len <=1){ return $arr; } $key = $arr[0]; $leftArr = $rightArr= array(); for($i=1; $i<$len; $i++){ if($arr[$i] <= $key){ $leftArr[] = $arr[$i]; } else{ $rightArr[] = $arr[$i]; } } $leftArr = quickSort($leftArr); $rightArr = quickSort($rightArr); return array_merge($leftArr, array($key), $rightArr); } 20、(递归的)列出目录下所有文件及目录,这里也有两种方法 function listDir($path){ $res = dir($path); while($file = $res->read()){ if($file == '.' || $file == '..'){ continue; } if(is_dir($path . '/' .$file)){ echo $path . '/' .$file . "\r\n"; listDir($path . '/' .$file); } else{ echo $path . '/' .$file . "\r\n"; } } $res->close(); } function listDir($path){ if(is_dir($path)){ if(FALSE !== ($res = opendir($path))){ while(FALSE !== ($file = readdir($res))){ if($file == '.' || $file == '..'){ continue; } $subPath = $path . '/' . $file; if(is_dir($subPath)){ echo $subPath . "\r\n"; listDir($subPath); } else{ echo $subPath . "\r\n"; } } } } } 21、找出相对的目录,比如/a/b/c/d/e.php相对于/a/b/13/34/c.php是/c/d/ function ralativePath($a, $b){ $a = explode('/', dirname($a)); $b = explode('/', dirname($b)); $c = '/'; foreach ($a as $k=> $v){ if($v != $b[$k]){ $c .= $v . '/'; } } echo $c; } 22、快速找出url中php后缀 function get_ext($url){ $data = parse_url($url); return pathinfo($data['path'], PATHINFO_EXTENSION); } 23、正则题,使用正则抓取网页,以网页meta为utf8为准,若是抓取的网页编码为big5之类的,需要转化为utf8再收录 function preg_meta($meta){ $replacement = "\\1utf8\\6\\7"; $pattern = '#(<meta\s+http-equiv=(\'|"|)Content-Type(\'|"|)\s+content=(\'|"|)text/html; charset=)(\w+)(\'|"|)(>)#i'; return preg_replace($pattern, $replacement, $meta); } echo preg_meta("<meta http-equiv=Content-Type content='text/html; charset=big5'><META http-equiv=\"Content-Type\" content='text/html; charset=big5'>"); 24、不用php的反转函数倒序输出字符串,如abc,反序输出cba function revstring($str){ for($i=strlen($str)-1; $i>=0; $i--){ echo $str{$i}; } } revstring('abc'); 25、常见端口 TCP 21端口:FTP 文件传输服务 SSH 22端口:SSH连接linux服务器,通过SSH连接可以远程管理Linux等设备 TCP 23端口:TELNET 终端仿真服务 TCP 25端口:SMTP 简单邮件传输服务 UDP 53端口:DNS 域名解析服务 TCP 80端口:HTTP 超文本传输服务 TCP 110端口:POP3 “邮局协议版本3”使用的端口 TCP 443端口:HTTPS 加密的超文本传输服务 TCP 1521端口:Oracle数据库服务 TCP 1863端口:MSN Messenger的文件传输功能所使用的端口 TCP 3389端口:Microsoft RDP 微软远程桌面使用的端口 TCP 5631端口:Symantec pcAnywhere 远程控制数据传输时使用的端口 UDP 5632端口:Symantec pcAnywhere 主控端扫描被控端时使用的端口 TCP 5000端口:MS SQL Server使用的端口 UDP 8000端口:腾讯QQ 26、linux常用的命令 top linux进程实时监控 ps 在Linux中是查看进程的命令。ps查看正处于Running的进程 mv 为文件或目录改名或将文件由一个目录移入另一个目录中。 find 查找文件 df 可显示所有文件系统对i节点和磁盘块的使用情况。 cat 打印文件类容 chmod 变更文件或目录的权限 chgrp 文件或目录的权限的掌控以拥有者及所诉群组来管理。可以使用chgrp指令取变更文件与目录所属群组 grep 是一种强大的文本搜索工具,它能使用正则表达式搜索文本,并把匹 配的行打印出来。 wc 为统计指定文件中的字节数、字数、行数,并将统计结果显示输出 27、对于大流量的网站,您采用什么样的方法来解决访问量问题 首先,确认服务器硬件是否足够支持当前的流量 其次,优化数据库访问。 第三,禁止外部的盗链。 第四,控制大文件的下载。 第五,使用不同主机分流主要流量 第六,使用流量分析统计软件 28、$_SERVER常用的字段 $_SERVER['PHP_SELF'] #当前正在执行脚本的文件名 $_SERVER['SERVER_NAME'] #当前运行脚本所在服务器主机的名称 $_SERVER['REQUEST_METHOD'] #访问页面时的请求方法。例如:“GET”、“HEAD”,“POST”,“PUT” $_SERVER['QUERY_STRING'] #查询(query)的字符串 $_SERVER['HTTP_HOST'] #当前请求的 Host: 头部的内容 $_SERVER['HTTP_REFERER'] #链接到当前页面的前一页面的 URL 地址 $_SERVER['REMOTE_ADDR'] #正在浏览当前页面用户的 IP 地址 $_SERVER['REMOTE_HOST'] #正在浏览当前页面用户的主机名 $_SERVER['SCRIPT_FILENAME'] #当前执行脚本的绝对路径名 $_SERVER['SCRIPT_NAME'] #包含当前脚本的路径。这在页面需要指向自己时非常有用 $_SERVER['REQUEST_URI'] #访问此页面所需的 URI。例如,“/index.html” 29、安装php扩展 进入扩展的目录 phpize命令得到configure文件 ./configure --with-php-config=/usr/local/php/bin/php-config make & make install 在php.ini中加入扩展名称.so 重启web服务器(nginx/apache) 30、php-fpm与nginx PHP-FPM也是一个第三方的FastCGI进程管理器,它是作为PHP的一个补丁来开发的,在安装的时候也需要和PHP源码一起编译,也就是说PHP-FPM被编译到PHP内核中,因此在处理性能方面更加优秀;同时它在处理高并发方面也比spawn-fcgi引擎好很多,因此,推荐Nginx+PHP/PHP-FPM这个组合对PHP进行解析。 FastCGI 的主要优点是把动态语言和HTTP Server分离开来,所以Nginx与PHP/PHP-FPM经常被部署在不同的服务器上,以分担前端Nginx服务器的压力,使Nginx专一处理静态请求和转发动态请求,而PHP/PHP-FPM服务器专一解析PHP动态请求 #fastcgi FastCGI是一个可伸缩地、高速地在HTTP server和动态脚本语言间通信的接口。多数流行的HTTP server都支持FastCGI,包括Apache、Nginx和lighttpd等,同时,FastCGI也被许多脚本语言所支持,其中就有PHP。 FastCGI是从CGI发展改进而来的。传统CGI接口方式的主要缺点是性能很差,因为每次HTTP服务器遇到动态程序时都需要重新启动脚本解析器来执行解析,然后结果被返回给HTTP服务器。这在处理高并发访问时,几乎是不可用的。另外传统的CGI接口方式安全性也很差,现在已经很少被使用了。 FastCGI接口方式采用C/S结构,可以将HTTP服务器和脚本解析服务器分开,同时在脚本解析服务器上启动一个或者多个脚本解析守护进程。当HTTP服务器每次遇到动态程序时,可以将其直接交付给FastCGI进程来执行,然后将得到的结果返回给浏览器。这种方式可以让HTTP服务器专一地处理静态请求或者将动态脚本服务器的结果返回给客户端,这在很大程度上提高了整个应用系统的性能。 Nginx+FastCGI运行原理 Nginx不支持对外部程序的直接调用或者解析,所有的外部程序(包括PHP)必须通过FastCGI接口来调用。FastCGI接口在Linux下是socket,(这个socket可以是文件socket,也可以是ip socket)。为了调用CGI程序,还需要一个FastCGI的wrapper(wrapper可以理解为用于启动另一个程序的程序),这个wrapper绑定在某个固定socket上,如端口或者文件socket。当Nginx将CGI请求发送给这个socket的时候,通过FastCGI接口,wrapper接纳到请求,然后派生出一个新的线程,这个线程调用解释器或者外部程序处理脚本并读取返回数据;接着,wrapper再将返回的数据通过FastCGI接口,沿着固定的socket传递给Nginx;最后,Nginx将返回的数据发送给客户端,这就是Nginx+FastCGI的整个运作过程。 31、ajax全称“Asynchronous Javascript And XML”(异步JavaScript和XML)

小川游鱼 2019-12-02 01:41:29 0 浏览量 回答数 0

回答

散列表(Hash table,也叫哈希表),是根据关键码值(Key value)而直接进行访问的数据结构。也就是说,它通过把关键码值映射到表中一个位置来访问记录,以加快查找的速度。这个映射函数叫做散列函数,存放记录的数组叫做散列表。 [编辑本段]基本概念 * 若结构中存在关键字和K相等的记录,则必定在f(K)的存储位置上。由此,不需比较便可直接取得所查记录。称这个对应关系f为散列函数(Hash function),按这个思想建立的表为散列表。 * 对不同的关键字可能得到同一散列地址,即key1≠key2,而f(key1)=f(key2),这种现象称冲突。具有相同函数值的关键字对该散列函数来说称做同义词。综上所述,根据散列函数H(key)和处理冲突的方法将一组关键字映象到一个有限的连续的地址集(区间)上,并以关键字在地址集中的“象” 作为记录在表中的存储位置,这种表便称为散列表,这一映象过程称为散列造表或散列,所得的存储位置称散列地址。 * 若对于关键字集合中的任一个关键字,经散列函数映象到地址集合中任何一个地址的概率是相等的,则称此类散列函数为均匀散列函数(Uniform Hash function),这就是使关键字经过散列函数得到一个“随机的地址”,从而减少冲突。 [编辑本段]常用的构造散列函数的方法 散列函数能使对一个数据序列的访问过程更加迅速有效,通过散列函数,数据元素将被更快地定位ǐ 1. 直接寻址法:取关键字或关键字的某个线性函数值为散列地址。即H(key)=key或H(key) = a•key + b,其中a和b为常数(这种散列函数叫做自身函数) 2. 数字分析法 3. 平方取中法 4. 折叠法 5. 随机数法 6. 除留余数法:取关键字被某个不大于散列表表长m的数p除后所得的余数为散列地址。即 H(key) = key MOD p, p<=m。不仅可以对关键字直接取模,也可在折叠、平方取中等运算之后取模。对p的选择很重要,一般取素数或m,若p选的不好,容易产生同义词。 [编辑本段]处理冲突的方法 1. 开放寻址法:Hi=(H(key) + di) MOD m, i=1,2,…, k(k<=m-1),其中H(key)为散列函数,m为散列表长,di为增量序列,可有下列三种取法: 1. di=1,2,3,…, m-1,称线性探测再散列; 2. di=1^2, (-1)^2, 2^2,(-2)^2, (3)^2, …, ±(k)^2,(k<=m/2)称二次探测再散列; 3. di=伪随机数序列,称伪随机探测再散列。 == 2. 再散列法:Hi=RHi(key), i=1,2,…,k RHi均是不同的散列函数,即在同义词产生地址冲突时计算另一个散列函数地址,直到冲突不再发生,这种方法不易产生“聚集”,但增加了计算时间。 3. 链地址法(拉链法) 4. 建立一个公共溢出区 [编辑本段]查找的性能分析 散列表的查找过程基本上和造表过程相同。一些关键码可通过散列函数转换的地址直接找到,另一些关键码在散列函数得到的地址上产生了冲突,需要按处理冲突的方法进行查找。在介绍的三种处理冲突的方法中,产生冲突后的查找仍然是给定值与关键码进行比较的过程。所以,对散列表查找效率的量度,依然用平均查找长度来衡量。 查找过程中,关键码的比较次数,取决于产生冲突的多少,产生的冲突少,查找效率就高,产生的冲突多,查找效率就低。因此,影响产生冲突多少的因素,也就是影响查找效率的因素。影响产生冲突多少有以下三个因素: 1. 散列函数是否均匀; 2. 处理冲突的方法; 3. 散列表的装填因子。 散列表的装填因子定义为:α= 填入表中的元素个数 / 散列表的长度 α是散列表装满程度的标志因子。由于表长是定值,α与“填入表中的元素个数”成正比,所以,α越大,填入表中的元素较多,产生冲突的可能性就越大;α越小,填入表中的元素较少,产生冲突的可能性就越小。 实际上,散列表的平均查找长度是装填因子α的函数,只是不同处理冲突的方法有不同的函数。 了解了hash基本定义,就不能不提到一些著名的hash算法,MD5 和 SHA-1 可以说是目前应用最广泛的Hash算法,而它们都是以 MD4 为基础设计的。那么他们都是什么意思呢? 这里简单说一下: (1) MD4 MD4(RFC 1320)是 MIT 的 Ronald L. Rivest 在 1990 年设计的,MD 是 Message Digest 的缩写。它适用在32位字长的处理器上用高速软件实现--它是基于 32 位操作数的位操作来实现的。 (2) MD5 MD5(RFC 1321)是 Rivest 于1991年对MD4的改进版本。它对输入仍以512位分组,其输出是4个32位字的级联,与 MD4 相同。MD5比MD4来得复杂,并且速度较之要慢一点,但更安全,在抗分析和抗差分方面表现更好 (3) SHA-1 及其他 SHA1是由NIST NSA设计为同DSA一起使用的,它对长度小于264的输入,产生长度为160bit的散列值,因此抗穷举(brute-force)性更好。SHA-1 设计时基于和MD4相同原理,并且模仿了该算法。 那么这些Hash算法到底有什么用呢? Hash算法在信息安全方面的应用主要体现在以下的3个方面: (1) 文件校验 我们比较熟悉的校验算法有奇偶校验和CRC校验,这2种校验并没有抗数据篡改的能力,它们一定程度上能检测并纠正数据传输中的信道误码,但却不能防止对数据的恶意破坏。 MD5 Hash算法的"数字指纹"特性,使它成为目前应用最广泛的一种文件完整性校验和(Checksum)算法,不少Unix系统有提供计算md5 checksum的命令。 (2) 数字签名 Hash 算法也是现代密码体系中的一个重要组成部分。由于非对称算法的运算速度较慢,所以在数字签名协议中,单向散列函数扮演了一个重要的角色。 对 Hash 值,又称"数字摘要"进行数字签名,在统计上可以认为与对文件本身进行数字签名是等效的。而且这样的协议还有其他的优点。 (3) 鉴权协议 如下的鉴权协议又被称作挑战--认证模式:在传输信道是可被侦听,但不可被篡改的情况下,这是一种简单而安全的方法。 MD5、SHA1的破解 2004年8月17日,在美国加州圣芭芭拉召开的国际密码大会上,山东大学王小云教授在国际会议上首次宣布了她及她的研究小组近年来的研究成果——对MD5、HAVAL-128、MD4和RIPEMD等四个著名密码算法的破译结果。 次年二月宣布破解SHA-1密码。 [编辑本段]实际应用 以上就是一些关于hash以及其相关的一些基本预备知识。那么在emule里面他具体起到什么作用呢? 大家都知道emule是基于P2P (Peer-to-peer的缩写,指的是点对点的意思的软件), 它采用了"多源文件传输协议”(MFTP,the Multisource FileTransfer Protocol)。在协议中,定义了一系列传输、压缩和打包还有积分的标准,emule 对于每个文件都有md5-hash的算法设置,这使得该文件独一无二,并且在整个网络上都可以追踪得到。 什么是文件的hash值呢? MD5-Hash-文件的数字文摘通过Hash函数计算得到。不管文件长度如何,它的Hash函数计算结果是一个固定长度的数字。与加密算法不同,这一个Hash算法是一个不可逆的单向函数。采用安全性高的Hash算法,如MD5、SHA时,两个不同的文件几乎不可能得到相同的Hash结果。因此,一旦文件被修改,就可检测出来。 当我们的文件放到emule里面进行共享发布的时候,emule会根据hash算法自动生成这个文件的hash值,他就是这个文件唯一的身份标志,它包含了这个文件的基本信息,然后把它提交到所连接的服务器。当有他人想对这个文件提出下载请求的时候, 这个hash值可以让他人知道他正在下载的文件是不是就是他所想要的。尤其是在文件的其他属性被更改之后(如名称等)这个值就更显得重要。而且服务器还提供了,这个文件当前所在的用户的地址,端口等信息,这样emule就知道到哪里去下载了。 一般来讲我们要搜索一个文件,emule在得到了这个信息后,会向被添加的服务器发出请求,要求得到有相同hash值的文件。而服务器则返回持有这个文件的用户信息。这样我们的客户端就可以直接的和拥有那个文件的用户沟通,看看是不是可以从他那里下载所需的文件。 对于emule中文件的hash值是固定的,也是唯一的,它就相当于这个文件的信息摘要,无论这个文件在谁的机器上,他的hash值都是不变的,无论过了多长时间,这个值始终如一,当我们在进行文件的下载上传过程中,emule都是通过这个值来确定文件。 那么什么是userhash呢? 道理同上,当我们在第一次使用emule的时候,emule会自动生成一个值,这个值也是唯一的,它是我们在emule世界里面的标志,只要你不卸载,不删除config,你的userhash值也就永远不变,积分制度就是通过这个值在起作用,emule里面的积分保存,身份识别,都是使用这个值,而和你的id和你的用户名无关,你随便怎么改这些东西,你的userhash值都是不变的,这也充分保证了公平性。其实他也是一个信息摘要,只不过保存的不是文件信息,而是我们每个人的信息。 那么什么是hash文件呢? 我们经常在emule日志里面看到,emule正在hash文件,这里就是利用了hash算法的文件校验性这个功能了,文章前面已经说了一些这些功能,其实这部分是一个非常复杂的过程,目前在ftp,bt等软件里面都是用的这个基本原理,emule里面是采用文件分块传输,这样传输的每一块都要进行对比校验,如果错误则要进行重新下载,这期间这些相关信息写入met文件,直到整个任务完成,这个时候part文件进行重新命名,然后使用move命令,把它传送到incoming文件里面,然后met文件自动删除,所以我们有的时候会遇到hash文件失败,就是指的是met里面的信息出了错误不能够和part文件匹配,另外有的时候开机也要疯狂hash,有两种情况一种是你在第一次使用,这个时候要hash提取所有文件信息,还有一种情况就是上一次你非法关机,那么这个时候就是要进行排错校验了。 关于hash的算法研究,一直是信息科学里面的一个前沿,尤其在网络技术普及的今天,他的重要性越来越突出,其实我们每天在网上进行的信息交流安全验证,我们在使用的操作系统密钥原理,里面都有它的身影,特别对于那些研究信息安全有兴趣的朋友,这更是一个打开信息世界的钥匙,他在hack世界里面也是一个研究的焦点。 一般的线性表、树中,记录在结构中的相对位置是随机的即和记录的关键字之间不存在确定的关系,在结构中查找记录时需进行一系列和关键字的比较。这一类查找方法建立在“比较”的基础上,查找的效率与比较次数密切相关。理想的情况是能直接找到需要的记录,因此必须在记录的存储位置和它的关键字之间建立一确定的对应关系f,使每个关键字和结构中一个唯一的存储位置相对应。因而查找时,只需根据这个对应关系f找到给定值K的像f(K)。若结构中存在关键字和K相等的记录,则必定在f(K)的存储位置上,由此不需要进行比较便可直接取得所查记录。在此,称这个对应关系f为哈希函数,按这个思想建立的表为哈希表(又称为杂凑法或散列表)。 哈希表不可避免冲突(collision)现象:对不同的关键字可能得到同一哈希地址 即key1≠key2,而hash(key1)=hash(key2)。具有相同函数值的关键字对该哈希函数来说称为同义词(synonym)。 因此,在建造哈希表时不仅要设定一个好的哈希函数,而且要设定一种处理冲突的方法。可如下描述哈希表:根据设定的哈希函数H(key)和所选中的处理冲突的方法,将一组关键字映象到一个有限的、地址连续的地址集(区间)上并以关键字在地址集中的“象”作为相应记录在表中的存储位置,这种表被称为哈希表。 对于动态查找表而言,1) 表长不确定;2)在设计查找表时,只知道关键字所属范围,而不知道确切的关键字。因此,一般情况需建立一个函数关系,以f(key)作为关键字为key的录在表中的位置,通常称这个函数f(key)为哈希函数。(注意:这个函数并不一定是数学函数) 哈希函数是一个映象,即:将关键字的集合映射到某个地址集合上,它的设置很灵活,只要这个地址集合的大小不超出允许范围即可。 现实中哈希函数是需要构造的,并且构造的好才能使用的好。 用途:加密,解决冲突问题。。。。 用途很广,比特精灵中就使用了哈希函数,你可 以自己看看。 具体可以学习一下数据结构和算法的书。 [编辑本段]字符串哈希函数 (著名的ELFhash算法) int ELFhash(char *key) return h%MOD; }

晚来风急 2019-12-02 01:22:24 0 浏览量 回答数 0

回答

递归4—递归的弱点 之所以没有把这段归为算法的讨论,因为这里讨论的不在是算法,而只是讨论一下滥用递归的不好的一面。 递归的用法似乎是很容易的,但是递归还是有她的致命弱点,那就是如果运用不恰当,滥用递归,程序的运行效率会非常的低,低到什么程度,低到出乎你的想像。当然,平时的小程序是看不出什么的,但是一旦在大项目里滥用递归,效率问题将引起程序的实用性的大大降低。 例子:求1到200的自然数的和。 第一种做法: #include <stdio.h> void main() { int i; int sum=0; for(i=1;i<=200;i++) { sum+=i; } printf("%d\n",sum); } 该代码中使用变量2个,计算200次。再看下个代码: #include <stdio.h> int add(int i) { if(i==1) { return i; } else { return i+add(i-1); } } void main() { int i; int sum=0; sum=add(200); printf("%d\n",sum); } 但看add()函数,每次调用要声明一个变量,每次调用要计算一次,所以应该是200个变量,200次计算,对比一下想想,如果程序要求递归次数非常多的时候,而且类似与这种情况,我们还能用递归去做吗。这个时候宁愿麻烦点去考虑其他办法,也要尝试摆脱递归的干扰。 21:21 | 添加评论 | 固定链接 | 引用通告 (0) | 记录它 | 计算机与 Internet 程序算法5—递归3—递归的再次挖掘 递归的魅力就在于递归的代码,写出来实在是太简练了,而且能解决很多看起来似乎有规律但是又不是一下子能表达清楚的一些问题。思路清晰了,递归一写出来问题立即就解决了,给人一重感觉,递归这么好用。我们在此再更深的挖掘一下递归的用法。 之前再强调一点,也许有人会问,你前边的例子用递归似乎是更麻烦了。是,是麻烦了,因为为了方便理解,只能举一些容易理解的例子,一般等实际应用递归的时候,远远不是这种状态。 好了我们现在看一个数字的序列;有一组数的集合{1,2,4,7,11,16,22,29,37,46,56……}我故意多给几项,一般是只给前4项让你找规律的。序列给了,要求是求前50项的和。规律。有。还是没有。一看就象有,但是又看不出来,我多给了几项,应该很快看出来了,哦,原来每相邻的两项的差是个自然数排列,2-1=1,4-2=2,7-4=3,11-7=4,16-11=5…… 好了,把规律找出来了,一开始可能觉得没头绪,没问题,咱们把这个序列存放到一个数组总可以吧。那我们就声明一个数组,存放前50个数据,一个一个相加总可以了。于是有了下边的写法: #include <stdio.h> void main() { int i,a[50],sum=0; a[0]=1; for(i=1;i<50;i++) { a[i]=a[i-1]+i; } for(i=0;i<50;i++) { sum+=a[i]; } printf("%d\n",sum); } 好了,代码运行一下,结果出来了,正确不正确呢。自己测试吧,把50项改成1、2、3、4、5……项,试试前多少项是不是正确,虽然这不是正确的测试方法,但是的确是常用的测试方法。 等到这个代码已经完全理解了,完全明白了正个计算过程,我们就应该对这段代码进行改写优化了,毕竟这个代码还是不值得用一个数组的,那么我们尝试着只用变量去做一下: #include <stdio.h> void main() { int i; int number=1; int sum=0; for(i=0;i<50;i++) { number+=i; sum+=number; } printf("%d\n",sum); } 不知道我这样写是不是跨度大了点,但是我不准备详细解释了,很多东西需要你去认真分析的,所以很多东西如果不懂,自己想清楚比别人解释的效果会更好,因为别人讲只能让你理解,如果你自己去想,你就在理解的同时学会了思考。 这个代码写出来,不要继续看下去,先自己尝试着把这个题目用递归做一下看看自己能不能写出来,当然,递归并不是那么轻松就能使用的,有时候也是需要去细心设计的。如果做出来了,对比一下下边的代码,如果没有写出来,建议认真分析后边的代码,然后最好是能完全掌握,能自己随时把这行代码写出来: #include <stdio.h> int add(int n,int num,int i) { num+=i; if(i>=n-1) { return num; } else { return num+add(n,num,i+1); } } void main() { int sum; sum=add(50,1,0); /*50表示前50象项*/ printf("%d\n",sum); } 当然这个代码中的n只是一个参考变量,如果把if(i>=n-1)中的n该成50,那么就不需要这个n了,函数两个参数就可以了,这样写是为了修改方便。 20:28 | 添加评论 | 固定链接 | 引用通告 (0) | 记录它 | 计算机与 Internet 程序算法4—递归2—递归的魅力 两天没有再写下去,因为毕竟有时候会有点心情问题,有时候觉得心情不好,一下子什么东西都想不起来了,很多时候写一些东西是需要状态的,一旦状态有了,想的东西才能顺利的写出来,虽然有些东西写出来在别人看来很垃圾,但是起码自己觉得还是相当满意的,我写这个本来就没有多少技术含量,只是想给初学程序的人一些指引,加快他们对程序的领悟。 好了,言归正传,继续上次递归的讨论,看看递归的魅力所在。 有这样一个问题,说一个猴子和一堆苹果,猴子一天吃一半,然后再吃一个,10天后剩下一个了,也就是说吃了10次,剩下1个了。问原来一共有多少苹果。 当然我们的目的不是求出苹果的数量,而是寻求一种解决问题的方法,这个问题一出来,通常对程序掌握深度不一样的朋友对这个题会有不同的认识,首先介绍一种解决方法,这种人脑袋还是比较聪明的,思路非常的明确,也有可能语言工具掌握的也不错,代码写出来非常准确,先看一下代码再做评价吧: #include <stdio.h> void main() { int day=10; int apple; int i,j; for(i=1;;i++) { apple=i; for(j=0;j<day;j++) { if(apple%2==0&&apple>0) { apple/=2; apple--; } else { break; } } if(j==day&&apple==1) { printf("%d\n",i); return; } } } 程序的大概思路很明确,简单介绍一下,这种写法就是从一个苹果开始算起,for(i=1;;i++)的作用就是改变苹果的数量,如果1个符合条件,那就试试2个,然后3个、4个一直到适合为止,里边的for循环就是把每一次取得的苹果的数目进行计算,如果每次都能顺利的被2整除(也就是说每次都能保证猴子能正好吃一半),然后再减一一直到最后,如果最后苹果剩下是一个而且天数正好是10天,那么就输出一下苹果的数目,整个程序退出,如果看不明白的没关系,这个写法非常的不适用,我们叫写出这种算法的人傻X,虽然这种人脑袋也挺聪明,能写出一些新鲜的写法,但是又脏又臭,代码既不简练又不高效。 所以说,有时候有些人以为自己学的很好了,自己所做的一切都是最好的,这种想法是不正确的,也许有些初学者没有什么经验写出来的代码却更让人容易明白点,那么也是先看看代码: #include <stdio.h> void main() { int day[11]; int i; day[0]=1; for(i=1;i<11;i++) { day[i]=(day[i-1]+1)*2; } printf("%d\n",day[10]); } 代码不长,而且也恰当的应用了题目中的规律,不是说要吃一半然后再吃一个吗。那我用数组来存放每天苹果的数量,用day[0]表示最后一天的苹果数量,那就是剩下的一个,然后就是找规律了,什么规律。就是如果猴子不多吃一个的话,那就是正好吃了一半,也就是说猴子当天吃了之后剩余的苹果的数目加1个然后再乘以2就是前一天的数目了,这样一想这个题目就简单的多了,于是这个题用数组就轻松的做出来了。 那么这个代码究竟是不是已经很好了呢,我们注意到,这里边每个数组元素只用了一次并没有被重复使用,再这种情况下我们是不是可以用一种方法代替数组呢。于是就有了更优化的写法,这个写法似乎已经是相当简练了: #include <stdio.h> void main() { int apple=1; int i; for(i=0;i<10;i++) { apple=(apple+1)*2; } printf("%d\n",apple); } 代码写到这里已经把问题完全抽象化了,所以我们就应该站在数学的角度去分析了。也许我们就应该结束了讨论,但是偏偏这个时候,又来了递归,悄悄的通过美丽的调用显示了一下她的魅力: #include <stdio.h> int apple(int i) { if(i==0) { return 1; } else { return (apple(i-1)+1)*2; } } void main() { int i; i=apple(10); printf("%d\n",i); } 原理都还是一样的,但是写出来的格式已经完全变掉了,没有了for循环。假想一个复杂的问题远比这个问题复杂,而且没有固定循环次数,那么我们再使用循环虽然也能解决问题,但是可能面临循环难以设计、控制等问题,这个时候用递归可能就会让问题变的非常的清晰。 另外说一点,一般我这里的代码,并不是从最差到最好的,基本排列是从最差到最合适的代码(当然是本人认为最合适的,也许还有更好的,本人能力所限了),然后最后给出一种比较违反常规的代码,一般是不赞成用最后一种代码的,当然有时候最后一种代码也许是最好的选择,看情况吧。 20:25 | 添加评论 | 固定链接 | 引用通告 (0) | 记录它 | 计算机与 Internet 10月15日 程序算法3—递归1—递归小显威力 现在用C语言实现一个字符串的倒序输出,当然,方法也是很多的,但是如果程序中能有相对优化的方法或者简单明了易读的方法,那对你自己或者别人都是一种幸福。 第一种写法,这类写法既浪费内存又不实用,一般是刚学程序的才这样做,程序的结构很简单,利用的是数组: #include <stdio.h> void main() { char c[2000]; int i,length=0; for(i=0;i<2000;i++) { scanf("%c",&c[i]); if(c[i]=='\n') { break; } else { length++; } } for(i=length;i>0;i--) { printf("%c",c[i-1]); } printf("\n"); } 这段代码中的数组,声明大了浪费内存空间,声明小了又怕不够,所以写这种代码的人一般写完之后会祈祷,祈祷测试的人不要输入的太多,太多就不能完全显示了。 与其这么提心吊胆,于是又有人想出了第二种方法,终于解决了一些问题,而且完全实现了程序的实际要求,于是,这种人经过一番苦想,觉得问题终于可以解决了,这种方法看起来是一种很不错的方法。 #include <stdio.h> #include <malloc.h> void main() { int i; char *c; c=(char *)malloc(1*sizeof(char)); for(i=0;;i++) { *(c+i)=getchar(); if(*(c+i)=='\n') { *(c+i)='\0'; break; } else c=(char *)realloc(c,(i+2)*sizeof(char)); } for(--i;i>=0;i--) { putchar(*(c+i)); } printf("\n"); free(c); } 怎么样。不错,准确的应用内存,几乎没有浪费什么空间,这种方法也体现了一下指针的强大功能,写这个程序虽然不敢说这个人已经掌握了指针的应用,但是起码可以说他已经会用指针了。代码写出来,看起来已经有点美感。 但是也有一些人还是比较喜欢动脑筋的,经过一番思考,终于想出了第三种比较容易写的方法,也许有写初学者可能觉得有些难度,但是事实上这个东西一点都不难,如果稍微有点程序功底之后再看这段代码,应该是相当轻松。 #include <stdio.h> void run() { char c; c=getchar(); if(c!='\n') { run(); } else { return; } putchar(c); } void main() { run(); printf("\n"); } 写出的代码让人眼前一亮,哇。原来递归功能简单而又好用,那我们为什么不好好利用呢。但是递归也不一定就是最好的选择,因为有时候虽然递归用起来很方便,但是效率却不高,以后的讨论中还会详细说明。

一键天涯 2019-12-02 01:24:01 0 浏览量 回答数 0

问题

js页面优化技巧总结

小柒2012 2019-12-01 21:44:12 8286 浏览量 回答数 5

回答

本人学习数据结构时看到的不错的总结,共享一下了 文件有一组记录组成,记录有若干数据项组成,唯一标识记录的数据项称关键字; 排序是将文件按关键字的递增(减)顺序排列; 排序文件中有相同的关键字时,若排序后相对次序保持不变的称稳定排序,否则称不稳定排序; 在排序过程中,文件放在内存中处理不涉及数据的内、外存交换的称内部排序,反之称外部排序; 排序算法的基本操作:1)比较关键字的大小;2)改变指向记录的指针或移动记录本身。 评价排序方法的标准:1)执行时间;2)所需辅助空间,辅助空间为O(1)称就地排序;另要注意算法的复杂程度。 若关键字类型没有比较运算符,可事先定义宏或函数表示比较运算。 8.2插入排序 8.2.1直接插入排序 实现过程: void insertsort(seqlist R) { int i, j; for(i=2;i<=n;i++) if(R[i].key< R[i-1].key{ R[0]=R[i];j=i-1; do{R[j+1]=R[j];j--;} while(R[0].key R[j+1]=R[0]; } } 复制代码 算法中引入监视哨R[0]的作用是:1)保存 R[i] 复制代码 的副本;2)简化边界条件,防止循环下标越界。 关键字比较次数最大为(n+2)(n-1)/2;记录移动次数最大为(n+4)(n-1)/2; 算法的最好时间是O(n);最坏时间是O(n^2);平均时间是O(n^2);是一种就地的稳定的排序; 8.2.2希尔排序 实现过程:是将直接插入排序的间隔变为d。d的取值要注意:1)最后一次必为1;2)避免d值互为倍数; 关键字比较次数最大为n^1.25;记录移动次数最大为1.6n^1.25; 算法的平均时间是O(n^1.25);是一种就地的不稳定的排序; 8.3交换排序 8.3.1冒泡排序 实现过程:从下到上相邻两个比较,按小在上原则扫描一次,确定最小值,重复n-1次。 关键字比较次数最小为n-1、最大为n(n-1)/2;记录移动次数最小为0,最大为3n(n-1)/2; 算法的最好时间是O(n);最坏时间是O(n^2);平均时间是O(n^2);是一种就地的稳定的排序; 8.3.2快速排序 实现过程:将第一个值作为基准,设置i,j指针交替从两头与基准比较,有交换后,交换j,i。i=j时确定基准,并以其为界限将序列分为两段。重复以上步骤。 关键字比较次数最好为nlog2n+nC(1)、最坏为n(n-1)/2; 算法的最好时间是O(nlog2n);最坏时间是O(n^2);平均时间是O(nlog2n);辅助空间为O(log2n);是一种不稳定排序; 8.4选择排序 8.4.1直接选择排序 实现过程:选择序列中最小的插入第一位,在剩余的序列中重复上一步,共重复n-1次。 关键字比较次数为n(n-1)/2;记录移动次数最小为0,最大为3(n-1); 算法的最好时间是O(n^2);最坏时间是O(n^2);平均时间是O(n^2);是一种就地的不稳定的排序; 8.4.2堆排序 实现过程:把序列按层次填入完全二叉树,调整位置使双亲大于或小于孩子,建立初始大根或小根堆,调整树根与最后一个叶子的位置,排除该叶子重新调整位置。 算法的最好时间是O(nlog2n);最坏时间是O(nlog2n);平均时间是O(nlog2n);是一种就地的不稳定排序; 8.5归并排序 实现过程:将初始序列分为2个一组,最后单数轮空,对每一组排序后作为一个单元,对2个单元排序,直到结束。 算法的最好时间是O(nlog2n);最坏时间是O(nlog2n);平均时间是O(nlog2n);辅助空间为O(n);是一种稳定排序; 8.6分配排序 8.6.1箱排序 实现过程:按关键字的取值范围确定箱子的个数,将序列按关键字放入箱中,输出非空箱的关键字。 在桶内分配和收集,及对各桶进行插入排序的时间为O(n),算法的期望时间是O(n),最坏时间是O(n^2)。 8.6.2基数排序 实现过程:按基数设置箱子,对关键字从低位到高位依次进行箱排序。 算法的最好时间是O(d*n+d*rd);最坏时间是O(d*n+d*rd);平均时间是O(d*n+d*rd);辅助空间O(n+rd);是一种稳定排序; 8.7各种内部排序方法的比较和选择 按平均时间复杂度分为: 1) 平方阶排序:直接插入、直接选择、冒泡排序; 2) 线性对数阶:快速排序、堆排序、归并排序; 3) 指数阶:希尔排序; 4) 线性阶:箱排序、基数排序。 选择合适排序方法的因素:1)待排序的记录数;2)记录的大小;3)关键字的结构和初始状态;4)对稳定性的要求;5)语言工具的条件;6)存储结构;7)时间和辅助空间复杂度。 结论: 1) 若规模较小可采用直接插入或直接选择排序; 2) 若文件初始状态基本有序可采用直接插入、冒泡或随机快速排序; 3) 若规模较大可采用快速排序、堆排序或归并排序; 4) 任何借助于比较的排序,至少需要O(nlog2n)的时间,箱排序和基数排序只适用于有明显结构特征的关键字; 5) 有的语言没有提供指针及递归,使归并、快速、基数排序算法复杂; 6) 记录规模较大时为避免大量移动记录可用链表作为存储结构,如插入、归并、基数排序,但快速、堆排序在链表上难以实现,可提取关键字建立索引表,然后对索引表排序。 附二: 第八章排序 ************************************************************************************* 记录中可用某一项来标识一个记录,则称为关键字项,该数据项的值称为关键字。 排序是使文件中的记录按关键字递增(或递减)次序排列起来。·基本操作:比较关键字大小;改变指向记录的指针或移动记录。 ·存储结构:顺序结构、链表结构、索引结构。 经过排序后这些具有相同关键字的记录之间的相对次序保持不变,则称这种排序方法是稳定的,否则排序算法是不稳定的。 排序过程中不涉及数据的内、外存交换则称之为"内部排序"(内排序),反之,若存在数据的内外存交换,则称之为外排序。 内部排序方法可分五类:插入排序、选择排序、交换排序、归并排序和分配排序。 评价排序算法好坏的标准主要有两条:执行时间和所需的辅助空间,另外算法的复杂程序也是要考虑的一个因素。 ************************************************************************************* 插入排序:·直接插入排序: ·逐个向前插入到合适位置。 ·哨兵(监视哨)有两个作用: ·作为临变量存放 R[i] 复制代码 ·是在查找循环中用来监视下标变量j是否越界。 ·直接插入排序是就地的稳定排序。时间复杂度为O(n^2),比较次数为(n+2)(n-1)/2;移动次数为(n+4)(n-1)/2; ·希尔排序: ·等间隔的数据比较并按要求顺序排列,最后间隔为1。 ·希尔排序是就地的不稳定排序。时间复杂度为O(n^1.25),比较次数为(n^1.25);移动次数为(1.6n^1.25); 交换排序:·冒泡排序:·自下向上确定最轻的一个。·自上向下确定最重的一个。·自下向上确定最轻的一个,后自上向下确定最重的一个。 ·冒泡排序是就地的稳定排序。时间复杂度为O(n^2),比较次数为n(n-1)/2;移动次数为3n(n-1)/2; ·快速排序:·以第一个元素为参考基准,设定、动两个指针,发生交换后指针交换位置,直到指针重合。重复直到排序完成。 ·快速排序是非就地的不稳定排序。时间复杂度为O(nlog2n),比较次数为n(n-1)/2; 选择排序:·直接选择排序: ·选择最小的放在比较区前。 ·直接选择排序就地的不稳定排序。时间复杂度为O(n^2)。比较次数为n(n-1)/2; ·堆排序 ·建堆:按层次将数据填入完全二叉树,从int(n/2)处向前逐个调整位置。 ·然后将树根与最后一个叶子交换值并断开与树的连接并重建堆,直到全断开。 ·堆排序是就地不稳定的排序,时间复杂度为O(nlog2n),不适宜于记录数较少的文件。。 归并排序: ·先两个一组排序,形成(n+1)/2组,再将两组并一组,直到剩下一组为止。 ·归并排序是非就地稳定排序,时间复杂度是O(nlog2n), 分配排序:·箱排序: ·按关键字的取值范围确定箱子数,按关键字投入箱子,链接所有非空箱。 ·箱排序的平均时间复杂度是线性的O(n). ·基数排序:·从低位到高位依次对关键字进行箱排序。 ·基数排序是非就稳定的排序,时间复杂度是O(d*n+d*rd)。 各种排序方法的比较和选择: ·.待排序的记录数目n;n较大的要用时间复杂度为O(nlog2n)的排序方法; ·记录的大小(规模);记录大最好用链表作为存储结构,而快速排序和堆排序在链表上难于实现; ·关键字的结构及其初始状态; ·对稳定性的要求; ·语言工具的条件; ·存储结构; ·时间和辅助空间复杂度。 排序(sort)或分类 所谓排序,就是要整理文件中的记录,使之按关键字递增(或递减)次序排列起来。其确切定义如下: 输入:n个记录R1,R2,…,Rn,其相应的关键字分别为K1,K2,…,Kn。 输出:Ril,Ri2,…,Rin,使得Ki1≤Ki2≤…≤Kin。(或Ki1≥Ki2≥…≥Kin)。 1.被排序对象--文件 被排序的对象--文件由一组记录组成。 记录则由若干个数据项(或域)组成。其中有一项可用来标识一个记录,称为关键字项。该数据项的值称为关键字(Key)。 注意: 在不易产生混淆时,将关键字项简称为关键字。 2.排序运算的依据--关键字 用来作排序运算依据的关键字,可以是数字类型,也可以是字符类型。 关键字的选取应根据问题的要求而定。 【例】在高考成绩统计中将每个考生作为一个记录。每条记录包含准考证号、姓名、各科的分数和总分数等项内容。若要惟一地标识一个考生的记录,则必须用"准考证号"作为关键字。若要按照考生的总分数排名次,则需用"总分数"作为关键字。 排序的稳定性 当待排序记录的关键字均不相同时,排序结果是惟一的,否则排序结果不唯一。 在待排序的文件中,若存在多个关键字相同的记录,经过排序后这些具有相同关键字的记录之间的相对次序保持不变,该排序方法是稳定的;若具有相同关键字的记录之间的相对次序发生变化,则称这种排序方法是不稳定的。 注意: 排序算法的稳定性是针对所有输入实例而言的。即在所有可能的输入实例中,只要有一个实例使得算法不满足稳定性要求,则该排序算法就是不稳定的。 排序方法的分类 1.按是否涉及数据的内、外存交换分 在排序过程中,若整个文件都是放在内存中处理,排序时不涉及数据的内、外存交换,则称之为内部排序(简称内排序);反之,若排序过程中要进行数据的内、外存交换,则称之为外部排序。 注意: ① 内排序适用于记录个数不很多的小文件 ② 外排序则适用于记录个数太多,不能一次将其全部记录放人内存的大文件。 2.按策略划分内部排序方法 可以分为五类:插入排序、选择排序、交换排序、归并排序和分配排序。 排序算法分析 1.排序算法的基本操作 大多数排序算法都有两个基本的操作: (1) 比较两个关键字的大小; (2) 改变指向记录的指针或移动记录本身。 注意: 第(2)种基本操作的实现依赖于待排序记录的存储方式。 2.待排文件的常用存储方式 (1) 以顺序表(或直接用向量)作为存储结构 排序过程:对记录本身进行物理重排(即通过关键字之间的比较判定,将记录移到合适的位置) (2) 以链表作为存储结构 排序过程:无须移动记录,仅需修改指针。通常将这类排序称为链表(或链式)排序; (3) 用顺序的方式存储待排序的记录,但同时建立一个辅助表(如包括关键字和指向记录位置的指针组成的索引表) 排序过程:只需对辅助表的表目进行物理重排(即只移动辅助表的表目,而不移动记录本身)。适用于难于在链表上实现,仍需避免排序过程中移动记录的排序方法。 3.排序算法性能评价 (1) 评价排序算法好坏的标准 评价排序算法好坏的标准主要有两条: ① 执行时间和所需的辅助空间 ② 算法本身的复杂程度 (2) 排序算法的空间复杂度 若排序算法所需的辅助空间并不依赖于问题的规模n,即辅助空间是O(1),则称之为就地排序(In-PlaceSou)。 非就地排序一般要求的辅助空间为O(n)。 (3) 排序算法的时间开销 大多数排序算法的时间开销主要是关键字之间的比较和记录的移动。有的排序算法其执行时间不仅依赖于问题的规模,还取决于输入实例中数据的状态。

马铭芳 2019-12-02 01:19:07 0 浏览量 回答数 0

回答

1、了解视频面试的有效交流成分 面试者可先试演盯着摄像头说话,让对方有一种面谈的感觉;增加一些无伤大雅的微动作,比如点头赞同对方;以及找到自己最适合视频说话的语调和语速,这些将会缩小与面试官的距离感。 2、熟悉面试平台的操作流程 可以使用一下自己常用的招聘APP,查找一下平台视频面试流程的详细说明 3、做好个人面试前的准备 天下大事,必作于细。除了对视频面试和面试平台的了解,个人的准备也是事关重要的。 - [1]个人的形象准备。 虽然是线上的视频面试,但还是可以看到彼此,我们都需要做好准备。比如面试官在国外的下午进行视频面试,国内刚好是晚上,如果此时一身家居服的你与面试官视频,对方难以感受到尊重。所以,无论任何时间点,符合面试的正式服装并且穿戴整齐,才能将专业度传递给面试官。 - [2]室内场所的选择。 选择一个安静的没有干扰的地方,视频区域整洁没有多余的杂物;灯光明亮,避免人像曝光,面试官可清晰看到你;确保坐的椅子舒适,利于自己在面试过程中精神保持专注。 - [3]个人设备和网络。 确认手机电量充足,对应的相机和麦克风功能可以正常使用;关闭任何会发出提示音的设备,避免面试中收到干扰;测试设备和网络是否能正常使用,减少面试中出现断网等低级错误。疫情未止,但这不会成为找工作面试的阻碍,在疫情期间做好面试的充足准备,提高线上面试的重视度,即便现场出现突发状况,镇静并且及时与对方沟通,商量解决方案,一切都能迎刃而解。总之,只要做好十足的准备,确保一切都是最佳状态,即便从未经历过视频面试的你,也能脱颖而出。 面试某技术岗位,事先练习面试题 比如Python,小编为大家精心准备了以下面试题 1.Python是如何进行内存管理的? 答:从三个方面来说,一对象的引用计数机制,二垃圾回收机制,三内存池机制 一、对象的引用计数机制 Python内部使用引用计数,来保持追踪内存中的对象,所有对象都有引用计数。 引用计数增加的情况: - 1,一个对象分配一个新名称 - 2,将其放入一个容器中(如列表、元组或字典) 引用计数减少的情况: - 1,使用del语句对对象别名显示的销毁 - 2,引用超出作用域或被重新赋值 sys.getrefcount( )函数可以获得对象的当前引用计数 多数情况下,引用计数比你猜测得要大得多。对于不可变数据(如数字和字符串),解释器会在程序的不同部分共享内存,以便节约内存。 二、垃圾回收 - 1,当一个对象的引用计数归零时,它将被垃圾收集机制处理掉。 - 2,当两个对象a和b相互引用时,del语句可以减少a和b的引用计数,并销毁用于引用底层对象的名称。然而由于每个对象都包含一个对其他对象的应用,因此引用计数不会归零,对象也不会销毁。(从而导致内存泄露)。为解决这一问题,解释器会定期执行一个循环检测器,搜索不可访问对象的循环并删除它们。 三、内存池机制 Python提供了对内存的垃圾收集机制,但是它将不用的内存放到内存池而不是返回给操作系统。 - 1,Pymalloc机制。为了加速Python的执行效率,Python引入了一个内存池机制,用于管理对小块内存的申请和释放。 - 2,Python中所有小于256个字节的对象都使用pymalloc实现的分配器,而大的对象则使用系统的malloc。 - 3,对于Python对象,如整数,浮点数和List,都有其独立的私有内存池,对象间不共享他们的内存池。也就是说如果你分配又释放了大量的整数,用于缓存这些整数的内存就不能再分配给浮点数。 2.什么是lambda函数?它有什么好处? 答:lambda 表达式,通常是在需要一个函数,但是又不想费神去命名一个函数的场合下使用,也就是指匿名函数 lambda函数:首要用途是指点短小的回调函数 lambda [arguments]:expression a=lambdax,y:x+y a(3,11) 3.Python里面如何实现tuple和list的转换? 答:直接使用tuple和list函数就行了,type()可以判断对象的类型 4.请写出一段Python代码实现删除一个list里面的重复元素 答: - 1,使用set函数,set(list) - 2,使用字典函数, a=[1,2,4,2,4,5,6,5,7,8,9,0] b={} b=b.fromkeys(a) c=list(b.keys()) c 5.编程用sort进行排序,然后从最后一个元素开始判断 a=[1,2,4,2,4,5,7,10,5,5,7,8,9,0,3] a.sort() last=a[-1] for i inrange(len(a)-2,-1,-1): if last==a[i]: del a[i] else:last=a[i] print(a) 6.Python里面如何拷贝一个对象?(赋值,浅拷贝,深拷贝的区别) 答:赋值(=),就是创建了对象的一个新的引用,修改其中任意一个变量都会影响到另一个。 浅拷贝:创建一个新的对象,但它包含的是对原始对象中包含项的引用(如果用引用的方式修改其中一个对象,另外一个也会修改改变){1,完全切片方法;2,工厂函数,如list();3,copy模块的copy()函数} 深拷贝:创建一个新的对象,并且递归的复制它所包含的对象(修改其中一个,另外一个不会改变){copy模块的deep.deepcopy()函数} 7.介绍一下except的用法和作用? 答:try…except…except…[else…][finally…] 执行try下的语句,如果引发异常,则执行过程会跳到except语句。对每个except分支顺序尝试执行,如果引发的异常与except中的异常组匹配,执行相应的语句。如果所有的except都不匹配,则异常会传递到下一个调用本代码的最高层try代码中。 try下的语句正常执行,则执行else块代码。如果发生异常,就不会执行 如果存在finally语句,最后总是会执行。 8.Python中pass语句的作用是什么? 答:pass语句不会执行任何操作,一般作为占位符或者创建占位程序,whileFalse:pass 9.介绍一下Python下range()函数的用法? 答:列出一组数据,经常用在for in range()循环中 10.如何用Python来进行查询和替换一个文本字符串? 答:可以使用re模块中的sub()函数或者subn()函数来进行查询和替换, 格式:sub(replacement, string[,count=0])(replacement是被替换成的文本,string是需要被替换的文本,count是一个可选参数,指最大被替换的数量) import re p=re.compile(‘blue|white|red’) print(p.sub(‘colour’,'blue socks and red shoes’)) colour socks and colourshoes print(p.sub(‘colour’,'blue socks and red shoes’,count=1)) colour socks and redshoes subn()方法执行的效果跟sub()一样,不过它会返回一个二维数组,包括替换后的新的字符串和总共替换的数量 11.Python里面match()和search()的区别? 答:re模块中match(pattern,string[,flags]),检查string的开头是否与pattern匹配。 re模块中research(pattern,string[,flags]),在string搜索pattern的第一个匹配值。 print(re.match(‘super’, ‘superstition’).span()) (0, 5) print(re.match(‘super’, ‘insuperable’)) None print(re.search(‘super’, ‘superstition’).span()) (0, 5) print(re.search(‘super’, ‘insuperable’).span()) (2, 7) 12.用Python匹配HTML tag的时候,<.>和<.?>有什么区别? 答:术语叫贪婪匹配( <.> )和非贪婪匹配(<.?> ) 例如: test <.> : test <.?> : 13.Python里面如何生成随机数? 答:random模块 随机整数:random.randint(a,b):返回随机整数x,a<=x<=b random.randrange(start,stop,[,step]):返回一个范围在(start,stop,step)之间的随机整数,不包括结束值。 随机实数:random.random( ):返回0到1之间的浮点数 random.uniform(a,b):返回指定范围内的浮点数。 14.有没有一个工具可以帮助查找python的bug和进行静态的代码分析? 答:PyChecker是一个python代码的静态分析工具,它可以帮助查找python代码的bug, 会对代码的复杂度和格式提出警告 Pylint是另外一个工具可以进行codingstandard检查 15.如何在一个function里面设置一个全局的变量? 答:解决方法是在function的开始插入一个global声明: def f() global x 16.单引号,双引号,三引号的区别 答:单引号和双引号是等效的,如果要换行,需要符号(),三引号则可以直接换行,并且可以包含注释 如果要表示Let’s go 这个字符串 单引号:s4 = ‘Let\’s go’ 双引号:s5 = “Let’s go” s6 = ‘I realy like“python”!’ 这就是单引号和双引号都可以表示字符串的原因了 最后小编祝福大家能在2020年找到心仪的工作哈

剑曼红尘 2020-03-12 16:06:50 0 浏览量 回答数 0

回答

如果对什么是线程、什么是进程仍存有疑惑,请先Google之,因为这两个概念不在本文的范围之内。 用多线程只有一个目的,那就是更好的利用cpu的资源,因为所有的多线程代码都可以用单线程来实现。说这个话其实只有一半对,因为反应“多角色”的程序代码,最起码每个角色要给他一个线程吧,否则连实际场景都无法模拟,当然也没法说能用单线程来实现:比如最常见的“生产者,消费者模型”。 很多人都对其中的一些概念不够明确,如同步、并发等等,让我们先建立一个数据字典,以免产生误会。 多线程:指的是这个程序(一个进程)运行时产生了不止一个线程 并行与并发: 并行:多个cpu实例或者多台机器同时执行一段处理逻辑,是真正的同时。 并发:通过cpu调度算法,让用户看上去同时执行,实际上从cpu操作层面不是真正的同时。并发往往在场景中有公用的资源,那么针对这个公用的资源往往产生瓶颈,我们会用TPS或者QPS来反应这个系统的处理能力。 并发与并行 线程安全:经常用来描绘一段代码。指在并发的情况之下,该代码经过多线程使用,线程的调度顺序不影响任何结果。这个时候使用多线程,我们只需要关注系统的内存,cpu是不是够用即可。反过来,线程不安全就意味着线程的调度顺序会影响最终结果,如不加事务的转账代码: void transferMoney(User from, User to, float amount){ to.setMoney(to.getBalance() + amount); from.setMoney(from.getBalance() - amount); } 同步:Java中的同步指的是通过人为的控制和调度,保证共享资源的多线程访问成为线程安全,来保证结果的准确。如上面的代码简单加入@synchronized关键字。在保证结果准确的同时,提高性能,才是优秀的程序。线程安全的优先级高于性能。 好了,让我们开始吧。我准备分成几部分来总结涉及到多线程的内容: 扎好马步:线程的状态 内功心法:每个对象都有的方法(机制) 太祖长拳:基本线程类 九阴真经:高级多线程控制类 扎好马步:线程的状态 先来两张图: 线程状态 线程状态转换 各种状态一目了然,值得一提的是"blocked"这个状态:线程在Running的过程中可能会遇到阻塞(Blocked)情况 调用join()和sleep()方法,sleep()时间结束或被打断,join()中断,IO完成都会回到Runnable状态,等待JVM的调度。 调用wait(),使该线程处于等待池(wait blocked pool),直到notify()/notifyAll(),线程被唤醒被放到锁定池(lock blocked pool ),释放同步锁使线程回到可运行状态(Runnable) 对Running状态的线程加同步锁(Synchronized)使其进入(lock blocked pool ),同步锁被释放进入可运行状态(Runnable)。 此外,在runnable状态的线程是处于被调度的线程,此时的调度顺序是不一定的。Thread类中的yield方法可以让一个running状态的线程转入runnable。内功心法:每个对象都有的方法(机制) synchronized, wait, notify 是任何对象都具有的同步工具。让我们先来了解他们 monitor 他们是应用于同步问题的人工线程调度工具。讲其本质,首先就要明确monitor的概念,Java中的每个对象都有一个监视器,来监测并发代码的重入。在非多线程编码时该监视器不发挥作用,反之如果在synchronized 范围内,监视器发挥作用。 wait/notify必须存在于synchronized块中。并且,这三个关键字针对的是同一个监视器(某对象的监视器)。这意味着wait之后,其他线程可以进入同步块执行。 当某代码并不持有监视器的使用权时(如图中5的状态,即脱离同步块)去wait或notify,会抛出java.lang.IllegalMonitorStateException。也包括在synchronized块中去调用另一个对象的wait/notify,因为不同对象的监视器不同,同样会抛出此异常。 再讲用法: synchronized单独使用: 代码块:如下,在多线程环境下,synchronized块中的方法获取了lock实例的monitor,如果实例相同,那么只有一个线程能执行该块内容 复制代码 public class Thread1 implements Runnable { Object lock; public void run() { synchronized(lock){ ..do something } } } 复制代码 直接用于方法: 相当于上面代码中用lock来锁定的效果,实际获取的是Thread1类的monitor。更进一步,如果修饰的是static方法,则锁定该类所有实例。 public class Thread1 implements Runnable { public synchronized void run() { ..do something } } synchronized, wait, notify结合:典型场景生产者消费者问题 复制代码 /** * 生产者生产出来的产品交给店员 */ public synchronized void produce() { if(this.product >= MAX_PRODUCT) { try { wait(); System.out.println("产品已满,请稍候再生产"); } catch(InterruptedException e) { e.printStackTrace(); } return; } this.product++; System.out.println("生产者生产第" + this.product + "个产品."); notifyAll(); //通知等待区的消费者可以取出产品了 } /** * 消费者从店员取产品 */ public synchronized void consume() { if(this.product <= MIN_PRODUCT) { try { wait(); System.out.println("缺货,稍候再取"); } catch (InterruptedException e) { e.printStackTrace(); } return; } System.out.println("消费者取走了第" + this.product + "个产品."); this.product--; notifyAll(); //通知等待去的生产者可以生产产品了 } 复制代码 volatile 多线程的内存模型:main memory(主存)、working memory(线程栈),在处理数据时,线程会把值从主存load到本地栈,完成操作后再save回去(volatile关键词的作用:每次针对该变量的操作都激发一次load and save)。 volatile 针对多线程使用的变量如果不是volatile或者final修饰的,很有可能产生不可预知的结果(另一个线程修改了这个值,但是之后在某线程看到的是修改之前的值)。其实道理上讲同一实例的同一属性本身只有一个副本。但是多线程是会缓存值的,本质上,volatile就是不去缓存,直接取值。在线程安全的情况下加volatile会牺牲性能。太祖长拳:基本线程类 基本线程类指的是Thread类,Runnable接口,Callable接口Thread 类实现了Runnable接口,启动一个线程的方法:  MyThread my = new MyThread();  my.start(); Thread类相关方法:复制代码 //当前线程可转让cpu控制权,让别的就绪状态线程运行(切换)public static Thread.yield() //暂停一段时间public static Thread.sleep() //在一个线程中调用other.join(),将等待other执行完后才继续本线程。    public join()//后两个函数皆可以被打断public interrupte() 复制代码 关于中断:它并不像stop方法那样会中断一个正在运行的线程。线程会不时地检测中断标识位,以判断线程是否应该被中断(中断标识值是否为true)。终端只会影响到wait状态、sleep状态和join状态。被打断的线程会抛出InterruptedException。Thread.interrupted()检查当前线程是否发生中断,返回booleansynchronized在获锁的过程中是不能被中断的。 中断是一个状态!interrupt()方法只是将这个状态置为true而已。所以说正常运行的程序不去检测状态,就不会终止,而wait等阻塞方法会去检查并抛出异常。如果在正常运行的程序中添加while(!Thread.interrupted()) ,则同样可以在中断后离开代码体 Thread类最佳实践:写的时候最好要设置线程名称 Thread.name,并设置线程组 ThreadGroup,目的是方便管理。在出现问题的时候,打印线程栈 (jstack -pid) 一眼就可以看出是哪个线程出的问题,这个线程是干什么的。 如何获取线程中的异常 不能用try,catch来获取线程中的异常Runnable 与Thread类似Callable future模式:并发模式的一种,可以有两种形式,即无阻塞和阻塞,分别是isDone和get。其中Future对象用来存放该线程的返回值以及状态 ExecutorService e = Executors.newFixedThreadPool(3); //submit方法有多重参数版本,及支持callable也能够支持runnable接口类型.Future future = e.submit(new myCallable());future.isDone() //return true,false 无阻塞future.get() // return 返回值,阻塞直到该线程运行结束 九阴真经:高级多线程控制类 以上都属于内功心法,接下来是实际项目中常用到的工具了,Java1.5提供了一个非常高效实用的多线程包:java.util.concurrent, 提供了大量高级工具,可以帮助开发者编写高效、易维护、结构清晰的Java多线程程序。1.ThreadLocal类 用处:保存线程的独立变量。对一个线程类(继承自Thread)当使用ThreadLocal维护变量时,ThreadLocal为每个使用该变量的线程提供独立的变量副本,所以每一个线程都可以独立地改变自己的副本,而不会影响其它线程所对应的副本。常用于用户登录控制,如记录session信息。 实现:每个Thread都持有一个TreadLocalMap类型的变量(该类是一个轻量级的Map,功能与map一样,区别是桶里放的是entry而不是entry的链表。功能还是一个map。)以本身为key,以目标为value。主要方法是get()和set(T a),set之后在map里维护一个threadLocal -> a,get时将a返回。ThreadLocal是一个特殊的容器。2.原子类(AtomicInteger、AtomicBoolean……) 如果使用atomic wrapper class如atomicInteger,或者使用自己保证原子的操作,则等同于synchronized //返回值为booleanAtomicInteger.compareAndSet(int expect,int update) 该方法可用于实现乐观锁,考虑文中最初提到的如下场景:a给b付款10元,a扣了10元,b要加10元。此时c给b2元,但是b的加十元代码约为:复制代码 if(b.value.compareAndSet(old, value)){ return ;}else{ //try again // if that fails, rollback and log} 复制代码 AtomicReference对于AtomicReference 来讲,也许对象会出现,属性丢失的情况,即oldObject == current,但是oldObject.getPropertyA != current.getPropertyA。这时候,AtomicStampedReference就派上用场了。这也是一个很常用的思路,即加上版本号3.Lock类  lock: 在java.util.concurrent包内。共有三个实现: ReentrantLockReentrantReadWriteLock.ReadLockReentrantReadWriteLock.WriteLock 主要目的是和synchronized一样, 两者都是为了解决同步问题,处理资源争端而产生的技术。功能类似但有一些区别。 区别如下:复制代码 lock更灵活,可以自由定义多把锁的枷锁解锁顺序(synchronized要按照先加的后解顺序)提供多种加锁方案,lock 阻塞式, trylock 无阻塞式, lockInterruptily 可打断式, 还有trylock的带超时时间版本。本质上和监视器锁(即synchronized是一样的)能力越大,责任越大,必须控制好加锁和解锁,否则会导致灾难。和Condition类的结合。性能更高,对比如下图: 复制代码 synchronized和Lock性能对比 ReentrantLock    可重入的意义在于持有锁的线程可以继续持有,并且要释放对等的次数后才真正释放该锁。使用方法是: 1.先new一个实例 static ReentrantLock r=new ReentrantLock(); 2.加锁       r.lock()或r.lockInterruptibly(); 此处也是个不同,后者可被打断。当a线程lock后,b线程阻塞,此时如果是lockInterruptibly,那么在调用b.interrupt()之后,b线程退出阻塞,并放弃对资源的争抢,进入catch块。(如果使用后者,必须throw interruptable exception 或catch)     3.释放锁    r.unlock() 必须做!何为必须做呢,要放在finally里面。以防止异常跳出了正常流程,导致灾难。这里补充一个小知识点,finally是可以信任的:经过测试,哪怕是发生了OutofMemoryError,finally块中的语句执行也能够得到保证。 ReentrantReadWriteLock 可重入读写锁(读写锁的一个实现)   ReentrantReadWriteLock lock = new ReentrantReadWriteLock()  ReadLock r = lock.readLock();  WriteLock w = lock.writeLock(); 两者都有lock,unlock方法。写写,写读互斥;读读不互斥。可以实现并发读的高效线程安全代码4.容器类 这里就讨论比较常用的两个: BlockingQueueConcurrentHashMap BlockingQueue阻塞队列。该类是java.util.concurrent包下的重要类,通过对Queue的学习可以得知,这个queue是单向队列,可以在队列头添加元素和在队尾删除或取出元素。类似于一个管  道,特别适用于先进先出策略的一些应用场景。普通的queue接口主要实现有PriorityQueue(优先队列),有兴趣可以研究 BlockingQueue在队列的基础上添加了多线程协作的功能: BlockingQueue 除了传统的queue功能(表格左边的两列)之外,还提供了阻塞接口put和take,带超时功能的阻塞接口offer和poll。put会在队列满的时候阻塞,直到有空间时被唤醒;take在队 列空的时候阻塞,直到有东西拿的时候才被唤醒。用于生产者-消费者模型尤其好用,堪称神器。 常见的阻塞队列有: ArrayListBlockingQueueLinkedListBlockingQueueDelayQueueSynchronousQueue ConcurrentHashMap高效的线程安全哈希map。请对比hashTable , concurrentHashMap, HashMap5.管理类 管理类的概念比较泛,用于管理线程,本身不是多线程的,但提供了一些机制来利用上述的工具做一些封装。了解到的值得一提的管理类:ThreadPoolExecutor和 JMX框架下的系统级管理类 ThreadMXBeanThreadPoolExecutor如果不了解这个类,应该了解前面提到的ExecutorService,开一个自己的线程池非常方便:复制代码 ExecutorService e = Executors.newCachedThreadPool(); ExecutorService e = Executors.newSingleThreadExecutor(); ExecutorService e = Executors.newFixedThreadPool(3); // 第一种是可变大小线程池,按照任务数来分配线程, // 第二种是单线程池,相当于FixedThreadPool(1) // 第三种是固定大小线程池。 // 然后运行 e.execute(new MyRunnableImpl()); 复制代码 该类内部是通过ThreadPoolExecutor实现的,掌握该类有助于理解线程池的管理,本质上,他们都是ThreadPoolExecutor类的各种实现版本。请参见javadoc: ThreadPoolExecutor参数解释 翻译一下:复制代码 corePoolSize:池内线程初始值与最小值,就算是空闲状态,也会保持该数量线程。maximumPoolSize:线程最大值,线程的增长始终不会超过该值。keepAliveTime:当池内线程数高于corePoolSize时,经过多少时间多余的空闲线程才会被回收。回收前处于wait状态unit:时间单位,可以使用TimeUnit的实例,如TimeUnit.MILLISECONDS workQueue:待入任务(Runnable)的等待场所,该参数主要影响调度策略,如公平与否,是否产生饿死(starving)threadFactory:线程工厂类,有默认实现,如果有自定义的需要则需要自己实现ThreadFactory接口并作为参数传入。 阿里云优惠券地址https://promotion.aliyun.com/ntms/yunparter/invite.html?userCode=nb3paa5b

景凌凯 2019-12-02 01:40:35 0 浏览量 回答数 0
阿里云大学 云服务器ECS com域名 网站域名whois查询 开发者平台 小程序定制 小程序开发 国内短信套餐包 开发者技术与产品 云数据库 图像识别 开发者问答 阿里云建站 阿里云备案 云市场 万网 阿里云帮助文档 免费套餐 开发者工具 企业信息查询 小程序开发制作 视频内容分析 企业网站制作 视频集锦 代理记账服务 2020阿里巴巴研发效能峰会 企业建站模板 云效成长地图 高端建站