数据结构与算法基础
數(shù)據(jù)結(jié)構(gòu)
一些概念
數(shù)據(jù)結(jié)構(gòu)就是研究數(shù)據(jù)的邏輯結(jié)構(gòu)和物理結(jié)構(gòu)以及它們之間相互關(guān)系,并對這種結(jié)構(gòu)定義相應(yīng)的運(yùn)算,而且確保經(jīng)過這些運(yùn)算后所得到的新結(jié)構(gòu)仍然是原來的結(jié)構(gòu)類型。
數(shù)據(jù):所有能被輸入到計(jì)算機(jī)中,且能被計(jì)算機(jī)處理的符號的集合。是計(jì)算機(jī)操作的對象的總稱。
數(shù)據(jù)元素:數(shù)據(jù)(集合)中的一個(gè)“個(gè)體”,數(shù)據(jù)及結(jié)構(gòu)中討論的基本單位
數(shù)據(jù)項(xiàng):數(shù)據(jù)的不可分割的最小單位。一個(gè)數(shù)據(jù)元素可由若干個(gè)數(shù)據(jù)項(xiàng)組成。
數(shù)據(jù)類型:在一種程序設(shè)計(jì)語言中,變量所具有的數(shù)據(jù)種類。整型、浮點(diǎn)型、字符型等等
邏輯結(jié)構(gòu):數(shù)據(jù)之間的相互關(guān)系。
集合 結(jié)構(gòu)中的數(shù)據(jù)元素除了同屬于一種類型外,別無其它關(guān)系。
線性結(jié)構(gòu) 數(shù)據(jù)元素之間一對一的關(guān)系
樹形結(jié)構(gòu) 數(shù)據(jù)元素之間一對多的關(guān)系
圖狀結(jié)構(gòu)或網(wǎng)狀結(jié)構(gòu) 結(jié)構(gòu)中的數(shù)據(jù)元素之間存在多對多的關(guān)系
物理結(jié)構(gòu)/存儲結(jié)構(gòu):數(shù)據(jù)在計(jì)算機(jī)中的表示。物理結(jié)構(gòu)是描述數(shù)據(jù)具體在內(nèi)存中的存儲(如:順序結(jié)構(gòu)、鏈?zhǔn)浇Y(jié)構(gòu)、索引結(jié)構(gòu)、哈希結(jié)構(gòu))等
在數(shù)據(jù)結(jié)構(gòu)中,從邏輯上可以將其分為線性結(jié)構(gòu)和非線性結(jié)構(gòu)
數(shù)據(jù)結(jié)構(gòu)的基本操作的設(shè)置的最重要的準(zhǔn)則是,實(shí)現(xiàn)應(yīng)用程序與存儲結(jié)構(gòu)的獨(dú)立。實(shí)現(xiàn)應(yīng)用程序是“邏輯結(jié)構(gòu)”,存儲的是“物理結(jié)構(gòu)”。邏輯結(jié)構(gòu)主要是對該結(jié)構(gòu)操作的設(shè)定,物理結(jié)構(gòu)是描述數(shù)據(jù)具體在內(nèi)存中的存儲(如:順序結(jié)構(gòu)、鏈?zhǔn)浇Y(jié)構(gòu)、索引結(jié)構(gòu)、希哈結(jié)構(gòu))等。
順序存儲結(jié)構(gòu)中,線性表的邏輯順序和物理順序總是一致的。但在鏈?zhǔn)酱鎯Y(jié)構(gòu)中,線性表的邏輯順序和物理順序一般是不同的。
算法五個(gè)特性: 有窮性、確定性、可行性、輸入、輸出
算法設(shè)計(jì)要求:正確性、可讀性、健壯性、高效率與低存儲量需求。(好的算法)
算法的描述有偽程序、流程圖、N-S結(jié)構(gòu)圖等。E-R圖是實(shí)體聯(lián)系模型,不是程序的描述方式。
設(shè)計(jì)算法在執(zhí)行時(shí)間時(shí)需要考慮:算法選用的規(guī)模、問題的規(guī)模
時(shí)間復(fù)雜度:算法的執(zhí)行時(shí)間與原操作執(zhí)行次數(shù)之和成正比。時(shí)間復(fù)雜度有小到大:O(1)、O(logn)、O(n)、O(nlogn)、O(n2)、O(n3)。冪次時(shí)間復(fù)雜度有小到大O(2n)、O(n!)、O(nn)
空間復(fù)雜度:若輸入數(shù)據(jù)所占空間只取決于問題本身,和算法無關(guān),則只需要分析除輸入和程序之外的輔助變量所占額外空間。
線性表
線性表是一種典型的線性結(jié)構(gòu)。頭結(jié)點(diǎn)無前驅(qū)有一個(gè)后繼,尾節(jié)點(diǎn)無后繼有一個(gè)前驅(qū)。鏈表只能順序查找,定位一個(gè)元素的時(shí)間為O(N),刪除一個(gè)元素的時(shí)間為O(1)
線性表的順序存儲結(jié)構(gòu):把線性表的結(jié)點(diǎn)按邏輯順序依次存放在一組地址連續(xù)的存儲單元里。用這種方法存儲的線性表簡稱順序表。是一種隨機(jī)存取的存儲結(jié)構(gòu)。順序存儲指內(nèi)存地址是一塊的,隨機(jī)存取指訪問時(shí)可以按下標(biāo)隨機(jī)訪問,存儲和存取是不一樣的。如果是存儲,則是指按順序的,如果是存取,則是可以隨機(jī)的,可以利用元素下標(biāo)進(jìn)行。數(shù)組比線性表速度更快的是:原地逆序、返回中間節(jié)點(diǎn)、選擇隨機(jī)節(jié)點(diǎn)。
便于線性表的構(gòu)造和任意元素的訪問
插入:插入新結(jié)點(diǎn),之后結(jié)點(diǎn)后移。平均時(shí)間復(fù)雜度:O(n)
刪除:刪除節(jié)點(diǎn),之后結(jié)點(diǎn)前移。平均時(shí)間復(fù)雜度:O(n)
線性鏈表:用一組任意的存儲單元來依次存放線性表的結(jié)點(diǎn),這組存儲單元即可以是連續(xù)的,也可以是不連續(xù)的,甚至是零散分布在內(nèi)存中的任意位置上的。因此,鏈表中結(jié)點(diǎn)的邏輯次序和物理次序不一定相同。為了能正確表示結(jié)點(diǎn)間的邏輯關(guān)系,在存儲每個(gè)結(jié)點(diǎn)值的同時(shí),還必須存儲指示其后繼結(jié)點(diǎn)的地址。data域是數(shù)據(jù)域,用來存放結(jié)點(diǎn)的值。next是指針域(亦稱鏈域),用來存放結(jié)點(diǎn)的直接后繼的地址(或位置)。不需要事先估計(jì)存儲空間大小。
單鏈表中每個(gè)結(jié)點(diǎn)的存儲地址是存放在其前趨結(jié)點(diǎn)next域中,而開始結(jié)點(diǎn)無前趨,故應(yīng)設(shè)頭指針head指向開始結(jié)點(diǎn)。同時(shí),由于最后一個(gè)結(jié)點(diǎn)無后繼,故結(jié)點(diǎn)的指針域?yàn)榭?#xff0c;即NULL。頭插法建表(逆序)、尾插法建表(順序)。增加頭結(jié)點(diǎn)的目的是算法實(shí)現(xiàn)上的方便,但增大了內(nèi)存開銷。
查找:只能從鏈表的頭指針出發(fā),順鏈域next逐個(gè)結(jié)點(diǎn)往下搜索,直到搜索到第i個(gè)結(jié)點(diǎn)為止。因此,鏈表不是隨機(jī)存取結(jié)構(gòu)。
插入:先找到表的第i-1的存儲位置,然后插入。新結(jié)點(diǎn)先連后繼,再連前驅(qū)。
刪除:首先找到ai-1的存儲位置p。然后令p–>next指向ai的直接后繼結(jié)點(diǎn),即把a(bǔ)i從鏈上摘下。最后釋放結(jié)點(diǎn)ai的空間.r=p->next;p->next=r->next;delete r。
判斷一個(gè)單向鏈表中是否存在環(huán)的最佳方法是快慢指針。
靜態(tài)鏈表:用一維數(shù)組來實(shí)現(xiàn)線性鏈表,這種用一維數(shù)組表示的線性鏈表,稱為靜態(tài)鏈表。靜態(tài):體現(xiàn)在表的容量是一定的。(數(shù)組的大小);鏈表:插入與刪除同前面所述的動(dòng)態(tài)鏈表方法相同。靜態(tài)鏈表中指針表示的是下一元素在數(shù)組中的位置。
靜態(tài)鏈表是用數(shù)組實(shí)現(xiàn)的,是順序的存儲結(jié)構(gòu),在物理地址上是連續(xù)的,而且需要預(yù)先分配大小。動(dòng)態(tài)鏈表是用申請內(nèi)存函數(shù)(C是malloc,C++是new)動(dòng)態(tài)申請內(nèi)存的,所以在鏈表的長度上沒有限制。動(dòng)態(tài)鏈表因?yàn)槭莿?dòng)態(tài)申請內(nèi)存的,所以每個(gè)節(jié)點(diǎn)的物理地址不連續(xù),要通過指針來順序訪問。靜態(tài)鏈表在插入、刪除時(shí)也是通過修改指針域來實(shí)現(xiàn)的,與動(dòng)態(tài)鏈表沒有什么分別
循環(huán)鏈表:是一種頭尾相接的鏈表。其特點(diǎn)是無須增加存儲量,僅對表的鏈接方式稍作改變,即可使得表處理更加方便靈活。
在單鏈表中,將終端結(jié)點(diǎn)的指針域NULL改為指向表頭結(jié)點(diǎn)的或開始結(jié)點(diǎn),就得到了單鏈形式的循環(huán)鏈表,并簡單稱為單循環(huán)鏈表。由于循環(huán)鏈表中沒有NULL指針,故涉及遍歷操作時(shí),其終止條件就不再像非循環(huán)鏈表那樣判斷p或p—>next是否為空,而是判斷它們是否等于某一指定指針,如頭指針或尾指針等。
雙向鏈表:在單鏈表的每個(gè)結(jié)點(diǎn)里再增加一個(gè)指向其直接前趨的指針域prior。這樣就形成的鏈表中有兩個(gè)方向不同的鏈。雙鏈表一般由頭指針唯一確定的,將頭結(jié)點(diǎn)和尾結(jié)點(diǎn)鏈接起來構(gòu)成循環(huán)鏈表,并稱之為雙向鏈表。設(shè)指針p指向某一結(jié)點(diǎn),則雙向鏈表結(jié)構(gòu)的對稱性可用下式描述:p—>prior—>next=p=p—>next—>prior。從兩個(gè)方向搜索雙鏈表,比從一個(gè)方向搜索雙鏈表的方差要小。
插入:先搞定插入節(jié)點(diǎn)的前驅(qū)和后繼,再搞定后結(jié)點(diǎn)的前驅(qū),最后搞定前結(jié)點(diǎn)的后繼。
在有序雙向鏈表中定位刪除一個(gè)元素的平均時(shí)間復(fù)雜度為O(n)
可以直接刪除當(dāng)前指針?biāo)赶虻墓?jié)點(diǎn)。而不需要像單向鏈表中,刪除一個(gè)元素必須找到其前驅(qū)。因此在插入數(shù)據(jù)時(shí),單向鏈表和雙向鏈表操作復(fù)雜度相同,而刪除數(shù)據(jù)時(shí),雙向鏈表的性能優(yōu)于單向鏈表
棧和隊(duì)列
棧
棧(Stack)是限制在表的一端進(jìn)行插入和刪除運(yùn)算的線性表,通常稱插入、刪除的這一端為棧頂(Top),另一端為棧底(Bottom)。先進(jìn)后出。top= -1時(shí)為空棧,top=0只能說明棧中只有一個(gè)元素,并且元素進(jìn)棧時(shí)top應(yīng)該自增
順序存儲棧:順序存儲結(jié)構(gòu)
鏈棧:鏈?zhǔn)酱鎯Y(jié)構(gòu)。插入和刪除操作僅限制在鏈頭位置上進(jìn)行。棧頂指針就是鏈表的頭指針。通常不會(huì)出現(xiàn)棧滿的情況。 不需要判斷棧滿但需要判斷???。
兩個(gè)棧共用靜態(tài)存儲空間,對頭使用也存在空間溢出問題。棧1的底在v[1],棧2的底在V[m],則棧滿的條件是top[1]+1=top[2]。
基本操作:刪除棧頂元素、判斷棧是否為空以及將棧置為空棧等
對于n各元素的入棧問題,可能的出棧順序有C(2n,n)/(n+1)個(gè)。
堆棧溢出一般是循環(huán)的遞歸調(diào)用、大數(shù)據(jù)結(jié)構(gòu)的局部變量導(dǎo)致的
應(yīng)用,代碼:
進(jìn)制轉(zhuǎn)換
括號匹配的檢驗(yàn)
行編輯程序
迷宮求解:若當(dāng)前位置“可通”,則納入路徑,繼續(xù)前進(jìn);若當(dāng)前位置“不可通”,則后退,換方向繼續(xù)探索;若四周“均無通路”,則將當(dāng)前位置從路徑中刪除出去。
表達(dá)式求解:前綴、中綴、后綴。
操作數(shù)之間的相對次序不變;
運(yùn)算符的相對次序不同;
中綴式丟失了括弧信息,致使運(yùn)算的次序不確定
前綴式的運(yùn)算規(guī)則為:連續(xù)出現(xiàn)的兩個(gè)操作數(shù)和在它們之前且緊靠它們的運(yùn)算符構(gòu)成一個(gè)最小表達(dá)式
后綴式的運(yùn)算規(guī)則為:運(yùn)算符在式中出現(xiàn)的順序恰為表達(dá)式的運(yùn)算順序;每個(gè)運(yùn)算符和在它之前出現(xiàn)且緊靠它的兩個(gè)操作數(shù)構(gòu)成一個(gè)最小表達(dá)式。
實(shí)現(xiàn)遞歸:多個(gè)函數(shù)嵌套調(diào)用的規(guī)則是:后調(diào)用先返回。
瀏覽器歷史紀(jì)錄,Android中的最近任務(wù),Activity的啟動(dòng)模式,CPU中棧的實(shí)現(xiàn),Word自動(dòng)保存,解析計(jì)算式,解析xml/json。解析XML時(shí),需要校驗(yàn)節(jié)點(diǎn)是否閉合,節(jié)點(diǎn)閉合的話,有頭尾符號相對應(yīng),遇到頭符號將其放入棧中,遇到尾符號時(shí),彈出棧的內(nèi)容,看是否有與之對應(yīng)的頭符號,棧的特性剛好符合符號匹配的就近原則。
不是所有的遞歸程序都需要棧來保護(hù)現(xiàn)場,比方說求階乘的,是單向遞歸,直接用循環(huán)去替代從1乘到n就是結(jié)果了,另外一些需要棧保存的也可以用隊(duì)列等來替代。不是所有的遞歸轉(zhuǎn)化為非遞歸都要用到棧。轉(zhuǎn)化為非遞歸主要有兩種方法:對于尾遞歸或單向遞歸,可以用循環(huán)結(jié)構(gòu)算法代替
隊(duì)列
隊(duì)列(Queue)也是一種運(yùn)算受限的線性表。它只允許在表的一端進(jìn)行插入,而在另一端進(jìn)行刪除。允許刪除的一端稱為隊(duì)頭(front),允許插入的一端稱為隊(duì)尾(rear)。先進(jìn)先出。
順序隊(duì)列:順序存儲結(jié)構(gòu)。當(dāng)頭尾指針相等時(shí)隊(duì)列為空。在非空隊(duì)列里,頭指針始終指向隊(duì)頭前一個(gè)位置,而尾指針始終指向隊(duì)尾元素的實(shí)際位置
循環(huán)隊(duì)列。在循環(huán)隊(duì)列中進(jìn)行出隊(duì)、入隊(duì)操作時(shí),頭尾指針仍要加1,朝前移動(dòng)。只不過當(dāng)頭尾指針指向向量上界(MaxSize-1)時(shí),其加1操作的結(jié)果是指向向量的下界0。除非向量空間真的被隊(duì)列元素全部占用,否則不會(huì)上溢。因此,除一些簡單的應(yīng)用外,真正實(shí)用的順序隊(duì)列是循環(huán)隊(duì)列。故隊(duì)空和隊(duì)滿時(shí)頭尾指針均相等。因此,我們無法通過front=rear來判斷隊(duì)列“空”還是“滿”
鏈隊(duì)列:鏈?zhǔn)酱鎯Y(jié)構(gòu)。限制僅在表頭刪除和表尾插入的單鏈表。顯然僅有單鏈表的頭指針不便于在表尾做插入操作,為此再增加一個(gè)尾指針,指向鏈表的最后一個(gè)結(jié)點(diǎn)。
設(shè)尾指針的循環(huán)鏈表表示隊(duì)列,則入隊(duì)和出隊(duì)算法的時(shí)間復(fù)雜度均為O(1)。用循環(huán)鏈表表示隊(duì)列,必定有鏈表的頭結(jié)點(diǎn),入隊(duì)操作在鏈表尾插入,直接插入在尾指針指向的節(jié)點(diǎn)后面,時(shí)間復(fù)雜度是常數(shù)級的;出隊(duì)操作在鏈表表頭進(jìn)行,也就是刪除表頭指向的節(jié)點(diǎn),時(shí)間復(fù)雜度也是常數(shù)級的。
隊(duì)空條件:rear==front,但是一般需要引入新的標(biāo)記來說明棧滿還是???#xff0c;比如每個(gè)位置布爾值
隊(duì)滿條件:(rear+1) % QueueSize==front,其中QueueSize為循環(huán)隊(duì)列的最大長度
計(jì)算隊(duì)列長度:(rear-front+QueueSize)% QueueSize
入隊(duì):(rear+1)% QueueSize
出隊(duì):(front+1)% QueueSize
假設(shè)以數(shù)組A[N]為容量存放循環(huán)隊(duì)列的元素,其頭指針是front,當(dāng)前隊(duì)列有X個(gè)元素,則隊(duì)列的尾指針值為(front+X mod N)
串
串(String)是零個(gè)或多個(gè)字符組成的有限序列。長度為零的串稱為空串(Empty String),它不包含任何字符。通常將僅由一個(gè)或多個(gè)空格組成的串稱為空白串(Blank String) 注意:空串和空白串的不同,例如“ ”和“”分別表示長度為1的空白串和長度為0的空串。
串的表示和實(shí)現(xiàn):
定長順序存儲表示。靜態(tài)存儲分配的順序表。
堆分配存儲表示。存儲空間是在程序執(zhí)行過程中動(dòng)態(tài)分配而得。所以也稱為動(dòng)態(tài)存儲分配的順序表
串的鏈?zhǔn)酱鎯Y(jié)構(gòu)。
串匹配:將主串稱為目標(biāo)串,子串稱之為模式串。蠻力法匹配。KMP算法匹配。Boyer-Moore算法匹配。
數(shù)組和廣義表
數(shù)組和廣義表可看成是一種特殊的線性表,其特殊在于: 表中的元素本身也是一種線性表。內(nèi)存連續(xù)。根據(jù)下標(biāo)在O(1)時(shí)間讀/寫任何元素。
二維數(shù)組,多維數(shù)組,廣義表、樹、圖都屬于非線性結(jié)構(gòu)
數(shù)組
數(shù)組的順序存儲:行優(yōu)先順序;列優(yōu)先順序。數(shù)組中的任一元素可以在相同的時(shí)間內(nèi)存取,即順序存儲的數(shù)組是一個(gè)隨機(jī)存取結(jié)構(gòu)。
關(guān)聯(lián)數(shù)組(Associative Array),又稱映射(Map)、字典( Dictionary)是一個(gè)抽象的數(shù)據(jù)結(jié)構(gòu),它包含著類似于(鍵,值)的有序?qū)Α?不是線性表。
矩陣的壓縮:
對稱矩陣、三角矩陣:直接存儲矩陣的上三角或者下三角元素。注意區(qū)分i>=j和i
廣義表
廣義表(Lists,又稱列表)是線性表的推廣。廣義表是n(n≥0)個(gè)元素a1,a2,a3,…,an的有限序列,其中ai或者是原子項(xiàng),或者是一個(gè)廣義表。若廣義表LS(n>=1)非空,則a1是LS的表頭,其余元素組成的表(a2,…an)稱為LS的表尾。廣義表的元素可以是廣義表,也可以是原子,廣義表的元素也可以為空。表尾是指除去表頭后剩下的元素組成的表,表頭可以為表或單元素值。所以表尾不可以是單個(gè)元素值。
例子:
A=()——A是一個(gè)空表,其長度為零。
B=(e)——表B只有一個(gè)原子e,B的長度為1。
C=(a,(b,c,d))——表C的長度為2,兩個(gè)元素分別為原子a和子表(b,c,d)。
D=(A,B,C)——表D的長度為3,三個(gè)元素都是廣義 表。顯然,將子表的值代入后,則有D=(( ),(e),(a,(b,c,d)))。
E=(a,E)——這是一個(gè)遞歸的表,它的長度為2,E相當(dāng)于一個(gè)無限的廣義表E=(a,(a,(a,(a,…)))).
三個(gè)結(jié)論:
廣義表的元素可以是子表,而子表的元素還可以是子表。由此,廣義表是一個(gè)多層次的結(jié)構(gòu),可以用圖形象地表示
廣義表可為其它表所共享。例如在上述例4中,廣義表A,B,C為D的子表,則在D中可以不必列出子表的值,而是通過子表的名稱來引用。
廣義表的遞歸性
考點(diǎn):
廣義表是0個(gè)或多個(gè)單因素或子表組成的有限序列,廣義表可以是自身的子表,廣義表的長度n>=0,所以可以為空表。廣義表的同級元素(直屬于同一個(gè)表中的各元素)具有線性關(guān)系
廣義表的表頭為空,并不代表該廣義表為空表。廣義表()和(())不同。前者是長度為0的空表,對其不能做求表頭和表尾的運(yùn)算;而后者是長度為l的非空表(只不過該表中惟一的一個(gè)元素是空表),對其可進(jìn)行分解,得到的表頭和表尾均是空表()
已知廣義表LS=((a,b,c),(d,e,f)),運(yùn)用head和tail函數(shù)取出LS中原子e的運(yùn)算是head(tail(head(tail(LS)))。根據(jù)表頭、表尾的定義可知:任何一個(gè)非空廣義表的表頭是表中第一個(gè)元素,它可以是原子,也可以是子表,而其表尾必定是子表。也就是說,廣義表的head操作,取出的元素是什么,那么結(jié)果就是什么。但是tail操作取出的元素外必須加一個(gè)表——“()“。tail(LS)=((d,e,f));head(tail(LS))=(d,e,f);tail(head(tail(LS)))=(e,f);head(tail(head(tail(LS))))=e。
二維以上的數(shù)組其實(shí)是一種特殊的廣義表
在(非空)廣義表中:1、表頭head可以是原子或者一個(gè)表 2、表尾tail一定是一個(gè)表 3.廣義表難以用順序存儲結(jié)構(gòu) 4.廣義表可以是一個(gè)多層次的結(jié)構(gòu)
樹和二叉樹
一種非線性結(jié)構(gòu)。樹是遞歸結(jié)構(gòu),在樹的定義中又用到了樹的概念。
基本術(shù)語:
樹結(jié)點(diǎn):包含一個(gè)數(shù)據(jù)元素及若干指向子樹的分支;
孩子結(jié)點(diǎn):結(jié)點(diǎn)的子樹的根稱為該結(jié)點(diǎn)的孩子;
雙親結(jié)點(diǎn):B結(jié)點(diǎn)是A結(jié)點(diǎn)的孩子,則A結(jié)點(diǎn)是B結(jié)點(diǎn)的雙親;
兄弟結(jié)點(diǎn):同一雙親的孩子結(jié)點(diǎn);
堂兄結(jié)點(diǎn):同一層上結(jié)點(diǎn);
結(jié)點(diǎn)層次:根結(jié)點(diǎn)的層定義為1;根的孩子為第二層結(jié)點(diǎn),依此類推;
樹的高(深)度:樹中最大的結(jié)點(diǎn)層
結(jié)點(diǎn)的度:結(jié)點(diǎn)子樹的個(gè)數(shù)
樹的度: 樹中最大的結(jié)點(diǎn)度。
葉子結(jié)點(diǎn):也叫終端結(jié)點(diǎn),是度為0的結(jié)點(diǎn);
分枝結(jié)點(diǎn):度不為0的結(jié)點(diǎn)(非終端結(jié)點(diǎn));
森林:互不相交的樹集合;
有序樹:子樹有序的樹,如:家族樹;
無序樹:不考慮子樹的順序;
二叉樹
二叉樹可以為空。二叉樹結(jié)點(diǎn)的子樹要區(qū)分左子樹和右子樹,即使只有一棵子樹也要進(jìn)行區(qū)分,說明它是左子樹,還是右子樹。這是二叉樹與樹的最主要的差別。注意區(qū)分:二叉樹、二叉查找樹/二叉排序樹/二叉搜索樹、二叉平衡(查找)樹
二叉平衡樹肯定是一顆二叉排序樹。堆不是一顆二叉平衡樹。
二叉樹與樹是不同的,二叉樹不等價(jià)于分支樹最多為二的有序樹。當(dāng)一個(gè)結(jié)點(diǎn)只包含一個(gè)子節(jié)點(diǎn)時(shí),對于有序樹并無左右孩子之分,而對于二叉樹來說依然有左右孩子之分,所以二叉樹與樹是兩種不同的結(jié)構(gòu)。
性質(zhì):
在二叉樹的第 i 層上至多有2i-1個(gè)結(jié)點(diǎn)。
深度為 k 的二叉樹上至多含 2k-1 個(gè)結(jié)點(diǎn)(k≥1)
對任何一棵二叉樹,若它含有n0個(gè)葉子結(jié)點(diǎn)、n2個(gè)度為 2 的結(jié)點(diǎn),則必存在關(guān)系式:n0= n2+1。
具有 n 個(gè)結(jié)點(diǎn)的完全二叉樹的深度為?log2 n?+1 。
n個(gè)結(jié)點(diǎn)的二叉樹中,完全二叉樹具有最小的路徑長度。
如果對一棵有n個(gè)結(jié)點(diǎn)的完全二叉樹的結(jié)點(diǎn)按層序編號,則對任一結(jié)點(diǎn)i(1<=i<=n),有:
如果i=1,則結(jié)點(diǎn)i無雙親,是二叉樹的根;如果i>1,則其雙親的編號是 i/2(整除)。
如果2i>n,無左孩子;否則,其左孩子是結(jié)點(diǎn)2i。
如果2i+1>n,則結(jié)點(diǎn)i無右孩子;否則,其右孩子是結(jié)點(diǎn)2i+1。
二叉樹的存儲結(jié)構(gòu)
順序存儲結(jié)構(gòu):僅僅適用于滿或完全二叉樹,結(jié)點(diǎn)之間的層次關(guān)系由性質(zhì)5確定。
二叉鏈表法:每個(gè)節(jié)點(diǎn)存儲左子樹和右子樹。三叉鏈表:左子樹、右子樹、父節(jié)點(diǎn),總的指針是n+2
在有n個(gè)結(jié)點(diǎn)的二叉鏈表中,值為非空的鏈域的個(gè)數(shù)為n-1。在有N個(gè)結(jié)點(diǎn)的二叉鏈表中必定有2N個(gè)鏈域。除根結(jié)點(diǎn)外,其余N-1個(gè)結(jié)點(diǎn)都有一個(gè)父結(jié)點(diǎn)。所以,一共有N-1個(gè)非空鏈域,其余2N-(N-1)=N+1個(gè)為空鏈域。
二叉鏈存儲法也叫孩子兄弟法,左指針指向左孩子,右指針指向右兄弟。而中序遍歷的順序是左孩子,根,右孩子。這種遍歷順序與存儲結(jié)構(gòu)不同,因此需要堆棧保存中間結(jié)果。而中序遍歷檢索二叉樹時(shí),由于其存儲結(jié)構(gòu)跟遍歷順序相符,因此不需要用堆棧。
遍歷二叉樹和線索二叉樹
遍歷二叉樹:使得每一個(gè)結(jié)點(diǎn)均被訪問一次,而且僅被訪問一次。非遞歸的遍歷實(shí)現(xiàn)要利用棧。
先序遍歷DLR:根節(jié)點(diǎn)->左子樹->右子樹
中序遍歷LDR:左子樹->根節(jié)點(diǎn)->右子樹。必須要有中序遍歷才能得到一棵二叉樹的正確順序
后續(xù)遍歷LRD:左子樹->右子樹->根節(jié)點(diǎn)。需要棧的支持。
層次遍歷:用一維數(shù)組存儲二叉樹時(shí),總是以層次遍歷的順序存儲結(jié)點(diǎn)。層次遍歷應(yīng)該借助隊(duì)列。
線索二叉樹:對二叉樹所有結(jié)點(diǎn)做某種處理可在遍歷過程中實(shí)現(xiàn);檢索(查找)二叉樹某個(gè)結(jié)點(diǎn),可通過遍歷實(shí)現(xiàn);如果能將二叉樹線索化,就可以簡化遍歷算法,提高遍歷速度,目的是加快查找結(jié)點(diǎn)的前驅(qū)或后繼的速度。
如何線索化?以中序遍歷為例,若能將中序序列中每個(gè)結(jié)點(diǎn)前趨、后繼信息保存起來,以后再遍歷二叉樹時(shí)就可以根據(jù)所保存的結(jié)點(diǎn)前趨、后繼信息對二叉樹進(jìn)行遍歷。對于二叉樹的線索化,實(shí)質(zhì)上就是遍歷一次二叉樹,只是在遍歷的過程中,檢查當(dāng)前結(jié)點(diǎn)左,右指針域是否為空,若為空,將它們改為指向前驅(qū)結(jié)點(diǎn)或后繼結(jié)點(diǎn)的線索。前驅(qū)就是在這一點(diǎn)之前走過的點(diǎn),不是下一將要去往的點(diǎn)。
加上結(jié)點(diǎn)前趨后繼信息(結(jié)索)的二叉樹稱為線索二叉樹。n個(gè)結(jié)點(diǎn)的線索二叉樹上每個(gè)結(jié)點(diǎn)有2個(gè)指針域(指向左孩子和右孩子),總共有2n個(gè)指針域;一個(gè)n個(gè)結(jié)點(diǎn)的樹有n-1條邊,那么空指針域= 2n - (n-1) = n + 1,即線索數(shù)為n+1。指針域tag為0,存放孩子指針,為1,存放前驅(qū)/后繼節(jié)點(diǎn)指針。
線索樹下結(jié)點(diǎn)x的前驅(qū)與后繼查找:設(shè)結(jié)點(diǎn)x相應(yīng)的左(右)標(biāo)志是線索標(biāo)志,則lchild(rchild)就是前驅(qū)(后繼),否則:
LDR–前驅(qū):左子樹中最靠右邊的結(jié)點(diǎn);后繼:右子樹中最靠左邊的結(jié)點(diǎn)
LRD–前驅(qū):右子樹的根,若無右子樹,為左子樹跟。后繼:x是根,后繼是空;x是雙親的右孩子、x是雙親的左孩子,但雙親無右孩子,雙親是后繼;x是雙親的左孩子,雙親有右孩子,雙親右子樹中最左的葉子是后繼
DLR–對稱于LRD線索樹—將LRD中所有左右互換,前驅(qū)與后繼互換,得到DLR的方法。
為簡化線索鏈表的遍歷算法,仿照線性鏈表,為線索鏈表加上一頭結(jié)點(diǎn),約定:
頭結(jié)點(diǎn)的lchild域:存放線索鏈表的根結(jié)點(diǎn)指針;
頭結(jié)點(diǎn)的rchild域: 中序序列最后一個(gè)結(jié)點(diǎn)的指針;
中序序列第一結(jié)點(diǎn)lchild域指向頭結(jié)點(diǎn);
中序序列最后一個(gè)結(jié)點(diǎn)的rchild域指向頭結(jié)點(diǎn);
中序遍歷的線索二叉樹以及線索二叉樹鏈表示意圖
一棵左右子樹均不空的二叉樹在前序線索化后,其中空的鏈域的個(gè)數(shù)是1。前序和后續(xù)線索化后空鏈域個(gè)數(shù)都是1,中序是2。二叉樹在線索化后,仍不能有效求解的問題是前序求前序先驅(qū),后序求后序后繼。
中序遍歷的順序?yàn)?#xff1a;左、根、右,所以對于每一非空的線索,左子樹結(jié)點(diǎn)的后繼為根結(jié)點(diǎn),右子樹結(jié)點(diǎn)的前驅(qū)為根結(jié)點(diǎn),再遞歸的執(zhí)行上面的過程,可得非空線索均指向其祖先結(jié)點(diǎn)。在中序線索二叉樹中,每一非空的線索均指向其祖先結(jié)點(diǎn)。
在二叉樹上加上結(jié)點(diǎn)前趨、后繼線索后,可利用線索對二叉樹進(jìn)行遍歷,此時(shí),不需棧,也不需遞歸?;静襟E:
p=T->lchild; p指向線索鏈表的根結(jié)點(diǎn);
若線索鏈表非空,循環(huán):
循環(huán),順著p左孩子指針找到最左下結(jié)點(diǎn);訪問之;
若p所指結(jié)點(diǎn)的右孩子域?yàn)榫€索,p的右孩子結(jié)點(diǎn)即為后繼結(jié)點(diǎn)循環(huán): p=p->rchild; 并訪問p所指結(jié)點(diǎn);(在此循環(huán)中,順著后繼線索訪問二叉樹中的結(jié)點(diǎn))
一旦線索“中斷”,p所指結(jié)點(diǎn)的右孩子域?yàn)橛液⒆又羔?#xff0c;p=p->rchild,使 p指向右孩子結(jié)點(diǎn);
樹和森林
樹的存儲結(jié)構(gòu):
雙親表示法
孩子表示法
利用圖表示樹
孩子兄弟表示法(二叉樹表示法):鏈表中每個(gè)結(jié)點(diǎn)的兩指針域分別指向其第一個(gè)孩子結(jié)點(diǎn)和下一個(gè)兄弟結(jié)點(diǎn)
將樹轉(zhuǎn)化成二叉樹:右子樹一定為空
加線:在兄弟之間加一連線
抹線:對每個(gè)結(jié)點(diǎn),除了其左孩子外,去除其與其余孩子之間的關(guān)系
旋轉(zhuǎn):以樹的根結(jié)點(diǎn)為軸心,將整樹順時(shí)針轉(zhuǎn)45°
森林轉(zhuǎn)換成二叉樹:
將各棵樹分別轉(zhuǎn)換成二叉樹
將每棵樹的根結(jié)點(diǎn)用線相連
以第一棵樹根結(jié)點(diǎn)為二叉樹的根
樹與轉(zhuǎn)換后的二叉樹的關(guān)系:轉(zhuǎn)換后的二叉樹的先序?qū)?yīng)樹的先序遍歷;轉(zhuǎn)換后的二叉樹的中序?qū)?yīng)樹的后序遍歷
哈弗曼樹/霍夫曼樹
一些概念
路徑:從一個(gè)祖先結(jié)點(diǎn)到子孫結(jié)點(diǎn)之間的分支構(gòu)成這兩個(gè)結(jié)點(diǎn)間的路徑;
路徑長度:路徑上的分支數(shù)目稱為路徑長度;
樹的路徑長度:從根到每個(gè)結(jié)點(diǎn)的路徑長度之和。
結(jié)點(diǎn)的權(quán):根據(jù)應(yīng)用的需要可以給樹的結(jié)點(diǎn)賦權(quán)值;
結(jié)點(diǎn)的帶權(quán)路徑長度:從根到該結(jié)點(diǎn)的路徑長度與該結(jié)點(diǎn)權(quán)的乘積;
樹的帶權(quán)路徑長度=樹中所有葉子結(jié)點(diǎn)的帶權(quán)路徑之和;通常記作 WPL=∑wi×li
哈夫曼樹:假設(shè)有n個(gè)權(quán)值(w1, w2, … , wn),構(gòu)造有n個(gè)葉子結(jié)點(diǎn)的二叉樹,每個(gè)葉子結(jié)點(diǎn)有一個(gè) wi作為它的權(quán)值。則帶權(quán)路徑長度最小的二叉樹稱為哈夫曼樹。最優(yōu)二叉樹。
前綴碼的定義:在一個(gè)字符集中,任何一個(gè)字符的編碼都不是另一個(gè)字符編碼的前綴?;舴蚵幋a就是前綴碼,可用于快速判斷霍夫曼編碼是否正確。霍夫曼樹是滿二叉樹,若有n個(gè)節(jié)點(diǎn),則共有(n+1)/2個(gè)碼子
給定n個(gè)權(quán)值作為n的葉子結(jié)點(diǎn),構(gòu)造一棵二叉樹,若帶權(quán)路徑長度達(dá)到最小,稱這樣的二叉樹為最優(yōu)二叉樹,也稱為霍夫曼樹(Huffman Tree)?;舴蚵鼧涫菐?quán)路徑長度最短的樹,權(quán)值較大的結(jié)點(diǎn)離根較近。
假設(shè)哈夫曼樹是二叉的話,則度為0的結(jié)點(diǎn)個(gè)數(shù)為N,度為2的結(jié)點(diǎn)個(gè)數(shù)為N-1,則結(jié)點(diǎn)總數(shù)為2N-1。哈夫曼樹的結(jié)點(diǎn)個(gè)數(shù)必為奇數(shù)。
哈夫曼樹不一定是完全二叉樹,但一定是最優(yōu)二叉樹。
若度為m的哈夫曼樹中,其葉結(jié)點(diǎn)個(gè)數(shù)為n,則非葉結(jié)點(diǎn)的個(gè)數(shù)為[(n-1)/(m-1)]。邊的數(shù)目等于度。
圖遍歷與回溯
圖搜索->形成搜索樹
窮舉法。
貪心法。多步?jīng)Q策,每步選擇使得構(gòu)成一個(gè)問題的可能解,同時(shí)滿足目標(biāo)函數(shù)。
回溯法。根據(jù)題意,選取度量標(biāo)準(zhǔn),然后將可能的選擇方法按度量標(biāo)準(zhǔn)所要求順序排好,每次處理一個(gè)量,得到該意義下的最優(yōu)解的分解處理。
圖
無向圖
回路或環(huán):第一個(gè)頂點(diǎn)和最后一個(gè)頂點(diǎn)相同的路徑。
簡單回路或簡單環(huán):除第一個(gè)頂點(diǎn)和最后一個(gè)頂點(diǎn)之外,其余頂點(diǎn)不重復(fù)出現(xiàn)的回路
連通:頂點(diǎn)v至v’ 之間有路徑存在
連通圖:無向圖圖 G 的任意兩點(diǎn)之間都是連通的,則稱G是連通圖。
連通分量:極大連通子圖,子圖中包含的頂點(diǎn)個(gè)數(shù)極大
所有頂點(diǎn)度的和必須為偶數(shù)
有向圖:
回路或環(huán):第一個(gè)頂點(diǎn)和最后一個(gè)頂點(diǎn)相同的路徑。
簡單回路或簡單環(huán):除第一個(gè)頂點(diǎn)和最后一個(gè)頂點(diǎn)之外,其余頂點(diǎn)不重復(fù)出現(xiàn)的回路。
連通:頂點(diǎn)v至v’之間有路徑存在
強(qiáng)連通圖:有向圖G的任意兩點(diǎn)之間都是連通的,則稱G是強(qiáng)連通圖。各個(gè)頂點(diǎn)間均可達(dá)。
強(qiáng)連通分量:極大連通子圖
有向圖頂點(diǎn)的度是頂點(diǎn)的入度與出度之和。鄰接矩陣中第V行中的1的個(gè)數(shù)是V的出度
生成樹:極小連通子圖。包含圖的所有n個(gè)結(jié)點(diǎn),但只含圖的n-1條邊。在生成樹中添加一條邊之后,必定會(huì)形成回路或環(huán)。
完全圖:有 n(n-1)/2 條邊的無向圖。其中n是結(jié)點(diǎn)個(gè)數(shù)。必定是連通圖。
有向完全圖:有n(n-1)條邊的有向圖。其中n是結(jié)點(diǎn)個(gè)數(shù)。每兩個(gè)頂點(diǎn)之間都有兩條方向相反的邊連接的圖。
一個(gè)無向圖 G=(V,E) 是連通的,那么邊的數(shù)目大于等于頂點(diǎn)的數(shù)目減一:|E|>=|V|-1,而反之不成立。如果 G=(V,E) 是有向圖,那么它是強(qiáng)連通圖的必要條件是邊的數(shù)目大于等于頂點(diǎn)的數(shù)目:|E|>=|V|,而反之不成立。沒有回路的無向圖是連通的當(dāng)且僅當(dāng)它是樹,即等價(jià)于:|E|=|V|-1。
圖的存儲形式
鄰接矩陣和加權(quán)鄰接矩陣
無權(quán)有向圖:出度: i行之和;入度: j列之和。
無權(quán)無向圖:i結(jié)點(diǎn)的度: i行或i列之和。
加權(quán)鄰接矩陣:相連為w,不相連為∞
鄰接表
用頂點(diǎn)數(shù)組表、邊(弧)表表示該有向圖或無向圖
頂點(diǎn)數(shù)組表:用數(shù)組存放所有的頂點(diǎn)。數(shù)組大小為圖頂點(diǎn)數(shù)n
邊表(邊結(jié)點(diǎn)表):每條邊用一個(gè)結(jié)點(diǎn)進(jìn)行表示。同一個(gè)結(jié)點(diǎn)的所有的邊形成它的邊結(jié)點(diǎn)單鏈表。
n個(gè)頂點(diǎn)的無向圖的鄰接表最多有n(n-1)個(gè)邊表結(jié)點(diǎn)。有n個(gè)頂點(diǎn)的無向圖最多有n*(n-1)/2條邊,此時(shí)為完全無向圖,而在鄰接表中每條邊存儲兩次,所以有n*(n-1)個(gè)結(jié)點(diǎn)
圖的遍歷
深度優(yōu)先搜索利用棧,廣度優(yōu)先搜索利用隊(duì)列
求一條從頂點(diǎn)i到頂點(diǎn)s的簡單路徑–深搜。求兩個(gè)頂點(diǎn)之間的一條長度最短的路徑–廣搜。當(dāng)各邊上的權(quán)值均相等時(shí),BFS算法可用來解決單源最短路徑問題。
生成樹和最小生成樹
每次遍歷一個(gè)連通圖將圖的邊分成遍歷所經(jīng)過的邊和沒有經(jīng)過的邊兩部分,將遍歷經(jīng)過的邊同圖的頂點(diǎn)構(gòu)成一個(gè)子圖,該子圖稱為生成樹。因此有DFS生成樹和BFS生成樹。
生成樹是連通圖的極小子圖,有n個(gè)頂點(diǎn)的連通圖的生成樹必定有n-1條邊,在生成樹中任意增加一條邊,必定產(chǎn)生回路。若砍去它的一條邊,就會(huì)把生成樹變成非連通子圖
最小生成樹:生成樹中邊的權(quán)值(代價(jià))之和最小的樹。最小生成樹問題是構(gòu)造連通網(wǎng)的最小代價(jià)生成樹。
Kruskal算法:令最小生成樹集合T初始狀態(tài)為空,在有n個(gè)頂點(diǎn)的圖中選取代價(jià)最小的邊并從圖中刪去。若該邊加到T中有回路則丟棄,否則留在T中;依此類推,直至T中有n-1條邊為止。
Prim算法、Kruskal算法和Dijkstra算法均屬于貪心算法。
Dijkstra算法解決的是帶權(quán)重的有向圖上單源最短路徑問題,該算法要求所有邊的權(quán)重都為非負(fù)值。
Dijkstra算法解決了從某個(gè)原點(diǎn)到其余各頂點(diǎn)的最短路徑問題,由循環(huán)嵌套可知該算法的時(shí)間復(fù)雜度為O(N*N)。若要求任一頂點(diǎn)到其余所有頂點(diǎn)的最短路徑,一個(gè)比較簡單的方法是對每個(gè)頂點(diǎn)當(dāng)做源點(diǎn)運(yùn)行一次該算法,等于在原有算法的基礎(chǔ)上,再來一次循環(huán),此時(shí)整個(gè)算法的復(fù)雜度就變成了O(N*N*N)。
Bellman-Ford算法解決的是一般情況下的單源最短路徑問題,在這里,邊的權(quán)重可以為負(fù)值。該算法返回一個(gè)布爾值,以表明是否存在一個(gè)從源節(jié)點(diǎn)可以到達(dá)的權(quán)重為負(fù)值的環(huán)路。如果存在這樣一個(gè)環(huán)路,算法將告訴我們不存在解決方案。如果沒有這種環(huán)路存在,算法將給出最短路徑和它們的權(quán)重。
雙連通圖和關(guān)節(jié)點(diǎn)
若從一個(gè)連通圖中刪去任何一個(gè)頂點(diǎn)及其相關(guān)聯(lián)的邊,它仍為一個(gè)連通圖的話,則該連通圖被稱為重(雙)連通圖。
若連通圖中的某個(gè)頂點(diǎn)和其相關(guān)聯(lián)的邊被刪去之后,該連通圖被分割成兩個(gè)或兩個(gè)以上的連通分量,則稱此頂點(diǎn)為關(guān)節(jié)點(diǎn)。
沒有關(guān)節(jié)點(diǎn)的連通圖為雙連通圖
若生成樹的根結(jié)點(diǎn),有兩個(gè)或兩個(gè)以上的分支,則此頂點(diǎn)(生成樹的根)必為關(guān)節(jié)點(diǎn);
對生成樹上的任意一個(gè)非葉“頂點(diǎn)”,若其某棵子樹中的所有“頂點(diǎn)”沒有和其祖先相通的回邊,則該“頂點(diǎn)”必為關(guān)節(jié)點(diǎn)。
有向無環(huán)圖及其應(yīng)用
拓?fù)渑判颉T谟绵徑颖肀硎緢D時(shí),對有n個(gè)頂點(diǎn)和e條弧的有向圖而言時(shí)間復(fù)雜度為O(n+e)。一個(gè)有向圖能被拓?fù)渑判虻某湟獥l件就是它是一個(gè)有向無環(huán)圖。拓?fù)湫蛄形ㄒ徊荒芪ㄒ淮_定有向圖。
AOV網(wǎng)(Activity On Vertex):用頂點(diǎn)表示活動(dòng),邊表示活動(dòng)的優(yōu)先關(guān)系的有向圖稱為AOV網(wǎng)。AOV網(wǎng)中不允許有回路,這意味著某項(xiàng)活動(dòng)以自己為先決條件。
拓?fù)溆行蛐蛄?#xff1a;把AOV網(wǎng)絡(luò)中各頂點(diǎn)按照它們相互之間的優(yōu)先關(guān)系排列一個(gè)線性序列的過程。若vi是vj前驅(qū),則vi一定在vj之前;對于沒有優(yōu)先關(guān)系的點(diǎn),順序任意。
拓?fù)渑判?#xff1a;對AOV網(wǎng)絡(luò)中頂點(diǎn)構(gòu)造拓?fù)溆行蛐蛄械倪^程。方法:
在有向圖中選一個(gè)沒有前驅(qū)的頂點(diǎn)且輸出之
從圖中刪除該頂點(diǎn)和所有以它為尾的弧
重復(fù)上述兩步,直至全部頂點(diǎn)均已輸出;或者當(dāng)圖中不存在無前驅(qū)的頂點(diǎn)為止(此時(shí)說明圖中有環(huán))
采用深度優(yōu)先搜索或拓?fù)渑判蛩惴梢耘袛喑鲆粋€(gè)有向圖中是否有環(huán)(回路).深度優(yōu)先搜索只要在其中記錄下搜索的節(jié)點(diǎn)數(shù)n,當(dāng)n大于圖中節(jié)點(diǎn)數(shù)時(shí)退出,并可以得出有回路。若有回路,則拓?fù)渑判蛟L問不到圖中所有的節(jié)點(diǎn),所以也可以得出回路。廣度優(yōu)先搜索過程中如果訪問到一個(gè)已經(jīng)訪問過的節(jié)點(diǎn),可能是多個(gè)節(jié)點(diǎn)指向這個(gè)節(jié)點(diǎn),不一定是存在環(huán)。
算法描述:
把鄰接表中入度為0的頂點(diǎn)依此進(jìn)棧
若棧不空,則
棧頂元素vj退棧并輸出;
在鄰接表中查找vj的直接后繼vk,把vk的入度減1;若vk的入度為0則進(jìn)棧
若棧空時(shí)輸出的頂點(diǎn)個(gè)數(shù)不是n,則有向圖有環(huán);否則,拓?fù)渑判蛲戤叀?br />AOE網(wǎng):帶權(quán)的有向無環(huán)圖,其中頂點(diǎn)表示事件,弧表示活動(dòng),權(quán)表示活動(dòng)持續(xù)時(shí)間。在工程上常用來表示工程進(jìn)度計(jì)劃。
一些定義:
事件的最早發(fā)生時(shí)間(ve(j)):從源點(diǎn)到j(luò)結(jié)點(diǎn)的最長的路徑。意味著事件最早能夠發(fā)生的時(shí)間。
事件的最遲發(fā)生時(shí)間(vl(j)):不影響工程的如期完工,事件j必須發(fā)生的時(shí)間。
活動(dòng)ai由弧
查找
順序查找、折半查找、索引查找、分塊查找是靜態(tài)查找,動(dòng)態(tài)查找有二叉排序樹查找,最優(yōu)二叉樹查找,鍵樹查找,哈希表查找
靜態(tài)查找表
順序表的順序查找:應(yīng)用范圍:順序表或線性鏈表表示的表,表內(nèi)元素之間無序。查找過程:從表的一端開始逐個(gè)進(jìn)行記錄的關(guān)鍵字和給定值的比較。
順序有序表的二分查找。平均查找時(shí)間(n+1)/n log2(n+1)
分塊查找:將表分成幾塊,塊內(nèi)無序,塊間有序,即前一塊中的最大值小于后一塊中的最小值。并且有一張索引表,每一項(xiàng)存放每一塊的最大值和指向該塊第一個(gè)元素的指針。索引表有序,塊內(nèi)無序。所以,塊間查找用二分查找,塊內(nèi)用順序查找,效率介于順序和二分之間;先確定待查記錄所在塊,再在塊內(nèi)查找。因此跟表中元素個(gè)數(shù)和塊中元素個(gè)數(shù)都有關(guān)。
用數(shù)組存放待查記錄,
建立索引表,由每塊中最大(小)的關(guān)鍵字及所屬塊位置的信息組成。
當(dāng)索引表較大時(shí),可以采用二分查找
在數(shù)據(jù)量極大時(shí),索引可能很多,可考慮建立索引表的索引,即二級索引,原則上索引不超過三級
分塊查找平均查找長度:ASLbs = Lb + Lw。其中,Lb是查找索引表確定所在塊的平均查找長度, Lw是在塊中查找元素的平均查找長度。在n一定時(shí),可以通過選擇s使ASL盡可能小。當(dāng)s=sqrt(n)時(shí),ASL最小。
時(shí)間:順序查找最差,二分最好,分塊介于兩者之間
空間:分塊最大,需要增加索引數(shù)據(jù)的空間
順序查找對表沒有特殊要求
分塊時(shí)數(shù)據(jù)塊之間在物理上可不連續(xù)。所以可以達(dá)到插入、刪除數(shù)據(jù)只涉及對應(yīng)的塊;另外,增加了索引的維護(hù)。
二分查找要求表有序,所以若表的元素的插入與刪除很頻繁,維持表有序的工作量極大。
在表不大時(shí),一般直接使用順序查找。
動(dòng)態(tài)查找
二叉排序樹的結(jié)點(diǎn)刪除:
x為葉子結(jié)點(diǎn),則直接刪除
x只有左子樹xL或只有右子樹xR ,則令xL或xR直接成為雙親結(jié)點(diǎn)f的子樹;
x即有左子樹xL也有右子樹xR,在xL中選值最大的代替x,該數(shù)據(jù)按二叉排序樹的性質(zhì)應(yīng)在最右邊。
平衡二叉樹:每個(gè)結(jié)點(diǎn)的平衡因子都為 1、-1、0 的二叉排序樹。或者說每個(gè)結(jié)點(diǎn)的左右子樹的高度最多差1的二叉排序樹。
平衡二叉樹的平衡:
左調(diào)整(新結(jié)點(diǎn)插入在左子樹上的調(diào)整):
LL(插入在結(jié)點(diǎn)左子樹的左子樹上):旋轉(zhuǎn)前后高度都為h+1
LR(新插入結(jié)點(diǎn)在左子樹的右子樹上):旋轉(zhuǎn)前后高度仍為h+1
右調(diào)整(新結(jié)點(diǎn)插入在右子樹上進(jìn)行的調(diào)整):
RR(插入在的右子樹的右子樹上):處理方法和 LL對稱
RL(插入在的右子樹的左子樹上):處理方法和 LR對稱
平衡樹建立方法:
按二叉排序樹插入結(jié)點(diǎn)
如引起結(jié)點(diǎn)平衡因子變?yōu)閨2|,則確定旋轉(zhuǎn)點(diǎn),該點(diǎn)是離根最遠(yuǎn)(或最接近于葉子的點(diǎn))
確定平衡類型后進(jìn)行平衡處理,平衡后以平衡點(diǎn)為根的子樹高不變
最小二叉平衡樹的節(jié)點(diǎn)的公式如下 F(n)=F(n-1)+F(n-2)+1 這個(gè)類似于一個(gè)遞歸的數(shù)列,可以參考Fibonacci數(shù)列,1是根節(jié)點(diǎn),F(n-1)是左子樹的節(jié)點(diǎn)數(shù)量,F(n-2)是右子樹的節(jié)點(diǎn)數(shù)量。
常見的平衡二叉樹:
紅黑樹是平衡二叉樹,也就是左右子樹是平衡的,高度大概相等。這種情況等價(jià)于一塊完全二叉樹的高度,查找的時(shí)間復(fù)雜度是樹的高度,為logn,插入操作的平均時(shí)間復(fù)雜度為O(logn),最壞時(shí)間復(fù)雜度為O(logn)
節(jié)點(diǎn)是紅色或黑色。
根是黑色。
所有葉子都是黑色(葉子是NIL節(jié)點(diǎn))。
每個(gè)紅色節(jié)點(diǎn)的兩個(gè)子節(jié)點(diǎn)都是黑色。(從每個(gè)葉子到根的所有路徑上不能有兩個(gè)連續(xù)的紅色節(jié)點(diǎn))
從任一節(jié)點(diǎn)到其每個(gè)葉子的所有簡單路徑 都包含相同數(shù)目的黑色節(jié)點(diǎn)。
avl樹也是自平衡二叉樹;紅黑樹和AVL樹查找、插入、刪除的時(shí)間復(fù)雜度相同;包含n個(gè)內(nèi)部結(jié)點(diǎn)的紅黑樹的高度是o(logn); TreeMap 是一個(gè)紅黑樹的實(shí)現(xiàn),能保證插入的值保證排序
STL和linux多使用紅黑樹作為平衡樹的實(shí)現(xiàn):
如果插入一個(gè)node引起了樹的不平衡,AVL和RB-Tree都是最多只需要2次旋轉(zhuǎn)操作,即兩者都是O(1);但是在刪除node引起樹的不平衡時(shí),最壞情況下,AVL需要維護(hù)從被刪node到root這條路徑上所有node的平衡性,因此需要旋轉(zhuǎn)的量級O(logN),而RB-Tree最多只需3次旋轉(zhuǎn),只需要O(1)的復(fù)雜度。
其次,AVL的結(jié)構(gòu)相較RB-Tree來說更為平衡,在插入和刪除node更容易引起Tree的unbalance,因此在大量數(shù)據(jù)需要插入或者刪除時(shí),AVL需要rebalance的頻率會(huì)更高。因此,RB-Tree在需要大量插入和刪除node的場景下,效率更高。自然,由于AVL高度平衡,因此AVL的search效率更高。
map的實(shí)現(xiàn)只是折衷了兩者在search、insert以及delete下的效率??傮w來說,RB-tree的統(tǒng)計(jì)性能是高于AVL的。
查找總結(jié)
既希望較快的查找又便于線性表動(dòng)態(tài)變化的查找方法是哈希法查找。二叉排序樹查找,最優(yōu)二叉樹查找,鍵樹查找,哈希法查找是動(dòng)態(tài)查找。分塊、順序、折半、索引順序查找均為靜態(tài)。分塊法應(yīng)該是將整個(gè)線性表分成若干塊進(jìn)行保存,若動(dòng)態(tài)變化則可以添加在表的尾部(非順序結(jié)構(gòu)),時(shí)間復(fù)雜度是O(1),查找復(fù)雜度為O(n);若每個(gè)表內(nèi)部為順序結(jié)構(gòu),則可用二分法將查找時(shí)間復(fù)雜度降至O(logn),但同時(shí)動(dòng)態(tài)變化復(fù)雜度則變成O(n);順序法是挨個(gè)查找,這種方法最容易實(shí)現(xiàn),不過查找時(shí)間復(fù)雜度都是O(n),動(dòng)態(tài)變化時(shí)可將保存值放入線性表尾部,則時(shí)間復(fù)雜度為O(1);二分法是基于順序表的一種查找方式,時(shí)間復(fù)雜度為O(logn);通過哈希函數(shù)將值轉(zhuǎn)化成存放該值的目標(biāo)地址,O(1)
二叉樹的平均查找長度為O(log2n)——O(n).二叉排序樹的查找效率與二叉樹的高度有關(guān),高度越低,查找效率越高。二叉樹的查找成功的平均查找長度ASL不超過二叉樹的高度。二叉樹的高度與二叉樹的形態(tài)有關(guān),n個(gè)節(jié)點(diǎn)的完全二叉樹高度最小,高度為[log2n]+1,n個(gè)節(jié)點(diǎn)的單只二叉樹的高度最大,高度為n,此時(shí)查找成功的ASL為最大(n+1)/2,因此二叉樹的高度范圍為[log2n]+1——n.
鏈?zhǔn)酱鎯Σ荒茈S機(jī)訪問,必須是順序存儲
B_樹的B+樹
B_樹
B-樹就是B樹。m階B_樹滿足或空,或?yàn)闈M足下列性質(zhì)的m叉樹:
?
樹中每個(gè)結(jié)點(diǎn)最多有m棵子樹
根結(jié)點(diǎn)在不是葉子時(shí),至少有兩棵子樹
除根外,所有非終端結(jié)點(diǎn)至少有?m/2?棵子樹
有s個(gè)子樹的非葉結(jié)點(diǎn)具有 n = s-1個(gè)關(guān)鍵字,結(jié)點(diǎn)的信息組織為:(n,A0,K1,A1,K2,A2 … Kn,An)。這里:n為關(guān)鍵字的個(gè)數(shù),ki(i=1,2,…,n)為關(guān)鍵字,且滿足Ki小于Ki+1,,Ai(i=0,1,..n)為指向子樹的指針。
所有的葉子結(jié)點(diǎn)都出現(xiàn)在同一層上,不帶信息(可認(rèn)為外部結(jié)點(diǎn)或失敗結(jié)點(diǎn))。
關(guān)鍵字集合分布在整顆樹中
任何一個(gè)關(guān)鍵字出現(xiàn)且只出現(xiàn)在一個(gè)結(jié)點(diǎn)中
搜索有可能在非葉子結(jié)點(diǎn)結(jié)束
其搜索性能等價(jià)于在關(guān)鍵字全集內(nèi)做一次二分查找
只適用于隨機(jī)檢索,不適用于順序檢索。
有結(jié)點(diǎn)的平衡因子都為零
M階B-樹中含有N個(gè)關(guān)鍵字,最大深度為log?m/2?(n+1)/2+2
B_樹中結(jié)點(diǎn)的插入
m代表B_樹的階,插入總發(fā)生在最低層
插入后關(guān)鍵字個(gè)數(shù)小于等于 m-1,完成。
插入后關(guān)鍵字個(gè)數(shù)等于m,結(jié)點(diǎn)分裂,以中點(diǎn)數(shù)據(jù)為界一分為二,中點(diǎn)數(shù)據(jù)放到雙親結(jié)點(diǎn)中。這樣就有可能使得雙親結(jié)點(diǎn)的數(shù)據(jù)個(gè)數(shù)為m,引起雙親結(jié)點(diǎn)的分裂,最壞情況下一直波及到根,引起根的分裂——B_樹長高。
3階B_樹的插入。每個(gè)結(jié)點(diǎn)最多3棵子樹,2個(gè)數(shù)據(jù);最少2棵子樹,1個(gè)數(shù)據(jù)。所以3階B_樹也稱為2-3樹。
B_樹中結(jié)點(diǎn)的刪除
刪除發(fā)生在最底層
被刪關(guān)鍵字所在結(jié)點(diǎn)中的關(guān)鍵字?jǐn)?shù)目大于等于 m/2 ,直接刪除。
刪除后結(jié)點(diǎn)中數(shù)據(jù)為?m/2?-2,而相鄰的左(右)兄弟中數(shù)據(jù)大于?m/2?-1,此時(shí)左(右兄弟)中最大(小)的數(shù)據(jù)上移到雙親中,雙親中接(靠)在它后(前)面的數(shù)據(jù)移到被刪數(shù)據(jù)的結(jié)點(diǎn)中
其左右兄弟結(jié)點(diǎn)中數(shù)據(jù)都是?m/2?-1,此時(shí)和左(右)兄弟合并,合并時(shí)連同雙親中相關(guān)的關(guān)鍵字。此時(shí),雙親中少了一項(xiàng),因此又可能引起雙親的合并,最壞一直到根,使B-樹降低一層。
刪除不在最底層
在大于被刪數(shù)據(jù)中選最小的代替被刪數(shù)據(jù),問題轉(zhuǎn)換成在最底層的刪除
B+樹
在實(shí)際的文件系統(tǒng)中,用的是B+樹或其變形。有關(guān)性質(zhì)與操作類似與B_樹。
?
差異:
有n棵子樹的結(jié)點(diǎn)中有n個(gè)關(guān)鍵字,每個(gè)關(guān)鍵字不保存數(shù)據(jù),只用來索引,所有數(shù)據(jù)都保存在葉子節(jié)點(diǎn)。
所有葉子結(jié)點(diǎn)中包含全部關(guān)鍵字信息,及對應(yīng)記錄位置信息及指向含有這些關(guān)鍵字記錄的指針,且葉子結(jié)點(diǎn)本身依關(guān)鍵字的大小自小而大的順序鏈接。(而B樹的葉子節(jié)點(diǎn)并沒有包括全部需要查找的信息)
所有非葉子為索引,結(jié)點(diǎn)中僅含有其子樹根結(jié)點(diǎn)中最大(或最小)關(guān)鍵字。 (而B樹的非終節(jié)點(diǎn)也包含需要查找的有效信息)
非葉最底層順序聯(lián)結(jié),這樣可以進(jìn)行順序查找
B+特性
所有關(guān)鍵字都出現(xiàn)在葉子結(jié)點(diǎn)的鏈表中(稠密索引),且鏈表中的關(guān)鍵字恰好是有序的;
不可能在非葉子結(jié)點(diǎn)命中
非葉子結(jié)點(diǎn)相當(dāng)于是葉子結(jié)點(diǎn)的索引(稀疏索引),葉子結(jié)點(diǎn)相當(dāng)于是存儲(關(guān)鍵字)數(shù)據(jù)的數(shù)據(jù)層
更適合文件索引系統(tǒng)
B+樹插入操作的平均時(shí)間復(fù)雜度為O(logn),最壞時(shí)間復(fù)雜度為O(logn)
查找過程
在 B+ 樹上,既可以進(jìn)行縮小范圍的查找,也可以進(jìn)行順序查找;
在進(jìn)行縮小范圍的查找時(shí),不管成功與否,都必須查到葉子結(jié)點(diǎn)才能結(jié)束;
若在結(jié)點(diǎn)內(nèi)查找時(shí),給定值≤Ki, 則應(yīng)繼續(xù)在 Ai 所指子樹中進(jìn)行查找
插入和刪除的操作:類似于B_樹進(jìn)行,即必要時(shí),也需要進(jìn)行結(jié)點(diǎn)的“分裂”或“合并”。
為什么說B+tree比B樹更適合實(shí)際應(yīng)用中操作系統(tǒng)的文件索引和數(shù)據(jù)庫索引?
B+tree的磁盤讀寫代價(jià)更低
B+tree的內(nèi)部結(jié)點(diǎn)并沒有指向關(guān)鍵字具體信息的指針。因此其內(nèi)部結(jié)點(diǎn)相對B 樹更小。如果把所有同一內(nèi)部結(jié)點(diǎn)的關(guān)鍵字存放在同一盤塊中,那么盤塊所能容納的關(guān)鍵字?jǐn)?shù)量也越多。一次性讀入內(nèi)存中的需要查找的關(guān)鍵字也就越多。相對來說IO讀寫次數(shù)也就降低了。
舉個(gè)例子,假設(shè)磁盤中的一個(gè)盤塊容納16bytes,而一個(gè)關(guān)鍵字2bytes,一個(gè)關(guān)鍵字具體信息指針2bytes。一棵9階B-tree(一個(gè)結(jié)點(diǎn)最多8個(gè)關(guān)鍵字)的內(nèi)部結(jié)點(diǎn)需要2個(gè)盤快。而B+樹內(nèi)部結(jié)點(diǎn)只需要1個(gè)盤快。當(dāng)需要把內(nèi)部結(jié)點(diǎn)讀入內(nèi)存中的時(shí)候,B樹就比B+樹多一次盤塊查找時(shí)間(在磁盤中就是盤片旋轉(zhuǎn)的時(shí)間)。
B+tree的查詢效率更加穩(wěn)定
由于非終結(jié)點(diǎn)并不是最終指向文件內(nèi)容的結(jié)點(diǎn),而只是葉子結(jié)點(diǎn)中關(guān)鍵字的索引。所以任何關(guān)鍵字的查找必須走一條從根結(jié)點(diǎn)到葉子結(jié)點(diǎn)的路。所有關(guān)鍵字查詢的路徑長度相同,導(dǎo)致每一個(gè)數(shù)據(jù)的查詢效率相當(dāng)。
B樹和B+樹都是平衡的多叉樹。B樹和B+樹都可用于文件的索引結(jié)構(gòu)。B樹和B+樹都能有效的支持隨機(jī)檢索。B+樹既能索引查找也能順序查找.
哈希表
在記錄的存儲地址和它的關(guān)鍵字之間建立一個(gè)確定的對應(yīng)關(guān)系;這樣不經(jīng)過比較,一次存取就能得到元素。
哈希函數(shù)——在記錄的關(guān)鍵字與記錄的存儲位置之間建立的一種對應(yīng)關(guān)系。是從關(guān)鍵字空間到存儲位置空間的一種映象。
哈希表——應(yīng)用哈希函數(shù),由記錄的關(guān)鍵字確定記錄在表中的位置信息,并將記錄根據(jù)此信息放入表中,這樣構(gòu)成的表叫哈希表。
Hash查找適合于關(guān)鍵字可能出現(xiàn)的值的集合遠(yuǎn)遠(yuǎn)大于實(shí)際關(guān)鍵字集合的情形。
更適合查找,不適合頻繁更新
Hash表等查找復(fù)雜依賴于Hash值算法的有效性,在最好的情況下,hash表查找復(fù)雜度為O(1)。只有無沖突的hash_table復(fù)雜度才是O(1)。一般是O(c),c為哈希關(guān)鍵字沖突時(shí)查找的平均長度。插入,刪除,查找都是O(1)。平均查找長度不隨表中結(jié)點(diǎn)數(shù)目的增加而增加,而是隨負(fù)載因子的增大而增大
由于沖突的產(chǎn)生,使得哈希表的查找過程仍然是一個(gè)給定值與關(guān)鍵字比較的過程。
根據(jù)抽屜原理,沖突是不可能完全避免的,所以,選擇好的散列函數(shù)和沖突處理方法:
構(gòu)造一個(gè)性能好,沖突少的Hash函數(shù)
如何解決沖突
常用的哈希函數(shù)
直接定址法。僅適合于:地址集合的大小 == 關(guān)鍵字集合的大小
數(shù)字分析法。對關(guān)鍵字進(jìn)行分析,取關(guān)鍵字的若干位或其組合作哈希地址。僅適合于:能預(yù)先估計(jì)出全體關(guān)鍵字的每一位上各種數(shù)字出現(xiàn)的頻度。
平方取中法。以關(guān)鍵字的平方值的中間幾位作為存儲地址。
折疊法。將關(guān)鍵字分割成位數(shù)相同的幾部分,然后取這幾部分的疊加和(舍去進(jìn)位)做哈希地址。移位疊加/間界疊加。適合于: 關(guān)鍵字的數(shù)字位數(shù)特別多,且每一位上數(shù)字分布大致均勻情況。
除留余數(shù)法。取關(guān)鍵字被某個(gè)不大于哈希表表長m的數(shù)p除后所得余數(shù)作哈希地址,即H(key)=key%p,p<=m。
隨機(jī)數(shù)法。取關(guān)鍵字的偽隨機(jī)函數(shù)值作哈希地址,即H(key)=random(key),適于關(guān)鍵字長度不等的情況。
沖突解決
開放定址法。當(dāng)沖突發(fā)生時(shí),形成一個(gè)探查序列;沿此序列逐個(gè)地址探查,直到找到一個(gè)空位置(開放的地址),將發(fā)生沖突的記錄放到該地址中。即Hi=(H(key)+di) % m,i=1,2,……k(k<=m-1),H(key)哈希函數(shù),m哈希表長,di增量序列。缺點(diǎn):刪除:只能作標(biāo)記,不能真正刪除;溢出;載因子過大、解決沖突的算法選擇不好會(huì)發(fā)生聚集問題。要求裝填因子α較小,故當(dāng)結(jié)點(diǎn)規(guī)模較大時(shí)會(huì)浪費(fèi)很多空間。
線性探測再散列:di=1,2,3,…,m-1
二次探測再散列:di=12,-12,22,-22,…,±k2(k<=m/2)
偽隨機(jī)探測再散列: di為偽隨機(jī)數(shù)序列
鏈地址法:將所有關(guān)鍵字為同義詞的記錄存儲在一個(gè)單鏈表中,并用一維數(shù)組存放頭指針。拉鏈法中可取α≥1,且結(jié)點(diǎn)較大時(shí),拉鏈法中增加的指針域可忽略不計(jì),因此節(jié)省空間。一旦發(fā)生沖突,在當(dāng)前位置給單鏈表增加結(jié)點(diǎn)就行。
其他方法:再哈希法、建立公共溢出區(qū)
在用拉鏈法構(gòu)造的散列表中,刪除結(jié)點(diǎn)的操作易于實(shí)現(xiàn)。拉鏈法的缺點(diǎn)是:指針需要額外的空間,故當(dāng)結(jié)點(diǎn)規(guī)模較小時(shí),開放定址法較為節(jié)省空間。由于拉鏈法中各鏈表上的結(jié)點(diǎn)空間是動(dòng)態(tài)申請的,故它更適合于造表前無法確定表長的情況。拉鏈法解決沖突時(shí),需要使用指針,指示下一個(gè)元素的存儲位置
開哈希表–鏈?zhǔn)降刂贩?閉哈希表–開放地址法.開哈希和閉哈希主要的區(qū)別在于,隨著哈希表的密集度提高,使用閉哈希時(shí),不僅會(huì)與相同哈希值的元素發(fā)生沖突,還容易與不同哈希值的元素發(fā)生沖突;而開哈希則不受哈希表疏密與否的影響,始終只會(huì)與相同哈希值的元素沖突而已。所以在密集度變大的哈希表中查找時(shí),顯然開哈希的平均搜索長度不會(huì)增長。
設(shè)有n個(gè)關(guān)鍵字具有相同的Hash函數(shù)值,則用線性探測法把這n個(gè)關(guān)鍵字映射到Hash表中需要做n*(n-1)/2次線性探測。如果使用二次探測再散列法將這n個(gè)關(guān)鍵字存入哈希表,至少要進(jìn)行n*(n+1)/2次探測
Hash查找效率:裝填因子=表中記錄數(shù)/表容量
有B+Tree/Hash_Map/STL Map三種數(shù)據(jù)結(jié)構(gòu)。對于內(nèi)存中數(shù)據(jù),查找性能較好的數(shù)據(jù)結(jié)構(gòu)是Hash_Map,對于磁盤中數(shù)據(jù),查找性能較好的數(shù)據(jù)結(jié)構(gòu)是B+Tree。Hash操作能根據(jù)散列值直接定位數(shù)據(jù)的存儲地址,設(shè)計(jì)良好的hash表能在常數(shù)級時(shí)間下找到需要的數(shù)據(jù),但是更適合于內(nèi)存中的查找。B+樹是一種是一種樹狀的數(shù)據(jù)結(jié)構(gòu),適合做索引,對磁盤數(shù)據(jù)來說,索引查找是比較高效的。STL_Map的內(nèi)部實(shí)現(xiàn)是一顆紅黑樹,但是只是一顆在內(nèi)存中建立二叉樹樹,不能用于磁盤操作,而其內(nèi)存查找性能也比不上Hash查找。
內(nèi)部排序
內(nèi)部排序:全部數(shù)據(jù)可同時(shí)放入內(nèi)存進(jìn)行的排序。
外部排序:文件中數(shù)據(jù)太多,無法全部調(diào)入內(nèi)存進(jìn)行的排序。
插入類:
直接插入排序。最壞情況是數(shù)據(jù)遞減序,數(shù)據(jù)比較和移動(dòng)量最大,達(dá)到O(n2),最好是數(shù)據(jù)是遞增序,比較和移動(dòng)最少為O(n)。趟數(shù)是固定的n-1,即使有序,也要依次從第二個(gè)元素開始。排序趟數(shù)不等于時(shí)間復(fù)雜度。
折半插入排序 。由于插入第i個(gè)元素到r[1]到r[i-1]之間時(shí),前i個(gè)數(shù)據(jù)是有序的,所以可以用折半查找確定插入位置,然后插入。
希爾排序。縮小增量排序。5-3-1。在實(shí)際應(yīng)用中,步長的選取可簡化為開始為表長n的一半(n/2),以后每次減半,最后為1。插入的改進(jìn),最后一趟已基本有序,比較次數(shù)和移動(dòng)次數(shù)相比直接插入最后一趟更少
交換類:
冒泡排序。O(n2)通常認(rèn)為冒泡是比較差的,可以加些改進(jìn),比如在一趟中無數(shù)據(jù)的交換,則結(jié)束等措施。
在數(shù)據(jù)已基本有序時(shí),冒泡是一個(gè)較好的方法
在數(shù)據(jù)量較少時(shí)(15個(gè)左右)可以用冒泡
快速排序。
時(shí)間復(fù)雜度。最好情況:每次支點(diǎn)總在中間,O(nlog2n),平均O(nlog2n)。最壞,數(shù)據(jù)已是遞增或遞減,O(n2)。pivotkey的選擇越靠近中央,即左右兩個(gè)子序列長度越接近,排序速度越快。越無序越快。
空間復(fù)雜度。需棧空間以實(shí)現(xiàn)遞歸,最壞情況:S(n)=O(n);一般情況:S(n)=O(log2n)
在序列已是有序的情況下,時(shí)間復(fù)雜度最高。原因:支點(diǎn)選擇不當(dāng)。改進(jìn):隨機(jī)選取支點(diǎn)或最左、最右、中間三個(gè)元素中的值處于中間的作為支點(diǎn),通常可以避免最壞情況。所以,快速排序在表已基本有序的情況下不合適。
在序列長度已較短時(shí),采用直接插入排序、起泡排序等排序方法。序列的個(gè)數(shù)通常取10左右。
選擇類排序:
簡單選擇排序。O(n2)??偙容^次數(shù)n(n-1)/2。
堆排序。建堆 O(n),篩選排序O(nlogn)。找出若干個(gè)數(shù)中最大/最小的前K個(gè)數(shù),用堆排序是最好。小根堆中最大的數(shù)一定是放在葉子節(jié)點(diǎn)上,堆本身是個(gè)完全二叉樹,完全二叉樹的葉子節(jié)點(diǎn)的位置大于[n/2]。時(shí)間復(fù)雜度不會(huì)因?yàn)榇判蛐蛄械挠行虺潭榷淖?#xff0c;但是待排序序列的有序程度會(huì)影響比較次數(shù)。
歸并排序。時(shí)間:與表長成正比,若一個(gè)表表長是m,另一個(gè)是n,則時(shí)間是O(m+n)。單獨(dú)一個(gè)數(shù)組歸并,時(shí)間:O(nlogn),空間:O(n),比較次數(shù)介于(nlogn)/2和(nlogn)-n+1,賦值操作的次數(shù)是(2nlogn)。歸并排序算法比較占用內(nèi)存,但卻是效率高且穩(wěn)定的排序算法。在外排序中使用。歸并的趟數(shù)是logn。
基數(shù)排序。在一般情況下,每個(gè)結(jié)點(diǎn)有 d 位關(guān)鍵字,必須執(zhí)行 t = d次分配和收集操作。分配的代價(jià):O(n);收集的代價(jià):O(rd) (rd是基數(shù));總的代價(jià)為:O( d ×(n + rd))。適用于以數(shù)字和字符串為關(guān)鍵字的情況。
枚舉排序,通常也被叫做秩排序,比較計(jì)數(shù)排序。對每一個(gè)要排序的元素,統(tǒng)計(jì)小于它的所有元素的個(gè)數(shù),從而得到該元素在整個(gè)序列中的位置,時(shí)間復(fù)雜度為O(n2)
比較法分類的下界:O(nlogn)
排序算法的一些特點(diǎn):
堆排序、冒泡排序、快速排序在每趟排序過程中,都會(huì)有一個(gè)元素被放置在其最終的位置上。
有字符序列 {Q,H,C,Y,P,A,M,S,R,D,F,X} ,新序列{F,H,C,D,P,A,M,Q,R,S,Y,X},是快速排序算法一趟掃描的結(jié)果。(拿Q作為分割點(diǎn),快速排序一輪。二路歸并,第一趟排序,得到 n / 2 個(gè)長度為 2 的各自有序的子序列,第二趟排序,得到 n / 4 個(gè)長度為 4 的各自有序的子序列H Q C Y A P M S D R F X。如果是快速排序的話,第一個(gè)元素t將會(huì)被放到一個(gè)最準(zhǔn)確的位置,t前的數(shù)均小于t,后面的數(shù)均大于t。希爾排序每個(gè)小分組內(nèi)將會(huì)是有序的。堆排序,把它構(gòu)成一顆二叉樹的時(shí)候,該堆要么就是大根堆,要么就是小根堆,第一趟Y排在最后;冒泡,那么肯定會(huì)有數(shù)據(jù)下沉的動(dòng)作,第一趟有A在第一位。)
在文件”局部有序”或文件長度較小的情況下,最佳內(nèi)部排序的方法是直接插入排序。(歸并排序要求待排序列已經(jīng)部分有序,而部分有序的含義是待排序列由若干有序的子序列組成,即每個(gè)子序列必須有序,并且其時(shí)間復(fù)雜度為O(nlog2n);直接插入排序在待排序列基本有序時(shí),每趟的比較次數(shù)大為降低,即n-1趟比較的時(shí)間復(fù)雜度由O(n^2)降至O(n)。在待排序的元素序列基本有序或者每個(gè)元素距其最終位置不遠(yuǎn)也可用插入排序,效率最高的排序方法是插入排序)
排序趟數(shù)與序列的原始狀態(tài)有關(guān)的排序方法是優(yōu)化冒泡和快速排序法。(插入排序和選擇排序不管序列的原始狀態(tài)是什么都要執(zhí)行n-1趟,優(yōu)化冒泡和快排不一定。仔細(xì)理解排序的次數(shù)和比較次數(shù)的區(qū)別)
不穩(wěn)定的排序方法:快排,堆排,希爾,選擇
要與關(guān)鍵字的初始排列次序無關(guān),那么就是最好、最壞、一般的情況下排序時(shí)間復(fù)雜度不變, 總共有堆排序,歸并排序,選擇排序,基數(shù)排序
快速排序、Shell 排序、歸并排序、直接插入排序的關(guān)鍵碼比較次數(shù)與記錄的初始排列有關(guān)。折半插入排序、選擇排序無關(guān)。(直接插入排序在完全有序的情況下每個(gè)元素只需要與他左邊的元素比較一次就可以確定他最終的位置;折半插入排序,比較次數(shù)是固定的,與初始排序無關(guān);快速排序,初始排序不影響每次劃分時(shí)的比較次數(shù),都要比較n次,但是初始排序會(huì)影響劃分次數(shù),所以會(huì)影響總的比較次數(shù),但快排平均比較次數(shù)最小;歸并排序在歸并的時(shí)候,如果右路最小值比左路最大值還大,那么只需要比較n次,如果右路每個(gè)元素分別比左路對應(yīng)位置的元素大,那么需要比較2*n-1次,所以與初始排序有關(guān))
精儉排序,即一對數(shù)字不進(jìn)行兩次和兩次以上的比較,插入和歸并是“精儉排序”。插入排序,前面是有序的,后面的每一個(gè)元素與前面有序的元素比較,比較過的就是有序的了,不會(huì)再比較一次。歸并每次合并后,內(nèi)部都是有序的,內(nèi)部的元素之間不用再比較。選擇排序,每次在后面的元素中找到最小的,找最小元素的過程是在沒有排好序的那部分進(jìn)行,所有肯定會(huì)比較多次。堆排序也需比較多次。
外部排序
生成合并段(run):讀入文件的部分記錄到內(nèi)存->在內(nèi)存中進(jìn)行內(nèi)部排序->將排好序的這些記錄寫入外存,形成合并段->再讀入該文件的下面的記錄,往復(fù)進(jìn)行,直至文件中的記錄全部形成合并段為止。
外部合并:將上一階段生成的合并段調(diào)入內(nèi)存,進(jìn)行合并,直至最后形成一個(gè)有序的文件。
外部排序指的是大文件的排序,即待排序的記錄存儲在外存儲器上,待排序的文件無法一次裝入內(nèi)存,需要在內(nèi)存和外部存儲器之間進(jìn)行多次數(shù)據(jù)交換,以達(dá)到排序整個(gè)文件的目的。外部排序最常用的算法是多路歸并排序,即將原文件分解成多個(gè)能夠一次性裝入內(nèi)存的部分,分別把每一部分調(diào)入內(nèi)存完成排序。然后,對已經(jīng)排序的子文件進(jìn)行多路歸并排序
不管初始序列是否有序, 冒泡、選擇排序時(shí)間復(fù)雜度是O(n^2),歸并、堆排序時(shí)間復(fù)雜度是O(nlogn)
外部排序的總時(shí)間 = 內(nèi)部排序(產(chǎn)出初始?xì)w并段)所需時(shí)間 + 外存信息讀取時(shí)間 + 內(nèi)部歸并所需的時(shí)間
外排中使用置換選擇排序的目的,是為了增加初始?xì)w并段的長度。減少外存讀寫次數(shù)需要減小歸并趟數(shù)
根據(jù)內(nèi)存容量設(shè)若干個(gè)輸入緩沖區(qū)和一個(gè)輸出緩沖區(qū)。若采用二路歸并,用兩個(gè)輸入緩沖。
歸并的方法類似于歸并排序的歸并算法。增加的是對緩沖的監(jiān)視,對于輸入,一旦緩沖空,要到相應(yīng)文件讀后續(xù)數(shù)據(jù),對于輸出緩沖,一旦緩沖滿,要將緩沖內(nèi)容寫到文件中去。
外排序和內(nèi)排序不只是考慮內(nèi)外排序算法的性能,還要考慮IO數(shù)據(jù)交換效率的問題,內(nèi)存存取速度遠(yuǎn)遠(yuǎn)高于外存。影響外排序的時(shí)間因素主要是內(nèi)存與外設(shè)交換信息的總次數(shù)
有效的算法設(shè)計(jì)
貪心法。Dijkstra的最短路徑(時(shí)間復(fù)雜度O(n2));Prim求最小生成樹鄰接表存儲時(shí)是O(n+e),圖O(n2);關(guān)鍵路徑及關(guān)鍵活動(dòng)的求法。
回溯法
分支限界法
分治法。分割、求解、合并。二分查找、歸并排序、快速排序。
動(dòng)態(tài)規(guī)劃。Floyd-Warshall算法求解圖中所有點(diǎn)對之間最短路徑時(shí)間復(fù)雜度為O(n3)
動(dòng)態(tài)規(guī)劃解題的方法是一種高效率的方法,其時(shí)間復(fù)雜度通常為O(n2),O(n3)等,可以解決相當(dāng)大的信息量。(數(shù)塔在n<=100層時(shí),可以在很短的時(shí)間內(nèi)得到問題解)
適用的原則:原則為優(yōu)化原則,即整體優(yōu)化可以分解為若干個(gè)局部優(yōu)化。
動(dòng)態(tài)規(guī)劃比窮舉法具有較少的計(jì)算次數(shù)
遞歸算法需要很大的??臻g,而動(dòng)態(tài)規(guī)劃不需要棧空間
貪心和動(dòng)態(tài)規(guī)劃的差別:
所謂貪心選擇性質(zhì)是指所求問題的整體最優(yōu)解可以通過一系列局部最優(yōu)的選擇,即貪心選擇來達(dá)到。這是貪心算法可行的第一個(gè)基本要素,也是貪心算法與動(dòng)態(tài)規(guī)劃算法的主要區(qū)別。
在動(dòng)態(tài)規(guī)劃算法中,每步所作的選擇往往依賴于相關(guān)子問題的解。因而只有在解出相關(guān)子問題后,才能作出選擇。而在貪心算法中,僅在當(dāng)前狀態(tài)下作出最好選擇,即局部最優(yōu)選擇。然后再去解作出這個(gè)選擇后產(chǎn)生的相應(yīng)的子問題。
貪心算法所作的貪心選擇可以依賴于以往所作過的選擇,但決不依賴于將來所作的選擇,也不依賴于子問題的解。正是由于這種差別,動(dòng)態(tài)規(guī)劃算法通常以自底向上的方式解各子問題,而貪心算法則通常以自頂向下的方式進(jìn)行,以迭代的方式作出相繼的貪心選擇,每作一次貪心選擇就將所求問題簡化為一個(gè)規(guī)模更小的子問題。
P問題
P問題,如果它可以通過運(yùn)行多項(xiàng)式次(即運(yùn)行時(shí)間至多是輸入量大小的多項(xiàng)式函數(shù)的一種算法獲得解決),可以找到一個(gè)能在多項(xiàng)式的時(shí)間里解決它的算法?!?確定性問題
NP問題,雖然可以用計(jì)算機(jī)求解,但是對于任意常數(shù)k,它們不能在O(nk)時(shí)間內(nèi)得到解答,可以在多項(xiàng)式的時(shí)間里驗(yàn)證一個(gè)解的問題。所有的P類問題都是NP問題。
NP完全問題,知道有效的非確定性算法,但是不知道是否存在有效的確定性算法,同時(shí),不能證明這些問題中的任何一個(gè)不存在有效的確定性算法。這類問題稱為NP完全問題。
---------------------
作者:小草莓lllll
來源:CSDN
原文:https://blog.csdn.net/qq_31196849/article/details/78529724
版權(quán)聲明:本文為博主原創(chuàng)文章,轉(zhuǎn)載請附上博文鏈接!
轉(zhuǎn)載于:https://www.cnblogs.com/xqjoker/p/11133191.html
總結(jié)
- 上一篇: 浪潮服务器风扇转速调节(已解决)
- 下一篇: 在架设网站服务器时,实习实习报告网站服务