五萬字長文 C C++ 面試知識總結(中)

本文是:五萬字長文:C/C++ 面試知識總結(上)的續篇java

C++ 11

  1. shared_ptrnode

  2. unique_ptrpython

  3. weak_ptrios

  4. auto_ptr(被 C++11 棄用)c++

  • Class shared_ptr 實現共享式擁有(shared ownership)概念。多個智能指針指向相同對象,該對象和其相關資源會在 「最後一個 reference 被銷燬」 時被釋放。爲了在結構較複雜的情景中執行上述工做,標準庫提供 weak_ptr、bad_weak_ptr 和 enable_shared_from_this 等輔助類。git

  • Class unique_ptr 實現獨佔式擁有(exclusive ownership)或嚴格擁有(strict ownership)概念,保證同一時間內只有一個智能指針能夠指向該對象。你能夠移交擁有權。它對於避免內存泄漏(resource leak)——如 new 後忘記 delete ——特別有用。程序員

shared_ptr

多個智能指針能夠共享同一個對象,對象的最末一個擁有着有責任銷燬對象,並清理與該對象相關的全部資源。github

  • 支持定製型刪除器(custom deleter),可防範 Cross-DLL 問題(對象在動態連接庫(DLL)中被 new 建立,卻在另外一個 DLL 內被 delete 銷燬)、自動解除互斥鎖
weak_ptr

weak_ptr 容許你共享但不擁有某對象,一旦最末一個擁有該對象的智能指針失去了全部權,任何 weak_ptr 都會自動成空(empty)。所以,在 default 和 copy 構造函數以外,weak_ptr 只提供 「接受一個 shared_ptr」 的構造函數。面試

  • 可打破環狀引用(cycles of references,兩個其實已經沒有被使用的對象彼此互指,使之看似還在 「被使用」 的狀態)的問題
unique_ptr

unique_ptr 是 C++11 纔開始提供的類型,是一種在異常時能夠幫助避免資源泄漏的智能指針。採用獨佔式擁有,意味着能夠確保一個對象和其相應的資源同一時間只被一個 pointer 擁有。一旦擁有着被銷燬或編程 empty,或開始擁有另外一個對象,先前擁有的那個對象就會被銷燬,其任何相應資源亦會被釋放。算法

  • unique_ptr 用於取代 auto_ptr
auto_ptr

被 c++11 棄用,緣由是缺少語言特性如 「針對構造和賦值」 的 std::move 語義,以及其餘瑕疵。

auto_ptr 與 unique_ptr 比較
  • auto_ptr 能夠賦值拷貝,複製拷貝後全部權轉移;unqiue_ptr 無拷貝賦值語義,但實現了move 語義;

  • auto_ptr 對象不能管理數組(析構調用 delete),unique_ptr 能夠管理數組(析構調用 delete[] );

強制類型轉換運算符

MSDN . 強制轉換運算符:t.cn/E4WIt5W

static_cast

  • 用於非多態類型的轉換

  • 不執行運行時類型檢查(轉換安全性不如 dynamic_cast)

  • 一般用於轉換數值數據類型(如 float -> int)

  • 能夠在整個類層次結構中移動指針,子類轉化爲父類安全(向上轉換),父類轉化爲子類不安全(由於子類可能有不在父類的字段或方法)

向上轉換是一種隱式轉換。

dynamic_cast

  • 用於多態類型的轉換

  • 執行行運行時類型檢查

  • 只適用於指針或引用

  • 對不明確的指針的轉換將失敗(返回 nullptr),但不引起異常

  • 能夠在整個類層次結構中移動指針,包括向上轉換、向下轉換

const_cast

  • 用於刪除 const、volatile 和 __unaligned 特性(如將 const int 類型轉換爲 int 類型 )

reinterpret_cast

  • 用於位的簡單從新解釋

  • 濫用 reinterpret_cast 運算符可能很容易帶來風險。 除非所需轉換自己是低級別的,不然應使用其餘強制轉換運算符之一。

  • 容許將任何指針轉換爲任何其餘指針類型(如 char*int* 或 One_class*Unrelated_class* 之類的轉換,但其自己並不安全)

  • 也容許將任何整數類型轉換爲任何指針類型以及反向轉換。

  • reinterpret_cast 運算符不能丟掉 const、volatile 或 __unaligned 特性。

  • reinterpret_cast 的一個實際用途是在哈希函數中,即,經過讓兩個不一樣的值幾乎不以相同的索引結尾的方式將值映射到索引。

bad_cast

  • 因爲強制轉換爲引用類型失敗,dynamic_cast 運算符引起 bad_cast 異常。

bad_cast 使用

try {  
    Circle& ref_circle = dynamic_cast<Circle&>(ref_shape);   
}  
catch (bad_cast b) {  
    cout << "Caught: " << b.what();  
} 
複製代碼

運行時類型信息 (RTTI)

dynamic_cast

  • 用於多態類型的轉換

typeid

  • typeid 運算符容許在運行時肯定對象的類型

  • type_id 返回一個 type_info 對象的引用

  • 若是想經過基類的指針得到派生類的數據類型,基類必須帶有虛函數

  • 只能獲取對象的實際類型

type_info

  • type_info 類描述編譯器在程序中生成的類型信息。 此類的對象能夠有效存儲指向類型的名稱的指針。 type_info 類還可存儲適合比較兩個類型是否相等或比較其排列順序的編碼值。 類型的編碼規則和排列順序是未指定的,而且可能因程序而異。

  • 頭文件:typeinfo

typeid、type_info 使用

class Flyable // 能飛的 {
public:
    virtual void takeoff() = 0;     // 起飛
    virtual void land() = 0;        // 降落
};
class Bird : public Flyable         // 鳥
{
public:
    void foraging() {...}           // 覓食
    virtual void takeoff() {...}
    virtual void land() {...}
};
class Plane : public Flyable        // 飛機
{
public:
    void carry() {...}              // 運輸
    virtual void take off() {...}
    virtual void land() {...}
};

class type_info {
public:
    const char* name() const;
    bool operator == (const type_info & rhs) const;
    bool operator != (const type_info & rhs) const;
    int before(const type_info & rhs) const;
    virtual ~type_info();
private:
    ...
};

class doSomething(Flyable *obj) // 作些事情 {
    obj->takeoff();

    cout << typeid(*obj).name() << endl;        // 輸出傳入對象類型("class Bird" or "class Plane")

    if(typeid(*obj) == typeid(Bird))            // 判斷對象類型
    {
        Bird *bird = dynamic_cast<Bird *>(obj); // 對象轉化
        bird->foraging();
    }

    obj->land();
};
複製代碼

Effective C++

  1. 視 C++ 爲一個語言聯邦(C、Object-Oriented C++、Template C++、STL)

  2. 寧肯以編譯器替換預處理器(儘可能以 constenuminline 替換 #define

  3. 儘量使用 const

  4. 肯定對象被使用前已先被初始化(構造時賦值(copy 構造函數)比 default 構造後賦值(copy assignment)效率高)

  5. 瞭解 C++ 默默編寫並調用哪些函數(編譯器暗自爲 class 建立 default 構造函數、copy 構造函數、copy assignment 操做符、析構函數)

  6. 若不想使用編譯器自動生成的函數,就應該明確拒絕(將不想使用的成員函數聲明爲 private,而且不予實現)

  7. 爲多態基類聲明 virtual 析構函數(若是 class 帶有任何 virtual 函數,它就應該擁有一個 virtual 析構函數)

  8. 別讓異常逃離析構函數(析構函數應該吞下不傳播異常,或者結束程序,而不是吐出異常;若是要處理異常應該在非析構的普通函數處理)

  9. 毫不在構造和析構過程當中調用 virtual 函數(由於這類調用從不降低至 derived class)

  10. 令 operator= 返回一個 reference to *this (用於連鎖賦值)

  11. 在 operator= 中處理 「自我賦值」

  12. 賦值對象時應確保複製 「對象內的全部成員變量」 及 「全部 base class 成分」(調用基類複製構造函數)

  13. 以對象管理資源(資源在構造函數得到,在析構函數釋放,建議使用智能指針,資源取得時機即是初始化時機(Resource Acquisition Is Initialization,RAII))

  14. 在資源管理類中當心 copying 行爲(廣泛的 RAII class copying 行爲是:抑制 copying、引用計數、深度拷貝、轉移底部資源擁有權(相似 auto_ptr))

  15. 在資源管理類中提供對原始資源(raw resources)的訪問(對原始資源的訪問可能通過顯式轉換或隱式轉換,通常而言顯示轉換比較安全,隱式轉換對客戶比較方便)

  16. 成對使用 new 和 delete 時要採起相同形式(new 中使用 [] 則 delete []new 中不使用 [] 則 delete

  17. 以獨立語句將 newed 對象存儲於(置入)智能指針(若是不這樣作,可能會由於編譯器優化,致使難以察覺的資源泄漏)

  18. 讓接口容易被正確使用,不易被誤用(促進正常使用的辦法:接口的一致性、內置類型的行爲兼容;阻止誤用的辦法:創建新類型,限制類型上的操做,約束對象值、消除客戶的資源管理責任)

  19. 設計 class 猶如設計 type,須要考慮對象建立、銷燬、初始化、賦值、值傳遞、合法值、繼承關係、轉換、通常化等等。

  20. 寧以 pass-by-reference-to-const 替換 pass-by-value (前者一般更高效、避免切割問題(slicing problem),但不適用於內置類型、STL迭代器、函數對象)

  21. 必須返回對象時,別妄想返回其 reference(毫不返回 pointer 或 reference 指向一個 local stack 對象,或返回 reference 指向一個 heap-allocated 對象,或返回 pointer 或 reference 指向一個 local static 對象而有可能同時須要多個這樣的對象。)

  22. 將成員變量聲明爲 private(爲了封裝、一致性、對其讀寫精確控制等)

  23. 寧以 non-member、non-friend 替換 member 函數(可增長封裝性、包裹彈性(packaging flexibility)、機能擴充性)

  24. 若全部參數(包括被this指針所指的那個隱喻參數)皆需要類型轉換,請爲此採用 non-member 函數

  25. 考慮寫一個不拋異常的 swap 函數

  26. 儘量延後變量定義式的出現時間(可增長程序清晰度並改善程序效率)

  27. 儘可能少作轉型動做(舊式:(T)expression、T(expression);新式:const_cast(expression)、dynamic_cast(expression)、reinterpret_cast(expression)、static_cast(expression);儘可能避免轉型、注重效率避免 dynamic_casts、儘可能設計成無需轉型、可把轉型封裝成函數、寧肯用新式轉型)

  28. 避免使用 handles(包括 引用、指針、迭代器)指向對象內部(以增長封裝性、使 const 成員函數的行爲更像 const、下降 「虛吊號碼牌」(dangling handles,如懸空指針等)的可能性)

  29. 爲 「異常安全」 而努力是值得的(異常安全函數(Exception-safe functions)即便發生異常也不會泄露資源或容許任何數據結構敗壞,分爲三種可能的保證:基本型、強列型、不拋異常型)

  30. 透徹瞭解 inlining 的裏裏外外(inlining 在大多數 C++ 程序中是編譯期的行爲;inline 函數是否真正 inline,取決於編譯器;大部分編譯器拒絕太過複雜(如帶有循環或遞歸)的函數 inlining,而全部對 virtual 函數的調用(除非是最平淡無奇的)也都會使 inlining 落空;inline 形成的代碼膨脹可能帶來效率損失;inline 函數沒法隨着程序庫的升級而升級)

  31. 將文件間的編譯依存關係降至最低(若是使用 object references 或 object pointers 能夠完成任務,就不要使用 objects;若是能過夠,儘可能以 class 聲明式替換 class 定義式;爲聲明式和定義式提供不一樣的頭文件)

  32. 肯定你的 public 繼承塑模出 is-a(是一種)關係(適用於 base classes 身上的每一件事情必定適用於 derived classes 身上,由於每個 derived class 對象也都是一個 base class 對象)

  33. 避免遮掩繼承而來的名字(可以使用 using 聲明式或轉交函數(forwarding functions)來讓被遮掩的名字再見天日)

  34. 區分接口繼承和實現繼承(在 public 繼承之下,derived classes 老是繼承 base class 的接口;pure virtual 函數只具體指定接口繼承;非純 impure virtual 函數具體指定接口繼承及缺省實現繼承;non-virtual 函數具體指定接口繼承以及強制性實現繼承)

  35. 考慮 virtual 函數之外的其餘選擇(如 Template Method 設計模式的 non-virtual interface(NVI)手法,將 virtual 函數替換爲 「函數指針成員變量」,以 tr1::function成員變量替換 virtual 函數,將繼承體系內的 virtual 函數替換爲另外一個繼承體系內的 virtual 函數)

  36. 毫不從新定義繼承而來的 non-virtual 函數

  37. 毫不從新定義繼承而來的缺省參數值,由於缺省參數值是靜態綁定(statically bound),而 virtual 函數倒是動態綁定(dynamically bound)

  38. 經過複合塑模 has-a(有一個)或 「根據某物實現出」(在應用域(application domain),複合意味 has-a(有一個);在實現域(implementation domain),複合意味着 is-implemented-in-terms-of(根據某物實現出))

  39. 明智而審慎地使用 private 繼承(private 繼承意味着 is-implemented-in-terms-of(根據某物實現出),儘量使用複合,當 derived class 須要訪問 protected base class 的成員,或須要從新定義繼承而來的時候 virtual 函數,或須要 empty base 最優化時,才使用 private 繼承)

  40. 明智而審慎地使用多重繼承(多繼承比單一繼承複雜,可能致使新的歧義性,以及對 virtual 繼承的須要,但確有正當用途,如 「public 繼承某個 interface class」 和 「private 繼承某個協助實現的 class」;virtual 繼承可解決多繼承下菱形繼承的二義性問題,但會增長大小、速度、初始化及賦值的複雜度等等成本)

  41. 瞭解隱式接口和編譯期多態(class 和 templates 都支持接口(interfaces)和多態(polymorphism);class 的接口是以簽名爲中心的顯式的(explicit),多態則是經過 virtual 函數發生於運行期;template 的接口是奠定於有效表達式的隱式的(implicit),多態則是經過 template 具現化和函數重載解析(function overloading resolution)發生於編譯期)

  42. 瞭解 typename 的雙重意義(聲明 template 類型參數是,前綴關鍵字 class 和 typename 的意義徹底相同;請使用關鍵字 typename 標識嵌套從屬類型名稱,但不得在基類列(base class lists)或成員初值列(member initialization list)內以它做爲 basee class 修飾符)

  43. 學習處理模板化基類內的名稱(可在 derived class templates 內經過 this-&gt; 指涉 base class templates 內的成員名稱,或藉由一個明白寫出的 「base class 資格修飾符」 完成)

  44. 將與參數無關的代碼抽離 templates(因類型模板參數(non-type template parameters)而形成代碼膨脹每每能夠經過函數參數或 class 成員變量替換 template 參數來消除;因類型參數(type parameters)而形成的代碼膨脹每每能夠經過讓帶有徹底相同二進制表述(binary representations)的實現類型(instantiation types)共享實現碼)

  45. 運用成員函數模板接受全部兼容類型(請使用成員函數模板(member function templates)生成 「可接受全部兼容類型」 的函數;聲明 member templates 用於 「泛化 copy 構造」 或 「泛化 assignment 操做」 時還須要聲明正常的 copy 構造函數和 copy assignment 操做符)

  46. 須要類型轉換時請爲模板定義非成員函數(當咱們編寫一個 class template,而它所提供之 「與此 template 相關的」 函數支持 「全部參數之隱式類型轉換」 時,請將那些函數定義爲 「class template 內部的 friend 函數」)

  47. 請使用 traits classes 表現類型信息(traits classes 經過 templates 和 「templates 特化」 使得 「類型相關信息」 在編譯期可用,經過重載技術(overloading)實如今編譯期對類型執行 if…else 測試)

  48. 認識 template 元編程(模板元編程(TMP,template metaprogramming)可將工做由運行期移往編譯期,所以得以實現早期錯誤偵測和更高的執行效率;TMP 可被用來生成 「給予政策選擇組合」(based on combinations of policy choices)的客戶定製代碼,也可用來避免生成對某些特殊類型並不適合的代碼)

  49. 瞭解 new-handler 的行爲(set_new_handler 容許客戶指定一個在內存分配沒法得到知足時被調用的函數;nothrow new 是一個頗具侷限的工具,由於它只適用於內存分配(operator new),後繼的構造函數調用仍是可能拋出異常)

Google C++ Style Guide

英文:Google C++ Style Guide  :t.cn/RqhluJP

中文:C++ 風格指南:t.cn/ELDTnur

Google C++ Style Guide 圖

STL

STL 索引

STL 方法含義索引:t.cn/E4WMXXs

STL 容器

容器 底層數據結構 時間複雜度 有無序 可不可重複 其餘
array 數組 隨機讀改 O(1) 無序 可重複 支持快速隨機訪問
vector 數組 隨機讀改、尾部插入、尾部刪除 O(1)
頭部插入、頭部刪除 O(n)
無序 可重複 支持快速隨機訪問
list 雙向鏈表 插入、刪除 O(1)
隨機讀改 O(n)
無序 可重複 支持快速增刪
deque 雙端隊列 頭尾插入、頭尾刪除 O(1) 無序 可重複 一箇中央控制器 + 多個緩衝區,支持首尾快速增刪,支持隨機訪問
stack deque / list 頂部插入、頂部刪除 O(1) 無序 可重複 deque 或 list 封閉頭端開口,不用 vector 的緣由應該是容量大小有限制,擴容耗時
queue deque / list 尾部插入、頭部刪除 O(1) 無序 可重複 deque 或 list 封閉頭端開口,不用 vector 的緣由應該是容量大小有限制,擴容耗時
priority_queue vector + max-heap 插入、刪除 O(log2n) 有序 可重複 vector容器+heap處理規則
set 紅黑樹 插入、刪除、查找 O(log2n) 有序 不可重複
multiset 紅黑樹 插入、刪除、查找 O(log2n) 有序 可重複
map 紅黑樹 插入、刪除、查找 O(log2n) 有序 不可重複
multimap 紅黑樹 插入、刪除、查找 O(log2n) 有序 可重複
hash_set 哈希表 插入、刪除、查找 O(1) 最差 O(n) 無序 不可重複
hash_multiset 哈希表 插入、刪除、查找 O(1) 最差 O(n) 無序 可重複
hash_map 哈希表 插入、刪除、查找 O(1) 最差 O(n) 無序 不可重複
hash_multimap 哈希表 插入、刪除、查找 O(1) 最差 O(n) 無序 可重複

STL 算法

STL 算法

t.cn/aEv0DV

算法 底層算法 時間複雜度 可不可重複
find 順序查找 O(n) 可重複
sort 內省排序 O(n*log2n) 可重複

數據結構

順序結構

順序棧(Sequence Stack)

SqStack.cpp:t.cn/E4WxO0b

順序棧數據結構和圖片

typedef struct {
    ElemType *elem;
    int top;
    int size;
    int increment;
} SqSrack;
複製代碼

隊列(Sequence Queue)

隊列數據結構

typedef struct {
    ElemType * elem;
    int front;
    int rear;
    int maxSize;
}SqQueue;
複製代碼
非循環隊列

非循環隊列圖片

SqQueue.rear++

循環隊列

循環隊列圖片

SqQueue.rear = (SqQueue.rear + 1) % SqQueue.maxSize

順序表(Sequence List)

SqList.cpp

順序表數據結構和圖片

typedef struct {
    ElemType *elem;
    int length;
    int size;
    int increment;
} SqList;
複製代碼

鏈式結構

LinkList.cpp

LinkList_with_head.cpp

鏈式數據結構

typedef struct LNode {
    ElemType data;
    struct LNode *next;
} LNode, *LinkList; 
複製代碼

鏈隊列(Link Queue)

鏈隊列圖片

線性表的鏈式表示

單鏈表(Link List)

單鏈表圖片

雙向鏈表(Du-Link-List)

雙向鏈表圖片

循環鏈表(Cir-Link-List)

循環鏈表圖片

哈希表

HashTable.cpp

概念

哈希函數:H(key): K -> D , key ∈ K

構造方法

  • 直接定址法

  • 除留餘數法

  • 數字分析法

  • 摺疊法

  • 平方取中法

衝突處理方法

  • 鏈地址法:key 相同的用單鏈表連接

  • 開放定址法

  • 線性探測法:key 相同 -> 放到 key 的下一個位置,Hi = (H(key) + i) % m

  • 二次探測法:key 相同 -> 放到 Di = 1^2, -1^2, …, ±(k)^2,(k<=m/2)

  • 隨機探測法:H = (H(key) + 僞隨機數) % m

線性探測的哈希表數據結構

線性探測的哈希表數據結構和圖片

typedef char KeyType;

typedef struct {
    KeyType key;
}RcdType;

typedef struct {
    RcdType *rcd;
    int size;
    int count;
    bool *tag;
}HashTable;
複製代碼

遞歸

概念

函數直接或間接地調用自身

遞歸與分治

  • 分治法

  • 問題的分解

  • 問題規模的分解

  • 折半查找(遞歸)

  • 歸併查找(遞歸)

  • 快速排序(遞歸)

遞歸與迭代

  • 迭代:反覆利用變量舊值推出新值

  • 折半查找(迭代)

  • 歸併查找(迭代)

廣義表

頭尾鏈表存儲表示

廣義表的頭尾鏈表存儲表示和圖片

// 廣義表的頭尾鏈表存儲表示
typedef enum {ATOM, LIST} ElemTag;
// ATOM==0:原子,LIST==1:子表
typedef struct GLNode {
    ElemTag tag;
    // 公共部分,用於區分原子結點和表結點
    union {
        // 原子結點和表結點的聯合部分
        AtomType atom;
        // atom 是原子結點的值域,AtomType 由用戶定義
        struct {
            struct GLNode *hp, *tp;
        } ptr;
        // ptr 是表結點的指針域,prt.hp 和 ptr.tp 分別指向表頭和表尾
    } a;
} *GList, GLNode;
複製代碼
擴展線性鏈表存儲表示

擴展線性鏈表存儲表示和圖片

// 廣義表的擴展線性鏈表存儲表示
typedef enum {ATOM, LIST} ElemTag;
// ATOM==0:原子,LIST==1:子表
typedef struct GLNode1 {
    ElemTag tag;
    // 公共部分,用於區分原子結點和表結點
    union {
        // 原子結點和表結點的聯合部分
        AtomType atom; // 原子結點的值域
        struct GLNode1 *hp; // 表結點的表頭指針
    } a;
    struct GLNode1 *tp;
    // 至關於線性鏈表的 next,指向下一個元素結點
} *GList1, GLNode1;
複製代碼

二叉樹

BinaryTree.cpp

性質

  1. 非空二叉樹第 i 層最多 2(i-1) 個結點 (i >= 1)

  2. 深度爲 k 的二叉樹最多 2k - 1 個結點 (k >= 1)

  3. 度爲 0 的結點數爲 n0,度爲 2 的結點數爲 n2,則 n0 = n2 + 1

  4. 有 n 個結點的徹底二叉樹深度 k = ⌊ log2(n) ⌋ + 1

  5. 對於含 n 個結點的徹底二叉樹中編號爲 i (1 <= i <= n) 的結點

  6. 若 i = 1,爲根,不然雙親爲 ⌊ i / 2 ⌋

  7. 若 2i > n,則 i 結點沒有左孩子,不然孩子編號爲 2i

  8. 若 2i + 1 > n,則 i 結點沒有右孩子,不然孩子編號爲 2i + 1

存儲結構

二叉樹數據結構

typedef struct BiTNode
{
    TElemType data;
    struct BiTNode *lchild, *rchild;
}BiTNode, *BiTree;
複製代碼
順序存儲

二叉樹順序存儲圖片

鏈式存儲

二叉樹鏈式存儲圖片

遍歷方式

  • 先序遍歷

  • 中序遍歷

  • 後續遍歷

  • 層次遍歷

分類

  • 滿二叉樹

  • 徹底二叉樹(堆)

  • 大頂堆:根 >= 左 && 根 >= 右

  • 小頂堆:根 <= 左 && 根 <= 右

  • 二叉查找樹(二叉排序樹):左 < 根 < 右

  • 平衡二叉樹(AVL樹):| 左子樹樹高 - 右子樹樹高 | <= 1

  • 最小失衡樹:平衡二叉樹插入新結點致使失衡的子樹:調整:

  • LL型:根的左孩子右旋

  • RR型:根的右孩子左旋

  • LR型:根的左孩子左旋,再右旋

  • RL型:右孩子的左子樹,先右旋,再左旋

其餘樹及森林

樹的存儲結構

  • 雙親表示法

  • 雙親孩子表示法

  • 孩子兄弟表示法

並查集

一種不相交的子集所構成的集合 S = {S1, S2, …, Sn}

平衡二叉樹(AVL樹)

性質
  • | 左子樹樹高 - 右子樹樹高 | <= 1

  • 平衡二叉樹一定是二叉搜索樹,反之則不必定

  • 最小二叉平衡樹的節點的公式:F(n)=F(n-1)+F(n-2)+1 (1 是根節點,F(n-1) 是左子樹的節點數量,F(n-2) 是右子樹的節點數量)

平衡二叉樹圖片

最小失衡樹

平衡二叉樹插入新結點致使失衡的子樹

調整:

  • LL 型:根的左孩子右旋

  • RR 型:根的右孩子左旋

  • LR 型:根的左孩子左旋,再右旋

  • RL 型:右孩子的左子樹,先右旋,再左旋

紅黑樹

紅黑樹的特徵是什麼?
  1. 節點是紅色或黑色。

  2. 根是黑色。

  3. 全部葉子都是黑色(葉子是 NIL 節點)。

  4. 每一個紅色節點必須有兩個黑色的子節點。(從每一個葉子到根的全部路徑上不能有兩個連續的紅色節點。)(新增節點的父節點必須相同)

  5. 從任一節點到其每一個葉子的全部簡單路徑都包含相同數目的黑色節點。(新增節點必須爲紅)

調整
  1. 變色

  2. 左旋

  3. 右旋

應用
  • 關聯數組:如 STL 中的 map、set
紅黑樹、B 樹、B+ 樹的區別?
  • 紅黑樹的深度比較大,而 B 樹和 B+ 樹的深度則相對要小一些

  • B+ 樹則將數據都保存在葉子節點,同時經過鏈表的形式將他們鏈接在一塊兒。

B 樹(B-tree)、B+ 樹(B+-tree)

B 樹、B+ 樹圖片

image
B 樹(B-tree)、B+ 樹(B+-tree)

B 樹(B-tree)、B+ 樹(B+-tree)
特色
  • 通常化的二叉查找樹(binary search tree)

  • 「矮胖」,內部(非葉子)節點能夠擁有可變數量的子節點(數量範圍預先定義好)

應用
  • 大部分文件系統、數據庫系統都採用B樹、B+樹做爲索引結構
區別
  • B+樹中只有葉子節點會帶有指向記錄的指針(ROWID),而B樹則全部節點都帶有,在內部節點出現的索引項不會再出如今葉子節點中。

  • B+樹中全部葉子節點都是經過指針鏈接在一塊兒,而B樹不會。

B樹的優勢

對於在內部節點的數據,可直接獲得,沒必要根據葉子節點來定位。

B+樹的優勢
  • 非葉子節點不會帶上 ROWID,這樣,一個塊中能夠容納更多的索引項,一是能夠下降樹的高度。二是一個內部節點能夠定位更多的葉子節點。

  • 葉子節點之間經過指針來鏈接,範圍掃描將十分簡單,而對於B樹來講,則須要在葉子節點和內部節點不停的往返移動。

B 樹、B+ 樹區別來自:differences-between-b-trees-and-b-trees、B樹和B+樹的區別:

t.cn/RrBAaZa

t.cn/E4WJhmZ

八叉樹

八叉樹圖片

image

八叉樹(octree),或稱八元樹,是一種用於描述三維空間(劃分空間)的樹狀數據結構。八叉樹的每一個節點表示一個正方體的體積元素,每一個節點有八個子節點,這八個子節點所表示的體積元素加在一塊兒就等於父節點的體積。通常中心點做爲節點的分叉中心。

用途
  • 三維計算機圖形

  • 最鄰近搜索

算法

排序

t.cn/E4WJUGz

排序算法 平均時間複雜度 最差時間複雜度 空間複雜度 數據對象穩定性
冒泡排序 O(n2) O(n2) O(1) 穩定
選擇排序 O(n2) O(n2) O(1) 數組不穩定、鏈表穩定
插入排序 O(n2) O(n2) O(1) 穩定
快速排序 O(n*log2n) O(n2) O(log2n) 不穩定
堆排序 O(n*log2n) O(n*log2n) O(1) 不穩定
歸併排序 O(n*log2n) O(n*log2n) O(n) 穩定
希爾排序 O(n*log2n) O(n2) O(1) 不穩定
計數排序 O(n+m) O(n+m) O(n+m) 穩定
桶排序 O(n) O(n) O(m) 穩定
基數排序 O(k*n) O(n2)
穩定

均按從小到大排列

  • k:表明數值中的 「數位」 個數

  • n:表明數據規模

  • m:表明數據的最大值減最小值

  • 來自:wikipedia . 排序算法

查找

查找算法 平均時間複雜度 空間複雜度 查找條件
順序查找 O(n) O(1) 無序或有序
二分查找(折半查找) O(log2n) O(1) 有序
插值查找 O(log2(log2n)) O(1) 有序
斐波那契查找 O(log2n) O(1) 有序
哈希查找 O(1) O(n) 無序或有序
二叉查找樹(二叉搜索樹查找) O(log2n)
紅黑樹 O(log2n)
2-3樹 O(log2n - log3n)
B樹/B+樹 O(log2n)

圖搜索算法

圖搜索算法 數據結構 遍歷時間複雜度 空間複雜度
BFS廣度優先搜索 鄰接矩陣
鄰接鏈表
O(|v|2)
O(|v|+|E|)
O(|v|2)
O(|v|+|E|)
DFS深度優先搜索 鄰接矩陣
鄰接鏈表
O(|v|2)
O(|v|+|E|)
O(|v|2)
O(|v|+|E|)

其餘算法

算法 思想 應用
分治法 把一個複雜的問題分紅兩個或更多的相同或類似的子問題,直到最後子問題能夠簡單的直接求解,原問題的解即子問題的解的合併 循環賽日程安排問題、排序算法(快速排序、歸併排序)
動態規劃 經過把原問題分解爲相對簡單的子問題的方式求解複雜問題的方法,適用於有重疊子問題和最優子結構性質的問題 揹包問題、斐波那契數列
貪心法 一種在每一步選擇中都採起在當前狀態下最好或最優(即最有利)的選擇,從而但願致使結果是最好或最優的算法 旅行推銷員問題(最短路徑問題)、最小生成樹、哈夫曼編碼

Problems

Single Problem

  • Chessboard Coverage Problem(棋盤覆蓋問題)

  • Knapsack Problem(揹包問題)

  • Neumann Neighbor Problem(馮諾依曼鄰居問題)

  • Round Robin Problem(循環賽日程安排問題)

  • Tubing Problem(輸油管道問題)

Leetcode Problems

  • Github . haoel/leetcode

  • Github . pezy/LeetCode

劍指 Offer

  • Github . zhedahht/CodingInterviewChinese2

  • Github . gatieme/CodingInterviews

Cracking the Coding Interview 程序員面試金典

  • Github . careercup/ctci

  • 牛客網 . 程序員面試金典

牛客網

  • 牛客網 . 在線編程專題

操做系統

進程與線程

對於有線程系統:

  • 進程是資源分配的獨立單位

  • 線程是資源調度的獨立單位

對於無線程系統:

  • 進程是資源調度、分配的獨立單位

進程之間的通訊方式以及優缺點

  • 管道(PIPE)

  • 優勢:簡單方便

  • 缺點:

  • 優勢:能夠實現任意關係的進程間的通訊

  • 缺點:

  • 有名管道:一種半雙工的通訊方式,它容許無親緣關係進程間的通訊

  • 無名管道:一種半雙工的通訊方式,只能在具備親緣關係的進程間使用(父子進程)

  1. 侷限於單向通訊

  2. 只能建立在它的進程以及其有親緣關係的進程之間

  3. 緩衝區有限

  4. 長期存於系統中,使用不當容易出錯

  5. 緩衝區有限

  • 信號量(Semaphore):一個計數器,能夠用來控制多個線程對共享資源的訪問

  • 優勢:能夠同步進程

  • 缺點:信號量有限

  • 信號(Signal):一種比較複雜的通訊方式,用於通知接收進程某個事件已經發生

  • 消息隊列(Message Queue):是消息的鏈表,存放在內核中並由消息隊列標識符標識

  • 優勢:能夠實現任意進程間的通訊,並經過系統調用函數來實現消息發送和接收之間的同步,無需考慮同步問題,方便

  • 缺點:信息的複製須要額外消耗 CPU 的時間,不適宜於信息量大或操做頻繁的場合

  • 共享內存(Shared Memory):映射一段能被其餘進程所訪問的內存,這段共享內存由一個進程建立,但多個進程均可以訪問

  • 優勢:無須複製,快捷,信息量大

  • 缺點:

  1. 通訊是經過將共享空間緩衝區直接附加到進程的虛擬地址空間中來實現的,所以進程間的讀寫操做的同步問題

  2. 利用內存緩衝區直接交換信息,內存的實體存在於計算機中,只能同一個計算機系統中的諸多進程共享,不方便網絡通訊

  • 套接字(Socket):可用於不一樣及其間的進程通訊

  • 優勢:

  • 缺點:需對傳輸的數據進行解析,轉化成應用級的數據。

  1. 傳輸數據爲字節級,傳輸數據可自定義,數據量小效率高

  2. 傳輸數據時間短,性能高

  3. 適合於客戶端和服務器端之間信息實時交互

  4. 能夠加密,數據安全性強

線程之間的通訊方式

  • 鎖機制:包括互斥鎖/量(mutex)、讀寫鎖(reader-writer lock)、自旋鎖(spin lock)、條件變量(condition)

  • 互斥鎖/量(mutex):提供了以排他方式防止數據結構被併發修改的方法。

  • 讀寫鎖(reader-writer lock):容許多個線程同時讀共享數據,而對寫操做是互斥的。

  • 自旋鎖(spin lock)與互斥鎖相似,都是爲了保護共享資源。互斥鎖是當資源被佔用,申請者進入睡眠狀態;而自旋鎖則循環檢測保持着是否已經釋放鎖。

  • 條件變量(condition):能夠以原子的方式阻塞進程,直到某個特定條件爲真爲止。對條件的測試是在互斥鎖的保護下進行的。條件變量始終與互斥鎖一塊兒使用。

  • 信號量機制(Semaphore)

  • 無名線程信號量

  • 命名線程信號量

  • 信號機制(Signal):相似進程間的信號處理

  • 屏障(barrier):屏障容許每一個線程等待,直到全部的合做線程都達到某一點,而後從該點繼續執行。

線程間的通訊目的主要是用於線程同步,因此線程沒有像進程通訊中的用於數據交換的通訊機制

進程之間的通訊方式以及優缺點來源於:進程線程面試題總結

進程之間私有和共享的資源

  • 私有:地址空間、堆、全局變量、棧、寄存器

  • 共享:代碼段,公共數據,進程目錄,進程 ID

線程之間私有和共享的資源

  • 私有:線程棧,寄存器,程序寄存器

  • 共享:堆,地址空間,全局變量,靜態變量

多進程與多線程間的對比、優劣與選擇

對比
對比維度 多進程 多線程 總結
數據共享、同步 數據共享複雜,須要用 IPC;數據是分開的,同步簡單 由於共享進程數據,數據共享簡單,但也是由於這個緣由致使同步複雜 各有優點
內存、CPU 佔用內存多,切換複雜,CPU 利用率低 佔用內存少,切換簡單,CPU 利用率高 線程佔優
建立銷燬、切換 建立銷燬、切換複雜,速度慢 建立銷燬、切換簡單,速度很快 線程佔優
編程、調試 編程簡單,調試簡單 編程複雜,調試複雜 進程佔優
可靠性 進程間不會互相影響 一個線程掛掉將致使整個進程掛掉 進程佔優
分佈式 適應於多核、多機分佈式;若是一臺機器不夠,擴展到多臺機器比較簡單 適應於多核分佈式 進程佔優
優劣
優劣 多進程 多線程
優勢 編程、調試簡單,可靠性較高 建立、銷燬、切換速度快,內存、資源佔用小
缺點 建立、銷燬、切換速度慢,內存、資源佔用大 編程、調試複雜,可靠性較差
選擇
  • 須要頻繁建立銷燬的優先用線程

  • 須要進行大量計算的優先使用線程

  • 強相關的處理用線程,弱相關的處理用進程

  • 可能要擴展到多機分佈的用進程,多核分佈的用線程

  • 都知足需求的狀況下,用你最熟悉、最拿手的方式

多進程與多線程間的對比、優劣與選擇來自:多線程仍是多進程的選擇及區別

Linux 內核的同步方式

緣由

在現代操做系統裏,同一時間可能有多個內核執行流在執行,所以內核其實象多進程多線程編程同樣也須要一些同步機制來同步各執行單元對共享數據的訪問。尤爲是在多處理器系統上,更須要一些同步機制來同步不一樣處理器上的執行單元對共享的數據的訪問。

同步方式

  • 原子操做

  • 信號量(semaphore)

  • 讀寫信號量(rw_semaphore)

  • 自旋鎖(spinlock)

  • 大內核鎖(BKL,Big Kernel Lock)

  • 讀寫鎖(rwlock)

  • 大讀者鎖(brlock-Big Reader Lock)

  • 讀-拷貝修改(RCU,Read-Copy Update)

  • 順序鎖(seqlock)

來自:Linux 內核的同步機制,第 1 部分、Linux 內核的同步機制,第 2 部分

死鎖

緣由

  • 系統資源不足

  • 資源分配不當

  • 進程運行推動順序不合適

產生條件

  • 互斥

  • 請求和保持

  • 不剝奪

  • 環路

預防

  • 打破互斥條件:改造獨佔性資源爲虛擬資源,大部分資源已沒法改造。

  • 打破不可搶佔條件:當一進程佔有一獨佔性資源後又申請一獨佔性資源而沒法知足,則退出原佔有的資源。

  • 打破佔有且申請條件:採用資源預先分配策略,即進程運行前申請所有資源,知足則運行,否則就等待,這樣就不會佔有且申請。

  • 打破循環等待條件:實現資源有序分配策略,對全部設備實現分類編號,全部進程只能採用按序號遞增的形式申請資源。

  • 有序資源分配法

  • 銀行家算法

文件系統

  • Windows:FCB 表 + FAT + 位圖

  • Unix:inode + 混合索引 + 成組連接

主機字節序與網絡字節序

主機字節序(CPU 字節序)

概念

主機字節序又叫 CPU 字節序,其不是由操做系統決定的,而是由 CPU 指令集架構決定的。主機字節序分爲兩種:

  • 大端字節序(Big Endian):高序字節存儲在低位地址,低序字節存儲在高位地址

  • 小端字節序(Little Endian):高序字節存儲在高位地址,低序字節存儲在低位地址

存儲方式

32 位整數 0x12345678 是從起始位置爲 0x00 的地址開始存放,則:

內存地址 0x00 0x01 0x02 0x03
大端 12 34 56 78
小端 78 56 34 12

大端小端圖片

[圖片上傳失敗...(image-86c73-1555590815042)] [圖片上傳失敗...(image-d802-1555590815042)]

判斷大端小端

判斷大端小端

能夠這樣判斷本身 CPU 字節序是大端仍是小端:

#include <iostream>
using namespace std;

int main()
{
    int i = 0x12345678;

    if (*((char*)&i) == 0x12)
        cout << "大端" << endl;
    else    
        cout << "小端" << endl;

    return 0;
}
複製代碼
各架構處理器的字節序
  • x86(Intel、AMD)、MOS Technology 650二、Z80、VAX、PDP-11 等處理器爲小端序;

  • Motorola 6800、Motorola 68000、PowerPC 970、System/370、SPARC(除 V9 外)等處理器爲大端序;

  • ARM(默認小端序)、PowerPC(除 PowerPC 970 外)、DEC Alpha、SPARC V九、MIPS、PA-RISC 及 IA64 的字節序是可配置的。

網絡字節序

網絡字節順序是 TCP/IP 中規定好的一種數據表示格式,它與具體的 CPU 類型、操做系統等無關,從而能夠保重數據在不一樣主機之間傳輸時可以被正確解釋。

網絡字節順序採用:大端(Big Endian)排列方式。

頁面置換算法

在地址映射過程當中,若在頁面中發現所要訪問的頁面不在內存中,則產生缺頁中斷。當發生缺頁中斷時,若是操做系統內存中沒有空閒頁面,則操做系統必須在內存選擇一個頁面將其移出內存,以便爲即將調入的頁面讓出空間。而用來選擇淘汰哪一頁的規則叫作頁面置換算法。

分類

  • 全局置換:在整個內存空間置換

  • 局部置換:在本進程中進行置換

算法

全局:

  • 工做集算法

  • 缺頁率置換算法

局部:

  • 最佳置換算法(OPT)

  • 先進先出置換算法(FIFO)

  • 最近最久未使用(LRU)算法

  • 時鐘(Clock)置換算法

計算機網絡

計算機經網絡體系結構:

計算機網絡體系結構.png

各層做用及協議

分層 做用 協議
物理層 經過媒介傳輸比特,肯定機械及電氣規範(比特 Bit) RJ4五、CLOCK、IEEE802.3(中繼器,集線器)
數據鏈路層 將比特組裝成幀和點到點的傳遞(幀 Frame) PPP、FR、HDLC、VLAN、MAC(網橋,交換機)
網絡層 負責數據包從源到宿的傳遞和網際互連(包 Packet) IP、ICMP、ARP、RARP、OSPF、IPX、RIP、IGRP(路由器)
運輸層 提供端到端的可靠報文傳遞和錯誤恢復( 段Segment) TCP、UDP、SPX
會話層 創建、管理和終止會話(會話協議數據單元 SPDU) NFS、SQL、NETBIOS、RPC
表示層 對數據進行翻譯、加密和壓縮(表示協議數據單元 PPDU) JPEG、MPEG、ASII
應用層 容許訪問OSI環境的手段(應用協議數據單元 APDU) FTP、DNS、Telnet、SMTP、HTTP、WWW、NFS

物理層

  • 傳輸數據的單位 ———— 比特

  • 數據傳輸系統:源系統(源點、發送器) --> 傳輸系統 --> 目的系統(接收器、終點)

通道:

  • 單向通道(單工通道):只有一個方向通訊,沒有反方向交互,如廣播

  • 雙向交替通行(半雙工通訊):通訊雙方均可發消息,但不能同時發送或接收

  • 雙向同時通訊(全雙工通訊):通訊雙方能夠同時發送和接收信息

通道複用技術:

  • 頻分複用(FDM,Frequency Division Multiplexing):不一樣用戶在不一樣頻帶,所用用戶在一樣時間佔用不一樣帶寬資源

  • 時分複用(TDM,Time Division Multiplexing):不一樣用戶在同一時間段的不一樣時間片,全部用戶在不一樣時間佔用一樣的頻帶寬度

  • 波分複用(WDM,Wavelength Division Multiplexing):光的頻分複用

  • 碼分複用(CDM,Code Division Multiplexing):不一樣用戶使用不一樣的碼,能夠在一樣時間使用一樣頻帶通訊

數據鏈路層

主要信道:

  • 點對點信道

  • 廣播信道

點對點信道

  • 數據單元 ———— 幀

三個基本問題:

  • 封裝成幀:把網絡層的 IP 數據報封裝成幀,SOH - 數據部分 - EOT

  • 透明傳輸:無論數據部分什麼字符,都能傳輸出去;能夠經過字節填充方法解決(衝突字符前加轉義字符)

  • 差錯檢測:下降誤碼率(BER,Bit Error Rate),普遍使用循環冗餘檢測(CRC,Cyclic Redundancy Check)

點對點協議(Point-to-Point Protocol):

  • 點對點協議(Point-to-Point Protocol):用戶計算機和 ISP 通訊時所使用的協議

廣播信道

廣播通訊:

  • 硬件地址(物理地址、MAC 地址)

  • 單播(unicast)幀(一對一):收到的幀的 MAC 地址與本站的硬件地址相同

  • 廣播(broadcast)幀(一對全體):發送給本局域網上全部站點的幀

  • 多播(multicast)幀(一對多):發送給本局域網上一部分站點的幀


限於文章字數限制,更多知識點總結請看「下篇


掃描下方二維碼,及時獲取更多互聯網求職面經javapython爬蟲大數據等技術,和海量資料分享:公衆號後臺回覆「csdn」便可免費領取【csdn】和【百度文庫】下載服務;公衆號後臺回覆「資料」:便可領取5T精品學習資料java面試考點java面經總結,以及幾十個java、大數據項目資料很全,你想找的幾乎都有

掃碼關注,及時獲取更多精彩內容。(博主今日頭條大數據工程師)
相關文章
相關標籤/搜索