算法工程师知识点总结

八种排序算法的时间复杂度复杂度

https://blog.csdn.net/q2213065359/article/details/82801717
https://www.jianshu.com/p/916b15eae350
归并排序、冒泡排序、插入排序、基数排序是稳定的
选择排序、快速排序、希尔排序、堆排序是不稳定的

在这里插入图片描述
选择、冒泡、直接插入:https://www.cnblogs.com/chengxiao/p/6103002.html
希尔排序:https://www.cnblogs.com/chengxiao/p/6104371.html
归并排序:https://www.cnblogs.com/chengxiao/p/6194356.html
堆排序:https://www.cnblogs.com/chengxiao/p/6129630.html

查找算法

平均查找速度:顺序>分块>折半>哈希。
顺序查找,时间复杂度O(N),
分块查找,时间复杂度O(logN+N/m);
折半查找,时间复杂度O(logN)
哈希查找,时间复杂度O(1)

折半查找
n个数据元素,二分查找法查找数据元素x的最多比较次数不超过log2(n)+1

常见结论

对大小均为n的有序表和无序表分别进行顺序查找,在等概率查找的情况下,对于查找成功,它们的平均查找长度是相同的,而对于查找失败,它们的平均查找长度是不同的

为什么数组查询比链表要快?而插入删除比链表效率低

1、数据存储结构分为顺序存储、链接存储、索引存储、散列存储。
2、数组属于顺序存储,用一段连续的内存位置来存储。
3、链表属于链接存储,用一组任意的存储单元来存储,不要求物理上相邻。
因为CPU缓存会读入一段连续的内存,顺序存储符合连续的内存,所以顺序存储可以被缓存处理,而链接存储并不是连续的,分散在堆中,所以只能内存去处理。
所以数组查询比链表要快。
而数组大小固定,插入和删除都需要移动元素,链表可以动态扩充,插入删除不需要移动元素,只需要更改元素中的指针。所以链表的插入删除比数组效率高。

函数中的局部变量在内存中如何申请

1、栈区:由编译器自动分配 存放函数的参数值,局部变量的值等,操作方式类似于数据结构中的栈。
2、堆区:一般由程序员分配释放,若程序员不释放,程序结束时可能有系统收回。它与数据结构中的堆是两回事。分配方式类似于链表。
3、全局区(静态区):全局变量和静态变量是存储放在一块的,初始化的全局变量和静态变量在一个区域,未初始化的在相邻的另一个区域。程序结束后由系统释放。
4、文字常量区:常量字符串就存放在这里。程序结束后有系统自动释放。
5、程序代码区:存放函数体的二进制代码。

程序运行中有两个存储空间可用,一个是栈,是归属于进程本身的,另外一个是堆,所有进程共用的。
局部变量在声明周期为函数内部,其存储空间位于栈中。当进入函数时,会对根据局部变量需求,在栈上申请一段内存空间,供局部变量使用。当局部变量生命周期结束后,在栈上释放。分配较大内存的时候可以用动态内存分配,malloc,new。

内存的操作new/delete,malloc/free,有什么区别

一、介绍
malloc/free是C/C++语言的标准库函数,在C语言中需要头文件#include<stdlib.h>的支持。而new/delete是C++的运算符。对于类对象而言,malloc/free无法满足动态对象的要求,对象要求在创建的同时自动执行构造函数,对象消亡时自动执行析构函数,malloc/free不在编译器的控制权限之内,无法执行构造函数和析构函数。
二、区别
1、new能自动计算需要分配的内存空间,而malloc需要手工计算字节数。

int *p = new int[2];
int *q = (int *)malloc(2*sizeof(int));

2、new与delete直接带具体类型的指针,malloc和free返回void类型的指针。

3、new类型是安全的,而malloc不是。例如int *p = new float[2];就会报错;而int p = malloc(2sizeof(int))编译时编译器就无法指出错误来。

4、new一般分为两步:new操作和构造。new操作对应与malloc,但new操作可以重载,可以自定义内存分配策略,不做内存分配,甚至分配到非内存设备上,而malloc不行。

5、new调用构造函数,malloc不能;delete调用析构函数,而free不能。

6、malloc/free需要库文件stdlib.h的支持,new/delete则不需要!

三、注意事项
delete和free被调用后,内存不会立即回收,指针也不会指向空,delete或free仅仅是告诉操作系统,这一块内存被释放了,可以用作其他用途。但是由于没有重新对这块内存进行写操作,所以内存中的变量数值并没有发生变化,出现野指针的情况。因此,释放完内存后,应该讲该指针指向NULL。

造成野指针的原因

指针变量没有被初始化(如果值不定,可以初始化为NULL)
指针被free或者delete后,没有置为NULL, free和delete只是把指针所指向的内存给释放掉,并没有把指针本身干掉,此时指针指向的是“垃圾”内存。释放后的指针应该被置为NULL.
指针操作超越了变量的作用范围,比如返回指向栈内存的指针就是野指针。

TCP和UDP的区别

1、TCP面向连接(如打电话要先拨号建立连接);UDP是无连接的,即发送数据之前不需要建立连接
2、TCP提供可靠的服务。也就是说,通过TCP连接传送的数据,无差错,不丢失,不重复,且按序到达;UDP尽最大努力交付,即不保证可靠交付。
3、TCP面向字节流,实际上是TCP把数据看成一连串无结构的字节流;UDP是面向报文的,UDP没有拥塞控制,因此网络出现拥塞不会使源主机的发送速率降低(对实时应用很有用,如IP电话,实时视频会议等)。
4、每一条TCP连接只能是点到点的;UDP支持一对一,一对多,多对一和多对多的交互通信
5、TCP首部开销20字节;UDP的首部开销小,只有8个字节
6、TCP的逻辑通信信道是全双工的可靠信道,UDP则是不可靠信道

UDP包含8个字节,包头结构:源端口 16位;目的端口 16位、;长度 16位;校验和 16位
TCP包含20个字节,包头结构:源端口号16位;目的端口号16位;序列号32位;确认号32位;HeaderLenAndFlag 前4位:TCP头长度、中6位:保留、后6位:标志位;窗口大小16位
;检验和16位;紧急数据偏移量16位。序列号是指客户端确认序列号以及以前的信息都收到了,窗口大小则是提高传输效率,保证信息按序到达。

scanf、printf、getchar等函数

scanf、printf遇到‘\0’停止;getchar遇到‘\n’或文件结束符EOF停止;scanf遇到空格、enter、tab作为数据的分隔符。

链表环问题

https://blog.csdn.net/doufei_ccst/article/details/10578315
链表是否有环:快慢指针

typedef struct node{
	char data ;
	node * next ;
}Node;
bool exitLoop(Node *head)
{
	Node *fast, *slow ;
	slow = fast = head ;

	while (slow != NULL && fast -> next != NULL)
	{
		slow = slow -> next ;
		fast = fast -> next -> next ;
		if (slow == fast)
			return true ;
	}
	return false ;
}

如果有环,寻找环的入口:从链表起点head开始到入口点的距离a,与从slow和fast的相遇点(如图)到入口点的距离相等,可以分别用一个指针(ptr1, prt2),同时从head与slow和fast的相遇点出发,每一次操作走一步,直到ptr1 == ptr2,此时的位置也就是入口点

Node* findLoopStart(Node *head)
{
	Node *fast, *slow ;
	slow = fast = head ;

	while (slow != NULL && fast -> next != NULL)
	{
		slow = slow -> next ;
		fast = fast -> next -> next ;
		if (slow == fast) break ;
	}
	if (slow == NULL || fast -> next == NULL) return NULL ; //没有环,返回NULL值

	Node * ptr1 = head ; //链表开始点
	Node * ptr2 = slow ; //相遇点
	while (ptr1 != ptr2) 
	{
		ptr1 = ptr1 -> next ;
		ptr2 = ptr2 -> next ;
	}
	return ptr1 ; //找到入口点
}

如果有环,求环结点的个数:
思路1:记录下相遇节点存入临时变量tempPtr,然后让slow(或者fast,都一样)继续向前走slow = slow -> next;一直到slow == tempPtr; 此时经过的步数就是环上节点的个数;

思路2: 从相遇点开始slow和fast继续按照原来的方式向前走slow = slow -> next; fast = fast -> next -> next;直到二者再次项目,此时经过的步数就是环上节点的个数 。
如果存在环,求出链表的长度
链表长度L = 起点到入口点的距离 + 环的长度r;
求出环上距离任意一个节点最远的点
在这里插入图片描述
如何判断两个无环链表是否相交,和7(扩展)如果相交,求出第一个相交的节点:问题转换
在这里插入图片描述

C++

C++类继承

父类成员的访问限定符通过继承派生到子类中之后,访问限定符的权限小于、等于原权限。其中,父类中的private成员只有父类本身及其友元可以访问,通过其他方式都不能进行访问,当然就包括继承。protected多用于继承当中,如果对父类成员的要求是——子类可访问而外部不可访问,则可以选择protected继承方式。

派生类对象的构造方式
1.先调用基类构造函数,构造基类部分成员变量,再调用派生类构造函数构造派生类部分的成员变量。2.基类部分成员的初始化方式在派生类构造函数的初始化列表中指定。3.若基类中还有成员对象,则先调用成员对象的构造函数,再调用基类构造函数,最后是派生类构造函数。析构顺序和构造顺序相反,先析构派生类,再析构基类。

基类和派生类中同名成员的关系
重载:一函数名相同,二形参类型、个数、顺序不同
覆盖:基类、派生类中的同名方法 函数头相同(参数、返回值),且基类中该方法为虚函数,则派生类中的同名方法将基类中方法覆盖。

指针和引用
基类指针(引用)可以指向派生类对象,但只能访问派生类中基类部分的方法,不能访问派生类部分方法
派生类指针(引用)不可以指向基类对象,解引用可能出错,因为派生类的一些方法可能基类没有。

虚函数、纯虚函数、抽象类

纯虚函数:是一种特殊的虚函数,在许多情况下,在基类中不能对虚函数给出有意义的实现,而把它声明为纯虚函数,它的实现留给该基类的派生类去做。这就是纯虚函数的作用。

虚函数:在某基类中声明为 virtual 并在一个或多个派生类中被重新定义的成员函数,用法格式为:virtual 函数返回类型 函数名(参数表) {函数体};实现多态性,通过指向派生类的基类指针或引用,访问派生类中同名覆盖成员函数。普通函数(非成员函数)、构造函数、inline函数、友元函数不能声明为虚函数。

1.为什么C++不支持普通函数为虚函数?
普通函数(非成员函数)只能被overload,不能被override,声明为虚函数也没有什么意思,因此编译器会在编译时邦定函数。
2.为什么C++不支持构造函数为虚函数?
这个原因很简单,主要是从语义上考虑,所以不支持。因为构造函数本来就是为了明确初始化对象成员才产生的,然而virtual function主要是为了再不完全了解细节的情况下也能正确处理对象。另外,virtual函数是在不同类型的对象产生不同的动作,现在对象还没有产生,如何使用virtual函数来完成你想完成的动作。(这不就是典型的悖论)
3.为什么C++不支持内联成员函数为虚函数?
其实很简单,那内联函数就是为了在代码中直接展开,减少函数调用花费的代价,虚函数是为了在继承后对象能够准确的执行自己的动作,这是不可能统一的。(再说了,inline函数在编译时被展开,虚函数在运行时才能动态的邦定函数)
4.为什么C++不支持静态成员函数为虚函数?
这也很简单,静态成员函数对于每个类来说只有一份代码,所有的对象都共享这一份代码,他也没有要动态邦定的必要性。
5.为什么C++不支持友元函数为虚函数?
因为C++不支持友元函数的继承,对于没有继承特性的函数没有虚函数的说法。

抽象类:含有纯虚函数的类称为“抽象类”。抽象类不能实例化对象,只能作为基类,派生类可以继承抽象类,对抽象类中的纯虚函数实现 函数重写覆盖。

纯虚函数和虚函数的区别:
1.虚函数和纯虚函数可以定义在同一个类(class)中,含有纯虚函数的类被称为抽象类(abstract class),而只含有虚函数的类(class)不能被称为抽象类(abstract class)。

2.虚函数可以被直接使用,也可以被子类(sub class)重载以后以多态的形式调用,而纯虚函数必须在子类(sub class)中实现该函数才可以使用,因为纯虚函数在基类(base class)只有声明而没有定义。

3.虚函数和纯虚函数都可以在子类(sub class)中被重载,以多态的形式被调用。

4.虚函数的定义形式:virtual {method body};纯虚函数的定义形式:virtual { } = 0;

在虚函数和纯虚函数的定义中不能有static标识符,原因很简单,被static修饰的函数在编译时候要求前期bind,然而虚函数却是动态绑定(run-time bind),而且被两者修饰的函数生命周期也不一样。

5.虚函数必须实现,如果不实现,编译器将报错,错误提示为:

error LNK****: unresolved external symbol “public: virtual void __thiscall
ClassName::virtualFunctionName(void)”

6.实现了纯虚函数的子类,该纯虚函数在子类中就编程了虚函数,子类的子类即孙子类可以覆盖,该虚函数,由多态方式调用的时候动态绑定。

7.虚函数是C++中用于实现多态的机制,核心理念就是通过基类访问派生类定义的函数

8.多态性指相同对象收到不同消息或不同对象收到相同消息时产生不同的实现动作。C++支持两种多态性:编译时多态性,运行时多态性。
a.编译时多态性:通过重载函数实现
b 运行时多态性:通过虚函数实现。

9.如果一个类中含有纯虚函数,那么任何试图对该类进行实例化的语句都将导致错误的产生,因为抽象基类(ABC)是不能被直接调用的。必须被子类继承重载以后,根据要求调用其子类的方法。

C++的多态

http://c.biancheng.net/view/264.html
派生类对象的地址可以赋值给基类指针。对于通过基类指针调用基类和派生类中都有的同名、同参数表的虚函数的语句,编译时并不确定要执行的是基类还是派生类的虚函数;而当程序运行到该语句时,如果基类指针指向的是一个基类对象,则基类的虚函数被调用,如果基类指针指向的是一个派生类对象,则派生类的虚函数被调用。这种机制就叫作“多态(polymorphism)”。

所谓“虚函数”,就是在声明时前面加了 virtual 关键字的成员函数。virtual 关键字只在类定义中的成员函数声明处使用,不能在类外部写成员函数体时使用。静态成员函数不能是虚函数。

包含虚函数的类称为“多态类”。

多态可以简单地理解为同一条函数调用语句能调用不同的函数;或者说,对不同对象发送同一消息,使得不同对象有各自不同的行为。

#include <iostream>
using namespace std;
class A
{
public:
    virtual void Print() { cout << "A::Print" << endl; }
};
class B : public A
{
public:
    virtual void Print() { cout << "B::Print" << endl; }
};
class D : public A
{
public:
    virtual void Print() { cout << "D::Print" << endl; }
};
class E : public B
{
    virtual void Print() { cout << "E::Print" << endl; }
};
int main()
{
    A  a; B b; D d; E e;
    A *pa = &a;  B *pb = &b;
    pa->Print();    //多态, a.Print()被调用,输出:A::Print
    pa = pb;        //基类指针pa指向派生类对象b
    pa->Print();  //b.Print()被调用,输出:B::Print
    pa = &d;       //基类指针pa指向派生类对象d
    pa->Print();  //多态, d. Print ()被调用,输出:D::Print
    pa = &e;       //基类指针pa指向派生类对象d
    pa->Print();  //多态, e.Print () 被调用,输出:E::Print
    return 0;
}

每个类都有同名、同参数表的虚函数 Print(每个 Print 函数声明时都加了 virtual 关键字)。根据多态的规则,对于语句“pa->Print()”,由于 Print 是虚函数,尽管 pa 是基类 A 的指针,编译时也不能确定调用的是哪个类的 Print 函数。当程序运行到该语句时,pa 指向的是哪个类的对象,调用的就是哪个类的 Print 函数。

例如,程序执行到第 26 行时,pa 指向的是基类对象 a,因此调用的就是类 A 的 Print 成员函数;执行到第 28 行时,pa 指向的是类 B 的对象,因此调用的是类 B 的 Print 成员函数;第 30 行也是如此;类 E 是类 A 的间接派生类,因此,执行到第 32 行时,多态规则仍然适用,此时 pa 指向派生类 E 的对象,故调用的是类 E 的 Print 成员函数。

需要强调的是,编译器不会分析程序的运行过程。编译器并没有通过分析程序上下文,得出在第 28 行 pa 指向的是类 B 的对象,因此第 28 行应该调用类 B 的 Print 成员函数这样的结论。

多态的语句调用哪个类的成员函数是在运行时才能确定的,编译时不能确定(具体原理后面会解释)。因此,多态的函数调用语句被称为是“动态联编”的,而普通的函数调用语句是“静态联编”的。

重载和多态的区别:重载是同名参数不同,通过参数来确定调用那个函数;但是多态是同名同参数,通过函数的实际类型决定调用那个函数。

C++中的static

类体中的数据成员的声明前加上static关键字,该数据成员就成为了该类的静态数据成员。
作用:1.隐藏;2.保持变量内容的持久;3.默认初始化为0(static变量)。
https://blog.csdn.net/m0_37962600/article/details/80038089

static和const的区别

static
1.static 局部变量 将一个变量声明为函数的局部变量,那么这个局部变量在函数执行完成之后不会被释放,而是继续保留在内存中
2.static 全局变量 表示一个变量在当前文件的全局内可访问
3.static 函数 表示一个函数只能在当前文件中被访问
4.static 类成员变量 表示这个成员为全类所共有
5.static 类成员函数 表示这个函数为全类所共有,而且只能访问静态成员变量

静态成员存在于内存,非静态成员需要实例化才会分配内存;非静态成员可以直接访问类中的静态成员,而静态成员不能访问非静态成员;非静态成员的生存期决定于该类的生存期,静态成员的生存期与程序生命期相同。
const
1.const 常量:定义时就初始化,以后不能更改
2.const 形参:func(const int a){};该形参在函数里不能改变
3.const 修饰类成员函数:该函数对成员变量只能进行只读操作

static的作用
(1)函数体内static变量的作用范围为该函数体,该变量的内存只被分配一次,因此其值在下次调用时仍维持上次的值;
(2)在模块内的static全局变量和函数可以被模块内的函数访问,但不能被模块外其它函数访问;
(3)在类中的static成员变量属于整个类所拥有,对类的所有对象只有一份拷贝;
(4)在类中的static成员函数属于整个类所拥有,这个函数不接收this指针,因而只能访问类的static成员变量。

const的作用
(1)阻止一个变量被改变
(2)声明常量指针和指针常量
(3)const修饰形参,表明它是一个输入参数,在函数内部不能改变其值;
(4)对于类的成员函数,若指定其为const类型,则表明其是一个常函数,不能修改类的成员变量;
(5)对于类的成员函数,有时候必须指定其返回值为const类型,以使得其返回值不为”左值”。

三种成员类型的赋值方式

在这里插入图片描述

不能重载的运算符

不能重载的运算符只有5个:

. (成员访问运算符)

.* (成员指针访问运算符)

:: (域运算符)

sizeof (长度运算符)

?: (条件运算符)

C++线程和多线程

智能指针

https://blog.csdn.net/pecywang/article/details/22595641
1.scoped_ptr:这是最常用的智能指针,当你new一块内存后,把内存地址交给scoped_ptr管理,这样就不用显式调用delete了,当离开作用于后,该内存会被自动释放,如

   int* p = new int;
   scoped_ptr<int> scoped_int_ptr(p);

注意:无须再delete p;这样会double free。另外一个重要的点是:scoped_ptr不允许传递指针,即他的拷贝构造和赋值函数都是private的,不允许调用,所以你不能写如下代码

    scoped_ptr<int> scoped_int_ptr2 =  scoped_int_ptr; // 不允许

2.auto_ptr:它和scoped_ptr用法基本是一致的,但是它允许指针传递,拷贝构造和赋值函数允许调用,故名思意,当发生赋值时,原对象的指针会转移给新对象,这时原对象的指针就为NULL了,不能再调用。所以,对指针要把握好,使用应谨慎。

3.shared_ptr:scoped_ptr一样包装了new操作符在堆上分配的动态对象,但它实现的是引用计数型的智能指针 ,可以被自由地拷贝和赋值,在任意的地方共享它,当没有代码使用(引用计数为0)它时才删除被包装的动态分配的对象。但是,shared_ptr注意不要有循环引用,否则会出现内存泄漏。例如:A和B对象互相引用,它们的引用计数都是1,当出了作用域之后,二者不能自动释放,出现了内存泄漏。

4.weak_ptr:是一种智能指针,它对被 std::shared_ptr 管理的对象存在非拥有性(“弱”)引用。在访问所引用的对象前必须先转换为 std::shared_ptr。

std::weak_ptr 用来表达临时所有权的概念:当某个对象只有存在时才需要被访问,而且随时可能被他人删除时,可以使用std::weak_ptr 来跟踪该对象。需要获得临时所有权时,则将其转换为 std::shared_ptr,此时如果原来的std::shared_ptr 被销毁,则该对象的生命期将被延长至这个临时的 std::shared_ptr 同样被销毁为止。

此外,std::weak_ptr 还可以用来避免 std::shared_ptr 的循环引用。

C++容器

参考:https://blog.csdn.net/u013719339/article/details/80615217
STL将通用容器分为了三类:顺序性容器、关联式容器、容器适配器。

1.3.1 顺序性容器vector、list、deque

顺序容器的各元素组成有顺序关系的线性表,它是一种线性结构的可序群集。其每个元素有各自固定的位置,使用添加或插入元素后位置顺移,位置与元素本身无关,而与操作时间和地点有关,它保存的是元素操作时的逻辑顺序。例如一次性追加多个元素,这些元素在容器中的相对位置与追加时的逻辑顺序是一致的,即与添加到容器的顺序一致。
vector:向量
1)可以直接访问任何元素。
2)线性顺序结构。可以指定一块连续的空间,也可以不预先指定大小,空间可自动扩展,也可以像数组一样被操作,即支持[]操作符和vector. at(),因此可看做动态数组,通常体现在追加数据push_back()和删除末尾数据pop_back()。
3)当分配空间不够时,vector会申请一块更大的内存块(以2的倍数增长),然后将原来的数据拷贝到新内存块中并将原内存块中的对象销毁,最后释放原来的内存空间。因此如果vector保存的数据量很大时会很消耗性能,因此在预先知道它大小时性能最优。
4)节省空间。因为它是连续存储,在存储数据的区域是没有浪费的,但实际上大多数时候是存不满的,因此实际上未存储的区域是浪费的。
5)在内部进行插入和删除的操作效率低。由于vector内部按顺序表结构设计,因此这样的操作基本上是被禁止的,它被设计成只能在后端进行追加和删除操作。
list:双链表
1)线性链表结构。
2)其数据由若干个节点构成,每个节点包括一个信息块(即实际存储的数据)、一个前驱指针和一个后驱指针。无需分配指定的内存大小且可任意伸缩,因此它存储在非连续的内存空间中,并且由指针将有序的元素链接起来。因而相比vector它也占更多的内存。
3)根据其结构可知随机检索的性能很差,vector是直接找到元素的地址,而它需要从头开始按顺序依次查找,因此检索靠后的元素时非常耗时。即不支持[ ]操作符和. at()。
4)由于list每个节点保存着它在链表中的位置,插入或删除一个元素仅对最多三个元素有所影响,因此它可以迅速在任何节点进行插入和删除操作。
deque:双端队列
1)是一种优化了的、对序列两端进行添加和删除操作、较快速地随机访问的基本序列容器。
2)采用多个连续的存储块保存对象,并在一个映射结构中保存对这些块及其顺序的跟踪。由于不需要重新分配空间,因此追加元素时比vector更有效。实际上内部有一个map指针。
3)支持随机访问,即支持[ ]操作符和. at(),但性能不如vector。
4)可以进行内部随机插入和删除,但性能不如list。

1.3.2 关联容器set、multiset、map、multimap

关联容器是二叉树结构,它根据元素特点排序,迭代器能以元素的特点“顺序地”获取元素。它是以键值的方式来保存数据,即把关键字和值关联起来保存,而顺序容器只能保存一种(可以认为它只保存关键字,也可以认为它只保存值)。
集合set:
1)快速查找,不允许重复值。
2)按一定顺序排列,集合中的每个元素被称作集合中的实例。
3)内部通过链表的方式组织,因此插入的时候比vector快,但在查找和末尾追加比vector慢。
map:
1)提供一种“键-值”关系的一对一的数据存储能力。键按一定顺序排列且不可重复(set也可以看成没有键只有值的特殊map形式)。
2)链表方式存储,继承了链表的优缺点。
3)一对多映射,基于关键字快速查找。
**multiset和multimap:**不要求元素唯一,其他同上。

关联容器特点:

1)红黑树的结构原理。
2)set和map保证了元素的唯一性,mulset和mulmap扩展了这一属性,可以允许元素不唯一。
3)元素是有序的集合,默认在插入的时候按升序排列。
4)插入和删除操作比vector快,比list慢。因为vector是顺序存储,而关联容器是链式存储;而同为链式结构,list是线性,而关联容器是排序的二叉树结构,因此每次都需要对元素重新排序,涉及到的元素变动更多。
5)对元素的检索操作比vector慢,比list快很多。vector是顺序的连续存储,这是最快的速度;而list需要逐个搜索,搜索时间与容器大小成正比,关联容器查找的复杂度log(n),因此容器越大,关联容器相对list越能体现其优越性。
6)在使用上set区别于顺序容器的是查询上虽然慢于vector,但却强于list。
7)在使用上map的功能是不可取代的,它保存了“键-值”关系的数据,而这种键值关系采用了类数组的方式。数组是用数字类型的下标来索引元素的位置,而map是用字符型关键字来索引元素的位置。在使用上map也提供了一种类数组操作的方式,即它可以通过下标来检索数据。在STL中只有vector和map可以通过类数组的方式操作元素。

1.3.3 容器适配器stack、queue、priority_queue

这是一个比较抽象的概念,C++的解释是:适配器是使一事物的行为类似于另一事物的行为的一种机制。容器适配器是让一种已经存在的容器类型采用另一种不同的抽象类型的工作方式来实现的一种机制。其实仅仅是发生了接口转换。可以将之理解为容器的容器,只是不依赖于具体的标准容器类型,可以理解为容器的模板,也可以将之理解为容器的接口。因此,适配器本身不能保存元素,而是“它保存一个容器,容器再保存元素”,所以它是通过调用另一种容器去实现保存功能。STL中提供的三种适配器可以由一种顺序容器去实现,默认下stack和queue基于deque容器实现,priority_queue基于vector容器实现,也可以在创建时可以自己指定具体的实现容器。但由于适配器的特点,并不是任何顺序容器都可以实现这些适配器。

栈stack后进先出。关联容器可以是任意一种顺序容器。 stack的成员函数都是针对其顶部元素进行操作:push(),pop(),top()。
队列queue先进先出。关联容器必须提供pop_front操作,因此vector不适用。它有两个出口。queue也是以deque作为底部结构,封闭其底端的出口和前端的入口。queue,只有顶端(两端)的元素能被外部使用,所以queue也没有迭代器,不提供遍历功能。
优先级priority_queue:最高优先级元素总是第一个处理。则需要提供随机访问功能,因此list不适用。

容器的使用

void Find_and_Draw_LeftLines(cv::Mat left_image, cv::Mat& left_res_img, int Parzen,int LightGray, int DifPerPix,int& deta_width)
{
	deta_width = 0;
	cv::Mat coutour, gray_img, threshould_img;
	int middle_width = cvRound(left_image.cols / 2); //cvRound 返回跟参数最接近的整数值
	int detect_width = 0;
	left_image.copyTo(left_res_img);
	if (left_image.channels() == 3 || left_image.channels() == 4)
	{
		cv::cvtColor(left_image, gray_img, CV_BGR2GRAY, 1);  //转为灰度图像
	}
	int half_Parzen = Parzen / 2;   // 5
	std::vector<cv::Point> points;
	for (int k = 0; k < gray_img.rows; k++)        //逐行循环
	{
		int max = 0;
		const uchar*ptr = gray_img.ptr<uchar>(k); //得到第k行的首地址
		std::vector<int> tmp_x;
		int sum =0;
		int j_num = 0;
		for (int j = 0; j < gray_img.cols - half_Parzen; j++)  //逐列循环, 处理每列像素
		{
			uchar pixel = ptr[j];
			if (pixel>LightGray)            //150
			{
				uchar pixel1 = ptr[j+ Parzen];     //10
				if (pixel1 -pixel>DifPerPix)  //25
				{
					sum += j+5;
					j_num++;
				}
				if (pixel1 - pixel>max)
				{
					max = pixel1 - pixel;

				}

			}
			
		}
		if (j_num !=0)
		{
			int avg_x = sum / j_num;
			points.push_back(cv::Point(avg_x, k));  //push_back插入
		}
		
		
	}
	if (points.size()>gray_img.rows/3)
	{
		cv::Vec4f line_para;
		cv::fitLine(points, line_para, cv::DIST_L2, 0, 1e-2, 1e-2);     //二维点的数组或vector,输出直线,Vec4f (2d)或Vec6f (3d)的vecto,
		//距离类型,距离参数 ,径向的精度参数 ,角度精度参数

		//获取点斜式的点和斜率  
		double cos_theta = line_para[0];
		double sin_theta = line_para[1];
		double x0 = line_para[2], y0 = line_para[3];

		double phi = atan2(sin_theta, cos_theta) + CV_PI / 2.0;    //反正切值
		double rho = y0 * cos_theta - x0 * sin_theta;              


		drawLine(left_image, phi, rho, cv::Scalar(0));         //划线
		
	}

	cv::imshow("left_image", left_image);
	cv::waitKey(33);

	//Calculation of the deviation between the middle line and the detection line
	//deta_width = //detect_width - middle_width;
}

友元函数

友元函数是指某些虽然不是类成员却能够访问类的所有成员的函数。类授予它的友元特别的访问权。

  1. 什么是友元函数?
    一个类的私有数据成员通常只能由类的函数成员来访问,而友元函数可以访问类的私有数据成员,也能访问其保护成员
  2. 友元函数的用处体现在哪里?
    2.1 使用友元函数可提高性能,如:用友元函数重载操作符和生成迭代器类
    2.2 用友元函数可以访问两个或多个类的私有数据,较其它方法使人们更容易理解程序的逻辑关系
  3. 使用友元函数前应注意:
    3.1 类的友元函数在类作用域之外定义,但可以访问类的私有和保护成员
    3.2 尽管类定义中有友元函数原型,友元函数仍然不是成员函数
    3.3 由于友元函数不是任何类的成员函数,所以不能用句柄(对象)加点操作符来调用
    3.4 public, private, protected成员访问符与友员关系的声明无关,因此友元关系声明可在类定义的任何位置,习惯上在类定义的开始位置
    3.5 友元关系是指定的,不是获取的,如果让类B成为类A的友元类,类A必须显式声明类B为自己的友元类
    3.6 友元关系不满足对称性和传递性
    3.7 如果一个友元函数想与两个或更多类成为友元关系,在每个类中都必须声明为友元函数
#include <iostream>
#include <string>
using namespace std;

class Student {
private:
	string  name; int age; int sore;
public:
	Student(string name, int age, int sore)
	{
		this->name = name;
		this->age = age;
		this->sore = sore;
	}
	friend void Display(Student student);
};


void Display(Student student)
{
	std::cout << student.name << "\t" << student.age << "\t" << student.sore << endl;

}

int main(void)
{
	Student a("TOM", 13, 96);
	Display(a);
	return 0;
}

构造函数和成员函数的区别

1.构造函数的命名必须和类名完全相同;而一般方法则不能和类名相同.
2.构造函数的功能主要用于在类的对象创建时定义初始化的状态.它没有返回值,也不能用void来修饰.这就保证了它不仅什么也不用自动返回,而且根本不能有任何选择.而其他方法都有返回值.即使是void返回值,尽管方法体本身不会自动返回什么,但仍然可以让它返回一些东西,而这些东西可能是不安全的.
3.构造函数不能被直接调用,必须通过new运算符在创建对象时才会自动调用,一般方法在程序执行到它的时候被调用.

构造函数和析构函数与重载、继承、虚函数等关系

构造函数可以被重载,析构函数不可以被重载。因为构造函数可以有多个且可以带参数,而析构函数只能有一个,且不能带参数。

构造函数不能被继承,析构函数可以被继承。

构造函数不能是虚函数,而析构函数可以。

inline函数

参考:https://blog.csdn.net/monk1992/article/details/81703737
引入inline函数的主要原因是用它替代C中复杂易错不易维护的宏函数。可以将内联理解为C++中对于函数专有的宏,对于C的函数宏的一种改进。对于常量宏,C++提供const替代;而对于函数宏,C++提供的方案则是inline。C++ 通过内联机制,既具备宏代码的效率,又增加了安全性,还可以自由操作类的数据成员。
例子:

//求0-9的平方
inline int inlineFunc(int num)
{  
  if(num>9||num<0)
      return -1;  
  return num*num;  
}  

int main(int argc,char* argv[])
{
    int a=8;
    int res=inlineFunc(a);
    cout<<"res:"<<res<<endl;
}

使用inline函数相当于

int main(int argc,char* argv[])
{
    int a=8;
    {  
        int _temp_b=8;  
        int _temp;  
        if (_temp_q >9||_temp_q<0) _temp = -1;  
        else _temp =_temp*_temp;  
        b = _temp;  
    }
} 

优点:
1.内联函数同宏函数一样将在被调用处进行代码展开,省去了参数压栈、栈帧开辟与回收,结果返回等,从而提高程序运行速度。
2.内联函数相比宏函数来说,在代码展开时,会做安全检查或自动类型转换(同普通函数),而宏定义则不会。
3.在类中声明同时定义的成员函数,自动转化为内联函数,因此内联函数可以访问类的成员变量,宏定义则不能。
4.内联函数在运行时可调试,而宏定义不可以。
缺点:
1.代码膨胀,占用内存。
2.inline函数无法随着函数库升级而升级。
如果f是函数库中的一个inline函数,使用它的用户会将f函数实体编译到他们的程序中。一旦函数库实现者改变f,所有用到f的程序都必须重新编译。如果f是non-inline的,用户程序只需重新连接即可。如果函数库采用的是动态连接,那这一升级的f函数可以不知不觉的被程序使用。
3.是否内联,程序员不可控。
inline函数只是对编译器的建议,是否对函数内联,决定权在于编译器。编译器认为调用某函数的开销相对该函数本身的开销而言微不足道或者不足以为之承担代码膨胀的后果则没必要内联该函数,若函数出现递归,有些编译器则不支持将其内联。
注意:
当在头文件中定义内联函数,那么被多个源文件包含时,如果编译器因为inline函数不适合被内联时,拒绝将inline函数进行内联处理,那么多个源文件在编译生成目标文件后都将各自保留一份inline函数的实体,这个时候程序在连接阶段就会出现重定义错误。解决办法是在需要inline的函数使用static

指针和引用

指针占用内存空间,四个字节,引用不占用内存空间,占用的空间也是引用对象本身占用空间,引用可以说是外号和别称。

指针可以是空指针,引用不可以,引用对象必须存在。

指针的自加是对地址的操作,引用的自加是对引用对象的操作。

静态数据成员和静态成员函数

https://blog.csdn.net/computer_liuyun/article/details/29235111

C++内存泄漏的情况

1.在类的构造函数和析构函数中没有匹配的调用new和delete函数
两种情况下会出现这种内存泄露:一是在堆里创建了对象占用了内存,但是没有显示地释放对象占用的内存;二是在类的构造函数中动态的分配了内存,但是在析构函数中没有释放内存或者没有正确的释放内存

2.没有正确地清除嵌套的对象指针

3.在释放对象数组时在delete中没有使用方括号
方括号是告诉编译器这个指针指向的是一个对象数组,同时也告诉编译器正确的对象地址值并调用对象的析构函数,如果没有方括号,那么这个指针就被默认为只指向一个对象,对象数组中的其他对象的析构函数就不会被调用,结果造成了内存泄露。如果在方括号中间放了一个比对象数组大小还大的数字,那么编译器就会调用无效对象(内存溢出)的析构函数,会造成堆的奔溃。如果方括号中间的数字值比对象数组的大小小的话,编译器就不能调用足够多个析构函数,结果会造成内存泄露。释放单个对象、单个基本数据类型的变量或者是基本数据类型的数组不需要大小参数,释放定义了析构函数的对象数组才需要大小参数。

4.指向对象的指针数组不等同于对象数组
对象数组是指:数组中存放的是对象,只需要delete []p,即可调用对象数组中的每个对象的析构函数释放空间
指向对象的指针数组是指:数组中存放的是指向对象的指针,不仅要释放每个对象的空间,还要释放每个指针的空间,delete []p只是释放了每个指针,但是并没有释放对象的空间,正确的做法,是通过一个循环,将每个对象释放了,然后再把指针释放了

5.缺少拷贝构造函数
两次释放相同的内存是一种错误的做法,同时可能会造成堆的奔溃。

按值传递会调用(拷贝)构造函数,引用传递不会调用。

在C++中,如果没有定义拷贝构造函数,那么编译器就会调用默认的拷贝构造函数,会逐个成员拷贝的方式来复制数据成员,如果是以逐个成员拷贝的方式来复制指针被定义为将一个变量的地址赋给另一个变量。这种隐式的指针复制结果就是两个对象拥有指向同一个动态分配的内存空间的指针。当释放第一个对象的时候,它的析构函数就会释放与该对象有关的动态分配的内存空间。而释放第二个对象的时候,它的析构函数会释放相同的内存,这样是错误的。

所以,如果一个类里面有指针成员变量,要么必须显示的写拷贝构造函数和重载赋值运算符,要么禁用拷贝构造函数和重载赋值运算符

6.缺少重载赋值运算符
这种问题跟上述问题类似,也是逐个成员拷贝的方式复制对象,如果这个类的大小是可变的,那么结果就是造成内存泄露,如下图:

7.没有将基类的析构函数定义为虚函数
当基类指针指向子类对象时,如果基类的析构函数不是virtual,那么子类的析构函数将不会被调用,子类的资源没有正确是释放,因此造成内存泄露。

KMP算法

http://www.ruanyifeng.com/blog/2013/05/Knuth%E2%80%93Morris%E2%80%93Pratt_algorithm.html

结构体占多少字节在这里插入图片描述

运算符重载

C++11新特性

参考:https://www.cnblogs.com/feng-sc/p/5710724.html#title11

1.auto

//示例代码1.0 http://www.cnblogs.com/feng-sc/p/5710724.html
auto AddTest(int a, int b) 
{
    return a + b;
}

int main()
{
    auto index = 10;
    auto str = "abc";
    auto ret = AddTest(1,2);
    std::cout << "index:" << index << std::endl;
    std::cout << "str:" << str << std::endl;
    std::cout << "res:" << ret << std::endl;
}

在这里插入图片描述
auto作为函数返回值时,只能用于定义函数,不能用于声明函数。但如果把实现写在头文件中,可以编译通过,因为编译器可以根据函数实现的返回值确定auto的真实类型。如果读者用过inline类成员函数,这个应该很容易明白,此特性与inline类成员函数类似。

2.nullptr关键字及用法

//示例代码1.0 http://www.cnblogs.com/feng-sc/p/5710724.html
class Test
{
public:
    void TestWork(int index)
    {
        std::cout << "TestWork 1" << std::endl;
    }
    void TestWork(int * index)
    {
        std::cout << "TestWork 2" << std::endl;
    }
};

int main()
{
    Test test;
    test.TestWork(NULL);
    test.TestWork(nullptr);
}

在这里插入图片描述
NULL在c++里表示空指针,我们调用test.TestWork(NULL),其实期望是调用的是void TestWork(int * index),但结果调用了void TestWork(int index)。但使用nullptr的时候,我们能调用到正确的函数。

for循环语法

//示例代码1.0 http://www.cnblogs.com/feng-sc/p/5710724.html
int main()
{
    int numbers[] = { 1,2,3,4,5 };
    std::cout << "numbers:" << std::endl;
    for (auto number : numbers)
    {
        std::cout << number << std::endl;
    }
}

STL容器

2.1、std::array
std::array跟数组并没有太大区别,对于多维数据使用std::arraystd::array相对于数组,增加了迭代器等函数

//示例代码1.0 http://www.cnblogs.com/feng-sc/p/5710724.html
#include <array>
int main()
{
    std::array<int, 4> arrayDemo = { 1,2,3,4 };
    std::cout << "arrayDemo:" << std::endl;
    for (auto itor : arrayDemo)
    {
        std::cout << itor << std::endl;
    }
    int arrayDemoSize = sizeof(arrayDemo);
    std::cout << "arrayDemo size:" << arrayDemoSize << std::endl;
    return 0;
}

在这里插入图片描述
2.2、std::forward_list
std::forward_list为从++新增的线性表,与list区别在于它是单向链表。我们在学习数据结构的时候都知道,链表在对数据进行插入和删除是比顺序存储的线性表有优势,因此在插入和删除操作频繁的应用场景中,使用list和forward_list比使用array、vector和deque效率要高很多。
2.3、std::unordered_map
std::unordered_map与std::map用法基本差不多,但STL在内部实现上有很大不同,std::map使用的数据结构为二叉树,而std::unordered_map内部是哈希表的实现方式,哈希map理论上查找效率为O(1)。但在存储效率上,哈希map需要增加哈希表的内存开销。
2.4、std::unordered_set
std::unordered_set的数据存储结构也是哈希表的方式结构,除此之外,std::unordered_set在插入时不会自动排序,这都是std::set表现不同的地方。

//示例代码1.0 http://www.cnblogs.com/feng-sc/p/5710724.html
#include <iostream>
#include <string>
#include <unordered_set>
#include <set>
int main()
{
    std::unordered_set<int> unorder_set;
    unorder_set.insert(7);
    unorder_set.insert(5);
    unorder_set.insert(3);
    unorder_set.insert(4);
    unorder_set.insert(6);
    std::cout << "unorder_set:" << std::endl;
    for (auto itor : unorder_set)
    {
        std::cout << itor << std::endl;
    }

    std::set<int> set;
    set.insert(7);
    set.insert(5);
    set.insert(3);
    set.insert(4);
    set.insert(6);
    std::cout << "set:" << std::endl;
    for (auto itor : set)
    {
        std::cout << itor << std::endl;
    }
}

在这里插入图片描述

多线程

在C++11以前,C++的多线程编程均需依赖系统或第三方接口实现,一定程度上影响了代码的移植性。C++11中,引入了boost库中的多线程部分内容,形成C++标准,形成标准后的boost多线程编程部分接口基本没有变化,这样方便了以前使用boost接口开发的使用者切换使用C++标准接口,把容易把boost接口升级为C++接口。
1.std::thread

//示例代码1.0 http://www.cnblogs.com/feng-sc/p/5710724.html
#include <thread>
void threadfun1()
{
    std::cout << "threadfun1 - 1\r\n" << std::endl;
    std::this_thread::sleep_for(std::chrono::seconds(1));
    std::cout << "threadfun1 - 2" << std::endl;
}

void threadfun2(int iParam, std::string sParam)
{
    std::cout << "threadfun2 - 1" << std::endl;
    std::this_thread::sleep_for(std::chrono::seconds(5));
    std::cout << "threadfun2 - 2" << std::endl;
}

int main()
{
    std::thread t1(threadfun1);
    std::thread t2(threadfun2, 10, "abc");
    t1.join();
    std::cout << "join" << std::endl;
    t2.detach();
    std::cout << "detach" << std::endl;
}

在这里插入图片描述
有以上输出结果可以得知,t1.join()会等待t1线程退出后才继续往下执行,t2.detach()将线程对象和线程解耦合,主线不等子线程结束,主函数退出,threadfun2还未执行完成,但是在主线程退出后,t2的线程也被已经被强退出。
std::atomic
从功能上看,简单地说,原子数据类型不会发生数据竞争,能直接用在多线程中而不必我们用户对其进行添加互斥资源锁的类型。从实现上,大家可以理解为这些原子类型内部自己加了锁。使用10个线程,把std::atomic_int类型的变量iCount从100减到1。

//示例代码1.0 http://www.cnblogs.com/feng-sc/p/5710724.html
#include <thread>
#include <atomic>
#include <stdio.h>
std::atomic_bool bIsReady = false;
std::atomic_int iCount = 100;
void threadfun1()
{
    if (!bIsReady) {
        std::this_thread::yield();
    }
    while (iCount > 0)
    {
        printf("iCount:%d\r\n", iCount--);
    }
}

int main()
{
    std::atomic_bool b;
    std::list<std::thread> lstThread;
    for (int i = 0; i < 10; ++i)
    {
        lstThread.push_back(std::thread(threadfun1));
    }
    for (auto& th : lstThread)
    {
        th.join();
    }
}

5.1、std::function、std::bind封装可执行对象
test.h

//示例代码1.0 http://www.cnblogs.com/feng-sc/p/5710724.html
class Test
{
public:
    void Add(std::function<int(int, int)> fun, int a, int b)
    {
        int sum = fun(a, b);
        std::cout << "sum:" << sum << std::endl;
    }
};

test.c

//示例代码1.0 http://www.cnblogs.com/feng-sc/p/5710724.html
int add(int a,int b)
{
    std::cout << "add" << std::endl;
    return a + b;
}

class TestAdd
{
public:
    int Add(int a,int b)
    {
        std::cout << "TestAdd::Add" << std::endl;
        return a + b;
    }
};

int main()
{
    Test test;
    test.Add(add, 1, 2);

    TestAdd testAdd;
    test.Add(std::bind(&TestAdd::Add, testAdd, std::placeholders::_1, std::placeholders::_2), 1, 2);
    return 0;
}

5.2、lamda表达式

//示例代码1.0 http://www.cnblogs.com/feng-sc/p/5710724.html
int main()
{
    auto add= [](int a, int b)->int{
        return a + b;
    };
    int ret = add(1,2);
    std::cout << "ret:" << ret << std::endl;
    return 0;
}

函数模板和类模板

所有模板实例化都是在生成类实例的时候,所有模板实例化都是在生成类实例之后才可以开始构造,参数类型不限。
函数模板的实例化是由编译程序在处理函数调用时自动完成的,而类模板的实例化必须由程序员在程序中显式地指定。类模板在实例化时必须显式地指明数据类型,编译器不能根据给定的数据推演出数据类型。

举例:

#include <iostream>
using namespace std;
 
template<class T1, class T2> //这里不能有分号
class Point{
public:
Point(T1 x, T2 y): m_x(x), m_y(y){ }
public:
T1 getX() const; //获取x坐标
void setX(T1 x); //设置x坐标
T2 getY() const; //获取y坐标
void setY(T2 y); //设置y坐标
private:
T1 m_x; //x坐标
T2 m_y; //y坐标
};
 
template<class T1, class T2> //模板头
T1 Point<T1, T2>::getX() const /*函数头*/ {
return m_x;
}
 
template<class T1, class T2>
void Point<T1, T2>::setX(T1 x){
m_x = x;
}

template<class T1, class T2>
T2 Point<T1, T2>::getY() const{
return m_y;
}
template<class T1, class T2>
void Point<T1, T2>::setY(T2 y){
m_y = y;
}

int main(){
Point<int, int> p1(10, 20);
cout<<"x="<<p1.getX()<<", y="<<p1.getY()<<endl;
Point<int, char*> p2(10, "东京180度");
cout<<"x="<<p2.getX()<<", y="<<p2.getY()<<endl;

Point<char*, char*> *p3 = new Point<char*, char*>("东京180度", "北纬210度");
cout<<"x="<<p3->getX()<<", y="<<p3->getY()<<endl;
return 0;
}

python

Python程序的执行过程 解释型语言和编译型语言

当python程序运行时,编译的结果则是保存在位于内存中的PyCodeObject中,当Python程序运行结束时,Python解释器则将PyCodeObject写回到pyc文件中。当python程序第二次运行时,首先程序会在硬盘中寻找pyc文件,如果找到,则直接载入,否则就重复上面的过程。
.pyc的过期时间:每次在载入之前都会先检查一下py文件和pyc文件保存的最后修改日期,如果不一致则重新生成一份pyc文件。

A. 其实Python是否保存成pyc文件和我们在设计缓存系统时是一样的,我们可以仔细想想,到底什么是值得扔在缓存里的,什么是不值得扔在缓存里的。

B. 在跑一个耗时的Python脚本时,我们如何能够稍微压榨一些程序的运行时间,就是将模块从主模块分开。(虽然往往这都不是瓶颈)

C. 在设计一个软件系统时,重用和非重用的东西是不是也应该分开来对待,这是软件设计原则的重要部分。

D. 在设计缓存系统(或者其他系统)时,我们如何来避免程序的过期,其实Python的解释器也为我们提供了一个特别常见而且有效的解决方案。

python数据类型

python 中的标准数据类型:
在这里插入图片描述
其中数字类型有:
在这里插入图片描述

列表

添加元素
list.append(单个元素):在list列表末端增加一个元素;
list.extend([元素1,元素2]):在list列表末端增加多个元素;
list.insert(元素序号,元素):在list列表任意位置增加一个元素

删除元素
list.remove(元素):从列表中删除一个元素,且并不要求此元素的位置;
del.list[元素序号]:从列表中删除指定位置的元素;
list_0 = list.pop():从列表末尾中弹出一个元素,则list列表中少一个元素;
list_0 = list.pop(元素序号):从列表中指定弹出一个元素,则list列表中少一个元素。

两个列表合并
两个list合并:a=[1,2,3,4,5,6] b=[‘a’,‘b’,‘c’,‘d’]
1、a+b 如下:
2、a+=b 这时a的值变成了合并后的结果:
3、a.extend(b) 和+=结果一样
4、a.append(b)将b看成list一个元素和a合并成一个新的list
5、a.insert(位置,内容) 位置从0开始
6、a[0:0] = b 使用切片

注意:extend接受一个参数,这个参数,总是一个list,并把list中的每个元素添加到原list中;append接受一个参数,这个参数可以是任意数据类型,并且追加到list的尾部

li1 = [1,2,3]
li2 = [4,5,6]

li1.extend(li2)
print(li1)#[1, 2, 3, 4, 5, 6]
li1.append(li2)
print(li1)#[1, 2, 3, 4, 5, 6, [4, 5, 6]]

列表乘整数

list1 = [[0, 0]]
list2 = list1 * 2		# list2 = [[0, 0], [0, 0]]
list2[0][0] = 1			# list2 = [[1, 0], [1, 0]]

其他操作
1、a = list.count(元素):计算它的参数在列表中出现的次数,并将次数返回;
2、a = list.index(元素):返回它的参数在列表中的位置,返回元素序号;#若有多个元素相同,此为只返回首端起第一个。
3、a = list.index(元素, 序号1,序号2):在序号1和序号2范围内,返回列表中元素位置。 #若有多个元素相同,此为只返回首端起第一个。
4、list.reverse() == list[::-1]:将整个列表内元素反过来排列:[1, 2, 3, 4].reverse() == [4, 3, 2, 1];
5、list.sort():将所有元素,从小到大排列;

字典

d = {key1 : value1, key2 : value2 }
键必须是唯一的,但值则不必。 值可以取任何数据类型,但键必须是不可变的,如字符串,数字或元组。
取值的方法:d[“key1”],d.get(“key1”)
取键值

print(dict1.keys(),dict1.values()) ## 单独取
print(dict1.items()) ## 同时取

对字典键值的排序

def dict_sort(dict,num):
    """
    :type s: str
    :rtype: int
    """
    tmp = dict
    dict=sorted(dict,key=dict.__getitem__) ## 默认小到大排序,reverse=True从大到小,sorted()中没有key,默认对键排序,返回键,key=dict.__getitem__对值排序,返回键;
    print(sorted(dict)) ## ['A', 'B', 'C', 'D', 'E', 'F', 'G']
    print(dict) ## ['C', 'A', 'B', 'F', 'G', 'D', 'E']
    print(tmp.get(dict[num-1]),dict[num-1]) ## 114 B
    return dict[num-1]

d = {'A': 112, 'B': 114, 'C': 100, 'D': 120, 'E':140,'F':115,'G':118}
num = 3
print(dict_sort(d,num)) ## B

dict1={'a':2,'e':3,'f':8,'d':4}
list1= sorted(dict1.items(),key=lambda x:x[0]) ## 0对键排序,返回字典;1对值排序,返回字典
print(list1) ## [('a', 2), ('d', 4), ('e', 3), ('f', 8)]

##交换键值两种方法
a={'a':1,'b':2,'c':3}
print(dict(zip(a.values(),a.keys())),a)## {1: 'a', 2: 'b', 3: 'c'} {'a': 1, 'b': 2, 'c': 3}
dic_new = dict([val, key] for key, val in a.items())## {1: 'a', 2: 'b', 3: 'c'}
print(dic_new)

##合并两个字典三种方法
b= {'aa':11,'bb':22,'cc':33}
print(dict(a,**b)) ## {'a': 1, 'b': 2, 'c': 3, 'aa': 11, 'bb': 22, 'cc': 33}
print(dict(a.items,b.items))## {'a': 1, 'b': 2, 'c': 3, 'aa': 11, 'bb': 22, 'cc': 33}
c = {}  
c.update(a)  
c.update(b)
print()c## {'a': 1, 'b': 2, 'c': 3, 'aa': 11, 'bb': 22, 'cc': 33}

python 中*args,**kwargs

def function(arg,*args,**kwargs):
    print(arg,args,kwargs)

function(6,7,8,9,a=1, b=2, c=3)

# 6 (7, 8, 9) {'a': 1, 'b': 2, 'c': 3}

lambda表达式

c = lambda x,y,z: x*y*z
c(2,3,4) ## 24

a = lambda *z:z #*z返回的是一个元祖
a('Testing1','Testing2') ## ('Testing1', 'Testing2')

(lambda x,y: x if x> y else y)(101,102) ## 直接传参 102

filter(lambda x:x%3==0,[1,2,3,4,5,6]) ## [3,6]

#判断是否以特定字母开头
Names = ['Anne', 'Amy', 'Bob', 'David', 'Carrie', 'Barbara', 'Zach']
B_Name= filter(lambda x: x.startswith('B'),Names) ## ['Bob', 'Barbara']

squares = map(lambda x:x**2,range(10))
filters = filter(lambda x:x>5 and x<50,squares) ## [9, 16, 25, 36, 49]

L = [1,2,3,4]
sum = reduce(lambda x,y:x+y,L) ## 10

death = [ ('James',32),('Alies',20),('Wendy',25)]
sorted(death,key=lambda age:age[1]) #按照第二个元素,索引为1排序 [('Alies', 20), ('Wendy', 25), ('James', 32)]

python生成器

列表生成式

list5 = [x for x in range(5)]
print(list5)  # [0, 1, 2, 3, 4]

生成器

gen = (x for x in range(5))
print(gen) # <generator object <genexpr> at 0x0000000000AA20F8>

调用生成器

for item in gen:
    print(item)
print(next(gen))#output:0
print(next(gen))#output:1
print(next(gen))#output:2
print(next(gen))#output:3
print(next(gen))#output:4
print(next(gen))#output:Traceback (most recent call last):StopIteration

yield 关键词
python有yield的关键词。其作用和return的功能差不多,就是返回一个值给调用者,只不过有yield的函数返回值后函数依然保持调用yield时的状态,当下次调用的时候,在原先的基础上继续执行代码,直到遇到下一个yield或者满足结束条件结束函数为止。

def triangle():
    _list, new_list = [], []
    while True:
        length = len(_list)
        if length == 0:
            new_list.append(1)
        else:
            for times in range(length + 1):
                if times == 0:
                    new_list.append(1)
                elif times == length:
                    new_list.append(1)
                else:
                    temp = _list[times - 1] + _list[times]
                    new_list.append(temp)
        yield new_list #返回值,然后挂起函数,等待下一次调用
        _list = new_list.copy()#调用后会继续执行下去
        new_list.clear()

n = 0
for result in triangle():
    n += 1
    print(result)
    if n == 10:
        break

'''
[1]
[1, 1]
[1, 2, 1]
[1, 3, 3, 1]
[1, 4, 6, 4, 1]
[1, 5, 10, 10, 5, 1]
[1, 6, 15, 20, 15, 6, 1]
[1, 7, 21, 35, 35, 21, 7, 1]
[1, 8, 28, 56, 70, 56, 28, 8, 1]
[1, 9, 36, 84, 126, 126, 84, 36, 9, 1]
'''

yolov3中的生成器

model.fit_generator(data_generator_wrapper(lines[:num_train], batch_size, input_shape, anchors, num_classes),
                steps_per_epoch=max(1, num_train//batch_size),
                validation_data=data_generator_wrapper(lines[num_train:], batch_size, input_shape, anchors, num_classes),
                validation_steps=max(1, num_val//batch_size),
                epochs=50,
                initial_epoch=0,
                callbacks=[logging, checkpoint])

def data_generator(annotation_lines, batch_size, input_shape, anchors, num_classes):
    '''data generator for fit_generator'''
    n = len(annotation_lines)
    i = 0
    while True:
        image_data = []
        box_data = []
        for b in range(batch_size):
            if i==0:
                np.random.shuffle(annotation_lines)
            image, box = get_random_data(annotation_lines[i], input_shape, random=True)
            image_data.append(image)
            box_data.append(box)
            i = (i+1) % n
        image_data = np.array(image_data)
        box_data = np.array(box_data)
        y_true = preprocess_true_boxes(box_data, input_shape, anchors, num_classes)
        yield [image_data, *y_true], np.zeros(batch_size)

def data_generator_wrapper(annotation_lines, batch_size, input_shape, anchors, num_classes):
    n = len(annotation_lines)
    if n==0 or batch_size<=0: return None
    return data_generator(annotation_lines, batch_size, input_shape, anchors, num_classes)
#著名的斐波拉契数列(Fibonacci):除第一个和第二个数外,任意一个数都可由前两个数相加得到
#1.举例:1, 1, 2, 3, 5, 8, 13, 21, 34, ...使用函数实现打印数列的任意前n项。

def fib(times): #times表示打印斐波拉契数列的前times位。
    n = 0
    a,b = 0,1
    while n<times:
        print(b)
        a,b = b,a+b
        n+=1
    return 'done'

fib(10)  #前10位:1 1 2 3 5 8 13 21 34 55

#2.将print(b)换成yield b,则函数会变成generator生成器。
#yield b功能是:每次执行到有yield的时候,会返回yield后面b的值给函数并且函数会暂停,直到下次调用或迭代终止;
def fib(times): #times表示打印斐波拉契数列的前times位。
    n = 0
    a,b = 0,1
    while n<times:
        yield b  
        a,b = b,a+b
        n+=1
    return 'done'

print(fib(10))  #<generator object fib at 0x000001659333A3B8>

3.对生成器进行迭代遍历元素
方法1:使用for循环
for x in fib(6):
    print(x)
''''结果如下,发现如何生成器是函数的话,使用for遍历,无法获取函数的返回值。
1
1
2
3
5
8
'''
方法2:使用next()函数来遍历迭代,可以获取生成器函数的返回值。同理也可以使用自带的__next__()函数,效果一样
f = fib(6)
while True:
    try:  #因为不停调用next会报异常,所以要捕捉处理异常。
        x = next(f)  #注意这里不能直接写next(fib(6)),否则每次都是重复调用1
        print(x)
    except StopIteration as e:
        print("生成器返回值:%s"%e.value)
        break
'''结果如下:
1
1
2
3
5
8
生成器返回值:done
'''

生成器使用总结:

1.生成器的好处是可以一边循环一边进行计算,不用一下子就生成一个很大的集合,占用内存空间。生成器的使用节省内存空间。

2.生成器保存的是算法,而列表保存的计算后的内容,所以同样内容的话生成器占用内存小,而列表占用内存大。每次调用 next(G) ,就计算出 G 的下一个元素的值,直到计算到最后一个元素,没有更多的元素时,抛出 StopIteration 的异常。

3.使用for 循环来遍历生成器内容,因为生成器也是可迭代对象。通过 for 循环来迭代它,不需要关心 StopIteration 异常。但是用for循环调用generator时,得不到generator的return语句的返回值。如果想要拿到返回值,必须用next()方法,且捕获StopIteration错误,返回值包含在StopIteration的value中。

4.在 Python 中,使用了 yield 的函数都可被称为生成器(generator)。生成器是一个返回迭代器的函数,只能用于迭代操作。更简单点理解生成器就是一个迭代器。

5.一个带有 yield 的函数就是一个 generator,它和普通函数不同,生成一个 generator 看起来像函数调用,但不会执行任何函数代码,直到对其调用 next()(在 for 循环中会自动调用 next())才开始执行。虽然执行流程仍按函数的流程执行,但每执行到一个 yield 语句就会中断,保存当前所有的运行信息,并返回一个迭代值,下次执行next() 方法时从 yield 的下一个语句继续执行。看起来就好像一个函数在正常执行的过程中被 yield 中断了数次,每次中断都会通过 yield 返回当前的迭代值。生成器不仅“记住”了它数据状态;生成器还“记住”了它在流控制构造中的位置。

python装饰器

装饰器的原理其实就是函数引用的传递

def set_func(func):
    def call_funct():
        print("---这是权限验证1---")
        print("---这是权限验证2——————")
        func()
    return call_funct

@set_func
def test_1():
    print("----test1----")

test_1()
#---这是权限验证1---
#---这是权限验证2——————
#----test1----

对有参数无返回值的函数进行修饰

def set_func(func):
    def call_funct(num):
        print("---这是权限验证1---")
        print("---这是权限验证2——————")
        func(num)
    return call_funct

@set_func
def test_1(num):
    print("----test1----%d"%num)

test_1(10000)
#---这是权限验证1---
#---这是权限验证2——————
#----test1----10000

统计函数时间

import time
def set_func(func):
    def call_funct():
        start_time=time.time()
        func()
        stop_time=time.time()
        print("运行时间是%f"%(stop_time-start_time))
    return call_funct

@set_func
def test_1():
    print("----test1----")
    for i in range(100000):
        pass
test_1()
#----test1----
#运行时间是0.001968

不定长参数的函数装饰器

def set_func(func):
    print("开启装饰器")
    def call_func(*args,**kwargs):
        print("11111")
        print("22222")
        func(*args,**kwargs)
    return  call_func

@set_func
def test(num,*args,**kwargs):
    print("test---%d"%num)
    print("test---",args)
    print("test---",kwargs)

test(100)
test(100,200)
test(100,200,300,mm=500)

'''
开启装饰器 # 当执行@set_func时,装饰器开始执行
11111
test---100
test--- ()
test--- {}
11111
test---100
test--- (200,)
test--- {}
11111
test---100
test--- (200, 300)
test--- {'mm': 500}
'''

对带有返回值的函数进行装饰,通用装饰器

def set_func(func):
    print("开启装饰器")
    def call_func(*args,**kwargs):
        print("11111")
        return func(*args,**kwargs)
    return call_func

@set_func
def test(num,*args,**kwargs):
    print("test---%d"%num)
    return "OK"

ret = test(100)
print(ret)

#开启装饰器
#11111
#test---100
#OK

多个装饰器对一个函数装饰,开启装饰器的顺序是从下到上,执行内部函数的时候,由上到下

def set_func(func):
    print("开启装饰器111")
    def call_func():
        print("111")
        return func()
    return  call_func

def add_qx(func):
    print("开启装饰器222")
    def call_func():
        print("222")
        return func()
    return  call_func


@set_func
@add_qx
def test():
    print("----test----")

test()

#开启装饰器222
#开启装饰器111
#111
#222
----test----

类的装饰器

class Test(object):
    def __init__(self,func):
        self.func=func

    def __call__(self, *args, **kwargs):
        print("这里是装饰器添加的功能")
        return self.func()

@Test
def get_str():
    return "111"

print(get_str())

#这里是装饰器添加的功能
#111

装饰器带参数

def set_level(level_num):
    print("开启装饰器")
    def set_func(func):
        def call_func(*args, **kwargs):
            # level = args[0]
            if level_num == 1:
                print("权限验证1")
            elif level_num == 2:
                print("权限验证2")
            return func()

        return call_func

    return set_func

@set_level(1)
def test1():
    print("test1")
    return "OK"

@set_level(2)
def test2():
    print("test2")
    return "OK"

test1()
test2()

'''
开启装饰器
开启装饰器
权限验证1
test1
权限验证2
test2
''''

python的深浅拷贝

数字和字符串中的内存都指向同一个地址,所以深拷贝和浅拷贝对于他们而言都是无意义的

  1. copy.copy 浅拷贝 只拷贝父对象,不会拷贝对象的内部的子对象。
  2. copy.deepcopy 深拷贝 拷贝对象及其子对象
import copy
a = [1, 2, 3, 4, ['a', 'b', 'c']]
b = copy.copy(a)       #浅拷贝
c = copy.deepcopy(a)  #深拷贝
a[4].append("d")
a[3] = 5
print(a,b,c)
## [1, 2, 3, 5, ['a', 'b', 'c', 'd']] 
## [1, 2, 3, 4, ['a', 'b', 'c', 'd']] 
## [1, 2, 3, 4, ['a', 'b', 'c']]

collections模块

https://www.cnblogs.com/dianel/p/10787693.html

counter类

import collections
obj = collections.Counter('aabbccc')
print(obj) ## Counter({'c': 3, 'a': 2, 'b': 2})按值的大小排序

## dict方法
print(dict(obj)) ##{'a': 2, 'b': 2, 'c': 3} dict方法后,按键排序

## list方法
print(list(obj)) ## ['a', 'b', 'c']

## elements方法
print(sorted(obj.elements()))

## items方法
print(obj.items()) # dict_items([('a', 2), ('b', 2), ('c', 3)])

## 增加元素
obj.update(['22','55'])
print(obj) ## Counter({'c': 3, 'a': 2, 'b': 2, '22': 1, '55': 1})

## 删除元素
obj.subtract(['11','55'])
print(obj) ## Counter({'c': 3, 'a': 2, 'b': 2, '22': 1, '55': 0, '11': -1})

numpy库

图像库cv2、PIL、scikit-image

线程池:

线程池是一种多线程处理形式,处理过程中将任务添加到队列,然后在创建线程后自动启动这些任务。线程池线程都是后台线程。每个线程都使用默认的堆栈大小,以默认的优先级运行,并处于多线程单元中。如果某个线程在托管代码中空闲(如正在等待某个事件),则线程池将插入另一个辅助线程来使所有处理器保持繁忙。如果所有线程池线程都始终保持繁忙,但队列中包含挂起的工作,则线程池将在一段时间后创建另一个辅助线程但线程的数目永远不会超过最大值。超过最大值的线程可以排队,但他们要等到其他线程完成后才启动。

线程池线程数设置:N核服务器,通过执行业务的单线程分析出本地计算时间为x,等待时间为y,则工作线程数(线程池线程数)设置为 N*(x+y)/x,能让CPU的利用率最大化。

python代码,使用from threading import Thread:


import socket
import threading
from threading import Thread
import threading
import sys
import time
import random
from Queue import Queue

host = ''
port = 8888
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.bind((host, port))
s.listen(3)

class ThreadPoolManger():
    """线程池管理器"""
    def __init__(self, thread_num):
        # 初始化参数
        self.work_queue = Queue()
        self.thread_num = thread_num
        self.__init_threading_pool(self.thread_num)

    def __init_threading_pool(self, thread_num):
        # 初始化线程池,创建指定数量的线程池
        for i in range(thread_num):
            thread = ThreadManger(self.work_queue)
            thread.start()

    def add_job(self, func, *args):
        # 将任务放入队列,等待线程池阻塞读取,参数是被执行的函数和函数的参数
        self.work_queue.put((func, args))

class ThreadManger(Thread):
    """定义线程类,继承threading.Thread"""
    def __init__(self, work_queue):
        Thread.__init__(self)
        self.work_queue = work_queue
        self.daemon = True

    def run(self):
        # 启动线程
        while True:
            target, args = self.work_queue.get()
            target(*args)
            self.work_queue.task_done()

# 创建一个有4个线程的线程池
thread_pool = ThreadPoolManger(4)

# 处理http请求,这里简单返回200 hello world
def handle_request(conn_socket):
    recv_data = conn_socket.recv(1024)
    reply = 'HTTP/1.1 200 OK \r\n\r\n'
    reply += 'hello world'
    print ('thread %s is running ' % threading.current_thread().name)
    conn_socket.send(reply)
    conn_socket.close()

# 循环等待接收客户端请求
while True:
    # 阻塞等待请求
    conn_socket, addr = s.accept()
    # 一旦有请求了,把socket扔到我们指定处理函数handle_request处理,等待线程池分配线程处理
    thread_pool.add_job(handle_request, *(conn_socket, ))

s.close()

python代码,使用import threadpool

'''
import time
def sayhello(str):
    print ("Hello ",str)
    time.sleep(2)

name_list =['aa','bb','cc']
start_time = time.time()
for i in range(len(name_list)):
    sayhello(name_list[i])
print ('%d second'% (time.time()-start_time))
'''
import time
import threadpool
def sayhello(str):
    print ("Hello ",str)
    time.sleep(2)

name_list =['aa','bb','cc']
start_time = time.time()
pool = threadpool.ThreadPool(10)
requests = threadpool.makeRequests(sayhello, name_list)
[pool.putRequest(req) for req in requests]
pool.wait()
print ('%d second'% (time.time()-start_time))

python垃圾回收机制

python采用的是引用计数机制为主,标记-清除和分代收集两种机制为辅的策略。
python里每一个东西都是对象,它们的核心就是一个结构体:PyObject

typedef struct_object {
            int ob_refcnt;
            struct_typeobject *ob_type;
        }PyObject;

PyObject是每个对象必有的内容,其中ob_refcnt就是做为引用计数。当一个对象有新的引用时,它的ob_refcnt就会增加,当引用它的对象被删除,它的ob_refcnt就会减少,引用计数为0时,该对象生命就结束了。
计数机制
优点:
1、简单
2、实时性:一旦没有引用,内存就直接释放了。不用像其他机制等到特定时机。实时性还带来一个好处:处理回收内存的时间分摊到了平时。
缺点:
1、维护引用计数消耗资源
2、循环引用
标记-清除机制
标记-清除机制,顾名思义,首先标记对象(垃圾检测),然后清除垃圾(垃圾回收)。
分代机制
将回收对象分成数个代,每个代就是一个链表(集合),代进行标记-清除的时间与代内对象存活时间成正比例关系。

回收对象的组织

xrange与range的区别

首先我们看看range: range([start,] stop[, step]),根据start与stop指定的范围以及step设定的步长,生成一个序列。注意这里是生成一个序列。

xrange的用法与range相同,即xrange([start,] stop[, step])根据start与stop指定的范围以及step设定的步长,他所不同的是xrange并不是生成序列,而是作为一个生成器。即他的数据生成一个取出一个。

所以相对来说,xrange比range性能优化很多,因为他不需要一下子开辟一块很大的内存,特别是数据量比较大的时候。

注意:
1、xrange和range这两个基本是使用在循环的时候。
2、 当需要输出一个列表的时候,就必须要使用range了。

反转字符串:

第一种:使用字符串切片

result = s[::-1]

第二种:使用列表的reverse方法

l = list(s)
l.reverse()
result = "".join(l)
l = list(s)
result = "".join(l[::-1])

第三种:使用reduce

result = reduce(lambda x,y:y+x,s)

第四种:使用递归函数

def func(s):
    if len(s) <1:
        return s
    return func(s[1:])+s[0]
result = func(s)

第五种:使用栈

def func(s):
    l = list(s) #模拟全部入栈
    result = ""
    while len(l)>0:
        result += l.pop() #模拟出栈
    return result
result = func(s)

第六种:for循环

def func(s):
    result = ""
    max_index = len(s)-1
    for index,value in enumerate(s):
        result += s[max_index-index]
    return result
result = func(s)

python 求1到100之间的素数

list1 = []
i = 2
for i in range(2,101):
    j = 2
    for j in range (2,i):
        if i%j == 0:
            break
    else:
        list1.append(i)
print(list1)
[2, 3, 5, 7, 11, 13, 17, 19, 23, 29, 31, 37, 41, 43, 47, 53, 59, 61, 67, 71, 73, 79, 83, 89, 97]

二叉树的深度

class Solution:
    def TreeDepth(self, pRoot):
        # write code here
        if pRoot == None:
            return 0
        lDepth = self.TreeDepth(pRoot.left)
        rDepth = self.TreeDepth(pRoot.right)
        return max(lDepth,rDepth)+1

二叉树的深度搜索

def depth_tree(tree_node):
    """
    # 深度优先过程
    :param tree_node:
    :return:
    """
    if tree_node is not None:
        print(tree_node._data)
        if tree_node._left is not None:
            return depth_tree(tree_node._left)
        if tree_node._right is not None:
            return depth_tree(tree_node._right)

二叉树的广度搜索

def level_queue(root):
    """
    # 广度优先过程
    :param root:
    :return:
    """
    if root is None:
        return
    my_queue = []
    node = root
    my_queue.append(node)
    while my_queue:
        node = my_queue.pop(0)
        print(node.elem)
        if node.lchild is not None:
            my_queue.append(node.lchild)
        if node.rchild is not None:
            my_queue.append(node.rchild)

将终端、控制台的信息保存在log中

1.使用sys,只保存不显示

import sys
f = open("test.log","a")
sys.stdout = f

2.使用logging.basicConfig,只保存不显示

logging.basicConfig(level=logging.DEBUG,#控制台打印的日志级别
                    filename='new.log',
                    filemode='a',##模式,有w和a,默认a
                    format=
                    '%(asctime)s - %(pathname)s[line:%(lineno)d] - %(levelname)s: %(message)s'#日志格式
                    level=logging.INFO #级别
                    )
logging.debug('debug 信息')
logging.info('info 信息')
logging.warning('warning 信息')
logging.error('error 信息')
logging.critical('critial 信息')

3.使用script和exit,script开始保存,exit停止保存

script. -a test.txt/.log  
exit

4.使用tee

python -u test.py | tee -a test.log 或者
nohup python -u test.py > test.log 2>&1 & 

nohup 放在命令的开头,表示不挂起(no hang up)进程也继续保持运行状态,一般配合&符号一起使用。如nohup command &,&放在命令到结尾,表示后台运行,防止终端一直被某个进程占用,这样终端可以执行别到任务。

linux命令

常用命令在这里: https://blog.csdn.net/weixin_43304184/article/details/85102655
查看文件大小命令
df -h
du -h --max-depth=1 /home
https://www.cnblogs.com/lixuwu/p/5944062.html
查看文件多少行命令
wc -l filename 就是查看文件里有多少行
wc -w filename 看文件里有多少个word。
wc -L filename 文件里最长的那一行是多少个字。

CUDA编程

简介

#include<stdio.h>

__global__ void add(int a, int b, int *c) {
	*c = a + b;
}
int main() {
	int c;
	int *dev_c;
	cudaMalloc((void**)&dev_c, sizeof(int));
	add << <1, 1 >> >(2, 7, dev_c);
	cudaMemcpy(&c, dev_c, sizeof(int), cudaMemcpyDeviceToHost);
	printf("2 + 7 = %d", c);
	return 0;
}

函数的定义带有了__global__这个标签,表示这个函数是在GPU上运行,这里涉及了GPU和主机之间的内存交换了,cudaMalloc是在GPU的内存里开辟一片空间,然后通过操作之后,这个内存里有了计算出来内容,再通过cudaMemcpy这个函数把内容从GPU复制出来。就是这么简单。
并行编程 kernel.cu

#include "cuda_runtime.h"
#include "device_launch_parameters.h"

#include <stdio.h>

cudaError_t addWithCuda(int *c, const int *a, const int *b, unsigned int size);

__global__ void addKernel(int *c, const int *a, const int *b)
{
    int i = threadIdx.x;
    c[i] = a[i] + b[i];
}

int main()
{
    const int arraySize = 5;
    const int a[arraySize] = { 1, 2, 3, 4, 5 };
    const int b[arraySize] = { 10, 20, 30, 40, 50 };
    int c[arraySize] = { 0 };

    // Add vectors in parallel.
    cudaError_t cudaStatus = addWithCuda(c, a, b, arraySize);
    if (cudaStatus != cudaSuccess) {
        fprintf(stderr, "addWithCuda failed!");
        return 1;
    }

    printf("{1,2,3,4,5} + {10,20,30,40,50} = {%d,%d,%d,%d,%d}\n",
        c[0], c[1], c[2], c[3], c[4]);

    // cudaDeviceReset must be called before exiting in order for profiling and
    // tracing tools such as Nsight and Visual Profiler to show complete traces.
    cudaStatus = cudaDeviceReset();
    if (cudaStatus != cudaSuccess) {
        fprintf(stderr, "cudaDeviceReset failed!");
        return 1;
    }

    return 0;
}

// Helper function for using CUDA to add vectors in parallel.
cudaError_t addWithCuda(int *c, const int *a, const int *b, unsigned int size)
{
    int *dev_a = 0;
    int *dev_b = 0;
    int *dev_c = 0;
    cudaError_t cudaStatus;

    // Choose which GPU to run on, change this on a multi-GPU system.
    cudaStatus = cudaSetDevice(0);
    if (cudaStatus != cudaSuccess) {
        fprintf(stderr, "cudaSetDevice failed!  Do you have a CUDA-capable GPU installed?");
        goto Error;
    }

    // Allocate GPU buffers for three vectors (two input, one output)    .
    cudaStatus = cudaMalloc((void**)&dev_c, size * sizeof(int));
    if (cudaStatus != cudaSuccess) {
        fprintf(stderr, "cudaMalloc failed!");
        goto Error;
    }

    cudaStatus = cudaMalloc((void**)&dev_a, size * sizeof(int));
    if (cudaStatus != cudaSuccess) {
        fprintf(stderr, "cudaMalloc failed!");
        goto Error;
    }

    cudaStatus = cudaMalloc((void**)&dev_b, size * sizeof(int));
    if (cudaStatus != cudaSuccess) {
        fprintf(stderr, "cudaMalloc failed!");
        goto Error;
    }

    // Copy input vectors from host memory to GPU buffers.
    cudaStatus = cudaMemcpy(dev_a, a, size * sizeof(int), cudaMemcpyHostToDevice);
    if (cudaStatus != cudaSuccess) {
        fprintf(stderr, "cudaMemcpy failed!");
        goto Error;
    }

    cudaStatus = cudaMemcpy(dev_b, b, size * sizeof(int), cudaMemcpyHostToDevice);
    if (cudaStatus != cudaSuccess) {
        fprintf(stderr, "cudaMemcpy failed!");
        goto Error;
    }

    // Launch a kernel on the GPU with one thread for each element.
    addKernel<<<1, size>>>(dev_c, dev_a, dev_b);

    // Check for any errors launching the kernel
    cudaStatus = cudaGetLastError();
    if (cudaStatus != cudaSuccess) {
        fprintf(stderr, "addKernel launch failed: %s\n", cudaGetErrorString(cudaStatus));
        goto Error;
    }
    
    // cudaDeviceSynchronize waits for the kernel to finish, and returns
    // any errors encountered during the launch.
    cudaStatus = cudaDeviceSynchronize();
    if (cudaStatus != cudaSuccess) {
        fprintf(stderr, "cudaDeviceSynchronize returned error code %d after launching addKernel!\n", cudaStatus);
        goto Error;
    }

    // Copy output vector from GPU buffer to host memory.
    cudaStatus = cudaMemcpy(c, dev_c, size * sizeof(int), cudaMemcpyDeviceToHost);
    if (cudaStatus != cudaSuccess) {
        fprintf(stderr, "cudaMemcpy failed!");
        goto Error;
    }

Error:
    cudaFree(dev_c);
    cudaFree(dev_a);
    cudaFree(dev_b);
    
    return cudaStatus;
}

算法

n位字符串,循环右移m位,要求时间复杂度线性,空间复杂度为o(n)

分析:首先,循环左移的意思类似于二进制串的循环左移,对于给定的字符串S(此处的字符串仅是指字符组成的串,并不是指编程语言中的字符串类型,因为大部分编程语言中的字符串类型是无法对字符串的某一字符进行修改的,只能对整个串进行修改),如S=‘abcdefghijkl’,对其循环左移3位,则变为S=‘defghijklabc’。通过对例子分析,循环左移3位等价于循环右移了len(S)-3=9位。
其次,其要求空间复杂度为O(1),因此不能通过对字符串分段复制再拼接的方式实现,因为该种方法的空间复杂度为O(n).
最后,具体的实现方式与采用的数据结构有关系,若对字符串的存储是通过链表进行的,则实现循环左移的方式很简单,只需将第m+1个节点变为链首,前m个节点拼接到最后一个节点后面即可。
如果字符串是通过数组的方式进行存储,则可采用三次反转的方式,首先分别对前m个字符、后n-m个字符进行反转(n为字符串长度),然后对整个串再次进行反转即可。

一条语句判断数x是否2的n次幂

二进制

int fun(int x)
{
    return !(x&(x-1));
}

筛子六个面,每个面的概率不一样,要求实现一个掷筛子的函数。

1.choice

np.random.seed(1)
p = np.array([0.1, 0.1, 0.2, 0.2, 0.2, 0.2])
index = np.random.choice([1, 2, 3, 4, 5, 6], p = p.ravel())

2.uniform:随机生成0-1数,根据所需概率划分0-1区间
def random_pick(some_list,probabilities):
  x=random.uniform(0,1)
  cumulative_probability=0.0
  for item,item_probability in zip(some_list,probabilities):
    cumulative_probability+=item_probability
    if x < cumulative_probability: break
  return item

手写logistics回归

import torch
from torch.autograd import Variable
import matplotlib.pyplot as plt
from torch import nn

# Part I: 创建数据
N = torch.ones(100, 2)  # 训练样本数
x0 = Variable(torch.normal(2 * N, 1))
y0 = Variable(torch.zeros(100, 1))
x1 = Variable(torch.normal(-2 * N, 1))
y1 = Variable(torch.ones(100, 1))
x = torch.cat((x0, x1), 0).type(torch.FloatTensor)
y = torch.cat((y0, y1), 0).type(torch.FloatTensor)

## 作出散点图
fig, ax = plt.subplots()
labels = ['class 0', 'class 1']
ax.scatter(x.numpy()[0:len(x0), 0], x.numpy()[0:len(x0), 1], label=labels[0])
ax.scatter(x.numpy()[len(x0):len(x), 0], x.numpy()[len(x0):len(x), 1], label=labels[1])
ax.legend()
# Part II 使用PyTorch Tensor实现Logistic回归

## 初始化w和b
w = Variable(torch.zeros(2, 1), requires_grad=True)
b = Variable(torch.zeros(1, 1), requires_grad=True)
EPOCHS = 200
likelihood = []
lr = 0.01
for epoch in range(EPOCHS):
    A = 1 / (1 + torch.exp(-(x.mm(w) + b)))  # Logistic函数
    J = -torch.mean(y * torch.log(A) + (1 - y) * torch.log(1 - A))  # 对数似然函数
    likelihood.append(-J.data.numpy().item())
    J.backward()  # 求似然函数对w和b的梯度
    w.data = w.data - lr * w.grad.data  # 更新w
    w.grad.data.zero_()
    b.data = b.data - lr * b.grad.data  # 更新b
    b.grad.data.zero_()

## 作出似然函数J的图像:
plt.plot(likelihood)
plt.ylabel("lieklihood")
plt.xlabel("epoch")
plt.show()

## 作出分类边界图像:  w1*x1+w2*x2+b=0
xa = list(range(-4, 5))
xb = []
for item in xa:
    xb.append(-(b.data + item * w[0]) / w[1])
fig, ax = plt.subplots()
labels = ['class 0', 'class 1']
ax.scatter(x.numpy()[0:len(x0), 0], x.numpy()[0:len(x0), 1], label=labels[0])
ax.scatter(x.numpy()[len(x0):len(x), 0], x.numpy()[len(x0):len(x), 1], label=labels[1])
ax.legend()
plt.plot(xa, xb)
plt.show()


# PartII 使用nn.Module实现Logistic回归

## 搭建nn模型,梯度下降求解参数w和b
class Logistic(nn.Module):
    def __init__(self):
        super(Logistic, self).__init__()
        self.linear = nn.Linear(2, 1)
        self.sigmoid = nn.Sigmoid()

    def forward(self, x):
        y_pred = self.linear(x)
        y_pred = self.sigmoid(y_pred)
        return y_pred


model = Logistic()
criterion = nn.BCELoss()
optimizer = torch.optim.SGD(model.parameters(), lr=0.001)
EPOCHS = 1000
costs = []
for epoch in range(EPOCHS):
    x = Variable(x)
    y = Variable(y)
    out = model(x)
    loss = criterion(out, y)
    costs.append(loss.data.item())
    optimizer.zero_grad()
    loss.backward()
    optimizer.step()

## 做出损失函数的图像
plt.plot(costs)
plt.show(range(len(costs)), costs)

## 做出分类边界的图像
w1, w2 = model.linear.weight[0]
b = model.linear.bias.item()
plot_x = range(-5, 6, 1)
plot_y = [-(w1 * item + b) / w2 for item in plot_x]

fig, ax = plt.subplots()
labels = ['class 0', 'class 1']
ax.scatter(x.numpy()[0:len(x0), 0], x.numpy()[0:len(x0), 1], label=labels[0])
ax.scatter(x.numpy()[len(x0):len(x), 0], x.numpy()[len(x0):len(x), 1], label=labels[1])
ax.legend()
ax.plot(plot_x, plot_y)

leetcode-198强盗抢劫

class Solution {
public:
    int rob(vector<int>& nums) {
        int length = nums.size();
        if(length == 0) return 0;
        if(length == 1) return nums[0];

        int previous = nums[0];
        int current = max(nums[0], nums[1]);
        for(int i=2; i<length; ++i){
            int temp = current;
            current = max(previous+nums[i], current);
            previous = temp;
        }

        return current;
    }
};

一个1-n的数,少了一个,找出来

1.求和思路:

public int findLost(int[] a,int n){
        int sum = 0;
        int sum1 = 0;
        for(int i=0;i<n;i++){
            sum+=a[i];
            sum1+=i;
        }
        int res = sum1+n-sum;
        return res;
    }

2.用异或的方法。任何数异或自己都等于0,任何数异或0都等于他自己。异或两次即可

public int findLost(int[] a,int n){
        int result = 0;
        for (int i = 0; i < a.length; i++) {
            result = result  ^ a[i];
        }
        for (int i = 0; i < n; i++) {
            result=result^(i+1);
        }
        return result;
    }

3.用n去分别取减1,2,…,n,看是否为0

给定一个数字n,输出包含n对括号的所有合法字符串

首先我们看怎么样的括号字符串是合法的呢?很容易观察到规律,就是从前到后扫描,右括号的数永远不大于左括号的数,到最后左括号的数和右括号的数是相等的。要考虑输出n对所有的合法的括号字符串,那我们可以用分裂的思路,一个字符串往后加:

如果左括号的数<n,则可以再加入左括号
如果右括号的数<左括号的数,则可以加入右括号

#include <iostream>
#include <string>
 
void print_legal_brackets(int n, std::string stack = "", int left = 0, int right = 0) {
    if (left == n && right == n) {
        std::cout << stack << std::endl;
        return;
    }   
 
    if (left < n) {
        print_legal_brackets(n, stack + "(",  left + 1, right);
    }   
 
    if (right < left) { 
        print_legal_brackets(n, stack + ")", left, right + 1); 
    } 
} 
 
int main(int argc, char *argv[]) { 
    int n = 0; 
    while (std::cin >> n) {
        print_legal_brackets(n);
    }   
    return 0;
}

链表逆序

有序数组的交集

求数组第K大值的下标

怎么判断链表是否有环

读入一个字符串str,输出字符串str中的连续最长的数字串

例如:输入abcd12345ed125ss123456789aa123456 输出123456789

#include<iostream>
using namespace std;
#include<string>

void Findnum(string str)
{
    size_t i=0;
    int count=0;//计数
    int maxnum=0;//标记最大数字串长度
    int pos=0;最大数字串的开始位置
    while(i<str.size())
    {
        while(!isdigit(str[i]))
           i++;//不是数字就往后走
        while(isdigit(str[i]))
        {
            count++;
            i++;
        }
        if(count>maxnum)
        {
            maxnum=count;//更新maxnum
            pos=i-maxnum;//标记pos
        }
        count=0;
    }
    for(int j=pos;j<pos+maxnum;j++)
        cout<<str[j];
    cout<<endl;
}
int main()
{
    string str;
    cin>>str;
    Findnum(str);
    return 0;
}

青蛙过河

一只青蛙想要过河。 假定河流被等分为 x 个单元格,并且在每一个单元格内都有可能放有一石子(也有可能没有)。 青蛙可以跳上石头,但是不可以跳入水中。

给定石子的位置列表(用单元格序号升序表示), 请判定青蛙能否成功过河(即能否在最后一步跳至最后一个石子上)。 开始时, 青蛙默认已站在第一个石子上,并可以假定它第一步只能跳跃一个单位(即只能从单元格1跳至单元格2)。

如果青蛙上一步跳跃了 k 个单位,那么它接下来的跳跃距离只能选择为 k - 1、k 或 k + 1个单位。 另请注意,青蛙只能向前方(终点的方向)跳跃。

class Solution(object):
    def canCross(self, stones):
        """
        :type stones: List[int]
        :rtype: bool
        """
        length = len(stones)
        if stones[1]-stones[0]!=1:
            return False
        jump = [[] for _ in range(length)]
        jump[1].append(1)
        for i in range(1, length):
            if not jump[i]:
                continue
            if i == length - 1:
                return True
            j = i + 1
            while j<length and stones[j] - stones[i] <= max(jump[i])+1:
                temp = stones[j] - stones[i]
                if temp in jump[i] or temp - 1 in jump[i] or temp + 1 in jump[i]:
                    jump[j].append(temp)
                j += 1
        return False

最大整除子集

给出一个由无重复的正整数组成的集合,找出其中最大的整除子集,子集中任意一对 (Si,Sj) 都要满足:Si % Sj = 0 或 Sj % Si = 0。
如果有多个目标子集,返回其中任何一个均可。

def largestDivisibleSubset(self, nums):
        """
        :type nums: List[int]
        :rtype: List[int]
        """
        if not nums: return nums
        if len(nums) == 1: return nums
        l = len(nums)
        nums.sort()

        dp = [[i] for i in nums]
        
        for i in range(1, l):
            for j in range(i-1, -1, -1):
                if not nums[i]%nums[j]:
                    dp[i] = max(dp[j] + [nums[i]], dp[i],key=len)

        return max(dp,key=len)

只有两个键的键盘

最初在一个记事本上只有一个字符 ‘A’。你每次可以对这个记事本进行两种操作:

Copy All (复制全部) : 你可以复制这个记事本中的所有字符(部分的复制是不允许的)。
Paste (粘贴) : 你可以粘贴你上一次复制的字符。
给定一个数字 n 。你需要使用最少的操作次数,在记事本中打印出恰好 n 个 ‘A’。输出能够打印出 n 个 ‘A’ 的最少操作次数。

def minSteps(self, n):
        """
        :type n: int
        :rtype: int
        """
        dp = [1001] * 1001
        dp[1] = 0
        for i in range(2, n+1):
            dp[i] = min(dp[j] + i // j for j in range(1,i) if i % j == 0)
        return dp[n]

丑数

编写一个程序,找出第 n 个丑数。
丑数就是只包含质因数 2, 3, 5 的正整数。

def nthUglyNumber(self, n):
        """
        :type n: int
        :rtype: int
        """
        res = [1]
        idx2 = 0
        idx3 = 0
        idx5 = 0
        for i in range(n-1):
            res.append(min(res[idx2]*2,res[idx3]*3,res[idx5]*5))
            if res[-1] == res[idx2]*2:
                idx2 += 1
            if res[-1] == res[idx3]*3:
                idx3 += 1
            if res[-1] == res[idx5]*5:
                idx5 += 1
        return res[-1]

给定一组非负整数,重新排列它们的顺序使之组成一个最大的整数。

class Solution(object):
    def strCmp(self, s1, s2):
        if s1 + s2 > s2 + s1:
            return 1
        return -1
    def largestNumber(self, nums):
        """
        :type nums: List[int]
        :rtype: str
        
        way2:
        s = ''
        for i in range(len(nums)-1):
            for j in range(i+1,len(nums)):
                if int(str(nums[i])+str(nums[j])) < int(str(nums[j])+str(nums[i])):
                    nums[i],nums[j] = nums[j],nums[i]
        for x in (nums):
            s += str(x)
        return str(int(s))
        """
        s = set(nums)
        if len(s) == 1 and 0 in s: # 处理[0,0]这种用例
            return "0"
        nums = sorted([str(n) for n in nums],cmp=self.strCmp, reverse=True )
        return "".join(nums)
  • 1
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值