日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當(dāng)前位置: 首頁 > 编程资源 > 编程问答 >内容正文

编程问答

数据结构算法常见面试考题

發(fā)布時(shí)間:2024/8/1 编程问答 49 豆豆
生活随笔 收集整理的這篇文章主要介紹了 数据结构算法常见面试考题 小編覺得挺不錯(cuò)的,現(xiàn)在分享給大家,幫大家做個(gè)參考.

(1) 紅黑樹的了解(平衡樹,二叉搜索樹),使用場景

把數(shù)據(jù)結(jié)構(gòu)上幾種樹集中的討論一下:

1.AVLtree

定義:最先發(fā)明的自平衡二叉查找樹。在AVL樹中任何節(jié)點(diǎn)的兩個(gè)子樹的高度最大差別為一,所以它也被稱為高度平衡樹。查找、插入和刪除在平均和最壞情況下都是O(log n)。增加和刪除可能需要通過一次或多次樹旋轉(zhuǎn)來重新平衡這個(gè)樹。

節(jié)點(diǎn)的平衡因子是它的左子樹的高度減去它的右子樹的高度(有時(shí)相反)。帶有平衡因子1、0或 -1的節(jié)點(diǎn)被認(rèn)為是平衡的。帶有平衡因子 -2或2的節(jié)點(diǎn)被認(rèn)為是不平衡的,并需要重新平衡這個(gè)樹。平衡因子可以直接存儲(chǔ)在每個(gè)節(jié)點(diǎn)中,或從可能存儲(chǔ)在節(jié)點(diǎn)中的子樹高度計(jì)算出來。
一般我們所看見的都是排序平衡二叉樹。

AVLtree使用場景:AVL樹適合用于插入刪除次數(shù)比較少,但查找多的情況。插入刪除導(dǎo)致很多的旋轉(zhuǎn),旋轉(zhuǎn)是非常耗時(shí)的。AVL 在linux內(nèi)核的vm area中使用。

2.二叉搜索樹

二叉搜索樹也是一種樹,適用與一般二叉樹的全部操作,但二叉搜索樹能夠?qū)崿F(xiàn)數(shù)據(jù)的快速查找。

二叉搜索樹滿足的條件:

1.非空左子樹的所有鍵值小于其根節(jié)點(diǎn)的鍵值
2.非空右子樹的所有鍵值大于其根節(jié)點(diǎn)的鍵值
3.左右子樹都是二叉搜索樹

二叉搜索樹的應(yīng)用場景:如果是沒有退化稱為鏈表的二叉樹,查找效率就是lgn,效率不錯(cuò),但是一旦退換稱為鏈表了,要么使用平衡二叉樹,或者之后的RB樹,因?yàn)殒湵砭褪蔷€性的查找效率。

3.紅黑樹的定義

紅黑樹是一種二叉查找樹,但在每個(gè)結(jié)點(diǎn)上增加了一個(gè)存儲(chǔ)位表示結(jié)點(diǎn)的顏色,可以是RED或者BLACK。通過對任何一條從根到葉子的路徑上各個(gè)著色方式的限制,紅黑樹確保沒有一條路徑會(huì)比其他路徑長出兩倍,因而是接近平衡的。

當(dāng)二叉查找樹的高度較低時(shí),這些操作執(zhí)行的比較快,但是當(dāng)樹的高度較高時(shí),這些操作的性能可能不比用鏈表好。紅黑樹(red-black tree)是一種平衡的二叉查找樹,它能保證在最壞情況下,基本的動(dòng)態(tài)操作集合運(yùn)行時(shí)間為O(lgn)。

紅黑樹必須要滿足的五條性質(zhì):

性質(zhì)一:節(jié)點(diǎn)是紅色或者是黑色; 在樹里面的節(jié)點(diǎn)不是紅色的就是黑色的,沒有其他顏色,要不怎么叫紅黑樹呢,是吧。

性質(zhì)二:根節(jié)點(diǎn)是黑色; 根節(jié)點(diǎn)總是黑色的。它不能為紅。

性質(zhì)三:每個(gè)葉節(jié)點(diǎn)(NIL或空節(jié)點(diǎn))是黑色;

性質(zhì)四:每個(gè)紅色節(jié)點(diǎn)的兩個(gè)子節(jié)點(diǎn)都是黑色的(也就是說不存在兩個(gè)連續(xù)的紅色節(jié)點(diǎn)); 就是連續(xù)的兩個(gè)節(jié)點(diǎn)不能是連續(xù)的紅色,連續(xù)的兩個(gè)節(jié)點(diǎn)的意思就是父節(jié)點(diǎn)與子節(jié)點(diǎn)不能是連續(xù)的紅色。

性質(zhì)五:從任一節(jié)點(diǎn)到其每個(gè)葉節(jié)點(diǎn)的所有路徑都包含相同數(shù)目的黑色節(jié)點(diǎn)。從根節(jié)點(diǎn)到每一個(gè)NIL節(jié)點(diǎn)的路徑中,都包含了相同數(shù)量的黑色節(jié)點(diǎn)。

紅黑樹的應(yīng)用場景:紅黑樹是一種不是非常嚴(yán)格的平衡二叉樹,沒有AVLtree那么嚴(yán)格的平衡要求,所以它的平均查找,增添刪除效率都還不錯(cuò)。廣泛用在C++的STL中。如map和set都是用紅黑樹實(shí)現(xiàn)的。

4.B樹定義

B樹和平衡二叉樹稍有不同的是B樹屬于多叉樹又名平衡多路查找樹(查找路徑不只兩個(gè)),不屬于二叉搜索樹的范疇,因?yàn)樗恢箖陕?#xff0c;存在多路。

B樹滿足的條件:

(1)樹種的每個(gè)節(jié)點(diǎn)最多擁有m個(gè)子節(jié)點(diǎn)且m>=2,空樹除外(注:m階代表一個(gè)樹節(jié)點(diǎn)最多有多少個(gè)查找路徑,m階=m路,當(dāng)m=2則是2叉樹,m=3則是3叉);
(2)除根節(jié)點(diǎn)外每個(gè)節(jié)點(diǎn)的關(guān)鍵字?jǐn)?shù)量大于等于ceil(m/2)-1個(gè)小于等于m-1個(gè),非根節(jié)點(diǎn)關(guān)鍵字?jǐn)?shù)必須>=2;(注:ceil()是個(gè)朝正無窮方向取整的函數(shù) 如ceil(1.1)結(jié)果為2)
(3)所有葉子節(jié)點(diǎn)均在同一層、葉子節(jié)點(diǎn)除了包含了關(guān)鍵字和關(guān)鍵字記錄的指針外也有指向其子節(jié)點(diǎn)的指針只不過其指針地址都為null對應(yīng)下圖最后一層節(jié)點(diǎn)的空格子
(4)如果一個(gè)非葉節(jié)點(diǎn)有N個(gè)子節(jié)點(diǎn),則該節(jié)點(diǎn)的關(guān)鍵字?jǐn)?shù)等于N-1;
(5)所有節(jié)點(diǎn)關(guān)鍵字是按遞增次序排列,并遵循左小右大原則;

B樹的應(yīng)用場景:構(gòu)造一個(gè)多階的B類樹,然后在盡量多的在結(jié)點(diǎn)上存儲(chǔ)相關(guān)的信息,保證層數(shù)盡量的少,以便后面我們可以更快的找到信息,磁盤的I/O操作也少一些,而且B類樹是平衡樹,每個(gè)結(jié)點(diǎn)到葉子結(jié)點(diǎn)的高度都是相同,這也保證了每個(gè)查詢是穩(wěn)定的。

5.B+樹

B+樹是B樹的一個(gè)升級版,B+樹是B樹的變種樹,有n棵子樹的節(jié)點(diǎn)中含有n個(gè)關(guān)鍵字,每個(gè)關(guān)鍵字不保存數(shù)據(jù),只用來索引,數(shù)據(jù)都保存在葉子節(jié)點(diǎn)。是為文件系統(tǒng)而生的。

相對于B樹來說B+樹更充分的利用了節(jié)點(diǎn)的空間,讓查詢速度更加穩(wěn)定,其速度完全接近于二分法查找。為什么說B+樹查找的效率要比B樹更高、更穩(wěn)定;我們先看看兩者的區(qū)別

(1)B+跟B樹不同,B+樹的非葉子節(jié)點(diǎn)不保存關(guān)鍵字記錄的指針,這樣使得B+樹每個(gè)節(jié)點(diǎn)所能保存的關(guān)鍵字大大增加;
(2)B+樹葉子節(jié)點(diǎn)保存了父節(jié)點(diǎn)的所有關(guān)鍵字和關(guān)鍵字記錄的指針,每個(gè)葉子節(jié)點(diǎn)的關(guān)鍵字從小到大鏈接;
(3)B+樹的根節(jié)點(diǎn)關(guān)鍵字?jǐn)?shù)量和其子節(jié)點(diǎn)個(gè)數(shù)相等;
(4)B+的非葉子節(jié)點(diǎn)只進(jìn)行數(shù)據(jù)索引,不會(huì)存實(shí)際的關(guān)鍵字記錄的指針,所有數(shù)據(jù)地址必須要到葉子節(jié)點(diǎn)才能獲取到,所以每次數(shù)據(jù)查詢的次數(shù)都一樣;

特點(diǎn):
在B樹的基礎(chǔ)上每個(gè)節(jié)點(diǎn)存儲(chǔ)的關(guān)鍵字?jǐn)?shù)更多,樹的層級更少所以查詢數(shù)據(jù)更快,所有指關(guān)鍵字指針都存在葉子節(jié)點(diǎn),所以每次查找的次數(shù)都相同所以查詢速度更穩(wěn)定;

應(yīng)用場景: 用在磁盤文件組織 數(shù)據(jù)索引和數(shù)據(jù)庫索引。

6.Trie樹(字典樹)

trie,又稱前綴樹,是一種有序樹,用于保存關(guān)聯(lián)數(shù)組,其中的鍵通常是字符串。與二叉查找樹不同,鍵不是直接保存在節(jié)點(diǎn)中,而是由節(jié)點(diǎn)在樹中的位置決定。一個(gè)節(jié)點(diǎn)的所有子孫都有相同的前綴,也就是這個(gè)節(jié)點(diǎn)對應(yīng)的字符串,而根節(jié)點(diǎn)對應(yīng)空字符串。一般情況下,不是所有的節(jié)點(diǎn)都有對應(yīng)的值,只有葉子節(jié)點(diǎn)和部分內(nèi)部節(jié)點(diǎn)所對應(yīng)的鍵才有相關(guān)的值。

在圖示中,鍵標(biāo)注在節(jié)點(diǎn)中,值標(biāo)注在節(jié)點(diǎn)之下。每一個(gè)完整的英文單詞對應(yīng)一個(gè)特定的整數(shù)。Trie 可以看作是一個(gè)確定有限狀態(tài)自動(dòng)機(jī),盡管邊上的符號一般是隱含在分支的順序中的。
鍵不需要被顯式地保存在節(jié)點(diǎn)中。圖示中標(biāo)注出完整的單詞,只是為了演示 trie 的原理。

trie樹的優(yōu)點(diǎn):利用字符串的公共前綴來節(jié)約存儲(chǔ)空間,最大限度地減少無謂的字符串比較,查詢效率比哈希表高。缺點(diǎn):Trie樹是一種比較簡單的數(shù)據(jù)結(jié)構(gòu).理解起來比較簡單,正所謂簡單的東西也得付出代價(jià).故Trie樹也有它的缺點(diǎn),Trie樹的內(nèi)存消耗非常大.

其基本性質(zhì)可以歸納為:

  • 根節(jié)點(diǎn)不包含字符,除根節(jié)點(diǎn)外每一個(gè)節(jié)點(diǎn)都只包含一個(gè)字符。
  • 從根節(jié)點(diǎn)到某一節(jié)點(diǎn),路徑上經(jīng)過的字符連接起來,為該節(jié)點(diǎn)對應(yīng)的字符串。
  • 每個(gè)節(jié)點(diǎn)的所有子節(jié)點(diǎn)包含的字符都不相同。
  • 典型應(yīng)用是用于統(tǒng)計(jì),排序和保存大量的字符串(但不僅限于字符串),所以經(jīng)常被搜索引擎系統(tǒng)用于文本詞頻統(tǒng)計(jì)。字典樹與字典很相似,當(dāng)你要查一個(gè)單詞是不是在字典樹中,首先看單詞的第一個(gè)字母是不是在字典的第一層,如果不在,說明字典樹里沒有該單詞,如果在就在該字母的孩子節(jié)點(diǎn)里找是不是有單詞的第二個(gè)字母,沒有說明沒有該單詞,有的話用同樣的方法繼續(xù)查找.字典樹不僅可以用來儲(chǔ)存字母,也可以儲(chǔ)存數(shù)字等其它數(shù)據(jù)。

    (2) 紅黑樹在STL上的應(yīng)用

    STL中set、multiset、map、multimap底層是紅黑樹實(shí)現(xiàn)的,而unordered_map、unordered_set 底層是哈希表實(shí)現(xiàn)的。

    multiset、multimap: 插入相同的key的時(shí)候,我們將后插入的key放在相等的key的右邊,之后不管怎么進(jìn)行插入或刪除操作,后加入的key始終被認(rèn)為比之前的大。

    (3) 了解并查集嗎?(低頻)

    什么是合并查找問題呢?

    顧名思義,就是既有合并又有查找操作的問題。舉個(gè)例子,有一群人,他們之間有若干好友關(guān)系。如果兩個(gè)人有直接或者間接好友關(guān)系,那么我們就說他們在同一個(gè)朋友圈中,這里解釋下,如果Alice是Bob好友的好友,或者好友的好友的好友等等,即通過若干好友可以認(rèn)識(shí),那么我們說Alice和Bob是間接好友。隨著時(shí)間的變化,這群人中有可能會(huì)有新的朋友關(guān)系,這時(shí)候我們會(huì)對當(dāng)中某些人是否在同一朋友圈進(jìn)行詢問。這就是一個(gè)典型的合并-查找操作問題,既包含了合并操作,又包含了查找操作。

    并查集,在一些有N個(gè)元素的集合應(yīng)用問題中,我們通常是在開始時(shí)讓每個(gè)元素構(gòu)成一個(gè)單元素的集合,然后按一定順序?qū)儆谕唤M的元素所在的集合合并,其間要反復(fù)查找一個(gè)元素在哪個(gè)集合中。

    并查集是一種樹型的數(shù)據(jù)結(jié)構(gòu),用于處理一些不相交集合(Disjoint Sets)的合并及查詢問題。

    并查集也是使用樹形結(jié)構(gòu)實(shí)現(xiàn)。不過,不是二叉樹。每個(gè)元素對應(yīng)一個(gè)節(jié)點(diǎn),每個(gè)組對應(yīng)一棵樹。在并查集中,哪個(gè)節(jié)點(diǎn)是哪個(gè)節(jié)點(diǎn)的父親以及樹的形狀等信息無需多加關(guān)注,整體組成一個(gè)樹形結(jié)構(gòu)才是重要的。類似森林

    (4) 貪心算法和動(dòng)態(tài)規(guī)劃的區(qū)別

    貪心算法:局部最優(yōu),劃分的每個(gè)子問題都最優(yōu),得到全局最優(yōu),但是不能保證是全局最優(yōu)解,所以對于貪心算法來說,解是從上到下的,一步一步最優(yōu),直到最后。

    動(dòng)態(tài)規(guī)劃:將問題分解成重復(fù)的子問題,每次都尋找左右子問題解中最優(yōu)的解,一步步得到全局的最優(yōu)解.重復(fù)的子問題可以通過記錄的方式,避免多次計(jì)算。所以對于動(dòng)態(tài)規(guī)劃來說,解是從小到上,從底層所有可能性中找到最優(yōu)解,再一步步向上。

    分治法:和動(dòng)態(tài)規(guī)劃類似,將大問題分解成小問題,但是這些小問題是獨(dú)立的,沒有重復(fù)的問題。獨(dú)立問題取得解,再合并成大問題的解。

    例子:比如錢幣分為1元3元4元,要拿6元錢,貪心的話,先拿4,再拿兩個(gè)1,一共3張錢;實(shí)際最優(yōu)卻是兩張3元就夠了。

    (5) 判斷一個(gè)鏈表是否有環(huán),如何找到這個(gè)環(huán)的起點(diǎn)

    給定一個(gè)單鏈表,只給出頭指針h:
    1、如何判斷是否存在環(huán)?
    2、如何知道環(huán)的長度?
    3、如何找出環(huán)的連接點(diǎn)在哪里?
    4、帶環(huán)鏈表的長度是多少?

    解法:
    1、對于問題1,使用追趕的方法,設(shè)定兩個(gè)指針slow、fast,從頭指針開始,每次分別前進(jìn)1步、2步。如存在環(huán),則兩者相遇;如不存在環(huán),fast遇到NULL退出。
    2、對于問題2,記錄下問題1的碰撞點(diǎn)p,slow、fast從該點(diǎn)開始,再次碰撞所走過的操作數(shù)就是環(huán)的長度s。
    3、問題3:有定理:碰撞點(diǎn)p到連接點(diǎn)的距離=頭指針到連接點(diǎn)的距離,因此,分別從碰撞點(diǎn)、頭指針開始走,相遇的那個(gè)點(diǎn)就是連接點(diǎn)。(證明在后面附注)
    4、問題3中已經(jīng)求出連接點(diǎn)距離頭指針的長度,加上問題2中求出的環(huán)的長度,二者之和就是帶環(huán)單鏈表的長度
    http://blog.sina.com.cn/s/blog_725dd1010100tqwp.html

    (6) 實(shí)現(xiàn)一個(gè)strcpy函數(shù)(或者memcpy),如果內(nèi)存可能重疊呢

    ——大家一般認(rèn)為名不見經(jīng)傳strcpy函數(shù)實(shí)現(xiàn)不是很難,流行的strcpy函數(shù)寫法是:

    1. char *my_strcpy(char *dst,const char *src) 2. { 3. assert(dst != NULL); 4. assert(src != NULL); 5. char *ret = dst; 6. while((* dst++ = * src++) != '\0') 7. ; 8. return ret; 9. }

    如果注意到:
    1,檢查指針有效性;
    2,返回目的指針des;
    3,源字符串的末尾 ‘\0’ 需要拷貝。

    內(nèi)存重疊

    內(nèi)存重疊:拷貝的目的地址在源地址范圍內(nèi)。所謂內(nèi)存重疊就是拷貝的目的地址和源地址有重疊。
    在函數(shù)strcpy和函數(shù)memcpy都沒有對內(nèi)存重疊做處理的,使用這兩個(gè)函數(shù)的時(shí)候只有程序員自己保證源地址和目標(biāo)地址不重疊,或者使用memmove函數(shù)進(jìn)行內(nèi)存拷貝。
    memmove函數(shù)對內(nèi)存重疊做了處理。
    strcpy的正確實(shí)現(xiàn)應(yīng)為:

    1. char *my_strcpy(char *dst,const char *src) 2. { 3. assert(dst != NULL); 4. assert(src != NULL); 5. char *ret = dst; 6. memmove(dst,src,strlen(src)+1); 7. return ret; 8. }

    memmove函數(shù)實(shí)現(xiàn)時(shí)考慮到了內(nèi)存重疊的情況,可以完成指定大小的內(nèi)存拷貝

    (7) 快排存在的問題,如何優(yōu)化

    快排的時(shí)間復(fù)雜度

    時(shí)間復(fù)雜度最快平均是O(nlogn),最慢的時(shí)候是O(n2);輔助空間也是O(logn);最開始學(xué)快排時(shí)最疑惑的就是這個(gè)東西不知道怎么得來的,一種是通過數(shù)學(xué)運(yùn)算可以的出來,還有一種是通過遞歸樹來理解就容易多了

    這張圖片別人博客那里弄過來的,所謂時(shí)間復(fù)雜度最理想的就是取到中位數(shù)情況,那么遞歸樹就是一個(gè)完全二叉樹,那么樹的深度也就是最低為Logn,這個(gè)時(shí)候每一次又需要n次比較,所以時(shí)間復(fù)雜度nlogn,當(dāng)快排為順序或者逆序時(shí),這個(gè)數(shù)為一個(gè)斜二叉樹,深度為n,同樣每次需要n次比較,那那么最壞需要n2的時(shí)間

    優(yōu)化:

    1.當(dāng)整個(gè)序列有序時(shí)退出算法;
    2.當(dāng)序列長度很小時(shí)(根據(jù)經(jīng)驗(yàn)是大概小于 8),應(yīng)該使用常數(shù)更小的算法,比如插入排序等;
    3.隨機(jī)選取分割位置;
    4.當(dāng)分割位置不理想時(shí),考慮是否重新選取分割位置;
    5.分割成兩個(gè)序列時(shí),只對其中一個(gè)遞歸進(jìn)去,另一個(gè)序列仍可以在這一函數(shù)內(nèi)繼續(xù)劃分,可以顯著減小棧的大小(尾遞歸):
    6.將單向掃描改成雙向掃描,可以減少劃分過程中的交換次數(shù)

    優(yōu)化1:當(dāng)待排序序列的長度分割到一定大小后,使用插入排序
    原因:對于很小和部分有序的數(shù)組,快排不如插排好。當(dāng)待排序序列的長度分割到一定大小后,繼續(xù)分割的效率比插入排序要差,此時(shí)可以使用插排而不是快排

    優(yōu)化2:在一次分割結(jié)束后,可以把與Key相等的元素聚在一起,繼續(xù)下次分割時(shí),不用再對與key相等元素分割

    優(yōu)化3:優(yōu)化遞歸操作
    快排函數(shù)在函數(shù)尾部有兩次遞歸操作,我們可以對其使用尾遞歸優(yōu)化

    優(yōu)點(diǎn):如果待排序的序列劃分極端不平衡,遞歸的深度將趨近于n,而棧的大小是很有限的,每次遞歸調(diào)用都會(huì)耗費(fèi)一定的棧空間,函數(shù)的參數(shù)越多,每次遞歸耗費(fèi)的空間也越多。優(yōu)化后,可以縮減堆棧深度,由原來的O(n)縮減為O(logn),將會(huì)提高性能。

    (8) Top K問題(可以采取的方法有哪些,各自優(yōu)點(diǎn)?)

    1.將輸入內(nèi)容(假設(shè)用數(shù)組存放)進(jìn)行完全排序,從中選出排在前K的元素即為所求。有了這個(gè)思路,我們可以選擇相應(yīng)的排序算法進(jìn)行處理,目前來看快速排序,堆排序和歸并排序都能達(dá)到O(nlogn)的時(shí)間復(fù)雜度。

    2.對輸入內(nèi)容進(jìn)行部分排序,即只對前K大的元素進(jìn)行排序(這K個(gè)元素即為所求)。此時(shí)我們可以選擇冒泡排序或選擇排序進(jìn)行處理,即每次冒泡(選擇)都能找到所求的一個(gè)元素。這類策略的時(shí)間復(fù)雜度是O(Kn)。

    3.對輸入內(nèi)容不進(jìn)行排序,顯而易見,這種策略將會(huì)有更好的性能開銷。我們此時(shí)可以選擇兩種策略進(jìn)行處理:

    用一個(gè)桶來裝前k個(gè)數(shù),桶里面可以按照最小堆來維護(hù)
    a)利用最小堆維護(hù)一個(gè)大小為K的數(shù)組,目前該小根堆中的元素是排名前K的數(shù),其中根是最小的數(shù)。此后,每次從原數(shù)組中取一個(gè)元素與根進(jìn)行比較,如大于根的元素,則將根元素替換并進(jìn)行堆調(diào)整(下沉),即保證小根堆中的元素仍然是排名前K的數(shù),且根元素仍然最小;否則不予處理,取下一個(gè)數(shù)組元素繼續(xù)該過程。該算法的時(shí)間復(fù)雜度是O(nlogK),一般來說企業(yè)中都采用該策略處理top-K問題,因?yàn)樵撍惴ú恍枰淮螌⒃瓟?shù)組中的內(nèi)容全部加載到內(nèi)存中,而這正是海量數(shù)據(jù)處理必然會(huì)面臨的一個(gè)關(guān)卡。

    b)利用快速排序的分劃函數(shù)找到分劃位置K,則其前面的內(nèi)容即為所求。該算法是一種非常有效的處理方式,時(shí)間復(fù)雜度是O(n)(證明可以參考算法導(dǎo)論書籍)。對于能一次加載到內(nèi)存中的數(shù)組,該策略非常優(yōu)秀。

    (9) Bitmap的使用,存儲(chǔ)和插入方法

    BitMap從字面的意思

    很多人認(rèn)為是位圖,其實(shí)準(zhǔn)確的來說,翻譯成基于位的映射。
    在所有具有性能優(yōu)化的數(shù)據(jù)結(jié)構(gòu)中,大家使用最多的就是hash表,是的,在具有定位查找上具有O(1)的常量時(shí)間,多么的簡潔優(yōu)美。但是數(shù)據(jù)量大了,內(nèi)存就不夠了。
    當(dāng)然也可以使用類似外排序來解決問題的,由于要走IO所以時(shí)間上又不行。
    所謂的Bit-map就是用一個(gè)bit位來標(biāo)記某個(gè)元素對應(yīng)的Value, 而Key即是該元素。由于采用了Bit為單位來存儲(chǔ)數(shù)據(jù),因此在存儲(chǔ)空間方面,可以大大節(jié)省。
    其實(shí)如果你知道計(jì)數(shù)排序的話(算法導(dǎo)論中有一節(jié)講過),你就會(huì)發(fā)現(xiàn)這個(gè)和計(jì)數(shù)排序很像。

    bitmap應(yīng)用

    1)可進(jìn)行數(shù)據(jù)的快速查找,判重,刪除,一般來說數(shù)據(jù)范圍是int的10倍以下。2)去重?cái)?shù)據(jù)而達(dá)到壓縮數(shù)據(jù)

    還可以用于爬蟲系統(tǒng)中url去重、解決全組合問題。

    BitMap應(yīng)用:排序示例

    假設(shè)我們要對0-7內(nèi)的5個(gè)元素(4,7,2,5,3)排序(這里假設(shè)這些元素沒有重復(fù))。那么我們就可以采用Bit-map的方法來達(dá)到排序的目的。要表示8個(gè)數(shù),我們就只需要8個(gè)Bit(1Bytes),首先我們開辟1Byte的空間,將這些空間的所有Bit位都置為0(如下圖:)

    然后遍歷這5個(gè)元素,首先第一個(gè)元素是4,那么就把4對應(yīng)的位置為1(可以這樣操作 p+(i/8)|(0×01<<(i%8)) 當(dāng)然了這里的操作涉及到Big-ending和Little-ending的情況,這里默認(rèn)為Big-ending。不過計(jì)算機(jī)一般是小端存儲(chǔ)的,如intel。小端的話就是將倒數(shù)第5位置1),因?yàn)槭菑牧汩_始的,所以要把第五位置為一(如下圖):

    然后再處理第二個(gè)元素7,將第八位置為1,,接著再處理第三個(gè)元素,一直到最后處理完所有的元素,將相應(yīng)的位置為1,這時(shí)候的內(nèi)存的Bit位的狀態(tài)如下:

    然后我們現(xiàn)在遍歷一遍Bit區(qū)域,將該位是一的位的編號輸出(2,3,4,5,7),這樣就達(dá)到了排序的目的。

    bitmap排序復(fù)雜度分析

    Bitmap排序需要的時(shí)間復(fù)雜度和空間復(fù)雜度依賴于數(shù)據(jù)中最大的數(shù)字。
    bitmap排序的時(shí)間復(fù)雜度不是O(N)的,而是取決于待排序數(shù)組中的最大值MAX,在實(shí)際應(yīng)用上關(guān)系也不大,比如我開10個(gè)線程去讀byte數(shù)組,那么復(fù)雜度為:O(Max/10)。也就是要是讀取的,可以用多線程的方式去讀取。時(shí)間復(fù)雜度方面也是O(Max/n),其中Max為byte[]數(shù)組的大小,n為線程大小。
    空間復(fù)雜度應(yīng)該就是O(Max/8)bytes吧

    BitMap算法流程

    假設(shè)需要排序或者查找的最大數(shù)MAX=10000000(lz:這里MAX應(yīng)該是最大的數(shù)而不是int數(shù)據(jù)的總數(shù)!),那么我們需要申請內(nèi)存空間的大小為int a[1 + MAX/32]。
    其中:a[0]在內(nèi)存中占32為可以對應(yīng)十進(jìn)制數(shù)0-31,依次類推:
    bitmap表為:
    a[0]--------->0-31
    a[1]--------->32-63
    a[2]--------->64-95
    a[3]--------->96-127

    我們要把一個(gè)整數(shù)N映射到Bit-Map中去,首先要確定把這個(gè)N Mapping到哪一個(gè)數(shù)組元素中去,即確定映射元素的index。我們用int類型的數(shù)組作為map的元素,這樣我們就知道了一個(gè)元素能夠表示的數(shù)字個(gè)數(shù)(這里是32)。于是N/32就可以知道我們需要映射的key了。所以余下來的那個(gè)N%32就是要映射到的位數(shù)。

    1.求十進(jìn)制數(shù)對應(yīng)在數(shù)組a中的下標(biāo):

    先由十進(jìn)制數(shù)n轉(zhuǎn)換為與32的余可轉(zhuǎn)化為對應(yīng)在數(shù)組a中的下標(biāo)。
    如十進(jìn)制數(shù)0-31,都應(yīng)該對應(yīng)在a[0]中,比如n=24,那么 n/32=0,則24對應(yīng)在數(shù)組a中的下標(biāo)為0。又比如n=60,那么n/32=1,則60對應(yīng)在數(shù)組a中的下標(biāo)為1,同理可以計(jì)算0-N在數(shù)組a中的下標(biāo)。
    i = N>>K % 結(jié)果就是N/(2^K)
    Note: map的范圍是[0, 原數(shù)組最大的數(shù)對應(yīng)的2的整次方數(shù)-1]。

    2.求十進(jìn)制數(shù)對應(yīng)數(shù)組元素a[i]在0-31中的位m:

    十進(jìn)制數(shù)0-31就對應(yīng)0-31,而32-63則對應(yīng)也是0-31,即給定一個(gè)數(shù)n可以通過模32求得對應(yīng)0-31中的數(shù)。
    m = n & ((1 << K) - 1) %結(jié)果就是n%(2^K)

    3.利用移位0-31使得對應(yīng)第m個(gè)bit位為1

    如a[i]的第m位置1:a[i] = a[i] | (1<<m)
    如:將當(dāng)前4對應(yīng)的bit位置1的話,只需要1左移4位與B[0] | 即可。

    Note:

    1 p+(i/8)|(0×01<<(i%8))這樣也可以?
    2 同理將int型變量a的第k位清0,即a=a&~(1<<k)

    BitMap算法評價(jià)

    優(yōu)點(diǎn):
    1. 運(yùn)算效率高,不進(jìn)行比較和移位;
    2. 占用內(nèi)存少,比如最大的數(shù)MAX=10000000;只需占用內(nèi)存為MAX/8=1250000Byte=1.25M。
    3.
    缺點(diǎn):
    1. 所有的數(shù)據(jù)不能重復(fù),即不可對重復(fù)的數(shù)據(jù)進(jìn)行排序。(少量重復(fù)數(shù)據(jù)查找還是可以的,用2-bitmap)。
    2. 當(dāng)數(shù)據(jù)類似(1,1000,10萬)只有3個(gè)數(shù)據(jù)的時(shí)候,用bitmap時(shí)間復(fù)雜度和空間復(fù)雜度相當(dāng)大,只有當(dāng)數(shù)據(jù)比較密集時(shí)才有優(yōu)勢。
    http://blog.csdn.net/pipisorry/article/details/62443757

    (10) 字典樹的理解以及在統(tǒng)計(jì)上的應(yīng)用

    Trie的核心思想是空間換時(shí)間。利用字符串的公共前綴來降低查詢時(shí)間的開銷以達(dá)到提高效率的目的。Trie樹也有它的缺點(diǎn),Trie樹的內(nèi)存消耗非常大.當(dāng)然,或許用左兒子右兄弟的方法建樹的話,可能會(huì)好點(diǎn).

    就是在海量數(shù)據(jù)中找出某一個(gè)數(shù),比如2億QQ號中查找出某一個(gè)特定的QQ號。。

    (11) N個(gè)骰子出現(xiàn)和為m的概率

    典型的可以用動(dòng)態(tài)規(guī)劃的思想來完成

    1.現(xiàn)在變量有:骰子個(gè)數(shù),點(diǎn)數(shù)和。當(dāng)有k個(gè)骰子,點(diǎn)數(shù)和為n時(shí),出現(xiàn)次數(shù)記為f(k,n)。那與k-1個(gè)骰子階段之間的關(guān)系是怎樣的?

    2.當(dāng)我有k-1個(gè)骰子時(shí),再增加一個(gè)骰子,這個(gè)骰子的點(diǎn)數(shù)只可能為1、2、3、4、5或6。那k個(gè)骰子得到點(diǎn)數(shù)和為n的情況有:
    (k-1,n-1):第k個(gè)骰子投了點(diǎn)數(shù)1
    (k-1,n-2):第k個(gè)骰子投了點(diǎn)數(shù)2
    (k-1,n-3):第k個(gè)骰子投了點(diǎn)數(shù)3

    (k-1,n-6):第k個(gè)骰子投了點(diǎn)數(shù)6
    在k-1個(gè)骰子的基礎(chǔ)上,再增加一個(gè)骰子出現(xiàn)點(diǎn)數(shù)和為n的結(jié)果只有這6種情況!
    所以:f(k,n)=f(k-1,n-1)+f(k-1,n-2)+f(k-1,n-3)+f(k-1,n-4)+f(k-1,n-5)+f(k-1,n-6)

    3.有1個(gè)骰子,f(1,1)=f(1,2)=f(1,3)=f(1,4)=f(1,5)=f(1,6)=1。
    用遞歸就可以解決這個(gè)問題:

    用迭代來完成

    (19) 海量數(shù)據(jù)問題(可參考左神的書)

    目前關(guān)于海量數(shù)據(jù)想到的解決辦法:
    1.bitmap
    2.桶排序,外部排序,將需要排序的放到外存上,不用全部放到內(nèi)存上

    (20) 一致性哈希

    說明

    http://www.zsythink.net/archives/1182

    優(yōu)點(diǎn)

    1.當(dāng)后端是緩存服務(wù)器時(shí),經(jīng)常使用一致性哈希算法來進(jìn)行負(fù)載均衡。使用一致性哈希的好處在于,增減集群的緩存服務(wù)器時(shí),只有少量的緩存會(huì)失效,回源量較小。
    2.盡量減少數(shù)據(jù)丟失問題,減少移動(dòng)數(shù)據(jù)的風(fēng)險(xiǎn)

    總結(jié)

    以上是生活随笔為你收集整理的数据结构算法常见面试考题的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。

    如果覺得生活随笔網(wǎng)站內(nèi)容還不錯(cuò),歡迎將生活随笔推薦給好友。