本文是:五萬字長文:C/C++ 面試知識總結(上)的續篇
C++ 11
shared_ptr
unique_ptr
weak_ptr
auto_ptr(被 C++11 棄用)
Class shared_ptr 實現共享式擁有(shared ownership)概念。多個智能指針指向相同對象,該對象和其相關資源會在 “最后一個 reference 被銷毀” 時被釋放。為了在結構較復雜的情景中執行上述工作,標準庫提供 weak_ptr、bad_weak_ptr 和 enable_shared_from_this 等輔助類。
Class unique_ptr 實現獨占式擁有(exclusive ownership)或嚴格擁有(strict ownership)概念,保證同一時間內只有一個智能指針可以指向該對象。你可以移交擁有權。它對于避免內存泄漏(resource leak)——如 new 后忘記 delete ——特別有用。
shared_ptr
多個智能指針可以共享同一個對象,對象的最末一個擁有著有責任銷毀對象,并清理與該對象相關的所有資源。
- 支持定制型刪除器(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 . 強制轉換運算符:http://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++
視 C++ 為一個語言聯邦(C、Object-Oriented C++、Template C++、STL)
寧可以編譯器替換預處理器(盡量以
const
、enum
、inline
替換#define
)盡可能使用 const
確定對象被使用前已先被初始化(構造時賦值(copy 構造函數)比 default 構造后賦值(copy assignment)效率高)
了解 C++ 默默編寫并調用哪些函數(編譯器暗自為 class 創建 default 構造函數、copy 構造函數、copy assignment 操作符、析構函數)
若不想使用編譯器自動生成的函數,就應該明確拒絕(將不想使用的成員函數聲明為 private,并且不予實現)
為多態基類聲明 virtual 析構函數(如果 class 帶有任何 virtual 函數,它就應該擁有一個 virtual 析構函數)
別讓異常逃離析構函數(析構函數應該吞下不傳播異常,或者結束程序,而不是吐出異常;如果要處理異常應該在非析構的普通函數處理)
絕不在構造和析構過程中調用 virtual 函數(因為這類調用從不下降至 derived class)
令
operator=
返回一個reference to *this
(用于連鎖賦值)在
operator=
中處理 “自我賦值”賦值對象時應確保復制 “對象內的所有成員變量” 及 “所有 base class 成分”(調用基類復制構造函數)
以對象管理資源(資源在構造函數獲得,在析構函數釋放,建議使用智能指針,資源取得時機便是初始化時機(Resource Acquisition Is Initialization,RAII))
在資源管理類中小心 copying 行為(普遍的 RAII class copying 行為是:抑制 copying、引用計數、深度拷貝、轉移底部資源擁有權(類似 auto_ptr))
在資源管理類中提供對原始資源(raw resources)的訪問(對原始資源的訪問可能經過顯式轉換或隱式轉換,一般而言顯示轉換比較安全,隱式轉換對客戶比較方便)
成對使用 new 和 delete 時要采取相同形式(
new
中使用[]
則delete []
,new
中不使用[]
則delete
)以獨立語句將 newed 對象存儲于(置入)智能指針(如果不這樣做,可能會因為編譯器優化,導致難以察覺的資源泄漏)
讓接口容易被正確使用,不易被誤用(促進正常使用的辦法:接口的一致性、內置類型的行為兼容;阻止誤用的辦法:建立新類型,限制類型上的操作,約束對象值、消除客戶的資源管理責任)
設計 class 猶如設計 type,需要考慮對象創建、銷毀、初始化、賦值、值傳遞、合法值、繼承關系、轉換、一般化等等。
寧以 pass-by-reference-to-const 替換 pass-by-value (前者通常更高效、避免切割問題(slicing problem),但不適用于內置類型、STL迭代器、函數對象)
必須返回對象時,別妄想返回其 reference(絕不返回 pointer 或 reference 指向一個 local stack 對象,或返回 reference 指向一個 heap-allocated 對象,或返回 pointer 或 reference 指向一個 local static 對象而有可能同時需要多個這樣的對象。)
將成員變量聲明為 private(為了封裝、一致性、對其讀寫精確控制等)
寧以 non-member、non-friend 替換 member 函數(可增加封裝性、包裹彈性(packaging flexibility)、機能擴充性)
若所有參數(包括被this指針所指的那個隱喻參數)皆須要類型轉換,請為此采用 non-member 函數
考慮寫一個不拋異常的 swap 函數
盡可能延后變量定義式的出現時間(可增加程序清晰度并改善程序效率)
盡量少做轉型動作(舊式:(T)expression、T(expression);新式:const_cast(expression)、dynamic_cast(expression)、reinterpret_cast(expression)、static_cast(expression);盡量避免轉型、注重效率避免 dynamic_casts、盡量設計成無需轉型、可把轉型封裝成函數、寧可用新式轉型)
避免使用 handles(包括 引用、指針、迭代器)指向對象內部(以增加封裝性、使 const 成員函數的行為更像 const、降低 “虛吊號碼牌”(dangling handles,如懸空指針等)的可能性)
為 “異常安全” 而努力是值得的(異常安全函數(Exception-safe functions)即使發生異常也不會泄露資源或允許任何數據結構敗壞,分為三種可能的保證:基本型、強列型、不拋異常型)
透徹了解 inlining 的里里外外(inlining 在大多數 C++ 程序中是編譯期的行為;inline 函數是否真正 inline,取決于編譯器;大部分編譯器拒絕太過復雜(如帶有循環或遞歸)的函數 inlining,而所有對 virtual 函數的調用(除非是最平淡無奇的)也都會使 inlining 落空;inline 造成的代碼膨脹可能帶來效率損失;inline 函數無法隨著程序庫的升級而升級)
將文件間的編譯依存關系降至最低(如果使用 object references 或 object pointers 可以完成任務,就不要使用 objects;如果能過夠,盡量以 class 聲明式替換 class 定義式;為聲明式和定義式提供不同的頭文件)
確定你的 public 繼承塑模出 is-a(是一種)關系(適用于 base classes 身上的每一件事情一定適用于 derived classes 身上,因為每一個 derived class 對象也都是一個 base class 對象)
避免遮掩繼承而來的名字(可使用 using 聲明式或轉交函數(forwarding functions)來讓被遮掩的名字再見天日)
區分接口繼承和實現繼承(在 public 繼承之下,derived classes 總是繼承 base class 的接口;pure virtual 函數只具體指定接口繼承;非純 impure virtual 函數具體指定接口繼承及缺省實現繼承;non-virtual 函數具體指定接口繼承以及強制性實現繼承)
考慮 virtual 函數以外的其他選擇(如 Template Method 設計模式的 non-virtual interface(NVI)手法,將 virtual 函數替換為 “函數指針成員變量”,以
tr1::function
成員變量替換 virtual 函數,將繼承體系內的 virtual 函數替換為另一個繼承體系內的 virtual 函數)絕不重新定義繼承而來的 non-virtual 函數
絕不重新定義繼承而來的缺省參數值,因為缺省參數值是靜態綁定(statically bound),而 virtual 函數卻是動態綁定(dynamically bound)
通過復合塑模 has-a(有一個)或 “根據某物實現出”(在應用域(application domain),復合意味 has-a(有一個);在實現域(implementation domain),復合意味著 is-implemented-in-terms-of(根據某物實現出))
明智而審慎地使用 private 繼承(private 繼承意味著 is-implemented-in-terms-of(根據某物實現出),盡可能使用復合,當 derived class 需要訪問 protected base class 的成員,或需要重新定義繼承而來的時候 virtual 函數,或需要 empty base 最優化時,才使用 private 繼承)
明智而審慎地使用多重繼承(多繼承比單一繼承復雜,可能導致新的歧義性,以及對 virtual 繼承的需要,但確有正當用途,如 “public 繼承某個 interface class” 和 “private 繼承某個協助實現的 class”;virtual 繼承可解決多繼承下菱形繼承的二義性問題,但會增加大小、速度、初始化及賦值的復雜度等等成本)
了解隱式接口和編譯期多態(class 和 templates 都支持接口(interfaces)和多態(polymorphism);class 的接口是以簽名為中心的顯式的(explicit),多態則是通過 virtual 函數發生于運行期;template 的接口是奠基于有效表達式的隱式的(implicit),多態則是通過 template 具現化和函數重載解析(function overloading resolution)發生于編譯期)
了解 typename 的雙重意義(聲明 template 類型參數是,前綴關鍵字 class 和 typename 的意義完全相同;請使用關鍵字 typename 標識嵌套從屬類型名稱,但不得在基類列(base class lists)或成員初值列(member initialization list)內以它作為 basee class 修飾符)
學習處理模板化基類內的名稱(可在 derived class templates 內通過
this->
指涉 base class templates 內的成員名稱,或藉由一個明白寫出的 “base class 資格修飾符” 完成)將與參數無關的代碼抽離 templates(因類型模板參數(non-type template parameters)而造成代碼膨脹往往可以通過函數參數或 class 成員變量替換 template 參數來消除;因類型參數(type parameters)而造成的代碼膨脹往往可以通過讓帶有完全相同二進制表述(binary representations)的實現類型(instantiation types)共享實現碼)
運用成員函數模板接受所有兼容類型(請使用成員函數模板(member function templates)生成 “可接受所有兼容類型” 的函數;聲明 member templates 用于 “泛化 copy 構造” 或 “泛化 assignment 操作” 時還需要聲明正常的 copy 構造函數和 copy assignment 操作符)
需要類型轉換時請為模板定義非成員函數(當我們編寫一個 class template,而它所提供之 “與此 template 相關的” 函數支持 “所有參數之隱式類型轉換” 時,請將那些函數定義為 “class template 內部的 friend 函數”)
請使用 traits classes 表現類型信息(traits classes 通過 templates 和 “templates 特化” 使得 “類型相關信息” 在編譯期可用,通過重載技術(overloading)實現在編譯期對類型執行 if…else 測試)
認識 template 元編程(模板元編程(TMP,template metaprogramming)可將工作由運行期移往編譯期,因此得以實現早期錯誤偵測和更高的執行效率;TMP 可被用來生成 “給予政策選擇組合”(based on combinations of policy choices)的客戶定制代碼,也可用來避免生成對某些特殊類型并不適合的代碼)
了解 new-handler 的行為(set_new_handler 允許客戶指定一個在內存分配無法獲得滿足時被調用的函數;nothrow new 是一個頗具局限的工具,因為它只適用于內存分配(operator new),后繼的構造函數調用還是可能拋出異常)
Google C++ Style Guide
英文:Google C++ Style Guide :http://t.cn/RqhluJP
中文:C++ 風格指南:http://t.cn/ELDTnur
Google C++ Style Guide 圖
STL
STL 索引
STL 方法含義索引:http://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 算法
算法 | 底層算法 | 時間復雜度 | 可不可重復 |
---|---|---|---|
find | 順序查找 | O(n) | 可重復 |
sort | 內省排序 | O(n*log2n) | 可重復 |
數據結構
順序結構
順序棧(Sequence Stack)
SqStack.cpp:http://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
性質
非空二叉樹第 i 層最多 2(i-1) 個結點 (i >= 1)
深度為 k 的二叉樹最多 2k - 1 個結點 (k >= 1)
度為 0 的結點數為 n0,度為 2 的結點數為 n2,則 n0 = n2 + 1
有 n 個結點的完全二叉樹深度 k = ? log2(n) ? + 1
對于含 n 個結點的完全二叉樹中編號為 i (1 <= i <= n) 的結點
若 i = 1,為根,否則雙親為 ? i / 2 ?
若 2i > n,則 i 結點沒有左孩子,否則孩子編號為 2i
若 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 型:右孩子的左子樹,先右旋,再左旋
紅黑樹
紅黑樹的特征是什么?
節點是紅色或黑色。
根是黑色。
所有葉子都是黑色(葉子是 NIL 節點)。
每個紅色節點必須有兩個黑色的子節點。(從每個葉子到根的所有路徑上不能有兩個連續的紅色節點。)(新增節點的父節點必須相同)
從任一節點到其每個葉子的所有簡單路徑都包含相同數目的黑色節點。(新增節點必須為紅)
調整
變色
左旋
右旋
應用
- 關聯數組:如 STL 中的 map、set
紅黑樹、B 樹、B+ 樹的區別?
紅黑樹的深度比較大,而 B 樹和 B+ 樹的深度則相對要小一些
B+ 樹則將數據都保存在葉子節點,同時通過鏈表的形式將他們連接在一起。
B 樹(B-tree)、B+ 樹(B+-tree)
B 樹、B+ 樹圖片
<figcaption style="margin: 10px 0px 0px; padding: 0px; max-width: 100%; box-sizing: border-box !important; overflow-wrap: break-word !important; line-height: inherit; text-align: center; color: rgb(153, 153, 153); font-size: 0.7em;">B 樹(B-tree)、B+ 樹(B+-tree)</figcaption>
特點
一般化的二叉查找樹(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+樹的區別:
八叉樹
八叉樹圖片
八叉樹(octree),或稱八元樹,是一種用于描述三維空間(劃分空間)的樹狀數據結構。八叉樹的每個節點表示一個正方體的體積元素,每個節點有八個子節點,這八個子節點所表示的體積元素加在一起就等于父節點的體積。一般中心點作為節點的分叉中心。
用途
三維計算機圖形
最鄰近搜索
算法
排序
排序算法 | 平均時間復雜度 | 最差時間復雜度 | 空間復雜度 | 數據對象穩定性 |
---|---|---|---|---|
冒泡排序 | 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)
優點:簡單方便
缺點:
優點:可以實現任意關系的進程間的通信
缺點:
有名管道:一種半雙工的通信方式,它允許無親緣關系進程間的通信
無名管道:一種半雙工的通信方式,只能在具有親緣關系的進程間使用(父子進程)
局限于單向通信
只能創建在它的進程以及其有親緣關系的進程之間
緩沖區有限
長期存于系統中,使用不當容易出錯
緩沖區有限
信號量(Semaphore):一個計數器,可以用來控制多個線程對共享資源的訪問
優點:可以同步進程
缺點:信號量有限
信號(Signal):一種比較復雜的通信方式,用于通知接收進程某個事件已經發生
消息隊列(Message Queue):是消息的鏈表,存放在內核中并由消息隊列標識符標識
優點:可以實現任意進程間的通信,并通過系統調用函數來實現消息發送和接收之間的同步,無需考慮同步問題,方便
缺點:信息的復制需要額外消耗 CPU 的時間,不適宜于信息量大或操作頻繁的場合
共享內存(Shared Memory):映射一段能被其他進程所訪問的內存,這段共享內存由一個進程創建,但多個進程都可以訪問
優點:無須復制,快捷,信息量大
缺點:
通信是通過將共享空間緩沖區直接附加到進程的虛擬地址空間中來實現的,因此進程間的讀寫操作的同步問題
利用內存緩沖區直接交換信息,內存的實體存在于計算機中,只能同一個計算機系統中的諸多進程共享,不方便網絡通信
套接字(Socket):可用于不同及其間的進程通信
優點:
缺點:需對傳輸的數據進行解析,轉化成應用級的數據。
傳輸數據為字節級,傳輸數據可自定義,數據量小效率高
傳輸數據時間短,性能高
適合于客戶端和服務器端之間信息實時交互
可以加密,數據安全性強
線程之間的通信方式
鎖機制:包括互斥鎖/量(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 6502、Z80、VAX、PDP-11 等處理器為小端序;
Motorola 6800、Motorola 68000、PowerPC 970、System/370、SPARC(除 V9 外)等處理器為大端序;
ARM(默認小端序)、PowerPC(除 PowerPC 970 外)、DEC Alpha、SPARC V9、MIPS、PA-RISC 及 IA64 的字節序是可配置的。
網絡字節序
網絡字節順序是 TCP/IP 中規定好的一種數據表示格式,它與具體的 CPU 類型、操作系統等無關,從而可以保重數據在不同主機之間傳輸時能夠被正確解釋。
網絡字節順序采用:大端(Big Endian)排列方式。
頁面置換算法
在地址映射過程中,若在頁面中發現所要訪問的頁面不在內存中,則產生缺頁中斷。當發生缺頁中斷時,如果操作系統內存中沒有空閑頁面,則操作系統必須在內存選擇一個頁面將其移出內存,以便為即將調入的頁面讓出空間。而用來選擇淘汰哪一頁的規則叫做頁面置換算法。
分類
全局置換:在整個內存空間置換
局部置換:在本進程中進行置換
算法
全局:
工作集算法
缺頁率置換算法
局部:
最佳置換算法(OPT)
先進先出置換算法(FIFO)
最近最久未使用(LRU)算法
時鐘(Clock)置換算法
計算機網絡
計算機經網絡體系結構:
各層作用及協議
分層 | 作用 | 協議 |
---|---|---|
物理層 | 通過媒介傳輸比特,確定機械及電氣規范(比特 Bit) | RJ45、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)幀(一對多):發送給本局域網上一部分站點的幀
限于文章字數限制,更多知識點總結請看“下篇”
掃描下方二維碼,及時獲取更多互聯網求職面經、java、python、爬蟲、大數據等技術,和海量資料分享:
公眾號菜鳥名企夢
后臺發送“csdn”即可免費領取【csdn】和【百度文庫】下載服務;
公眾號菜鳥名企夢
后臺發送“資料”:即可領取5T精品學習資料、java面試考點和java面經總結,以及幾十個java、大數據項目,資料很全,你想找的幾乎都有