C++面經(jīng)

1. 語(yǔ)言基礎(chǔ)

1.1 C++的四種類型轉(zhuǎn)換:

  • const_cast => 用于將const變量轉(zhuǎn)為非const;
  • static_cast => 用于各種隱式轉(zhuǎn)換,比如非const轉(zhuǎn)const,void*轉(zhuǎn)指針等, static_cast能用于多態(tài)向上轉(zhuǎn)化,如果向下轉(zhuǎn)能成功但是不安全,結(jié)果未知;
  • dynamic_cast => 用于動(dòng)態(tài)類型轉(zhuǎn)換( 適用于包含虛函數(shù)的類 ) ,適用于指針和引用;
  • reinterpret_cast => 幾乎什么都可以轉(zhuǎn),比如將int轉(zhuǎn)指針,可能會(huì)出問(wèn)題,盡量少用;
  • 為什么不用 C 風(fēng)格的強(qiáng)制類型轉(zhuǎn)換 => C的強(qiáng)制轉(zhuǎn)換表面上看起來(lái)功能強(qiáng)大什么都能轉(zhuǎn),但是轉(zhuǎn)化不夠明確,不能進(jìn)行錯(cuò)誤檢查,容易出錯(cuò)。

1.2 海倫公式求三角形面積:

已知三角形三邊a, b, c

p = \frac{a + b + c}{2}

S = \sqrt{p(p - a)(p-b)(p-c)}

1.3 四種智能指針

  • auto_ptr(c++98的方案,cpp11已經(jīng)拋棄) => 采用所有權(quán)模式;( 所有權(quán)可剝奪
  • unique_ptr => 用于替換 auto_ptr => 占式擁有或嚴(yán)格擁有概念,保證同一時(shí)間內(nèi)只有一個(gè)智能指針可以指向該對(duì)象
  • shared_ptr => 引用計(jì)數(shù)解決方案
  • weak_ptr => 一種弱引用,不會(huì)增加對(duì)象的引用計(jì)數(shù)( 解決 shared_ptr 相互引用問(wèn)題

1.4 fork

  • 寫時(shí)復(fù)制;
  • 子進(jìn)程中返回大于0的pid_t,父進(jìn)程中返回0;

1.5 靜態(tài)函數(shù)和虛函數(shù):

  • 靜態(tài)函數(shù)在編譯時(shí)就確定調(diào)用時(shí)機(jī);
  • 虛函數(shù)在運(yùn)行時(shí)動(dòng)態(tài)綁定,因?yàn)椴捎昧颂摵瘮?shù)表,所以在調(diào)用時(shí)會(huì)增加內(nèi)存開(kāi)銷。

1.6 const

  • 對(duì)于局部對(duì)象,常量存放在棧區(qū),對(duì)于全局對(duì)象,常量存放在全局/靜態(tài)存儲(chǔ)區(qū),對(duì)于字面值常量,常量存放在常量存儲(chǔ)區(qū);
  • const 可用于函數(shù)重載
  • 指向常量的指針,和常量指針:
    • const char *p1 => p1 可以改變指向,但是不能修改所指的對(duì)象
    • char *const p2 => p2 本身是一個(gè)常量指針,不可以改變指向

1.7 malloc 和 new

  • malloc/free 是C語(yǔ)言的庫(kù)函數(shù),需指明申請(qǐng)的空間大小 => 對(duì)于類類型,不會(huì)調(diào)用構(gòu)造和析構(gòu)函數(shù);
  • new/delete 是C++的關(guān)鍵字,會(huì)調(diào)用構(gòu)造和析構(gòu)函數(shù)

1.8 C++ 引用、移動(dòng)、轉(zhuǎn)發(fā)

https://guodong.plus/2020/0307-190855/

2. 數(shù)據(jù)結(jié)構(gòu)

2.1 紅黑樹(shù)

http://www.itdecent.cn/p/e136ec79235c

2.2 map和set底層都是用紅黑樹(shù)(RB-tree)實(shí)現(xiàn)的;

unordered map 底層用的是 Hash 表

2.3 STL迭代器刪除元素:

  • 對(duì)于序列容器vector,deque來(lái)說(shuō),使用erase(itertor)后,后邊的每個(gè)元素的迭代器都會(huì)失效,但是后邊每個(gè)元素都會(huì)往前移動(dòng)一個(gè)位置,但是 erase會(huì)返回下一個(gè)有效的迭代器 ;

  • 對(duì)于關(guān)聯(lián)容器map set來(lái)說(shuō),使用了erase(iterator)后,當(dāng)前元素的迭代器失效,但是其結(jié)構(gòu)是紅黑樹(shù),刪除當(dāng)前元素的,不會(huì)影響到下一個(gè)元素的迭代器,所以在 調(diào)用erase之前,記錄下一個(gè)元素的迭代器即可 ;

  • 對(duì)于list來(lái)說(shuō),它使用了不連續(xù)分配的內(nèi)存,并且它的erase方法也會(huì)返回下一個(gè)有效的iterator,因此上面兩種正確的方法都可以使用。

3. 操作系統(tǒng)

3.1 I/O模式

https://segmentfault.com/a/1190000003063859

  • 阻塞I/O(blocking IO)

    preview
  • 非阻塞I/O(nonblocking IO)

    preview
  • I/O多路復(fù)用(IO multiplexing)

    preview
  • 信號(hào)驅(qū)動(dòng)I/O(signal driven IO)=> 不常用

  • 異步I/O(asynchronous IO)

    preview

3.2 blocking和non-blocking的區(qū)別

調(diào)用blocking IO會(huì)一直block住對(duì)應(yīng)的進(jìn)程直到操作完成,而non-blocking IO在kernel還準(zhǔn)備數(shù)據(jù)的情況下會(huì)立刻返回。

3.3 synchronous IO和asynchronous IO的區(qū)別

兩者的區(qū)別就在于synchronous IO做”IO operation”的時(shí)候會(huì)將process阻塞。按照這個(gè)定義,之前所述的blocking IO,non-blocking IO,IO multiplexing都屬于synchronous IO。

preview

3.4 I/O多路復(fù)用 => select、poll、epoll

  • select

    int select (int n, fd_set *readfds, fd_set *writefds, fd_set *exceptfds, struct timeval *timeout);
    
  • poll

    struct pollfd {
        int fd; /* file descriptor */
        short events; /* requested events to watch */
        short revents; /* returned events witnessed */
    };
    int poll (struct pollfd *fds, unsigned int nfds, int timeout);
    
  • epoll

    https://zhuanlan.zhihu.com/p/63179839

    => epoll 采用紅黑樹(shù)和雙向鏈表實(shí)現(xiàn)

    int epoll_create(int size);//創(chuàng)建一個(gè)epoll的句柄,size用來(lái)告訴內(nèi)核這個(gè)監(jiān)聽(tīng)的數(shù)目一共有多大
    int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event);
    int epoll_wait(int epfd, struct epoll_event * events, int maxevents, int timeout);
    

3.5 進(jìn)程間通信方式

  • 管道通信 => 半雙工

    • 匿名管道 => 只能用于有親緣關(guān)系的進(jìn)程之間;
    • 命名管道 => 允許沒(méi)有親緣關(guān)系的的進(jìn)程之間進(jìn)行半雙工的管道通信。
  • 消息隊(duì)列

    消息隊(duì)列( message queue ) : 消息隊(duì)列是由消息的鏈表,存放在內(nèi)核中并由消息隊(duì)列標(biāo)識(shí)符標(biāo)識(shí)。消息隊(duì)列克服了信號(hào)傳遞信息少、管道只能承載無(wú)格式字節(jié)流以及緩沖區(qū)大小受限等缺點(diǎn)。

  • 信號(hào)量通信

    信號(hào)量( semophore ) : 信號(hào)量是一個(gè)計(jì)數(shù)器,可以用來(lái)控制多個(gè)進(jìn)程對(duì)共享資源的訪問(wèn)。它常作為一種鎖機(jī)制,防止某進(jìn)程正在訪問(wèn)共享資源時(shí),其他進(jìn)程也訪問(wèn)該資源。因此,主要作為進(jìn)程間以及同一進(jìn)程內(nèi)不同線程之間的同步手段。

  • 信號(hào)

    信號(hào) ( sinal ) : 信號(hào)是一種比較復(fù)雜的通信方式,用于通知接收進(jìn)程某個(gè)事件已經(jīng)發(fā)生。

  • 共享內(nèi)存

    共享內(nèi)存( shared memory ) :共享內(nèi)存就是映射一段能被其他進(jìn)程所訪問(wèn)的內(nèi)存,這段共享內(nèi)存由一個(gè)進(jìn)程創(chuàng)建,但多個(gè)進(jìn)程都可以訪問(wèn)。共享內(nèi)存是最快的 IPC 方式,它是針對(duì)其他進(jìn)程間通信方式運(yùn)行效率低而專門設(shè)計(jì)的。它往往與其他通信機(jī)制,如信號(hào)量,配合使用,來(lái)實(shí)現(xiàn)進(jìn)程間的同步和通信。

  • 套接字

3.6 虛擬地址空間

為了防止不同進(jìn)程同一時(shí)刻在物理內(nèi)存中運(yùn)行而對(duì)物理內(nèi)存的爭(zhēng)奪和踐踏,采用了虛擬內(nèi)存。

虛擬內(nèi)存技術(shù)使得不同進(jìn)程在運(yùn)行過(guò)程中,它所看到的是自己獨(dú)自占有了當(dāng)前系統(tǒng)的4G內(nèi)存。所有進(jìn)程共享同一物理內(nèi)存,每個(gè)進(jìn)程只把自己目前需要的虛擬內(nèi)存空間映射并存儲(chǔ)到物理內(nèi)存上。 事實(shí)上,在每個(gè)進(jìn)程創(chuàng)建加載時(shí),內(nèi)核只是為進(jìn)程“創(chuàng)建”了虛擬內(nèi)存的布局,具體就是初始化進(jìn)程控制表中內(nèi)存相關(guān)的鏈表,實(shí)際上并不立即就把虛擬內(nèi)存對(duì)應(yīng)位置的程序數(shù)據(jù)和代碼(比如.text .data段)拷貝到物理內(nèi)存中,只是建立好虛擬內(nèi)存和磁盤文件之間的映射就好(叫做存儲(chǔ)器映射),等到運(yùn)行到對(duì)應(yīng)的程序時(shí),才會(huì)通過(guò)缺頁(yè)異常,來(lái)拷貝數(shù)據(jù)。還有進(jìn)程運(yùn)行過(guò)程中,要?jiǎng)討B(tài)分配內(nèi)存,比如malloc時(shí),也只是分配了虛擬內(nèi)存,即為這塊虛擬內(nèi)存對(duì)應(yīng)的頁(yè)表項(xiàng)做相應(yīng)設(shè)置,當(dāng)進(jìn)程真正訪問(wèn)到此數(shù)據(jù)時(shí),才引發(fā)缺頁(yè)異常。

請(qǐng)求分頁(yè)系統(tǒng)、請(qǐng)求分段系統(tǒng)和請(qǐng)求段頁(yè)式系統(tǒng)都是針對(duì)虛擬內(nèi)存的,通過(guò)請(qǐng)求實(shí)現(xiàn)內(nèi)存與外存的信息置換。

  • 虛擬內(nèi)存的好處:

    1. 擴(kuò)大地址空間;

    2. 內(nèi)存保護(hù):每個(gè)進(jìn)程運(yùn)行在各自的虛擬內(nèi)存地址空間,互相不能干擾對(duì)方。虛存還對(duì)特定的內(nèi)存地址提供寫保護(hù),可以防止代碼或數(shù)據(jù)被惡意篡改。

    3. 公平內(nèi)存分配。采用了虛存之后,每個(gè)進(jìn)程都相當(dāng)于有同樣大小的虛存空間。

    4. 當(dāng)進(jìn)程通信時(shí),可采用虛存共享的方式實(shí)現(xiàn)。

    5. 當(dāng)不同的進(jìn)程使用同樣的代碼時(shí),比如庫(kù)文件中的代碼,物理內(nèi)存中可以只存儲(chǔ)一份這樣的代碼,不同的進(jìn)程只需要把自己的虛擬內(nèi)存映射過(guò)去就可以了,節(jié)省內(nèi)存

    6. 虛擬內(nèi)存很適合在多道程序設(shè)計(jì)系統(tǒng)中使用,許多程序的片段同時(shí)保存在內(nèi)存中。當(dāng)一個(gè)程序等待它的一部分讀入內(nèi)存時(shí),可以把CPU交給另一個(gè)進(jìn)程使用。在內(nèi)存中可以保留多個(gè)進(jìn)程,系統(tǒng)并發(fā)度提高

    7. 在程序需要分配連續(xù)的內(nèi)存空間的時(shí)候,只需要在虛擬內(nèi)存空間分配連續(xù)空間,而不需要實(shí)際物理內(nèi)存的連續(xù)空間,可以利用碎片

  • 虛擬內(nèi)存的代價(jià):

    1. 虛存的管理需要建立很多數(shù)據(jù)結(jié)構(gòu),這些數(shù)據(jù)結(jié)構(gòu)要占用額外的內(nèi)存

    2. 虛擬地址到物理地址的轉(zhuǎn)換,增加了指令的執(zhí)行時(shí)間。

    3. 頁(yè)面的換入換出需要磁盤I/O,這是很耗時(shí)的

    4. 如果一頁(yè)中只有一部分?jǐn)?shù)據(jù),會(huì)浪費(fèi)內(nèi)存。

3.7 操作系統(tǒng)中程序的內(nèi)存結(jié)構(gòu)

img
  • text段(代碼段):存放程序執(zhí)行代碼的一塊內(nèi)存區(qū)域。這部分區(qū)域的大小在程序運(yùn)行前就已經(jīng)確定,并且內(nèi)存區(qū)域?qū)儆谥蛔x。在代碼段中,也有可能包含一些只讀的常數(shù)變量;

  • data段(數(shù)據(jù)段):存放程序中已初始化的全局變量的一塊內(nèi)存區(qū)域。數(shù)據(jù)段也屬于靜態(tài)內(nèi)存分配;

    • 已初始化數(shù)據(jù)區(qū);
    • BBS(未初始化區(qū))=> 的內(nèi)容并不存放在磁盤上的程序文件中。其原因是內(nèi)核在程序開(kāi)始運(yùn)行前將它們?cè)O(shè)置為0。需要存放在程序文件中的只有正文段和初始化數(shù)據(jù)段
  • 棧區(qū)(運(yùn)行時(shí)):

    由編譯器自動(dòng)釋放,存放函數(shù)的參數(shù)值、局部變量等。每當(dāng)一個(gè)函數(shù)被調(diào)用時(shí),該函數(shù)的返回類型和一些調(diào)用的信息被存放到棧中。然后這個(gè)被調(diào)用的函數(shù)再為他的自動(dòng)變量和臨時(shí)變量在棧上分配空間。每調(diào)用一個(gè)函數(shù)一個(gè)新的棧就會(huì)被使用。棧區(qū)是從高地址位向低地址位增長(zhǎng)的,是一塊連續(xù)的內(nèi)存區(qū)域,最大容量是由系統(tǒng)預(yù)先定義好的,申請(qǐng)的棧空間超過(guò)這個(gè)界限時(shí)會(huì)提示溢出,用戶能從棧中獲取的空間較小

  • 堆區(qū)(運(yùn)行時(shí)):

    用于動(dòng)態(tài)分配內(nèi)存,位于BSS和棧中間的地址區(qū)域。由程序員申請(qǐng)分配和釋放。堆是從低地址位向高地址位增長(zhǎng),采用鏈?zhǔn)酱鎯?chǔ)結(jié)構(gòu)。頻繁的malloc/free造成內(nèi)存空間的不連續(xù),產(chǎn)生碎片。當(dāng)申請(qǐng)堆空間時(shí)庫(kù)函數(shù)是按照一定的算法搜索可用的足夠大的空間。因此堆的效率比棧要低的多

3.8 死鎖發(fā)生的條件和解決辦法

3.8.1 死鎖發(fā)生條件

  • 互斥條件:進(jìn)程對(duì)所分配到的資源不允許其他進(jìn)程訪問(wèn),若其他進(jìn)程訪問(wèn)該資源,只能等待,直至占有該資源的進(jìn)程使用完成后釋放該資源;

  • 請(qǐng)求和保持條件:進(jìn)程獲得一定的資源后,又對(duì)其他資源發(fā)出請(qǐng)求,但是該資源可能被其他進(jìn)程占有,此時(shí)請(qǐng)求阻塞,但該進(jìn)程不會(huì)釋放自己已經(jīng)占有的資源

  • 不可剝奪條件:進(jìn)程已獲得的資源,在未完成使用之前,不可被剝奪,只能在使用后自己釋放

  • 環(huán)路等待條件:進(jìn)程發(fā)生死鎖后,必然存在一個(gè)進(jìn)程-資源之間的環(huán)形鏈

3.8.2 死鎖解決

  • 資源一次性分配,從而剝奪請(qǐng)求和保持條件
  • 可剝奪資源:即當(dāng)進(jìn)程新的資源未得到滿足時(shí),釋放已占有的資源,從而破壞不可剝奪的條件
  • 資源有序分配法:系統(tǒng)給每類資源賦予一個(gè)序號(hào),每個(gè)進(jìn)程按編號(hào)遞增的請(qǐng)求資源,釋放則相反,從而破壞環(huán)路等待的條件

3.9 虛擬內(nèi)存置換方式

  • FIFO

    => 缺點(diǎn):無(wú)法體現(xiàn)頁(yè)面冷熱信息;

  • LFU => 按引用計(jì)數(shù)排序

    => 缺點(diǎn):需要排序;緩存顛簸;

  • LRU => 使用棧,新頁(yè)面或者命中頁(yè)放到棧底,每次淘汰棧頂元素

    => 優(yōu)點(diǎn):LRU算法對(duì)熱點(diǎn)數(shù)據(jù)命中率是很高的

    => 缺點(diǎn):

    • 緩存顛簸,當(dāng)緩存(1,2,3)滿了,之后數(shù)據(jù)訪問(wèn)(0,3,2,1,0,3,2,1。。。);
    • 緩存污染,突然大量偶發(fā)性的數(shù)據(jù)訪問(wèn),會(huì)讓內(nèi)存中存放大量冷數(shù)據(jù)
  • LRU-K(LRU-2、LRU-3)

    img
    1. 數(shù)據(jù)第一次被訪問(wèn),加入到訪問(wèn)歷史列表;

    2. 如果數(shù)據(jù)在訪問(wèn)歷史列表里后沒(méi)有達(dá)到K次訪問(wèn),則按照一定規(guī)則(FIFO,LRU)淘汰;

    3. 當(dāng)訪問(wèn)歷史隊(duì)列中的數(shù)據(jù)訪問(wèn)次數(shù)達(dá)到K次后,將數(shù)據(jù)索引從歷史隊(duì)列刪除,將數(shù)據(jù)移到緩存隊(duì)列中,并緩存此數(shù)據(jù),緩存隊(duì)列重新按照時(shí)間排序;

    4. 緩存數(shù)據(jù)隊(duì)列中被再次訪問(wèn)后,重新排序;

    5. 需要淘汰數(shù)據(jù)時(shí),淘汰緩存隊(duì)列中排在末尾的數(shù)據(jù),即:淘汰“倒數(shù)第K次訪問(wèn)離現(xiàn)在最久”的數(shù)據(jù)。

    LRU-K具有LRU的優(yōu)點(diǎn),同時(shí)能夠避免LRU的缺點(diǎn),實(shí)際應(yīng)用中LRU-2是綜合各種因素后最優(yōu)的選擇,LRU-3或者更大的K值命中率會(huì)高,但適應(yīng)性差,需要大量的數(shù)據(jù)訪問(wèn)才能將歷史訪問(wèn)記錄清除掉。

    【命中率】

    LRU-K降低了“緩存污染”帶來(lái)的問(wèn)題,命中率比LRU要高。

    【復(fù)雜度】

    LRU-K隊(duì)列是一個(gè)優(yōu)先級(jí)隊(duì)列,算法復(fù)雜度和代價(jià)比較高。

    【代價(jià)】

    由于LRU-K還需要記錄那些被訪問(wèn)過(guò)、但還沒(méi)有放入緩存的對(duì)象,因此內(nèi)存消耗會(huì)比LRU要多;

    當(dāng)數(shù)據(jù)量很大的時(shí)候,內(nèi)存消耗會(huì)比較可觀。

    LRU-K需要基于時(shí)間進(jìn)行排序(可以需要淘汰時(shí)再排序,也可以即時(shí)排序),CPU消耗比LRU要高

  • 2Q

    類似LRU-2。使用一個(gè)FIFO隊(duì)列和一個(gè)LRU隊(duì)列

3.10 進(jìn)程狀態(tài)轉(zhuǎn)換圖

img

3.11 僵尸進(jìn)程

  • 孤兒進(jìn)程:父進(jìn)程退出,子進(jìn)程還在運(yùn)行,即成為孤兒進(jìn)程;
  • 僵尸進(jìn)程:子進(jìn)程退出,父進(jìn)程并沒(méi)有調(diào)用wait或waitpid獲取子進(jìn)程的狀態(tài)信息,那么子進(jìn)程的進(jìn)程描述符仍然保存在系統(tǒng)中。這種進(jìn)程稱之為僵尸進(jìn)程。
  • 如果父進(jìn)程未退出,且沒(méi)有處理僵尸進(jìn)程,僵尸進(jìn)程會(huì)一直占用空間,如果父進(jìn)程退出,則會(huì)有init進(jìn)程接管孤兒進(jìn)程,進(jìn)而處理進(jìn)程狀態(tài)信息

3.12 虛擬內(nèi)存、物理內(nèi)存和共享內(nèi)存

https://cloud.tencent.com/developer/article/1493029

3.13 Linux內(nèi)存管理

https://zhuanlan.zhihu.com/p/149581303

4. 數(shù)據(jù)庫(kù)

4.1 數(shù)據(jù)庫(kù)事務(wù)

事務(wù)(Transaction)是由一系列對(duì)系統(tǒng)中數(shù)據(jù)進(jìn)行訪問(wèn)與更新的操作所組成的一個(gè)程序執(zhí)行邏輯單元。事務(wù)是DBMS中最基礎(chǔ)的單位,事務(wù)不可分割

事務(wù)具有4個(gè)基本特征,分別是:原子性(Atomicity)、一致性(Consistency)、隔離性(Isolation)、持久性(Duration),簡(jiǎn)稱ACID

  • 原子性

    原子性是指事務(wù)包含的所有操作要么全部成功,要么全部失敗回滾。

  • 一致性

    事務(wù)必須使數(shù)據(jù)庫(kù)從一個(gè)一致性狀態(tài)變換到另一個(gè)一致性狀態(tài)。

  • 隔離性

    隔離性是當(dāng)多個(gè)用戶并發(fā)訪問(wèn)數(shù)據(jù)庫(kù)時(shí),比如操作同一張表時(shí),數(shù)據(jù)庫(kù)為每一個(gè)用戶開(kāi)啟的事務(wù),不能被其他事務(wù)的操作所干擾,多個(gè)并發(fā)事務(wù)之間要相互隔離,有四種隔離級(jí)別

    • Read uncommitted(讀未提交)

      一個(gè)事務(wù)可以讀取另一個(gè)事務(wù)未提交的數(shù)據(jù) => 存在臟讀問(wèn)題

    • Read committed(讀已提交)

      一個(gè)事務(wù)要等另一個(gè)事務(wù)提交后才能讀取數(shù)據(jù) => 解決臟讀,存在不可重復(fù)讀問(wèn)題

    • Repeatable read(重復(fù)讀)

      就是在開(kāi)始讀取數(shù)據(jù)(事務(wù)開(kāi)啟)時(shí),不再允許修改操作 => 解決臟讀、不可重復(fù)讀問(wèn)題,存在幻讀問(wèn)題

      不允許修改操作,但是允許插入操作

    • Serializable(序列化)=> 最高級(jí)別的事務(wù)隔離

      事務(wù)串行化順序執(zhí)行,可以避免臟讀、不可重復(fù)讀與幻讀。但是這種事務(wù)隔離級(jí)別效率低下,比較耗數(shù)據(jù)庫(kù)性能,一般不使用

  • 持久性

    持久性是指一個(gè)事務(wù)一旦被提交了,那么對(duì)數(shù)據(jù)庫(kù)中的數(shù)據(jù)的改變就是永久性的,即便是在數(shù)據(jù)庫(kù)系統(tǒng)遇到故障的情況下也不會(huì)丟失提交事務(wù)的操作

4.2 數(shù)據(jù)庫(kù)索引

mysql數(shù)據(jù)庫(kù)索引分類

數(shù)據(jù)庫(kù)索引是為了增加查詢速度而對(duì)表字段附加的一種標(biāo)識(shí),是對(duì)數(shù)據(jù)庫(kù)表中一列或多列的值進(jìn)行排序的一種結(jié)構(gòu)。DB在執(zhí)行一條Sql語(yǔ)句的時(shí)候,默認(rèn)的方式是根據(jù)搜索條件進(jìn)行全表掃描,遇到匹配條件的就加入搜索結(jié)果集合。如果我們對(duì)某一字段增加索引,查詢時(shí)就會(huì)先去索引列表中一次定位到特定值的行數(shù),大大減少遍歷匹配的行數(shù),所以能明顯增加查詢的速度。

  • 優(yōu)點(diǎn):
    • 通過(guò)創(chuàng)建唯一性索引,可以保證數(shù)據(jù)庫(kù)表中每一行數(shù)據(jù)的唯一性;
    • 可以大大加快數(shù)據(jù)的檢索速度,這也是創(chuàng)建索引的最主要的原因;
    • 在使用分組和排序子句進(jìn)行數(shù)據(jù)檢索時(shí),同樣可以顯著減少查詢中分組和排序的時(shí)間;
    • 通過(guò)使用索引,可以在查詢的過(guò)程中,使用優(yōu)化隱藏器,提高系統(tǒng)的性能。
  • 缺點(diǎn):
    • 創(chuàng)建索引和維護(hù)索引要耗費(fèi)時(shí)間,這種時(shí)間隨著數(shù)據(jù)量的增加而增加;
    • 索引需要占物理空間,除了數(shù)據(jù)表占數(shù)據(jù)空間之外,每一個(gè)索引還要占一定的物理空間,如果要建立聚簇索引,那么需要的空間就會(huì)更大;
    • 當(dāng)對(duì)表中的數(shù)據(jù)進(jìn)行增加、刪除和修改的時(shí)候,索引也要?jiǎng)討B(tài)的維護(hù),這樣就降低了數(shù)據(jù)的維護(hù)速度。
  • 添加索引原則:
    • 在查詢中很少使用或者參考的列不應(yīng)該創(chuàng)建索引;
    • 只有很少數(shù)據(jù)值的列也不應(yīng)該增加索引;
    • 定義為text、image和bit數(shù)據(jù)類型的列不應(yīng)該增加索引;
    • 當(dāng)修改性能遠(yuǎn)遠(yuǎn)大于檢索性能時(shí),不應(yīng)該創(chuàng)建索引。

4.3 唯一索引和普通索引

唯一索引和普通索引的區(qū)別:https://blog.csdn.net/Srodong/article/details/88838046

4.4 聚簇索引和非聚簇索引

https://cloud.tencent.com/developer/article/1541265

  • 聚簇索引:將數(shù)據(jù)存儲(chǔ)與索引放到了一塊,找到索引也就找到了數(shù)據(jù)
  • 非聚簇索引:將數(shù)據(jù)存儲(chǔ)于索引分開(kāi)結(jié)構(gòu),索引結(jié)構(gòu)的葉子節(jié)點(diǎn)指向了數(shù)據(jù)的對(duì)應(yīng)行,myisam通過(guò)key_buffer把索引先緩存到內(nèi)存中,當(dāng)需要訪問(wèn)數(shù)據(jù)時(shí)(通過(guò)索引訪問(wèn)數(shù)據(jù)),在內(nèi)存中直接搜索索引,然后通過(guò)索引找到磁盤相應(yīng)數(shù)據(jù),這也就是為什么索引不在key buffer命中時(shí),速度慢的原因
image-20210727212349176
  • InnoDB使用的是聚簇索引,將主鍵組織到一棵B+樹(shù)中,而行數(shù)據(jù)就儲(chǔ)存在葉子節(jié)點(diǎn)上,若使用"where id = 14"這樣的條件查找主鍵,則按照B+樹(shù)的檢索算法即可查找到對(duì)應(yīng)的葉節(jié)點(diǎn),之后獲得行數(shù)據(jù)。
  • 對(duì)Name列進(jìn)行條件搜索,則需要兩個(gè)步驟第一步在輔助索引B+樹(shù)中檢索Name,到達(dá)其葉子節(jié)點(diǎn)獲取對(duì)應(yīng)的主鍵。第二步使用主鍵在主索引B+樹(shù)種再執(zhí)行一次B+樹(shù)檢索操作,最終到達(dá)葉子節(jié)點(diǎn)即可獲取整行數(shù)據(jù)。(重點(diǎn)在于通過(guò)其他鍵需要建立輔助索引

MyISM使用的是非聚簇索引,非聚簇索引的兩棵B+樹(shù)看上去沒(méi)什么不同,節(jié)點(diǎn)的結(jié)構(gòu)完全一致只是存儲(chǔ)的內(nèi)容不同而已,主鍵索引B+樹(shù)的節(jié)點(diǎn)存儲(chǔ)了主鍵,輔助鍵索引B+樹(shù)存儲(chǔ)了輔助鍵。表數(shù)據(jù)存儲(chǔ)在獨(dú)立的地方,這兩顆B+樹(shù)的葉子節(jié)點(diǎn)都使用一個(gè)地址指向真正的表數(shù)據(jù),對(duì)于表數(shù)據(jù)來(lái)說(shuō),這兩個(gè)鍵沒(méi)有任何差別。由于索引樹(shù)是獨(dú)立的,通過(guò)輔助鍵檢索無(wú)需訪問(wèn)主鍵的索引樹(shù)。

4.5 索引覆蓋

如果要查詢的字段都建立過(guò)索引,那么引擎會(huì)直接在索引表中查詢而不會(huì)訪問(wèn)原始數(shù)據(jù)(否則只要有一個(gè)字段沒(méi)有建立索引就會(huì)做全表掃描),這叫索引覆蓋。因此我們需要盡可能的在select后只寫必要的查詢字段,以增加索引覆蓋的幾率。

這里值得注意的是不要想著為每個(gè)字段建立索引,因?yàn)閮?yōu)先使用索引的優(yōu)勢(shì)就在于其體積小。

4.6 索引的類型

  • 主鍵索引 : 數(shù)據(jù)列不允許重復(fù),不允許為NULL,一個(gè)表只能有一個(gè)主鍵。

  • 唯一索引 : 數(shù)據(jù)列不允許重復(fù),允許為NULL值,一個(gè)表允許多個(gè)列創(chuàng)建唯一索引。

    • 可以通過(guò) ALTER TABLE table_name ADD UNIQUE (column); 創(chuàng)建唯一索引

    • 可以通過(guò) ALTER TABLE table_name ADD UNIQUE (column1,column2); 創(chuàng)建唯一組合索引

  • 普通索引 : 基本的索引類型,沒(méi)有唯一性的限制,允許為NULL值。

    • 可以通過(guò) ALTER TABLE table_name ADD INDEX index_name (column); 創(chuàng)建普通索引
    • 可以通過(guò) ALTER TABLE table_name ADD INDEX index_name(column1, column2, column3); 創(chuàng)建組合索引
  • 全文索引 :是目前搜索引擎使用的一種關(guān)鍵技術(shù)。

    • 可以通過(guò) ALTER TABLE table_name ADD FULLTEXT (column); 創(chuàng)建全文索引

4.7 MySQL 最左前綴匹配原則

https://www.cnblogs.com/xuwc/p/14007766.html

因?yàn)榻⒙?lián)合索引(a, b, c)時(shí),底層是B+樹(shù)實(shí)現(xiàn)的,所以首先會(huì)按照a的順序建立樹(shù),在a相等的情況下b才是有序的,在a和b相等的情況下c才是有序的,所以想直接查詢 b = 1,是利用不到這個(gè)聯(lián)合索引的。

4.8 MySQL 索引的分類

按數(shù)據(jù)結(jié)構(gòu)分類可分為:B+tree索引、Hash索引、Full-text索引
按物理存儲(chǔ)分類可分為:聚簇索引、二級(jí)索引(輔助索引)。
按字段特性分類可分為:主鍵索引、普通索引、前綴索引。
按字段個(gè)數(shù)分類可分為:單列索引、聯(lián)合索引(復(fù)合索引、組合索引)。

4.8.1 按數(shù)據(jù)結(jié)構(gòu)分類

MySQL索引按數(shù)據(jù)結(jié)構(gòu)分類可分為:B+tree索引、Hash索引、Full-text索引。

InnoDB MyISAM Memory
B+tree索引
Hash索引 × ×
Full-text索引 √(MySQL5.6+) ×

注:InnoDB實(shí)際上也支持Hash索引,但是InnoDB中Hash索引的創(chuàng)建由存儲(chǔ)引擎引擎自動(dòng)優(yōu)化創(chuàng)建,不能人為干預(yù)是否為表創(chuàng)建Hash索引

B+tree 是MySQL中被存儲(chǔ)引擎采用最多的索引類型。B+tree 中的 B 代表平衡(balance),而不是二叉(binary),因?yàn)?B+tree 是從最早的平衡二叉樹(shù)演化而來(lái)的。下面展示B+tree數(shù)據(jù)結(jié)構(gòu)與其他數(shù)據(jù)結(jié)構(gòu)的對(duì)比。

1. B+tree與B-tree的對(duì)比

B-tree 中的每個(gè)節(jié)點(diǎn)根據(jù)實(shí)際情況可以包含多條數(shù)據(jù)信息和子節(jié)點(diǎn),如下圖所示為一個(gè)3階的B-tree:

B-tree結(jié)構(gòu)(圖片來(lái)源于網(wǎng)絡(luò))

(圖片來(lái)源于網(wǎng)絡(luò))

相對(duì)于B-tree,B+tree有以下兩點(diǎn)不同:

  • B+tree 非葉子節(jié)點(diǎn)只存儲(chǔ)鍵值信息, 數(shù)據(jù)記錄都存放在葉子節(jié)點(diǎn)中。而B(niǎo)-tree的非葉子節(jié)點(diǎn)也存儲(chǔ)數(shù)據(jù)。所以B+tree單個(gè)節(jié)點(diǎn)的數(shù)據(jù)量更小,在相同的磁盤I/O次數(shù)下,能查詢更多的節(jié)點(diǎn)。
  • B+tree 所有葉子節(jié)點(diǎn)之間都采用單鏈表連接。適合MySQL中常見(jiàn)的基于范圍的順序檢索場(chǎng)景,而B(niǎo)-tree無(wú)法做到這一點(diǎn)。
B+tree結(jié)構(gòu)(圖片來(lái)源于網(wǎng)絡(luò))
2. B+tree與紅黑樹(shù)的對(duì)比
紅黑樹(shù)結(jié)構(gòu)(圖片來(lái)源于網(wǎng)絡(luò))

(圖片來(lái)源于網(wǎng)絡(luò))

紅黑樹(shù)是一種弱平衡二叉查找樹(shù)。通過(guò)對(duì)任何一條從根到葉子的路徑上各個(gè)節(jié)點(diǎn)著色的方式的限制,紅黑樹(shù)確保沒(méi)有一條路徑會(huì)比其他路徑長(zhǎng)出兩倍。

對(duì)于有N個(gè)葉子結(jié)點(diǎn)的 B+tree,其搜索復(fù)雜度為 O(logdN) ,其中 d(degree) 為 B+tree 的度,表示節(jié)點(diǎn)允許的最大子節(jié)點(diǎn)個(gè)數(shù)為d個(gè),在實(shí)際應(yīng)用當(dāng)中,d值一般是大于100的,即使數(shù)據(jù)量達(dá)到千萬(wàn)級(jí)別時(shí)B+tree的高度依然維持在3-4左右,保證了3-4次磁盤I/O操作就能查詢到目標(biāo)數(shù)據(jù)。

紅黑樹(shù)是二叉樹(shù),節(jié)點(diǎn)子節(jié)點(diǎn)個(gè)數(shù)為兩個(gè),意味著其搜索復(fù)雜度為 O(logN),樹(shù)的高度也會(huì)比 B+tree 高出不少,因此紅黑樹(shù)檢索到目標(biāo)數(shù)據(jù)所需經(jīng)歷的磁盤I/O次數(shù)更多。

3. B+tree與Hash的對(duì)比

Hash 索引結(jié)構(gòu)的特殊性,其檢索效率非常高,索引的檢索可以一次定位,不像B-Tree 索引需要從根節(jié)點(diǎn)到枝節(jié)點(diǎn),最后才能訪問(wèn)到頁(yè)節(jié)點(diǎn)這樣多次的IO訪問(wèn),所以 Hash 索引的查詢效率要遠(yuǎn)高于 B-Tree 索引。雖然 Hash 索引效率高,但是 Hash 索引本身由于其特殊性也帶來(lái)了很多限制和弊端,主要有以下這些。

Hash 索引僅僅能滿足 = , IN<=>(表示NULL安全的等價(jià)) 查詢,不能使用范圍查詢。

由于 Hash 索引比較的是進(jìn)行 Hash 運(yùn)算之后的 Hash值,所以它只能用于等值的過(guò)濾,不能用于基于范圍的過(guò)濾,因?yàn)榻?jīng)過(guò)相應(yīng)的 Hash算法處理之后的 Hash 值的大小關(guān)系,并不能保證和Hash運(yùn)算前完全一樣。

Hash 索引無(wú)法適用數(shù)據(jù)的排序操作。

由于 Hash 索引中存放的是經(jīng)過(guò) Hash 計(jì)算之后的 Hash值,而且Hash值的大小關(guān)系并不一定和 Hash運(yùn)算前的鍵值完全一樣,所以數(shù)據(jù)庫(kù)無(wú)法利用索引的數(shù)據(jù)來(lái)避免任何排序運(yùn)算;

Hash 索引不能利用部分索引鍵查詢。

對(duì)于組合索引,Hash 索引在計(jì)算 Hash 值的時(shí)候是組合索引鍵合并后再一起計(jì)算 Hash 值,而不是單獨(dú)計(jì)算 Hash值,所以通過(guò)組合索引的前面一個(gè)或幾個(gè)索引鍵進(jìn)行查詢的時(shí)候,Hash 索引也無(wú)法被利用。

Hash 索引依然需要回表掃描。

Hash 索引是將索引鍵通過(guò) Hash 運(yùn)算之后,將 Hash運(yùn)算結(jié)果的 Hash值和所對(duì)應(yīng)的行指針信息存放于一個(gè) Hash 表中,由于不同索引鍵可能存在相同 Hash 值,所以即使取滿足某個(gè) Hash 鍵值的數(shù)據(jù)的記錄條數(shù),也無(wú)法從 Hash索引中直接完成查詢,還是要通過(guò)訪問(wèn)表中的實(shí)際數(shù)據(jù)進(jìn)行相應(yīng)的比較,并得到相應(yīng)的結(jié)果。

Hash索引遇到大量Hash值相等的情況后性能并不一定就會(huì)比B-Tree索引高。

選擇性比較低的索引鍵,如果創(chuàng)建 Hash 索引,那么將會(huì)存在大量記錄指針信息存于同一個(gè)Hash值相關(guān)聯(lián)。這樣要定位某一條記錄時(shí)就會(huì)非常麻煩,會(huì)浪費(fèi)多次表數(shù)據(jù)的訪問(wèn),而造成整體性能低下

由于范圍查詢是MySQL數(shù)據(jù)庫(kù)查詢中常見(jiàn)的場(chǎng)景,Hash表不適合做范圍查詢,它更適合做等值查詢。另外Hash表還存在Hash函數(shù)選擇和Hash值沖突等問(wèn)題。因此,B+tree索引要比Hash表索引有更廣的適用場(chǎng)景。

4.8.2 按物理存儲(chǔ)分類

MySQL索引按葉子節(jié)點(diǎn)存儲(chǔ)的是否為完整表數(shù)據(jù)分為:聚簇索引、二級(jí)索引(輔助索引)。全表數(shù)據(jù)存儲(chǔ)在聚簇索引中,聚簇索引以外的其他索引叫做二級(jí)索引,也叫輔助索引。

1. 聚簇索引

聚簇索引的每個(gè)葉子節(jié)點(diǎn)存儲(chǔ)了一行完整的表數(shù)據(jù),葉子節(jié)點(diǎn)間按id列遞增連接,可以方便地進(jìn)行順序檢索。

聚簇索引B+tree示意圖(圖片來(lái)源于網(wǎng)絡(luò))

(圖片來(lái)源于網(wǎng)絡(luò))

InnoDB表要求必須有聚簇索引,默認(rèn)在主鍵字段上建立聚簇索引,在沒(méi)有主鍵字段的情況下,表的第一個(gè)非空的唯一索引將被建立為聚簇索引,在前兩者都沒(méi)有的情況下,InnoDB將自動(dòng)生成一個(gè)隱式的自增id列,并在此列上建立聚簇索引。

以MyISAM為存儲(chǔ)引擎的表不存在聚簇索引。

MyISAM表中的主鍵索引和非主鍵索引的結(jié)構(gòu)是一樣的,索引的葉子節(jié)點(diǎn)不存儲(chǔ)表數(shù)據(jù),存放的是表數(shù)據(jù)的地址。所以,MyISAM表可以沒(méi)有主鍵。

MyISAM索引B+tree示意圖(圖片來(lái)源于網(wǎng)絡(luò))

(圖片來(lái)源于網(wǎng)絡(luò))

MyISAM表的數(shù)據(jù)和索引是分開(kāi)存儲(chǔ)的。MyISAM表的主鍵索引和非主鍵索引的區(qū)別僅在于主鍵索引的B+tree上的key必須符合主鍵的限制,非主鍵索引B+tree上的key只要符合相應(yīng)字段的特性就可以了。

2. 二級(jí)索引

二級(jí)索引的葉子節(jié)點(diǎn)并不存儲(chǔ)一行完整的表數(shù)據(jù),而是存儲(chǔ)了聚簇索引所在列的值。

二級(jí)索引B+tree示意圖(圖片來(lái)源于網(wǎng)絡(luò))

(圖片來(lái)源于網(wǎng)絡(luò))

回表查詢

由于二級(jí)索引的葉子節(jié)點(diǎn)不存儲(chǔ)完整的表數(shù)據(jù),索引當(dāng)通過(guò)二級(jí)索引查詢到聚簇索引列值后,還需要回到聚簇索引也就是表數(shù)據(jù)本身進(jìn)一步獲取數(shù)據(jù)。

回表查詢示意圖(圖片來(lái)源于網(wǎng)絡(luò))

(圖片來(lái)源于網(wǎng)絡(luò))

回表查詢 需要額外的 B+tree 搜索過(guò)程,必然增大查詢耗時(shí)。

需要注意的是,通過(guò)二級(jí)索引查詢時(shí),回表不是必須的過(guò)程,當(dāng)SELECT的所有字段在單個(gè)二級(jí)索引中都能夠找到時(shí),就不需要回表,MySQL稱此時(shí)的二級(jí)索引為覆蓋索引或觸發(fā)了索引覆蓋
可以用Explain命令查看SQL語(yǔ)句的執(zhí)行計(jì)劃,執(zhí)行計(jì)劃的Extra字段中若出現(xiàn)Using index,表示查詢觸發(fā)了索引覆蓋

4.8.3 按字段特性分類

MySQL索引按字段特性分類可分為:主鍵索引、普通索引、前綴索引。

1. 主鍵索引

建立在主鍵上的索引被稱為主鍵索引,一張數(shù)據(jù)表只能有一個(gè)主鍵索引,索引列值不允許有空值,通常在創(chuàng)建表時(shí)一起創(chuàng)建。

2. 唯一索引

建立在UNIQUE字段上的索引被稱為唯一索引,一張表可以有多個(gè)唯一索引,索引列值允許為空,列值中出現(xiàn)多個(gè)空值不會(huì)發(fā)生重復(fù)沖突。

3. 普通索引

建立在普通字段上的索引被稱為普通索引。

4. 前綴索引

前綴索引是指對(duì)字符類型字段的前幾個(gè)字符或?qū)ΧM(jìn)制類型字段的前幾個(gè)bytes建立的索引,而不是在整個(gè)字段上建索引。前綴索引可以建立在類型為char、varchar、binary、varbinary的列上,可以大大減少索引占用的存儲(chǔ)空間,也能提升索引的查詢效率。

4.8.4 按索引字段個(gè)數(shù)分類

MySQL索引按字段個(gè)數(shù)分類可分為:單列索引、聯(lián)合索引(復(fù)合索引、組合索引)

1. 單列索引

建立在單個(gè)列上的索引被稱為單列索引。

2. 聯(lián)合索引(復(fù)合索引、組合索引)

建立在多個(gè)列上的索引被稱為聯(lián)合索引,又叫復(fù)合索引、組合索引。

5. 數(shù)據(jù)結(jié)構(gòu)

5.1 AVL樹(shù)(平衡二叉樹(shù))

以樹(shù)中所有結(jié)點(diǎn)為根的樹(shù)的左右子樹(shù)高度之差的絕對(duì)值不超過(guò)1

5.2 紅黑樹(shù)

紅黑樹(shù)是一種二叉查找樹(shù),但在每個(gè)節(jié)點(diǎn)增加一個(gè)存儲(chǔ)位表示節(jié)點(diǎn)的顏色,可以是紅或黑(非紅即黑)。通過(guò)對(duì)任何一條從根到葉子的路徑上各個(gè)節(jié)點(diǎn)著色的方式的限制,紅黑樹(shù)確保沒(méi)有一條路徑會(huì)比其它路徑長(zhǎng)出兩倍 ,因此,紅黑樹(shù)是一種弱平衡二叉樹(shù),相對(duì)于要求嚴(yán)格的AVL樹(shù)來(lái)說(shuō),它的旋轉(zhuǎn)次數(shù)少,所以對(duì)于搜索,插入,刪除操作較多的情況下,通常使用紅黑樹(shù)。

  • 性質(zhì):

    • 每個(gè)節(jié)點(diǎn)非紅即黑;
    • 根節(jié)點(diǎn)是黑的;
    • 每個(gè)葉節(jié)點(diǎn)(葉節(jié)點(diǎn)即樹(shù)尾端NULL指針或NULL節(jié)點(diǎn))都是黑的;
    • 如果一個(gè)節(jié)點(diǎn)是紅色的,則它的子節(jié)點(diǎn)必須是黑色的;
    • 對(duì)于任意節(jié)點(diǎn)而言,其到葉子點(diǎn)樹(shù)NULL指針的每條路徑都包含相同數(shù)目的黑節(jié)點(diǎn);
  • 與 AVL 樹(shù)相比:

    AVL 樹(shù)是高度平衡的,頻繁的插入和刪除,會(huì)引起頻繁的rebalance,導(dǎo)致效率下降;紅黑樹(shù)不是高度平衡的,算是一種折中,插入最多兩次旋轉(zhuǎn),刪除最多三次旋轉(zhuǎn)。

    紅黑樹(shù)在查找,插入刪除的性能都是O(logn),且性能穩(wěn)定,所以STL里面很多結(jié)構(gòu)包括map底層實(shí)現(xiàn)都是使用的紅黑樹(shù)。

5.3 哈夫曼樹(shù)

哈夫曼樹(shù)是一種帶權(quán)路徑長(zhǎng)度最短的二叉樹(shù),也稱為最優(yōu)二叉樹(shù)。

  • 構(gòu)建哈夫曼樹(shù):

    ds52
    • 將每個(gè)節(jié)點(diǎn)單獨(dú)作為一個(gè)樹(shù),構(gòu)成森林;
    • 選擇當(dāng)前森林中根節(jié)點(diǎn)權(quán)值最小的兩棵樹(shù),生成一個(gè)新的節(jié)點(diǎn)作為根節(jié)點(diǎn),其權(quán)值為兩棵樹(shù)的根節(jié)點(diǎn)的權(quán)值之和;
    • 從森林中刪除這兩棵樹(shù),并將新樹(shù)加到森林中;
    • 遞歸執(zhí)行前兩步,直到森林中只剩下一棵樹(shù),那棵樹(shù)即為構(gòu)造好的哈夫曼樹(shù)。
  • 哈夫曼編碼:

    ds50

    根據(jù)字符出現(xiàn)的頻率設(shè)計(jì)的一種可變編碼(VLC),概率越低的字符權(quán)值越低,構(gòu)造一個(gè)哈夫曼樹(shù),這樣概率越高的字符的編碼越短,概率越低的字符編碼越短,可以使得整個(gè)文本的長(zhǎng)度被大幅壓縮。

5.4 B樹(shù)

5.5 B+樹(shù)

5.6 堆和棧的區(qū)別

  • 申請(qǐng)方式:

    棧由系統(tǒng)自動(dòng)分配和管理,堆由程序員手動(dòng)分配和管理。

  • 效率:

    棧由系統(tǒng)分配,速度快,不會(huì)有內(nèi)存碎片;

    堆由程序員分配,速度較慢,可能由于操作不當(dāng)產(chǎn)生內(nèi)存碎片。

  • 擴(kuò)展方向:

    棧從高地址向低地址進(jìn)行擴(kuò)展,堆由低地址向高地址進(jìn)行擴(kuò)展。

  • 程序局部變量是使用的??臻g,new/malloc動(dòng)態(tài)申請(qǐng)的內(nèi)存是堆空間,函數(shù)調(diào)用時(shí)會(huì)進(jìn)行形參和返回值的壓棧出棧,也是用的??臻g。

5.7 Array和List的區(qū)別

  • Array
    • 優(yōu)點(diǎn):
      • 隨機(jī)訪問(wèn)性強(qiáng);
      • 查找速度快。
    • 缺點(diǎn):
      • 插入和刪除效率低;
      • 可能浪費(fèi)內(nèi)存;
      • 內(nèi)存空間要求高,必須有足夠的連續(xù)內(nèi)存空間;
      • 數(shù)組大小固定,不能動(dòng)態(tài)拓展。
  • List
    • 優(yōu)點(diǎn):
      • 插入刪除速度快;
      • 內(nèi)存利用率高,不會(huì)浪費(fèi)內(nèi)存;
      • 大小沒(méi)有固定,拓展很靈活。
    • 缺點(diǎn):
      • 不能隨機(jī)查找,必須從第一個(gè)開(kāi)始遍歷,查找效率低。

5.8 排序算法

img

5.9 解決Hash沖突的方法

開(kāi)放定址法、再哈希法、鏈地址法建立公共溢出區(qū)

5.10 分布式 rehash

http://www.itdecent.cn/p/b17bc9719645

5.11 并查集(Union-Find)

https://labuladong.gitbook.io/algo/mu-lu-ye-1/mu-lu-ye-2/unionfind-suan-fa-xiang-jie

  • 目標(biāo):解決圖論中 動(dòng)態(tài)連通性問(wèn)題

  • 實(shí)現(xiàn):

    • 用數(shù)組模擬一個(gè)森林;
    • 每個(gè)節(jié)點(diǎn)初始都指向自己;
    • 合并是找到兩棵樹(shù)的根節(jié)點(diǎn),把其中一個(gè)掛到另一個(gè)上面就可以了;
    class UF {
        // 連通分量個(gè)數(shù)
        private int count;
        // 存儲(chǔ)一棵樹(shù)
        private int[] parent;
        // 記錄樹(shù)的“重量”
        private int[] size;
    
        public UF(int n) {
            this.count = n;
            parent = new int[n];
            size = new int[n];
            for (int i = 0; i < n; i++) {
                parent[i] = i;
                size[i] = 1;
            }
        }
    
        public void union(int p, int q) {
            int rootP = find(p);
            int rootQ = find(q);
            if (rootP == rootQ)
                return;
    
            // 小樹(shù)接到大樹(shù)下面,較平衡
            if (size[rootP] > size[rootQ]) {
                parent[rootQ] = rootP;
                size[rootP] += size[rootQ];
            } else {
                parent[rootP] = rootQ;
                size[rootQ] += size[rootP];
            }
            count--;
        }
    
        public boolean connected(int p, int q) {
            int rootP = find(p);
            int rootQ = find(q);
            return rootP == rootQ;
        }
    
        private int find(int x) {
            while (parent[x] != x) {
                // 進(jìn)行路徑壓縮
                parent[x] = parent[parent[x]];
                x = parent[x];
            }
            return x;
        }
    
        public int count() {
            return count;
        }
    }
    
    img
  • 優(yōu)化:

    • 小樹(shù)掛到大樹(shù)上面;
    • 路徑壓縮
  • 應(yīng)用:

    • 可以作為DFS的一種替代方案,很多使用 DFS 深度優(yōu)先算法解決的問(wèn)題,也可以用 Union-Find 算法解決;
    • 表示一種等價(jià)性
  • 相關(guān)例題:

6. 算法

6.1 數(shù)據(jù)流中的中位數(shù)

https://leetcode-cn.com/problems/shu-ju-liu-zhong-de-zhong-wei-shu-lcof/

思路:使用兩個(gè)堆,大頂堆存較小的一半數(shù),小頂堆存較大的一半數(shù)

6.2 用摩爾投票法解決求眾數(shù)問(wèn)題

  • 相關(guān)題目:
  • 解題思路:
    • 記錄n個(gè)候選者的票數(shù),如果發(fā)現(xiàn)n+1個(gè)不同的候選者則全部抵消一票,最后留下來(lái)的候選者有可能為眾數(shù)票獲得者;
    • 再遍歷一遍查看余下的候選者是否超過(guò)了指定比例的票數(shù),如果超過(guò)則當(dāng)選。

6.3 Top K 問(wèn)題

  • 相關(guān)題目:

  • 解題思路:

    • 全部排序,取前K個(gè);
    • 用快排的思想,進(jìn)行分區(qū)劃分,當(dāng)找到一個(gè)分區(qū)元素的下標(biāo)等于k,且前k個(gè)均大于(或小于)這個(gè)元素,停止;(O(n)
    • 用堆(大頂堆或者小頂堆),持續(xù)push,最后pop K 個(gè)元素即可;(O(nlogn))
    • 用優(yōu)先隊(duì)列實(shí)現(xiàn)最小堆,先往其中push前K個(gè)元素,然后每次push一次,pop一次,最后取堆頂元素即可。

6.4 統(tǒng)計(jì) 1~n 整數(shù)中 1 出現(xiàn)的次數(shù)

  • 相關(guān)題目:

  • 解題思路:

    • 統(tǒng)計(jì)每個(gè)數(shù)位上1可能出現(xiàn)的次數(shù),然后加和

    • 對(duì)于每個(gè)數(shù)位上的1如何統(tǒng)計(jì),思路如下:

      • cur == 0

        Picture1.png
      • cur == 1

        Picture2.png
      • cur > 1

        Picture3.png

6.5 數(shù)字序列中的某位數(shù)字

  • 相關(guān)題目:

    數(shù)字序列中某一位的數(shù)字

  • 解題思路:

    • 1 ~ 9, 10 ~ 99, 100 ~ 999, ... => 若干個(gè)區(qū)間
    • 定位到其中一個(gè)區(qū)間,該區(qū)間內(nèi)所有的數(shù)字的位數(shù)都是相等的,可以簡(jiǎn)單的定位到指定數(shù)字

6.6 把數(shù)組排成最小的數(shù)

  • 相關(guān)題目:

    把數(shù)組排成最小的數(shù)

    輸入一個(gè)非負(fù)整數(shù)數(shù)組,把數(shù)組里所有數(shù)字拼接起來(lái)排成一個(gè)數(shù),打印能拼接出的所有數(shù)字中最小的一個(gè)。

  • 解題思路:

    • 對(duì)于任意兩個(gè)數(shù)字 x, y,定義排序規(guī)則為:

      • x + y > y + x => x > y
      • x + y < y + x => x < y
    • 自定義 C++ priority_queue 比較函數(shù):

      struct queue_cmp {
        bool operator()(const string &a, const string &b) {
          return stol(a + b) > stol(b + a);
        }
      };
      
    • 使用優(yōu)先隊(duì)列實(shí)現(xiàn)最小堆來(lái)排序:

      priority_queue<string, vector<string>, queue_cmp> q;
      

6.7 最長(zhǎng)不包含重復(fù)字符的子字符串

  • 相關(guān)題目:

    最長(zhǎng)不含重復(fù)字符的子字符串

    請(qǐng)從字符串中找出一個(gè)最長(zhǎng)的不包含重復(fù)字符的子字符串,計(jì)算該最長(zhǎng)子字符串的長(zhǎng)度。

  • 解題思路:

    • 雙指針滑動(dòng)法:

    • 動(dòng)態(tài)規(guī)劃+哈希表:
      • 用hash表記錄某個(gè)字符最后出現(xiàn)的位置;
      • dp[i] 表示以 s[i] 結(jié)尾的最長(zhǎng)的不含重復(fù)字符的子串的長(zhǎng)度;
      • base case: dp[0] = 1;
      • 狀態(tài)轉(zhuǎn)移方程:
        • f(i)=\left\{ \begin{aligned} f(i - 1) + 1 & &dict[i] < i - f(i - 1) \\ i - dict[i] & &dict[i] >= i - f(i - 1) \\ \end{aligned} \right.

6.8 丑數(shù)

  • 相關(guān)題目:

    丑數(shù)

    我們把只包含質(zhì)因子 2、3 和 5 的數(shù)稱作丑數(shù)(Ugly Number)。求按從小到大的順序的第 n 個(gè)丑數(shù)。

  • 解題思路:

    • 最小堆:

      • 先將1放入;
      • 每次取堆頂元素,并對(duì)堆頂元素分別×2、3、5,加入堆;
      • 循環(huán)取n次即可;
    • 動(dòng)態(tài)規(guī)劃:

      • dp[i] 表示第 i+1 個(gè) 丑數(shù);

      • pa => 指向下一個(gè)丑數(shù) * 2,pb => 指向下一個(gè)丑數(shù) * 3, pc 指向下一個(gè)丑數(shù) * 5;

      • base case: dp[0] = 1, pa = 0, pb = 0, pc = 0;

      • 狀態(tài)轉(zhuǎn)移方程:

        f(i)=\left\{ \begin{aligned} min\{f(pa) * 2, f(pb) * 3, f(pc) * 5\} && i > 0 \\ \end{aligned} \right.

        if (dp[i] == n1) a++;
        if (dp[i] == n2) b++;
        if (dp[i] == n3) c++;
        

6.9 數(shù)組中的逆序?qū)Γy)

6.10 數(shù)組中數(shù)字出現(xiàn)的次數(shù)(中)

  • 相關(guān)題目:

    劍指 Offer 56 - I. 數(shù)組中數(shù)字出現(xiàn)的次數(shù)

    一個(gè)整型數(shù)組 nums 里除兩個(gè)數(shù)字之外,其他數(shù)字都出現(xiàn)了兩次。請(qǐng)寫程序找出這兩個(gè)只出現(xiàn)一次的數(shù)字。要求時(shí)間復(fù)雜度是O(n),空間復(fù)雜度是O(1)。

  • 解題思路:

    • 對(duì)于一個(gè)數(shù)組中除了一個(gè)數(shù)字之外,其它數(shù)字都出現(xiàn)了兩次的情況,只要對(duì)所有的數(shù)字進(jìn)行異或,剩下的結(jié)果就是那個(gè)數(shù)字;
    • 對(duì)于一個(gè)數(shù)組中除了兩個(gè)數(shù)字之外,其它數(shù)字都出現(xiàn)了兩次的情況 => 可以將數(shù)組劃分成兩個(gè)子數(shù)組,保證兩個(gè)子數(shù)組都符合上面的情況,就可以簡(jiǎn)化成異或計(jì)算。
    • 所以核心問(wèn)題是 => 怎么去將數(shù)組里面的元素劃分成兩組,保證每組里面除了一個(gè)數(shù)字之外,其它數(shù)字都出現(xiàn)了兩次
    • 假設(shè)這兩個(gè)只出現(xiàn)一次的數(shù)字為 xy ,那么對(duì)數(shù)組的所有數(shù)字異或的結(jié)果 => x \oplus y ,記為 z
    • 那么只要根據(jù) z 的第一個(gè)為1的位來(lái)分組即可;

擴(kuò)展:劍指 Offer 56 - II. 數(shù)組中數(shù)字出現(xiàn)的次數(shù) II

6.11 約瑟夫環(huán)問(wèn)題

6.12 最長(zhǎng)遞增子序列(中)

  • 相關(guān)題目:

    給你一個(gè)整數(shù)數(shù)組 nums ,找到其中最長(zhǎng)嚴(yán)格遞增子序列的長(zhǎng)度。

    子序列是由數(shù)組派生而來(lái)的序列,刪除(或不刪除)數(shù)組中的元素而不改變其余元素的順序。例如,[3,6,2,7] 是數(shù)組 [0,3,1,6,2,2,7] 的子序列。

    最長(zhǎng)遞增子序列

  • 解題思路:

    • 動(dòng)態(tài)規(guī)劃:

      • dp[i] => 以 nums[i] 結(jié)尾的最長(zhǎng)遞增子序列長(zhǎng)度

      • base case: dp[i] = 1

      • 狀態(tài)轉(zhuǎn)移方程:

        f(n) = max\{f(i) | 0 <= i < n \&\& nums[i] < nums[n]\} + 1

6.13 俄羅斯信封套娃問(wèn)題(難)

  • 相關(guān)題目:

    給你一個(gè)二維整數(shù)數(shù)組 envelopes ,其中 envelopes[i] = [wi, hi] ,表示第 i 個(gè)信封的寬度和高度。

    當(dāng)另一個(gè)信封的寬度和高度都比這個(gè)信封大的時(shí)候,這個(gè)信封就可以放進(jìn)另一個(gè)信封里,如同俄羅斯套娃一樣。

    請(qǐng)計(jì)算 最多能有多少個(gè) 信封能組成一組“俄羅斯套娃”信封(即可以把一個(gè)信封放到另一個(gè)信封里面)。

    注意:不允許旋轉(zhuǎn)信封。

    俄羅斯套娃信封問(wèn)題

  • 解題思路:

    • 按 w 升序之后,相同的 w 情況下按 h 降序進(jìn)行排序;(因?yàn)橄嗤瑢挾鹊牟荒芮短?,所以要?duì)h進(jìn)行降序排序)

      // 先按第一個(gè)元素升序,按第二個(gè)元素降序
      sort(envelopes.begin(), envelopes.end(), [](const vector<int> &a, const vector<int> &b) {
        if (a[0] == b[0]) {
          return a[1] > b[1];
        }
        return a[0] < b[0];
      });
      
    • 對(duì)排序后的數(shù)組,按 h 進(jìn)行最長(zhǎng)遞增子序列求解即可。

7. Redis

https://juejin.cn/post/6844903982066827277

8. 其它

7.1 HTTP2.0

https://mp.weixin.qq.com/s?srcid=0729OoCJZv9OMMrHijFaNuhH&scene=23&sharer_sharetime=1627488323223&mid=2247486228&sharer_shareid=59606716db4868d8bb3a343d6a801bcd&sn=d2f686ff92344889764454bcdb0e8a07&idx=1&__biz=Mzg2ODU1MDkwMw%3D%3D&chksm=ceabda8cf9dc539a52809389b5e4494e00d1cc52a42350dae15043182f7c1963d7c90e12b626&mpshare=1#rd

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容