算法工程師知識點總結

八種排序算法的時間複雜度複雜度

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)
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章