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

歡迎訪問 生活随笔!

生活随笔

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

编程问答

软件设计师-3.数据结构与算法基础

發(fā)布時間:2023/12/16 编程问答 46 豆豆
生活随笔 收集整理的這篇文章主要介紹了 软件设计师-3.数据结构与算法基础 小編覺得挺不錯的,現(xiàn)在分享給大家,幫大家做個參考.

結(jié)構(gòu):結(jié)構(gòu)是指元素之間的關(guān)系。

邏輯結(jié)構(gòu):元素之間的相互關(guān)系稱為數(shù)據(jù)的邏輯結(jié)構(gòu),可劃分為線性結(jié)構(gòu)非線性結(jié)構(gòu)

  • 常用的線性結(jié)構(gòu)有:線性表,棧隊列、數(shù)組和串。

  • 常見的非線性結(jié)構(gòu)有:二維數(shù)組,多維數(shù)組,廣義表,樹(二叉樹),圖。

存儲結(jié)構(gòu):數(shù)據(jù)元素及元素之間的存儲形式稱為存儲結(jié)構(gòu),可分為順序存儲鏈接存儲兩種基本方式。

  • 順序存儲時,相鄰數(shù)據(jù)元素的存放地址相鄰(邏輯與物理統(tǒng)一);要求內(nèi)存中可用存儲單元的地址必須是連續(xù)的。
  • 鏈接存儲時,相鄰數(shù)據(jù)元素可隨意存放,但所占存儲空間分兩部分,一部分存放節(jié)點信息,另一部分存放表示節(jié)點間關(guān)系的指針。

3.1 線性結(jié)構(gòu)

3.1.1線性表

3.1.1.1 順序表

存儲結(jié)構(gòu):順序存儲結(jié)構(gòu)(順序表)

存儲方式:內(nèi)存是連續(xù)分配的,并且是靜態(tài)分配的,在使用前需要分配固定大小的空間。

操作:

  • 訪問第i個元素:根據(jù)下標(biāo),可以直接訪問對應(yīng)的元素。

  • 查找是否含有某值:依次比對每個元素的值,直到找到。

  • 刪除第i個元素

    例如刪除 a4,刪除后,需要將 a5、a6、a7依次向前移動一個位置。

    含有n個元素的線性表采用順序存儲,等概率刪除其中任一個元素,平均需要移動(n-1)/2個元素。

  • 第i元素前插入某值

    例如在 a4 前插入元素,則a4、a5、a6、a7依次向后移動一個位置,然后再插入新元素。

    含有n個元素的線性表采用順序存儲,等概率插入一個元素,平均需要移動n/2個元素。

  • 3.1.1.2 鏈表

    存儲結(jié)構(gòu):鏈?zhǔn)酱鎯Y(jié)構(gòu)(鏈表)。

    鏈表(linked-list)存儲方式:鏈表的內(nèi)存是不連續(xù)的,前一個元素存儲地址的下一個地址中存儲的不一定是下一個元素。訪問某結(jié)點應(yīng)找上一結(jié)點提供的地址,每一節(jié)點有一指針變量存放下一結(jié)點的地址。數(shù)據(jù)元素的邏輯順序是通過鏈表中的指針鏈接次序?qū)崿F(xiàn)的。

    操作:

  • 訪問第i個元素:通過head開始查找,根據(jù)地址指針找到下一個元素,依次找到第i個元素

  • 查找是否含有某值:依次比對每個元素的值,直到找到。

  • 刪除第i個元素:

    例如刪除B結(jié)點,刪除后,A的下一個地址需要指向C結(jié)點,即1021

  • 第i元素前插入某值:

    例如在B結(jié)點前插入,插入元素后,A結(jié)點的下一個地址為新插入結(jié)點的地址;新插入結(jié)點的下一個地址為 B。

  • 其他概念

    • 尾結(jié)點:最后一個有效結(jié)點。

    • 首結(jié)點:第一個有效結(jié)點。

    • 頭結(jié)點:第一個有效結(jié)點之前的那個結(jié)點,存放鏈表首地址。

    • 頭指針:指向頭結(jié)點的指針變量。

    • 尾指針:指向尾結(jié)點的指針變量。

    特點:

  • n個節(jié)點離散分布,彼此通過指針相聯(lián)系。
  • 除頭結(jié)點和尾結(jié)點外,每個結(jié)點只有一個前驅(qū)結(jié)點和一個后續(xù)結(jié)點。頭結(jié)點沒有前驅(qū)結(jié)點,尾結(jié)點沒有后繼節(jié)點。
  • 頭結(jié)點不存放有效數(shù)據(jù),只存放鏈表首地址。其中數(shù)據(jù)類型通首節(jié)點類型一樣。
  • 頭結(jié)點的目的是為了方便對鏈表的操作,比如在鏈表頭進行節(jié)點的刪除和插入。
  • 以上講解的是單鏈表,除了單鏈表外,鏈表該有循環(huán)鏈表、雙向鏈表,如下圖所示

    數(shù)據(jù)結(jié)構(gòu)中的邏輯結(jié)構(gòu)是指數(shù)據(jù)對象中元素之間的相互關(guān)系。按邏輯結(jié)構(gòu)可將數(shù)據(jù)結(jié)構(gòu)分為()。

    A.靜態(tài)結(jié)構(gòu)和動態(tài)結(jié)構(gòu)

    B.線性結(jié)構(gòu)和非線性結(jié)構(gòu)

    C.散列結(jié)構(gòu)和索引結(jié)構(gòu)

    D.順序結(jié)構(gòu)和鏈表結(jié)構(gòu)

    答案 B

    若某線性表長度為n且采用順序存儲方式,則運算速度最快的操作是()。

    A.查找與給定值相匹配的元素的位置

    B.查找并返回第i個元素的值(1≤i≤n)

    C.刪除第i個元素(1≤i≤n)

    D.在第i個元素(1≤i≤n )之前插入一個新元素

    答案 B

    以下關(guān)于單鏈表存儲結(jié)構(gòu)特征的敘述中,不正確的是()。

    A. 表中結(jié)點所占用存儲空間的地址不必是連續(xù)的

    B. 在表中任意位置進行插入和刪除操作都不用移動元素

    C. 所需空間與結(jié)點個數(shù)成正比

    D. 可隨機訪問表中的任一結(jié)點

    答案 D

    3.1.2棧和隊列

    隊列:先進先出(FIFO——first in first out)

    • 隊尾(rear)進行插入工作;
    • 隊頭(front)進行讀取(刪除)操作。

    棧:先進后出(FILO——first in last out);

    • 棧頂(top):進行插入和讀取(刪除)操作的一端稱為棧頂;
    • 棧底(bottom):固定不變,不可進行插入和刪除操作;
    • 進棧: Push the stack
    • 出棧:Pop the stack

    題1:元素按照 a、b、c的次序入棧,請嘗試寫出其所有可能的出棧序列。

    這類題的解法主要是先將abc三個字符的素有排列給寫出來,然后看看哪個是不可能的。

    列出所有的排列: abc、acb、bac、bca、cab、cba

    然后看看哪個不符合要求:cab

    所以答案是 abc、acb、bac、bca、cba

    3.1.3串

    由字符(數(shù)組、字母、下劃線等)構(gòu)成的一維數(shù)組。

    概念:

    • 空串:無任何字符的字符串。
    • 空白串:由空白符號(空格、制表符等)構(gòu)成的字符串。
    • 子串:串中任一個連續(xù)的字符組成的子序列稱為該串的子串。
    • 非平凡子串:非空且不等同意字符串本身。
    • 串的模式匹配:模式串在主串中首次出現(xiàn)的位置。
    • 字符串比較:從左至右按取ASCII碼值進行比較。

    設(shè)S 是一個長度為n的非空字符串,其中的字符各不相同,則其互異的非平凡子串(非空且不同于S本身)個數(shù)為( )。

    A.2n-1

    B.n2

    C.n(n+1)/2

    D.(n+2) (n-1)/2

    答案 選D

    分析:我們可以使用帶入法來對本地進行分析,不需要真的推導(dǎo)公式。

    比如,當(dāng)長度為1時,它的非平凡子串的個數(shù)是0,看看哪個公式符合即可;若還是不行,則當(dāng)長度為2時,它的非平凡子串的個數(shù)是2,再看看哪個公式符合。

    3.2 數(shù)組、矩陣和廣義表

    3.2.1 數(shù)組

    數(shù)組是由n個相同的元素所組成的序列。

    注意:

  • 空間連續(xù),統(tǒng)一劃分。
  • 元素類型相同,每個元素占用存儲單元相同。
  • 下標(biāo)有序,n個元素,下標(biāo)是 0~n-1。
  • 3.2.1.1 一維數(shù)組

    A[i]的存儲地址為:a+i*len。 首地址為a,len表示單個元素所占用的存儲單元。

    以存放首地址為100,每個元素占用3個存儲單元為例。

    A[i] 地址 = 100 + i*3

    3.2.1.2 二維數(shù)組

    3*4的二維數(shù)組

    假設(shè)有n*m的二維數(shù)組,下標(biāo)從0開始,按行存儲,則

    • A[i][j]的偏移量為 (i*n+j) * len
    • A[i][j]的存儲地址為 (i*n+j) * len + 數(shù)組首地址

    3.2.2 矩陣

    矩陣是很多科學(xué)與工程計算領(lǐng)域研究的數(shù)學(xué)對象。在數(shù)據(jù)結(jié)構(gòu)中,主要討論如何在節(jié)省存儲空間的情況下使矩陣的各種運算能高效的進行。

    3.2.2.1 特殊矩陣

    在一些矩陣中,存在很多相同的元素或者是0的元素。為了節(jié)省空間,可以對這類矩陣進行壓縮存儲,即多個值相同的元素只分配一個存儲單元,對0不分配存儲單元。假如值相同的元素或0元素在矩陣中的分布有一定的規(guī)律,則稱此類矩陣為特殊矩陣,否則為稀疏矩陣。

    對稱矩陣:若每一對元素(Aij、Aji)僅占用一個存儲單元,則可將n2個元素壓縮存儲到能存放 n(n+1)/2 個元素的存儲空間中。假設(shè)一維數(shù)組 B[n(n+1)/2]作為n階對稱矩陣A中元素的存儲空間,則 B[k] 與 矩陣元素 Aij 之間存在一一對應(yīng)的關(guān)系,如下所示 $$ k = i(i-1)/2 + j k = j(j-1)/2 + i $$ 對角矩陣:若以行為主序?qū)階三角矩陣的非0元素近占用一個存儲單元,則可將n2個元素壓縮存儲到能存放 3n-2 個元素的存儲空間中。假設(shè)一維數(shù)組B[3n-2]z作為n階對角矩陣A中元素的存儲空間,則B[k]與 矩陣元素 Aij 之間存在一一對應(yīng)的關(guān)系,如下所示 $$ k=3×(i-1) + j-i+1+1 = 2i +j-2 $$

    3.2.2.2 非特殊矩陣(稀疏矩陣)

    稀疏矩陣:非0元素的個數(shù)遠(yuǎn)遠(yuǎn)少于0元素的個數(shù),且非0元素的分布沒有規(guī)律。

    對于稀疏矩陣,存儲非0元素時必須同時存儲器位置(即行號和列號),所以三元組(i,j,a??) 可唯一確定矩陣中的一個元素。

    三元組表為 (1,2,12),(1,4,9),(2,4,7),(3,1,1),(4,1,2),(4,4,1)

    3.2.2.3 矩陣的乘法

    矩陣的乘法運算:設(shè)A為m*p的矩陣,B為p*n的矩陣,那么m*n的矩陣C為矩陣A與B的乘積,記作 C = AB,其中C中的第i行第j列元素可以表示為:

    如下所示:

    A的行與B的列依次相乘

    f(1)=1,f(2)=1,n>2時f(n)=f(n-1)+f(n-2) 據(jù)此可以導(dǎo)出,n>1時,有向量的遞推關(guān)系式:

    (f(n+1),f(n))=(f(n),f(n-1))A

    其中A是2x2矩陣( )。從而,(f(n+1),f(n)=(f(2),f(1))*( )

    解析:

    首先我們知道f(n)=f(n-1)+f(n-2),即n位置的數(shù)是前兩個數(shù)字之和。 f(n+1)=f(n)+f(n-1) 是成立的, f(n-1)=f(n-2)+f(n-3)也是成立的。

    選項1:

    • 這是一個矩陣乘法,我們可以使用代入的方式來進行計算。
    • (f(n),f(n-1))與A選項矩陣進行相乘
      • 結(jié)果是 ( f(n)x0+f(n-1)x1, f(n)x1+f(n-1)x1 ) = ( f(n-1), f(n) + f(n-1) )
      • 而 f(n+1)=f(n)+f(n-1) ,對上面的結(jié)果進行替換, ( f(n-1), f(n+1) )
      • 算出的結(jié)果不等于 (f(n+1),f(n))
    • (f(n),f(n-1))與B選項矩陣進行相乘
      • 結(jié)果是 ( f(n)x1+f(n-1)x1, f(n)x0+f(n-1)x1 ) = ( f(n) + f(n-1), f(n-1) )
      • 替換后 ( f(n+1), f(n-1) ) 結(jié)果也是錯誤的
    • (f(n),f(n-1))與C選項矩陣進行相乘
      • ( f(n)x1+f(n-1)x0, f(n)x1+f(n-1)x1 ) = ( f(n), f(n) + f(n-1) )
      • 替換后 ( f(n), f(n+1) ) 結(jié)果也是錯誤的
    • (f(n),f(n-1))與D選項矩陣進行相乘
      • ( f(n)x1+f(n-1)x1, f(n)x1+f(n-1)x0 ) = ( f(n) + f(n-1), f(n) )
      • 替換后 ( f(n+1), f(n) ),結(jié)果正確
    • 所以選項1的答案是 D

    選項2:

    • 由選項1可知 ( f(n+1),f(n) )=( f(n),f(n-1) )A
    • 對n進行降維,即令n=n-1后, ( f(n),f(n-1) )=( f(n-2),f(n-3) )A
    • 繼續(xù)降維 ( f(n-1),f(n-2) )=( f(n-3),f(n-4) )A
    • ...
    • 最終 ( f(3),f(4) ) = ( f(2),f(1) )A
    • 再次回到 ( f(n+1),f(n) )=( f(n),f(n-1) )A ,將 f(n),f(n-1)替換成 ( f(n-2),f(n-3) )A ,則 ( f(n+1),f(n) )= ( f(n+1),f(n) )=( f(n-2),f(n-3) ) A2
    • 繼續(xù)替換 ( f(n-2),f(n-3) ),則 ( f(n+1),f(n) )=( f(n-3),f(n-4) ) A3
    • 最終發(fā)現(xiàn)由 ( f(n+1),f(n) ) 替換到 ( f(2),f(1) ) A,一共經(jīng)過了n-1次替換。(也可以發(fā)現(xiàn)規(guī)律,就是后一個參數(shù)與A的次方之后等于n,則 ( f(2),f(1) ) 情況下,應(yīng)該是A的n-1次方)
    • 所以答案選擇 A

    3.2.3 廣義表

    廣義表(Lists,又稱列表)是一種非連續(xù)性的數(shù)據(jù)結(jié)構(gòu),是線性表的一種推廣。即廣義表中放松對表元素的原子限制,容許它們具有其自身結(jié)構(gòu)。

    廣義表通常記作: $$ Ls=( a1,a2,…,an) $$ ai即可以是一個元素,也可以是一個廣義表,分別稱為原子和子表。

    廣義表的長度是指廣義表中元素的個數(shù)。廣義表的深度是指廣義表展開后所含的括號的最大層數(shù)。

    在此,只討論廣義表的兩個重要的基本運算:取表頭head(Ls)和取表尾tail(Ls)。

    • 取表頭head(Ls):非空廣義表LS的第一個元素為表頭,它可以是一個單元素,也可以是一個子表。
    • 取表尾tail(Ls):在非空廣義表中,出表頭元素外,由其余元素所構(gòu)成的表稱為表尾。非空廣義表的表尾必定是一個表。

    特點:

    • 廣義表可以是多層次結(jié)構(gòu),因為廣義表的元素可以是子表,而子表的元素還可以是子表。
    • 廣義表的元素可以是已經(jīng)定義的廣義表的名字,所以一個廣義表可以被其他廣義表所共享。
    • 廣義表可以是一個遞歸表,即廣義表的元素也可以是本廣義表的名字

    存儲結(jié)構(gòu),采用鏈?zhǔn)酱鎯Α?/p>

    由于廣義表中可同時存儲原子和子表兩種形式的數(shù)據(jù),因此鏈表節(jié)點的結(jié)構(gòu)也有兩種,如圖所示:

    表示原子的節(jié)點由兩部分構(gòu)成,分別是 tag 標(biāo)記位和原子的值,表示子表的節(jié)點由三部分構(gòu)成,分別是 tag 標(biāo)記位、hp 指針和 tp 指針。

    tag 標(biāo)記位用于區(qū)分此節(jié)點是原子還是子表,通常原子的 tag 值為 0,子表的 tag 值為 1。子表節(jié)點中的 hp 指針用于連接本子表中存儲的原子或子表,tp 指針用于連接廣義表中下一個原子或子表。

    例如,廣義表 {a,{b,c,d}} 是由一個原子 a 和子表 {b,c,d} 構(gòu)成,而子表 {b,c,d} 又是由原子 b、c 和 d 構(gòu)成,用鏈表存儲該廣義表如圖 2 所示:

    3.3 樹

    3.3.1 樹與二叉樹的定義

    ?樹的基本概念

    父結(jié)點:例如結(jié)點1是結(jié)點2的父節(jié)點

    子結(jié)點:結(jié)點2是結(jié)點1的子節(jié)點

    兄弟結(jié)點:擁有同一父結(jié)點的兩個子結(jié)點為兄弟結(jié)點。例如 結(jié)點2和結(jié)點3是兄弟節(jié)點

    葉子結(jié)點:無子結(jié)點的結(jié)點。如 結(jié)點4是葉子節(jié)點。

    結(jié)點度:一個結(jié)點擁有幾個子節(jié)點,它的度就是幾。例如結(jié)點2的度為2

    樹的度:所有結(jié)點中,度最大的那個結(jié)點的度就是樹的度。如上圖,所有的結(jié)點中,每個節(jié)點的度最大不超過2,所以樹的度為2.

    樹的度為2的樹就是二叉樹。s

    層(深度、高度):一共有多少層。如上圖,層是4層。

    結(jié)點1是整個樹的根節(jié)點。

    3.3.2 二叉樹的性質(zhì)與存儲結(jié)構(gòu)

    ?二叉樹的劃分

    滿二叉樹:每一層都不能再插入一個結(jié)點。

    完全二叉樹:1~倒數(shù)第二層是滿的,最后一層葉子節(jié)點是從左到右依次排序(左全右空)。

    非完全二叉樹:不是滿二叉樹、完全二叉樹的樹就是非完全二叉樹。

    ?二叉樹的特性

  • 在二叉樹的第i層上最多有2 ? ?1個結(jié)點(i≥1)

  • 深度為k的二叉樹最多有2?-1個結(jié)點(k≥1)

  • 葉子節(jié)點數(shù)為n。度為2的節(jié)點數(shù)為m,則n=m+1。

  • 如果對一顆有n個結(jié)點的完全二叉樹的結(jié)點按序編號(從第一層到log?n+1層,每層從左到右),則對任一結(jié)點i(1≤i≤n),有:

    • 如果i=1,則結(jié)點i無父結(jié)點,是二叉樹的根;如果i>1,則父結(jié)點是 i/2。

    • 如果 2i>n,則結(jié)點 i 為葉子結(jié)點,無左子結(jié)點;否則,其左子結(jié)點是結(jié)點2i。

      例如下圖 9,2i=18 > 17,無左孩子結(jié)點

    • 如果 2i+1>n,則結(jié)點i無右子結(jié)點,否則其右子結(jié)點是結(jié)點 2i+1。

      例如下圖 9,2i+1=19 > 17,無右孩子結(jié)點

    • 以上規(guī)律可以結(jié)合下圖來看

  • 提問:一個二叉樹如果共有65個節(jié)點,問至少有多少層?最多有多少層?

    最少有7層,可以套用公式深度為k的二叉樹最多有2?-1個結(jié)點,當(dāng)有6層是最多有63個結(jié)點,放不下65,所以最少有7層。

    最多有65層,每層放一個結(jié)點。

    ?二叉樹的存儲結(jié)構(gòu)

  • 顯然對于完全二叉樹和滿二叉樹采用順序存儲結(jié)構(gòu)即簡單又節(jié)省空間,對于一般的二叉樹則不宜采用順序存儲結(jié)構(gòu)。因為一般的二叉樹也必須按照完全二叉樹的形式存儲,也就是要添加上一些實際不存在的虛點。

    如下圖所示

  • 對于普通的二叉樹采用鏈?zhǔn)酱鎯Y(jié)構(gòu)

    由于二叉樹包含數(shù)據(jù)元素、左子樹的根、右子樹的根及雙親信息,因此可以用三叉鏈表或二叉鏈表來存儲二叉樹,鏈表的頭指針指向二叉樹的根結(jié)點。

  • 題1:對下圖所示的二叉樹進行順序存儲(根結(jié)點編號為1,對于編號為i的結(jié)點,其左孩子結(jié)點為2i,右孩子結(jié)點為2i+1)并用一維數(shù)組BT來表示。已知結(jié)點X、E和D在數(shù)組BT中的下標(biāo)為分別為1、2、3,可推出結(jié)點G、K和H在數(shù)組BT中的下標(biāo)分別為( )。

    A.10、11、12

    B.12、24、25

    C.11、12、13

    D.11、22、23

    答案 D

    分析:由題可知 E下標(biāo)2,它的右子節(jié)點F的下標(biāo)為 2E+1 = 5;

    ? G的坐標(biāo)為 2F+1=11

    ? K的坐標(biāo)為 2G = 22

    ? H的坐標(biāo)為 2G+1=23

    題2:完全二叉樹的特點是葉子結(jié)點分布在最后兩層,且除最后一層之外,其他層的結(jié)點數(shù)都達(dá)到最大值,那么25個結(jié)點的完全二叉樹的高度(即層數(shù))為()。

    A.3

    B.4

    C.5

    D.6

    答案 5

    3.3.3二叉樹的遍歷

    • 前序/先序遍歷:先遍歷根結(jié)點,然后遍歷左子樹,最后遍歷右子樹。

      上圖前序遍歷的結(jié)果是: 1 2 4 5 7 8 3 6

    • 中序遍歷:先遍歷左子樹,然后遍歷根結(jié)點,最后遍歷右子樹

      上圖前序遍歷的結(jié)果是:4 2 7 8 5 1 3 6

    • 后序遍歷:先遍歷左子樹,然后遍歷右子樹,最后遍歷根結(jié)點

      上圖前序遍歷的結(jié)果是:4 8 7 5 2 6 3 1

    • 層序遍歷:從上往下逐層遍歷

      上圖前序遍歷的結(jié)果是:1 2 3 4 5 6 7 8

    提問:由前序為 ABHFDECG;中序序列為 HBEDFAGC 構(gòu)造的二叉樹。

    由前序序列找到根結(jié)點,再有中序序列來確認(rèn)哪個是左子結(jié)點,哪個是右子結(jié)點。

  • 由前序序列,知道 A 為根結(jié)點。

  • 由中序序列,知道左子樹為 HBEDF ,右子樹為 GC。

  • 再回到前序序列,根據(jù)步驟2知道,BHFDE 是左子樹,CG為右子樹。

  • 根據(jù)步驟3, C 是右子樹的根結(jié)點。

  • 步驟2可知,中序遍歷是 GC 是右子樹,可知道 G 是 C 的左子節(jié)點。

  • 由步驟3 知道左子樹的前序序列為 BHFDE,可知B是根結(jié)點

  • 由步驟2 HBEDF ,可知H 為左節(jié)點,EDF 為右子樹

  • 由前序 FDE 可知, F是根結(jié)點

  • 由中序EDF,可知,ED為F的左子樹

  • 由前序 DE 可知,D是根結(jié)點

  • 由中序 ED,可知 E 是 D 的左子結(jié)點

  • 3.3.4線索二叉樹

    二叉樹的遍歷實質(zhì)上是一個非線性結(jié)構(gòu)進行線性化的過程,它使得每個節(jié)點(除第一個和最后一個)在這些線性序列中有且僅有一個直接前驅(qū)和直接后繼。但在二叉鏈表存儲結(jié)構(gòu)中只能找打一個節(jié)點的左、右孩子,不能直接得到結(jié)點在任一遍歷序列中的前驅(qū)和后繼,這些信息只能在遍歷的動態(tài)過程中才能得到,因此引入線索二叉樹來保存這些動態(tài)過程得到的信息。

    為了保持前驅(qū)和后繼信息,可考慮在每個節(jié)點中增加兩個指針域來存放遍歷時得到的前驅(qū)和后繼信息,這樣就可以為以后的訪問帶來方便。但增加指針信息會降低存儲空間的利用率,因此可考慮采用其他方法。

    若n個節(jié)點的二叉樹采用二叉鏈表做存儲結(jié)構(gòu),則鏈表中必然有n+1個空指針域,可以使用這些空指針域來存放結(jié)點的前驅(qū)和后繼信息。為此,需要在節(jié)點中增加ltag和rtag,以區(qū)分孩子指針的指向,如下所示: $$ |ltag|lchild|data|rchild|rtag| $$ 其中:

    ltag:0-lchild域指示結(jié)點的左孩子;1-lchild域指示結(jié)點的直接前驅(qū)。

    rtag:0-rchild域指示結(jié)點的右孩子;1-rchild域指示結(jié)點的直接后繼。

    若二叉樹的二叉鏈表采用以上所示的結(jié)構(gòu),則響應(yīng)的鏈表稱為線索鏈表,其中指向節(jié)點前驅(qū)、后繼的指針稱為線索。加上線索的二叉樹稱為線索二叉樹。對二叉樹以某種次序遍歷使其稱為線索二叉樹的過程稱為線索化。中序線索二叉樹及其存儲結(jié)構(gòu)如圖所示。

    如何進行線索化呢?

    實質(zhì)上是在遍歷過程中用線索取代空指針。因此,設(shè)指針p執(zhí)行正在訪問的結(jié)點,則遍歷時設(shè)立一個指針 pre,使其時鐘執(zhí)行剛剛訪問過的節(jié)點(即p所示結(jié)點的前驅(qū)結(jié)點),這樣就記下了遍歷過程中結(jié)點被訪問的先后關(guān)系。

  • 若p執(zhí)行的節(jié)點有空指針域,則將響應(yīng)的標(biāo)志域置為1
  • 若pre!=null且pre所指節(jié)點的rtag等于1,則靈 pre->rchild=p
  • 若p所指向節(jié)點的ltag=1,則p->lchild = pre;
  • 使pre執(zhí)行剛剛訪問過的節(jié)點,即令pre=p;
  • 需要說明的是,用這種方法得到的線索二叉樹,其線索并不完整。也就是說部分節(jié)點的前驅(qū)或后繼信息還需要通過進一步運算來得到。

    如何在線索二叉樹中查找結(jié)點的前驅(qū)和后繼呢?以中序線索二叉樹為例,令p執(zhí)行樹種的某個結(jié)點,查找p所指結(jié)點的后繼節(jié)點的方法:

  • 若p-rtag==1,則p->rchild指向其后繼結(jié)點。
  • 若p-rtag==0,則p所指節(jié)點的中序后繼必然是其右子樹中進行中序遍歷得到的第一個節(jié)點。也就是說,從p所指節(jié)點的右子樹的根觸發(fā),沿著左孩子指針鏈向下查找,直找到一個沒有左孩子的節(jié)點位置,這個節(jié)點就是就是p所指節(jié)點的直接后繼節(jié)點,也稱其為p的右子樹的“最左下”的節(jié)點。
  • 3.3.5特殊二叉樹

    3.3.5.1 二叉查找樹

    左子樹小于根,右子樹大于根

    特點:

  • 二叉查找樹的中序遍歷序列為從小到大排列的序列。
  • 值最小的結(jié)點無左子樹,值最大的結(jié)點無右子樹。
  • 每一層從左到右進行遍歷的序列為從小到大排列的序列。
  • 上圖插入結(jié)點:序列(89,48,56,48,20,112,51)

  • 若查找二叉樹為空樹,則以新結(jié)點為查找二叉樹。
  • 將要插入結(jié)點值域父節(jié)點值比較,確定放左子樹還是右子樹,若子樹為空,則作為子樹的根結(jié)點插入。
  • 若該鍵值結(jié)點已存在,則不再插入。
  • 3.3.5.2 哈夫曼樹(最優(yōu)二叉樹)

    需要了解的基本概念:

    • 樹的路徑長度:從樹根到樹中每一結(jié)點的路徑長度之和。

      如下圖左邊的樹,結(jié)點2的路徑長度為2,結(jié)點4的長度為3,結(jié)點8的長度為3,結(jié)點1的長度為1

    • 權(quán):在一些應(yīng)用中,賦予樹中結(jié)點的一個有某種意義的實數(shù)。

    • 帶權(quán)路徑長度:結(jié)點到樹根之間的路徑長度與該結(jié)點上權(quán)的乘積。

      如下圖左邊的樹,結(jié)點2的帶權(quán)路徑長度為2x2=4,結(jié)點4的長度為3x4=12,結(jié)點8的長度為3x8=24,結(jié)點1的長度為1*1=1

    • 樹的帶權(quán)路徑長度(樹的代價):樹中所有葉結(jié)點的帶權(quán)路徑長度之和。

      如下圖左邊的樹,樹的代價 = 4+12+24+1=41

    哈夫曼樹就是樹的代價最小的那顆樹。

    假設(shè)有一組權(quán)值 50,20,30,40,10,請嘗試構(gòu)造哈夫曼樹。

  • 最小的兩個構(gòu)造一顆子樹,10和20 ,并讓他們的父節(jié)點為子節(jié)點之和即30

  • 從上面剔除10,20,并將30加入即【50,30,30,40】,再找到最小的兩個值 30,30,再次按照步驟1構(gòu)造樹

  • 再次按照步驟2得到數(shù)組【50,60,40】,選擇最小的進行樹的構(gòu)造

  • 再次按照步驟2得到數(shù)組【90,60】,選擇最小的進行樹的構(gòu)造

  • 3.3.6樹和森林

    3.3.6.1樹的存儲結(jié)構(gòu)

  • 樹的雙親表示法(雙親結(jié)點就是父結(jié)點)

    該表示法用一組地址連續(xù)的單元存儲樹的結(jié)點,并在每個結(jié)點中附設(shè)一個指示器,指出其雙親在該存儲結(jié)構(gòu)中的位置(即父結(jié)點所在元素的下標(biāo))。

    ?

    雙親表示法求指定結(jié)點的雙親和父結(jié)點都十分方便,但是求一個結(jié)點的孩子結(jié)點的時候就比較麻煩。

  • 樹的孩子表示法

    孩子表示法就是把每個節(jié)點的孩子節(jié)點存到一個單鏈表中,這個鏈表成為“孩子鏈表”,每個節(jié)點都對應(yīng)一個孩子鏈表,沒有孩子的結(jié)點,對應(yīng)的孩子鏈表為空,結(jié)點的數(shù)據(jù)和孩子的頭指針,我們還是用一個順序表來存儲。

    孩子表示法在找節(jié)點的孩子的時候十分方便,但是在找他的雙親的時候又有些麻煩,于是我們可以在這個順序表的每個結(jié)點加上一個雙親域形成帶雙親的孩子表示法。

  • 孩子兄弟表示法

    孩子兄弟表示法是三種存儲方式中最好操作的,它的本質(zhì)是二叉樹,只不過,它的右指針從右孩子變成了兄弟,其他與二叉樹相同,如下圖。

    如果想找到某個節(jié)點的第n個孩子,可以先通過他的指針找到第一個孩子,然后通過第一個孩子的兄弟結(jié)點遍歷n-1次,此時得到的結(jié)點就是它的第n個孩子。

  • 3.3.6.2 樹和森林的遍歷

    由于樹的每個結(jié)點可以有多個子樹,因此遍歷樹的方法有兩種,即先根遍歷和后根遍歷。

  • 先根遍歷

    先訪問樹的根結(jié)點,然后依次先根遍歷各課子樹。對樹的先根遍歷等同于對轉(zhuǎn)換的二叉樹進行先序遍歷。

  • 后根遍歷

    先依次后邊遍歷樹根的各課子樹,然后訪問樹的根結(jié)點。對樹的后根遍歷等同于對轉(zhuǎn)換的二叉樹進行中序遍歷。

  • 森林的遍歷

  • 先序遍歷

    若森林非空,首先訪問森林中第一棵樹的根結(jié)點,然后先序遍歷第一課樹根結(jié)點的子樹森林,最后先序遍歷出第一課樹之外剩余的樹所構(gòu)成的森林。

  • 中序遍歷

    若森林非空,首先訪問森林中第一棵樹的子樹森林,然后先序遍歷第一課樹的根結(jié)點,最后中序遍歷出第一課樹之外剩余的樹所構(gòu)成的森林。

  • 3.3.6.3 樹、森林和二叉樹之間的轉(zhuǎn)化

    二叉樹與樹是一 一對應(yīng)的關(guān)系,給定一棵樹有其對應(yīng)的唯一的二叉樹,同理,給定一個二叉樹,也有唯一對應(yīng)的樹(或森林)與之對應(yīng)。

    二叉樹與樹轉(zhuǎn)化的實質(zhì)就是,拿右指針為其兄弟,左指針為其孩子的二叉樹解釋成為一棵樹,本質(zhì)是與二叉樹差不多的,只是他的右指針不再是他的右孩子,而是他的兄弟了,所以我們在解釋的時候一定要注意他的指向含義。

    根據(jù)我們對二叉樹的定義,我們知道,任何一棵樹對應(yīng)的二叉鏈表的根節(jié)點的是沒有兄弟的,那么如果我們遇到了,根節(jié)點有兄弟的我們應(yīng)該如何理解呢?

    其實,這個時候,我們可以將森林中的各個樹的根節(jié)點,視為兄弟,這樣子,這個樹我們就可以解釋了,其實他是一個森林對應(yīng)的二叉樹。他的根節(jié)點和他的第一個孩子視為是這個森林中的第一個樹,右節(jié)點則是森林中的其他的樹,這樣子我們對根節(jié)點有兄弟的二叉樹也可以做解釋。

  • 樹轉(zhuǎn)二叉樹

  • 森林轉(zhuǎn)二叉樹

  • 二叉樹轉(zhuǎn)森林

    相當(dāng)于2的逆過程,對于每一個右節(jié)點已經(jīng)不再是右孩子,而是該結(jié)點對應(yīng)的兄弟,除此之外其余正常

  • 3.4 圖

    3.4.1圖的定義和存儲

    3.4.1.1 圖的定義

    完全圖:

    • 在無向圖中,若每對頂點之間都有一條邊相連,則稱該圖為完全圖。
    • 在有向圖中,若每對頂點之間都有2條有向邊相連,則稱該圖為完全圖。

    問題:n個頂點的無向圖和有向圖的完全圖的邊的個數(shù)為多少?

    答案:無向圖的完全圖有n(n-1)/2個邊,有向圖的完全圖有n(n-1)個邊。

    連通圖:指圖中任意兩個頂點之間都有一個路徑相連

    注意:

  • 并不是兩個點之間直接相連,而是經(jīng)過n個頂點后,A可以與B相連,則為連通。
  • 圖中的一個頂點,與剩下的所有頂點都有路徑可以連通。
  • 針對有向圖而言,有強連通(帶上方向是連通圖)和弱連通(帶上方向不是連通圖,去掉方向后是連通圖)。

    3.4.1.2 圖的轉(zhuǎn)換-無向圖轉(zhuǎn)鄰接矩陣

    用一個n階方陣R來存放圖中各結(jié)點的關(guān)聯(lián)信息,其矩陣元素R??定義為:

    無向圖轉(zhuǎn)換的鄰接矩陣為對稱矩陣。

    矩陣列和行分別表示頂點的序號,R??

    • i對應(yīng)的行序號,j對應(yīng)的列序號。例如下圖,圈出的表示 R??=1,R??=0

    • 針對出發(fā),標(biāo)記與其他列頂點的關(guān)系。例如,從第2行,標(biāo)注的是頂點2與其他頂點的關(guān)系。

    • 針對出發(fā),標(biāo)記與其他行頂點的關(guān)系。例如,從第2列,標(biāo)注的是頂點2與其他頂點的關(guān)系。

    3.4.1.3 圖的轉(zhuǎn)換-有向圖轉(zhuǎn)鄰接矩陣

    與無向圖的轉(zhuǎn)換方式相同,當(dāng)路徑上有權(quán)值的時候,在矩陣中就填入權(quán)值,否則就填入0、1。

    • 該頂點為起點的有向邊個數(shù),叫做出度。其實就是從改行的頂點出發(fā)的所有路徑。

    • 該頂點為終點的有向邊個數(shù),,叫做入度。其實就是所有指向達(dá)該頂點的路徑。

    • 出入與入度之和叫做度。

    3.4.1.4 圖的轉(zhuǎn)換-有向圖轉(zhuǎn)鄰接鏈表

    首先把每個頂點的鄰接頂點用鏈表表示,然后用一個一維數(shù)組來順序存儲上每個鏈表的表頭指針。

    3.5 算法特性與復(fù)雜度

    3.5.1 算法特性

    基本特性

    • 有窮性:執(zhí)行有窮步之后結(jié)束。

    • 確定性:算法中每一條指令都必須有確切的含義,不能含糊不清。

    • 輸入輸出數(shù)目約定:輸入(>=0)。輸出(>=1)。

    • 有效性(可行性):算法的每個步驟都有效執(zhí)行并能得到確定的結(jié)果。

      例如 a=0,b/a就是無效的。

    算法評價指標(biāo):

    • 正確性:正確實現(xiàn)算法功能,最重要的指標(biāo)。
    • 友好性:具有良好的使用性。
    • 可讀性:可讀、可以理解的,方便分析、修改和移植。
    • 健壯性:對不合理的數(shù)據(jù)或非法的操作能進行檢查、糾正。
    • 效率:對計算機資源的消耗,包括計算機內(nèi)存和運行時間的消耗。

    3.5.2 算法復(fù)雜度

    時間復(fù)雜度

    程序運行從開始到結(jié)束所需要的時間。通常分析時間復(fù)雜度的方法是從算法中選取一種對于所研究的問題來說是基本運算的操作,以該操作重復(fù)執(zhí)行的次數(shù)為算法的時間度量。一般來說,算法中原操作重復(fù)執(zhí)行的次數(shù)是規(guī)模n的某個函數(shù)T(n)。由于許多情況下要精確計算T(n)是困難的,因此引入了漸進時間復(fù)雜度在數(shù)量上估計一個算法的執(zhí)行時間。其定義如下:

    如果存在兩個常數(shù)c和m,對于所有的n,當(dāng)n>=m時由 f(n) <= g(n),則有f(n)=O(g(n))。也就是說,隨著n的增大, f(n) 逐漸也不大于 g(n) 。例如,一個程序的實際執(zhí)行時間常見的對算法執(zhí)行所需時間的度量: $$ O(1)<O(log?(n))<O(n)<O(nlog?(n))<O(n2)<O(n3)<O(2?) $$

    O(1) :一次可以執(zhí)行完,沒有循環(huán)、遞歸

    O(log?(n)):對半拆分、對半拆分。二分查找

    O(n):依次循環(huán),例如輸出數(shù)組元素

    O(nlog?(n)):歸并排序、快速排序

    O(n2):插入排序、選擇排序

    考試一般會考,對應(yīng)的查找、排序算法,它的時間復(fù)雜度。

    空間復(fù)雜度

    是指對一個算法在運行過程中臨時占用存儲空間大小的度量。一個算法的空間復(fù)雜度值考慮在運行過程中為局部變量分配的存儲空間的大小。

    3.6 查找

    3.6.1順序查找

    順序查找:將待查找的關(guān)鍵字跟表中的數(shù)據(jù)從頭至尾按順序進行比較。

    平均查找長度(等概率情況):

    3.6.2二分查找

    二分法查找(折半查找)的基本思想是:(設(shè)R[low,...,high]是當(dāng)前的查找區(qū))

  • 確定該區(qū)間的中點位置:mid = (low+high)/2。 (向下取整,例如6.5 就是6)
  • 將待查的k值與R[mid]比較,若相等,則查找成功并返回此位置,否則需要確定新的查找區(qū)間,繼續(xù)二分查找,具體方法如下(假設(shè)是從小到大):
    • 若 R[mid] > k,則由表的有序性可知,R[mid,...,high]均大于k,因此若表中存在關(guān)鍵字等于k的結(jié)點,則該結(jié)點必定是在 mid 左邊的子表R[low,...,mid-1]中。因此,新的查找區(qū)間是左子表R[low,...,high],其中high=mid-1。
    • 若 R[mid] < k,則要查找的k必定在 mid 右邊的子表R[mid+1,...,high]中。因此,新的查找區(qū)間是左子表R[low,...,high],其中l(wèi)ow=mid+1。
    • 若 R[mid] = k,則查找成功,算法結(jié)束。
  • 下次查找是針對新的查找區(qū)間進行,重復(fù)步驟1和2。
  • 在查找過程中,low逐步增加,而high逐步減少。如果 high < low ,則查找失敗,算法結(jié)束。
  • 例子,請給出在含有12個元素的有序表{1,4,10,16,17,18,23,29,33,40,50,51} 中二分查找關(guān)鍵字17的過程。

    二分查找在查找成功時關(guān)鍵字的比較次數(shù)最多為 log?(n) + 1 次

    二分查找的時間復(fù)雜度為 O(log?(n)) 次

    二分查找僅適用于元素有序的順序表

    二分查找(循環(huán)法)

    int bigSearch(int r[], int low, int high, int key){//r[low,...,high]中的元素按非遞減順序,用二分查找法在數(shù)組r中查找與key相同的元素,若找到則返回該元素在數(shù)組的下標(biāo),否則返回-1int mid;while (low<=high) {mid = (low+high)/2;if(key == r[mid]) return mid;else if(key < r[mid]) high = mid-1;else low = mid+1;}return -1; }

    非遞減即遞增的!

    二分查找(遞歸法)

    int bigSearch(int r[], int low, int high, int key){//r[low,...,high]中的元素按非遞減順序,用二分查找法在數(shù)組r中查找與key相同的元素,若找到則返回該元素在數(shù)組的下標(biāo),否則返回-1int mid;if(low<=high) {mid = (low+high)/2;if(key == r[mid]) return mid;else if(key < r[mid]) bigSearch(r,low,mid-1,key) ;else bigSearch(r,mid+1,high,key);}return -1; }

    3.6.3散列表查找

    3.6.3.1 散列表查找-線性探查法

    散列表查找的基本思想是:已知關(guān)鍵字集合U,最大關(guān)鍵字為m,設(shè)計一個函數(shù)Hash,它以關(guān)鍵字為自變量,關(guān)鍵字的存儲地址為因變量,將關(guān)鍵字映射到一個有限的、地址連續(xù)的區(qū)間 T[0,...,n-1] (n<<m) 中,這個區(qū)間就稱為散列表,散列表查找中使用的轉(zhuǎn)換函數(shù)稱為散列函數(shù)。

    關(guān)鍵碼為(3,18,26,29,17),存儲空間為10,散列函數(shù) h = key % 7

  • 存儲3, 3%7=3,存放在3的位置。
  • 存儲18, 18%7=4,存放在4的位置。
  • 存儲26, 26%7=5,存放在5的位置。
  • 存儲29, 29%7=1,存放在1的位置。
  • 存儲17, 17%7=3,本該存放在3的位置,但是3的位置已經(jīng)有值,產(chǎn)生了沖突,所以從3的后面位置(位置4)開始查找,直到有空閑的地方,即存放在位置6。
  • 3.6.3.2 散列表查找-拉鏈法

    關(guān)鍵碼為(3,18,26,29,17),散列函數(shù) h = key % 7,用鏈地址法。

    3.7 排序

    穩(wěn)定排序與不穩(wěn)定排序

    穩(wěn)定排序通俗地講就是能保證排序前2個相等的數(shù)其在序列的前后位置順序和排序后它們兩個的前后位置順序相同。在簡單形式化一下,如果Ai = Aj,Ai原來在位置前,排序后Ai還是要在Aj位置前。與之相反就是不穩(wěn)定排序

    內(nèi)排序與外排序

    內(nèi)排序是被排序的數(shù)據(jù)元素全部存放在計算機內(nèi)存中的排序算法。若待排序記錄的數(shù)量龐大,在排序的過程中需要使用到外部存儲介質(zhì)如磁盤等,這種涉及內(nèi)外存儲器數(shù)據(jù)交換的排序過程稱為外部排序,又稱為外排序

    排序方法分類:

    • 插入類排序

      直接插入排序 、希爾排序

    • 交換類排序

      冒泡排序、快速排序

    • 選擇類排序

      簡單選擇排序、堆排序

    • 歸并排序

    • 基數(shù)排序

    3.7.1 插入類排序-直接插入排序

    即當(dāng)插入第i個記錄時,R?,R?,...,R??? 均已排好序,因此,將第i個記錄 R? 依次與 R???,...,R?,R? 進行比較,找到合適的位置插入。它簡單明了,但速度很慢。

    元 素 :16 3 25 11 17

    第一趟:16 3 25 11 17

    第二趟:3 16 25 11 17

    第三趟:3 16 25 11 17

    第四趟:3 11 16 25 17

    結(jié) 果 :3 11 16 17 25

    void insertSort(int data[], int n) {/**用直接插入排序法將data[0]~data[n-1]中的n個整數(shù)進行升序排列*/int i,j; int tmp;for(i=1; i<n; i++) {if(data[i] < data[i-1]) { // 將data[i]插入有序子序列data[0]~data[i-1]tmp = data[i]; // 備份待插入的元素data[i] = data[i-1];for(j=i-2; j>=0&&data[j]>tmp; j--)//查找插入位置并將元素后移data[j+1]=data[j];data[j+1]=tmp;//插入正確位置}} }

    時間復(fù)雜度 O(n2)

    穩(wěn)定性穩(wěn)定的

    空間復(fù)雜度 O(1)

    使用的元素基本有序且與算法的排序方向一致,它會非常快速。

    3.7.2 插入類排序-希爾排序

    先取一個小于n的整數(shù) d? 作為第一個增量,把文件的全部記錄分成 d? 個組。所有距離 d? 的倍數(shù)的記錄放在同一個組中。先在各組內(nèi)進行直接插入排序;然后,取第二個增量 d? < d? 重復(fù)上述的分組和排序,直至所取的增量 d? = 1(d? <d??? <O<d? < d?),即所有記錄放在同一組中進行直接插入排序為止。該方法實質(zhì)行是一種分組插入方法

    在此我們選擇增量gap=length/2,縮小增量繼續(xù)以gap = gap/2的方式,這種增量選擇我們可以用一個序列來表示,{n/2,(n/2)/2...1},稱為增量序列。希爾排序的增量序列的選擇與證明是個數(shù)學(xué)難題,我們選擇的這個增量序列是比較常用的,也是希爾建議的增量,稱為希爾增量,但其實這個增量序列不是最優(yōu)的。此處我們做示例使用希爾增量。

    void hearSort(int data[], int len) {int i,j,k,tmp,gap;//gap為希爾增量for(gap=len/2; gap>0; gap/=2) {for(i=0;i<gap;i++) {// 變量 i 為每次分組的第一個元素下標(biāo)for(j=i+gap; j<len; j+=gap) { //對步長為gap的元素進行直插排序,當(dāng)gap為1時,就是直插排序tmp = data[j];// 備份 data[j] 的值for(k=j-gap; k>=0&&data[k]>tmp;k-=gap) {data[k+gap]=data[k];}data[k+gap]=tmp;//將其插入正確的位置}}} }

    時間復(fù)雜度:O(n2)

    穩(wěn)定性:不穩(wěn)定

    空間復(fù)雜度:O(1)

    3.7.3 選擇類排序-直接選擇排序

    過程:首先在所有記錄中選出排序碼最小的記錄,把它與第1個記錄交換,然后再其余的記錄內(nèi)選出排序碼最小的記錄,與第二個記錄交換......依次類推,直到所有記錄排完為止。

    void selectSort(int data[], int n) {int i,j,k;int temp;for(i=0; i<n-1; i++) {for(k=i,j=i+1; j<n;j++) { //k表示data[i]~data[n-1]中最小元素的下標(biāo)if(data[j]<data[k]) k=j;}if(k!=i) { //將本趟找出的最小元素與data[i]交換temp = data[i];data[i] = data[k];data[k] = temp;}} }

    時間復(fù)雜度:O(n2)

    穩(wěn)定性:不穩(wěn)定

    空間復(fù)雜度:O(1)

    3.7.4 選擇類排序-堆排序

    堆是具有以下性質(zhì)的完全二叉樹:每個結(jié)點的值都大于或等于其左右孩子結(jié)點的值,稱為大頂堆;或者每個結(jié)點的值都小于或等于其左右孩子結(jié)點的值,稱為小頂堆。如下圖:

    同時,我們對堆中的結(jié)點按層進行編號,將這種邏輯結(jié)構(gòu)映射到數(shù)組中就是下面這個樣子

    該數(shù)組從邏輯上講就是一個堆結(jié)構(gòu),我們用簡單的公式來描述一下堆的定義就是:

    大頂堆:arr[i] >= arr[2i+1] && arr[i] >= arr[2i+2]

    小頂堆:arr[i] <= arr[2i+1] && arr[i] <= arr[2i+2]

    堆排序的基本思想是:將待排序序列構(gòu)造成一個大頂堆,此時,整個序列的最大值就是堆頂?shù)母?jié)點。將其與末尾元素進行交換,此時末尾就為最大值。然后將剩余n-1個元素重新構(gòu)造成一個堆,這樣會得到n個元素的次小值。如此反復(fù)執(zhí)行,便能得到一個有序序列了。

    步驟:

  • 構(gòu)造初始堆。將給定無序序列構(gòu)造程一個大頂堆(一般升序采用大頂堆,降序采用小頂堆)

    假設(shè)給定無序序列結(jié)構(gòu)如下

  • 此時我們從最后一個非葉子結(jié)點開始(葉結(jié)點自然不用調(diào)整,第一個非葉子結(jié)點 arr.length/2-1=5/2-1=1,也就是下面的6結(jié)點),從左至右,從下至上進行調(diào)整。

  • 找到第二個非葉節(jié)點4,由于[4,9,8]中9元素最大,4和9交換。

  • 這時,交換導(dǎo)致了子根[4,5,6]結(jié)構(gòu)混亂,繼續(xù)調(diào)整,[4,5,6]中6最大,交換4和6。

  • 此時,我們就將一個無需序列構(gòu)造成了一個大頂堆

  • 將堆頂元素與末尾元素進行交換,使末尾元素最大。然后繼續(xù)調(diào)整堆,再將堆頂元素與末尾元素交換,得到第二大元素。如此反復(fù)進行交換、重建、交換。

  • 將堆頂元素9和末尾元素4進行交換

  • 重新調(diào)整結(jié)構(gòu),使其繼續(xù)滿足堆定義

  • 再將堆頂元素8與末尾元素5進行交換,得到第二大元素8.

  • 后續(xù)過程,繼續(xù)進行調(diào)整,交換,如此反復(fù)進行,最終使得整個序列有序

  • void swap(int arr[], int a, int b) {int tmp = arr[a];arr[a] = arr[b];arr[b] = tmp;}/*** 調(diào)整大頂堆(僅是調(diào)整過程,建立在大頂堆已構(gòu)建的基礎(chǔ)上)*/void adjustHeap(int []arr,int i,int length){int temp = arr[i];//先取出當(dāng)前元素ifor(int k=i*2+1;k<length;k=k*2+1){//從i結(jié)點的左子結(jié)點開始,也就是2i+1處開始if(k+1<length && arr[k]<arr[k+1]){//如果左子結(jié)點小于右子結(jié)點,k指向右子結(jié)點k++;}if(arr[k] >temp){//如果子節(jié)點大于父節(jié)點,將子節(jié)點值賦給父節(jié)點(不用進行交換)arr[i] = arr[k]; i = k;}else{break;}}arr[i] = temp;//將temp值放到最終的位置}void heapSort(int[] arr, int length){//1.構(gòu)建大頂堆for(int i=length/2-1;i>=0;i--){adjustHeap(arr,i,length);//從第一個非葉子結(jié)點從下至上,從右至左調(diào)整結(jié)構(gòu)}//2.調(diào)整堆結(jié)構(gòu)+交換堆頂元素與末尾元素for(int j=length-1;j>0;j--){swap(arr,0,j);//將堆頂元素與末尾元素進行交換adjustHeap(arr,0,j);//重新對堆進行調(diào)整}}

    堆排序的時間復(fù)雜度為:O(nlog?(n))

    穩(wěn)定性:不穩(wěn)定

    空間復(fù)雜度:O(1)

    3.7.5 交換類排序-冒泡排序

    冒泡排序的基本思想是,通過相鄰元素之間的比較和交換,將排序較小的元素逐漸從底部移向頂部。由于整個排序的過程就像水底下的氣泡一樣逐漸向上冒,因此稱為冒泡算法。

    void bubbleSort(int arr[], int n) { //冒泡排序int temp;int i,j;for(i=0;i<n-1;i++) { //外循環(huán)排序為排序趟數(shù),n個數(shù)進行n-1趟for(j=0;j<n-1-i;j++) { //內(nèi)循環(huán)為每趟比較的次數(shù),第趟比較 n-i 次if(arr[j] > arr[j+1]) { //相鄰元素比較,若逆序則交換temp = arr[j];arr[j]=arr[j+1];arr[j+1]=temp;}}} }

    時間復(fù)雜度:O(n2)

    穩(wěn)定性:穩(wěn)定

    空間復(fù)雜度:O(1)

    3.7.6 交換類排序-快速排序

    快速排序采用的是分治法,其基本思想是將原問題分解成若干個規(guī)模更小但結(jié)構(gòu)與其原問題相似的子問題。通過遞歸解決這些子問題,然后再將這些子問題的解 組合成原問題的解。

    一般情況下它的排序速度很快,只有當(dāng)數(shù)據(jù)基本有序的時候速度是最慢的。

    快速排序包括兩個步驟:

  • 在待排序的n個記錄中任取一個記錄,以該記錄的排序碼為準(zhǔn),將所有記錄都分成兩組,第一組都小于該數(shù),第二組都大于該數(shù)。
  • 采用相同的方法對左、右兩組分別進行排序,直到所有記錄都排到相應(yīng)的位置為止。
  • 以下例子是模擬第一遍分組的過程:

    初始數(shù)組 57 68 59 52 72 28 96 33 24 19

  • 首先找到基準(zhǔn)數(shù) 57,此時l=0,h=9

  • 從右側(cè)開始, 19小于57,l(0)與h(9)處數(shù)據(jù)進行交換(數(shù)組為 19 68 59 52 72 28 96 33 24 57) 。

  • 從左側(cè)開始(l=l+1=1),68大于57停止移動,l(1)與h(9)處數(shù)據(jù)進行交換(數(shù)組為 19 57 59 52 72 28 96 33 24 68

  • 從右側(cè)開始(h=h-1=8),24小于57停止移動,l(1)與h(8)處數(shù)據(jù)進行交換(數(shù)組為19 24 59 52 72 28 96 33 57 68)

  • 從左側(cè)開始(l=l+1=2),59大于57停止移動,l(2)與h(8)處數(shù)據(jù)進行交換(數(shù)組為19 24 57 52 72 28 96 33 59 68)

  • 從右側(cè)開始(h=h-1=7),33小于57停止移動,l(2)與h(7)處數(shù)據(jù)進行交換(數(shù)組為19 24 33 52 72 28 96 57 59 68)

  • 從左側(cè)開始(l=l+1=3),52小于57繼續(xù)移動(l=l+1=4),72大于57停止移動,l(4)與h(7)處數(shù)據(jù)進行交換

    (數(shù)組為19 24 33 52 57 28 96 72 59 68)

  • 從右側(cè)開始(h=h-1=6),96大于57繼續(xù)移動(h=h-1=5),28小于57停止移動,l(4)與h(5)處數(shù)據(jù)進行交換

    (數(shù)組為19 24 33 52 28 57 96 72 59 68)

  • 從左側(cè)開始(l=l+1=5),此時發(fā)現(xiàn)l=h=5,第一次排序過程結(jié)束,數(shù)組為 19 24 33 52 28 57 96 72 59 68,57的左邊部分小于它,右邊部分大于它。接下來講左邊部分【19 24 33 52 28】和右邊部分【96 72 59 68】繼續(xù)進行排序,直到最后排序結(jié)束為止。

  • void quickSort(int a[],int n) {//快速排序int l,h;int pivot=a[0];//設(shè)置基準(zhǔn)值l=0;h=n-1;while(l<h) {while(l<h && a[h]>pivot) h--;//從右向左移動,大于基準(zhǔn)值者保持原位if(l<h) {a[l]=a[h]; l++;}while(l<h && a[l]<=pivot) l++;//從左向右移動,小于基準(zhǔn)值者保持原位if(l<h) {a[h]=a[l]; h--;}}a[l]=pivot;//基準(zhǔn)元素歸位if(l>1) quickSort(a,l); //遞歸地對左子序列進行快速排序if(n-l-1>1) quickSort(a+l+1,n-l-1); //遞歸地對右子序列進行快速排序 }

    時間復(fù)雜度:O(n2)

    穩(wěn)定性:不穩(wěn)定

    空間復(fù)雜度:O(nlog?(n))

    3.7.7歸并排序

    歸并排序(MERGE-SORT)是利用歸并的思想實現(xiàn)的排序方法,該算法采用經(jīng)典的分治(divide-and-conquer)策略(分治法將問題(divide)成一些小的問題然后遞歸求解,而**治(conquer)**的階段則將分的階段得到的各答案"修補"在一起,即分而治之)。

    分而治之

    可以看到這種結(jié)構(gòu)很像一棵完全二叉樹,本文的歸并排序我們采用遞歸去實現(xiàn)(也可采用迭代的方式去實現(xiàn))。階段可以理解為就是遞歸拆分子序列的過程,遞歸深度為log2n。

    再來看看階段,我們需要將兩個已經(jīng)有序的子序列合并成一個有序序列,比如上圖中的最后一次合并,要將[4,5,7,8]和[1,2,3,6]兩個已經(jīng)有序的子序列,合并為最終序列[1,2,3,4,5,6,7,8],來看下實現(xiàn)步驟。

    /*** 歸并排序* @param arr 數(shù)組* @param left 開始下標(biāo)* @param right 結(jié)束下標(biāo)* @param temp 臨時數(shù)組*/void mergeSort(int[] arr,int left,int right,int[] temp) {if (left<right) {//當(dāng)還能繼續(xù)分解數(shù)組時遞歸int mid=(left+right)/2;//向左進行分解-遞歸mergeSort(arr,left,mid,temp);//向右進行分解-遞歸mergeSort(arr,mid+1,right,temp);//每次分解都進行合并merge(arr,left,mid,right,temp);}}/*** 歸并排序的合并過程* @param left 左側(cè)下標(biāo)* @param mid 中間下標(biāo)* @param right 右側(cè)下標(biāo)* @param temp 臨時數(shù)組*/void merge(int[] arr,int left,int mid,int right,int[] temp) {int i=left; //左邊數(shù)組的初始索引值int j=mid+1; //右邊數(shù)組的初始索引值int t=0; //臨時數(shù)組的索引值int tempLeft;//1、通過比較左右兩邊的數(shù)組將數(shù)值有序放入臨時數(shù)組while (i<=mid && j<=right) {if (arr[i]<=arr[j]) {temp[t++]=arr[i++];} else {temp[t++]=arr[j++];}}//2、將左邊數(shù)組和右邊數(shù)組剩余的元素遷移到while (i<=mid) { temp[t++]=arr[i++]; }while (j<=right) { temp[t++]=arr[j++]; }//3、最后將臨時數(shù)組遷移到arr中t=0; tempLeft=left;while (tempLeft<=right) {arr[tempLeft++]=temp[t++];}}

    3.7.8基數(shù)排序

    基數(shù)排序介紹

    • 基數(shù)排序?qū)儆?#34;分配式排序",又稱"桶子發(fā)",顧名思義,它是通過鍵值的各個位的值,將要排序的元素分配到某些桶中,達(dá)到排序的作用
    • 基數(shù)排序法是屬于穩(wěn)定性的排序,基數(shù)排序法是效率高的穩(wěn)定性 排序法
    • 基數(shù)排序的實現(xiàn)方式是:將整數(shù)按位數(shù)切割成不同的數(shù)字,然后按照每個位數(shù)分別比較

    基數(shù)排序的思想

    將所有待比較數(shù)值統(tǒng)一為同樣的數(shù)位長度,數(shù)位較短的數(shù)前面補零.然后,從最低位開始,依次進行一次排序.這樣從最低位排序一直到最高位排序完成以后,數(shù)列就變成一個有序序列.

    基數(shù)排序圖文說明

    將數(shù)組{53,3,524,748,14,214} 使用基數(shù)排序,進行升序排序

    void radixSort(int[] array, int length){int i,j,l,tmp;int maxNumLength=1;//記錄數(shù)據(jù)的最大位數(shù)為幾位,比如25 2位,130 3位int n=1;//代表位數(shù)對應(yīng)的數(shù):1,10,100...int k=0;//保存每一位排序后的結(jié)果用于下一位的排序輸入int[][] bucket=new int[10][length];//排序桶用于保存每次排序后的結(jié)果,這一位上排序結(jié)果相同的數(shù)字放在同一個桶里int[] order=new int[10];//用于保存每個桶里有多少個數(shù)字int max;//最大值//獲取到待排序數(shù)中的最大值的位數(shù)max = array[0];for (i = 0; i < length; i++) {if (array[i] > max) { max = array[i]; }}while(max/10>0) {maxNumLength++;max = max/10;}//基數(shù)排序n=1;for(l=0;l<maxNumLength;l++) {//裝桶for(i=0;i<length;i++) {tmp=(array[i]/n)%10;bucket[tmp][order[tmp]]=array[i];order[tmp]++;}n*=10;//從桶中取出數(shù)據(jù)for(i=0;i<10;i++) {if(order[i]>0) {for(j=0;j<order[i];j++) {array[k++]=bucket[i][j];}}order[i]=0;//將桶里計數(shù)器置0,用于下一次位排序}k=0;//將k置0,用于下一輪保存位排序結(jié)果}}

    3.7.9 排序總結(jié)

    3.8 錯誤習(xí)題集

    題2

    給定一個有n個元素的有序線性表。若采用順序存儲結(jié)構(gòu),則在等概率前提下,刪除其中的一個元素平均需要移動()個元素。

    A.(n+1)/2

    B.n/2

    C.(n-1)/2

    D.1

    答案 C

    題目要求計算進行刪除時平均移動元素個數(shù),如 a、b、c、d、e、f ,若要刪除f,則無需一定任何元素,直接刪除即可;若要刪除e,則需移動一個元素,即把f移至e位置;若要刪除d,則需移動2個元素,把把e移至d位置,f移至e位置;依次類推,要刪除第一個元素,則要移動n-1個元素。

    y偶遇每個元素被刪除的概率是相等的,所以平均需要移動的元素個數(shù)為 ( 0+(n-1) )/2 = (n-1)/2。

    (最小數(shù)+最大數(shù))/2為平均數(shù)。

    題3

    下來敘述中,不正確的是()。

    A.線性表在鏈?zhǔn)酱鎯r,查找第i個元素的時間與i的值成正比。

    B.線性表在鏈?zhǔn)酱鎯r,查找第i個元素的時間與i的值有關(guān)。

    C.線性表在順序存儲時,查找第i個元素的時間與i的值成正比。

    D.線性表在順序存儲時,查找第i個元素的時間與i的值無關(guān)。

    答案 C

    順序存儲結(jié)構(gòu)的特點是“順序存儲,隨機存取”,也就是說線性表在順序存儲時,查找第i個元素的時間與i的值無關(guān)。

    鏈?zhǔn)酱鎯Y(jié)構(gòu)的特點是“隨機存儲,順序存取”,也就是說鏈?zhǔn)酱鎯Y(jié)構(gòu)的數(shù)據(jù)元素可以隨機地存儲在內(nèi)存單元,但訪問其任意一個數(shù)據(jù)元素時,都必須從其頭指針開始逐個進行訪問。

    題8

    在一顆度為4的樹T中,若有20個度為4的結(jié)點,10個度為3的結(jié)點,1個度為2的結(jié)點,10個度為1的結(jié)點,則樹T的葉子節(jié)點的個數(shù)是()。

    A.41

    B.82

    C.113

    D.122

    答案B

    在樹中,除根節(jié)點外,其余所有結(jié)點都是由其雙親節(jié)點引出的。一個度為n的節(jié)點表示由該結(jié)點引出n個孩子節(jié)點,因此樹T的結(jié)點個為20*4+10*3+1*2+10*1+1= 123 ,其中最后的1位根節(jié)點,則葉子結(jié)點的個數(shù)為 123-(20-10-1-10)=82個。

    題11

    在查找算法中,可用平均查找長度(記為ASL)來衡量一個查找算法的優(yōu)劣,其定義為:

    此處P?為查找表中第i個記錄的概率,C?為查找第i個記錄時同關(guān)鍵字比較次數(shù),n為表中記錄數(shù)。

    以下敘述中均假定每個記錄被查找的概率相等,即 p?=1/n (i=1,2,...,n)。當(dāng)表中的記錄連續(xù)有序存儲在一個一維數(shù)組中時,采用順序查找與折半查找方法查找的ASL值分別是()。

    A.O(n),O(n)

    B.O(n),O(lbn)

    C.O(lbn),O(n)

    D.O(lbn),O(lbn)

    答案 B

    順序查找的基本思想是從表的一端開始,順序掃描線性表,依次將掃描到的節(jié)點關(guān)鍵字和給定值k相比較。若當(dāng)前掃描到的節(jié)點關(guān)鍵字與k相等,則查找成功;若掃描結(jié)束后,仍未找到關(guān)鍵字等于k的結(jié)點,則查找失敗。順序查找方法既適用于線性表的順序存儲結(jié)構(gòu),也適用于線性表的鏈?zhǔn)酱鎯Y(jié)構(gòu)。

    成功的順序查找的平均查找長度如下: $$ ASL=np? + (n-1)p? + ... + 2p??? + p? $$ 在等概率情況下,P?=1/n(1≤i≤n),故成功的平均查找長度為(n+...+2+1)= (n+1)/2,即查找成功時的平均比較次數(shù)為表長的一半。若k值不在表中,則需要進行n+1次比較之后才能確定查找失敗。查找的時間復(fù)雜度的為 O(n)。

    若事先知道表中個結(jié)點的查找概率不相等,以及它們的分布情況,則應(yīng)將表中結(jié)點查找概率由小到大的順序存放,以便提高順序查找的效率。

    順序查找的優(yōu)點是算法簡單,且對表的結(jié)構(gòu)無任何要求,無論是用向量還是用鏈表來存放節(jié)點,也無論節(jié)點之間是否按關(guān)鍵字有序,它都同樣使用。其缺點是查找效率低,因此當(dāng)n較大時不宜采用順序查找。

    二分查找又稱這版查找,是一種效率較高的查找方法。二分查找要求線性表是有序表,即表中結(jié)點按關(guān)鍵字有序,并且要求用向量作為表的存儲結(jié)構(gòu)。

    二分查找的基本思想是(設(shè)R[low,...,high])是當(dāng)前的查找區(qū)間:

  • 確定該區(qū)間的中點位置:mid=(low+high)/2
  • 將待查的k值與 R[mid].key 比較,若相等,則查找成功并返回次位置,否則需要確定新的查找區(qū)間,繼續(xù)二分查找,具體方法如下:
    • 若R[mid].key > k,則表的有序性可知 R[mid,...,high]均大于k,因此若表中存在關(guān)鍵字等于k的節(jié)點,則該在R[low,...,mid-1]中。因此,新的查找區(qū)間是在左子表R[low,...,high],其中,high=mid-1
    • 若R[mid].key < k,則k在R[mid+1,...,high]中,即新的查找區(qū)間是在左子表R[low,...,high],其中,low=mid+1
    • 若R[mid].key = k,則查找成功,算法結(jié)束。
  • 下一次查找針對新的查找區(qū)間進行,重復(fù) 1、2步驟
  • 在查找過程中,low逐步增加,high逐步減少。如果 high<low,則查找失敗,算法結(jié)束。
  • 因此,從初始的查找區(qū)間R[1,...,n]開始,每經(jīng)過一次與當(dāng)前查找區(qū)間中點位置上結(jié)點關(guān)鍵字的比較,就可確定是否成功,不成功則當(dāng)前的區(qū)間就縮小一半。重復(fù)這一過程,知道找到關(guān)鍵字為k的節(jié)點,或直至當(dāng)前的查找區(qū)間為空時為止。查找的時間復(fù)雜度為:O(log?n).

    因此,答案是 B

    題12★

    根據(jù)使用頻率,為5個字符設(shè)計哈夫曼編碼不可能是()。

    A.111,110,10,01,00

    B.000,001,010,011,1

    C.001,000,10,01,11

    D.110,100,101,11,1

    答案 D

    哈夫曼編碼屬于前綴編碼,根據(jù)前綴編碼的定義,任一字符的編碼都不是另一字符編碼的前綴。而在選項D中,1是前面4個字符的前綴,明顯違反了這一原則,所以不屬于哈夫曼編碼。

    題13

    二叉樹在線索化后,仍不能有效解決的問題是()。

    A.先序線索二叉樹中求先序后繼

    B.中序線索二叉樹中求中序后繼

    C.中序線索二叉樹中求中序前驅(qū)

    D.后序線索二叉樹中求后序后繼

    答案 D

    在中序線索二叉樹中,查找結(jié)點P的中序后繼分為以下兩種情況。

  • 若結(jié)點P的右子樹為空,則直接得到中序后繼。
  • 若結(jié)點P的右子樹非空,則中序后繼是P的右子樹中最左下的結(jié)點。
  • 在中序線索二叉樹中,查找結(jié)點p的前驅(qū)結(jié)點也有兩種情況

  • 若結(jié)點P的左子樹為空,則直接得到中序前驅(qū)。
  • 若結(jié)點P的左子樹非空,則中序前驅(qū)是P的左子樹中最右下的結(jié)點。
  • 因此,在中序線索二叉樹中,查找中序前驅(qū)中序后繼都可以有效的解決。

    先序線索二叉樹中,查找結(jié)點先序后繼很簡單,僅從P出發(fā)就可以找到,但是找其先序前驅(qū)必須要知道 P 的雙親結(jié)點

    后序線索二叉樹中,查找結(jié)點后序前驅(qū)很簡單,僅從P出發(fā)就可以找到,但是找其后序后繼必須要知道 P 的雙親結(jié)點

    題14

    由元素序列(27,16,75,38,51)構(gòu)造平衡二叉樹,則首次出現(xiàn)的最小不平衡子樹的根(即離插入結(jié)點最近且平衡因子的絕對值為2的結(jié)點)為()。

    A.27

    B.38

    C.51

    D.75

    答案 D

    平衡二叉樹的構(gòu)造過程如圖:

    根據(jù)題目要求,首次出現(xiàn)最小不平衡子樹的根是 75.

    題15

    若 G 是一個具有36條邊的非連通無向圖(不含自回路和多重邊),則圖G至少有()個頂點。

    A.11

    B.10

    C.9

    D.8

    答案 B

    因G為非連通圖,所以G中至少含有兩個連通子圖,而且該圖不含有回路和多重邊。題目問的是至少有多少個頂點,因此一個連通圖可以看成是只有1個頂點,另一個連通圖可以看成是一個完全圖(因為完全圖在最小頂點的情況下能得到的邊數(shù)最多),這樣該題就轉(zhuǎn)化為“36條邊的完全圖有多少個頂點”,因為具有n個頂點的無向完全圖的邊條數(shù)為 n*(n-1)/2,可以算出 n=9 滿足條件。在加上一個連通圖(只有一個頂點),則圖G至少有10個頂點。

    題16

    有向圖 的所有拓?fù)渑判蛐蛄杏?() 個。

    A.2

    B.4

    C.6

    D.7

    答案 A

    拓?fù)渑判蚴菍OV網(wǎng)中所有頂點排成一個線性序列的過程,并且該列滿足:若在AOV網(wǎng)中從頂點v? 到v?有一條路徑,則在該線性序列中,頂點v?必在頂點v?之前。

    對AOV網(wǎng)進行拓?fù)渑判虻姆椒ㄈ缦?#xff1a;

  • 在AOV網(wǎng)中選擇一個入度為0的頂點,且輸出它
  • 從網(wǎng)中刪除該頂點即該頂點有關(guān)的所有弧。
  • 重復(fù)上述兩步,知道網(wǎng)中不存在入度為0的頂點為止。
  • 本題中 A必須是第一個元素,E必須是最后一個元素,D必須是倒數(shù)第二個元素,即序列 A**DE,其中*為B或C,所以共兩種拓?fù)渑判蛐蛄小?/p>

    2中情況的拓?fù)渑判蜻^程如下:

    題19

    用插入排序和歸并排序算法對數(shù)組<3,1,4,1,5,9,6,5>進行從小到大排序,則分別需要進行()次數(shù)組元素之間的比較。

    A.12,14

    B.10,14

    C.12,16

    D.10,16

    答案 A

    插入排序是逐個將待排序元素插入到已排序的有序表中。用插入排序算法對數(shù)組<3,1,4,1,5,9,6,5>進行排序的過程:

    • 原元素序列:監(jiān)視哨(3),1,4,1,5,9,6,5
    • 第一趟排序:3 (1,3),4,1,5,9,6,5 3插入時與1比較1次
    • 第二趟排序:4 (1,3,4),1,5,9,6,5 4插入時與3比較1次
    • 第三趟排序:1 (1,1,3,4),5,9,6,5 1插入時比較3次
    • 第四趟排序:5 (1,1,3,4,5),9,6,5 5插入時與4比較1次
    • 第五趟排序:9 (1,1,3,4,5,9),6,5 9插入時與5比較1次
    • 第六趟排序:6 (1,1,3,4,5,6,9),5 6插入時比較2次
    • 第七趟排序:5 (1,1,3,4,5,5,6,9) 5插入時比較3次

    整個排序的比較次數(shù) 1+1+3+1+1+2+3 = 12

    歸并排序的思想是將兩個相鄰的有序子序列歸并為一個序列,然后再將新產(chǎn)生的相鄰序列進行歸并,當(dāng)只剩下一個有序序列時算法結(jié)束。那么用歸并排序算法對數(shù)組<3,1,4,1,5,9,6,5>進行排序的過程:

    • 原元素序列:3,1,4,1,5,9,6,5
    • 第一趟排序:[1,3] [1,4] [5,9] [5,6] 比較4次
    • 第二趟排序:[1,1,3,4] [5,5,6,9] 前半部分比較3次,后半部分比較3次
    • 第三趟排序:[1,1,3,4,5,5,6,9] 5分別與 1、2、3、4比較一次

    整個排序過程需要比較的次數(shù)為 4+3+3+4 = 14

    題20

    遞歸算法的執(zhí)行過程,一般來說,可先后分成()兩個階段。

    A.試探和回歸

    B.遞推和回歸

    C.試探和返回

    D.遞推和返回

    答案 B

    遞歸算法的執(zhí)行過程分為遞推回歸兩個階段。在遞推階段,把較復(fù)雜的問題(規(guī)模為n)的求解推到比原問題簡單一些的問題(規(guī)模小于n)的求解。

    在回歸階段,當(dāng)獲得最簡單的情況后,逐級返回,依次得到稍復(fù)雜問題的解。

    下面列舉一個經(jīng)典的遞歸算法的例子——菲波那切數(shù)列問題來說明這一過程。

    菲波那切數(shù)列為:0,1,1,2,3,...,即

    fib(0)=0;

    fib(1)=1;

    fib(n)=fib(n-1) + fib(n-2) (當(dāng)n>1時)

    寫成遞歸函數(shù)有:

    int fib(int n) {if(n==0) return 0;if(n==1) return 1;if(n>1) return fib(n-1) + fib(n-2); }

    這個例子的遞推過程為:求解 fib(n) ,把它分解到 fib(n-1) + fib(n-2) 。也就是說,為計算 f(n),必須先計算 fib(n-1) 和 fib(n-2) ,而計算 fib(n-1) 和 fib(n-2) 又必須先計算 fib(n-3) 和 fib(n-4)。依次類推,直至計算 fib(1) 和 fib(0),分別能立即得到結(jié)果 1 和 0。在遞推階段,必須要有終止遞歸的條件。例如在 fib(n) 中,當(dāng) n 為 1和0的情況。

    回歸過程:得到 fib(1) 和 fib(0) 后,返回得到 fib(2) 的結(jié)果......在得到了 fib(n-1) 和 fib(n-2) 的結(jié)果后,返回得到 fib(n) 的結(jié)果。

    題23

    若循環(huán)隊列以數(shù)組 Q[0,...,m-1]作為其存儲結(jié)構(gòu),變量rear表示循環(huán)隊列中隊尾元素的實際位置,其移動按 read = (rear+1) mod m 進行,變量 length 表示當(dāng)前循環(huán)隊列中元素的個數(shù),則循環(huán)隊列的隊首元素的實際位置是()。

    A. rear - length

    B.(rear - length + m) mod m

    C.(1 + rear + m - length) mod m

    D.m - length

    答案 C

    其實這種題目在考場上最好的解題方法是找一個實際的例子,往里面一套便知道了。下面理解以下原理。因為 rear 表示的是隊尾元素的實際位置(注意,不是隊尾指針)。而且題中有“移動按 read = (rear+1) mod m 進行”,這說明:隊列存放元素的順序為 Q[1],Q[2],...,Q[0]。所以在理想情況下 rear - length +1 能算出隊首元素的位置,即當(dāng) m=8, rear=5, length=2 時, rear - length +1 = 4,4就是正確的隊首元素實際位置。但 rear - length +1 有一種情況無法處理,即當(dāng) m=8, read=1, length=5 時無法計算出。

    所以在 rear + 1 -length 的基礎(chǔ)上加上 m 再與 m 求模,以此方法來計算。

    題25

    若廣義表 L=( (a,b,c),e ),則L的長度和深度分別為()。

    A.2和1

    B.2和2

    C.4和2

    D.4和1

    答案 B

    廣義表記作 LS=(a1,a2,...,an)其中LS是廣義表名,n是它的長度,所以本表的長度為2.而廣義表中嵌套括號的層數(shù)為其深度,所以L的深度為2.

    題27

    已知一個線性表(38,25,74,63,52,48),假定采用散列函數(shù) h(key) = key % 7 計算散列地址,并散列存儲在散列表 A[0,...,6] 中,若采用線性探測方法來解決沖突,則在該散列表上進行等概率成功查找的平均查找長度為()。

    A.1.5

    B.1.7

    C.2.0

    D.2.3

    答案 C

    要計算散列表上的平均查找長度,首先必須知道再建立散列表時,每個數(shù)據(jù)存儲時進行了幾次散列。這樣就知道哪一個元素的查找長度是多少。散列表的填表過程如下:

  • 首先存入第一個元素 38,由于 h(38) = 38 % 7 = 3,又因為 3號單元現(xiàn)在沒有數(shù)據(jù),所以把38存入3號單元

    0123456
    38
  • 接著存入25,由于 h(25) = 25 % 7 = 4,又因為 4號單元現(xiàn)在沒有數(shù)據(jù),所以把25存入4號單元

    0123456
    3825
  • 接著存入 74,由于 h(74) = 74 % 7 = 4,此時4號單元已被25占據(jù),所以進行線性再散列,線性再散列公式為 H? = (h(key)+d? ) % m,其中 d? 為 1,2,3,4,...,所以 H? = (4 + 1) % 7 = 5,此時 單元5沒有數(shù)據(jù),所以把74存入到5號單元

    0123456
    382574
  • 接著存入 63,由于 h(63) = 63 % 7 = 0,又因為 0號單元現(xiàn)在沒有數(shù)據(jù),所以把63存入0號單元

    0123456
    63382574
  • 接著存入 52,由于 h(52) = 52 % 7 = 3,此時3號單元已被38占據(jù),所以進行線性散列 H? = (3+1)%7 = 4,但4號單元也被占據(jù)了,所以再次散列 H? = (3+2)%7 = 5,但5號單元也被占據(jù)了,所以再次散列 H? = (3+3)%7 = 6,6號單元為空,所以把52存入6號單元

    0123456
    6338257452
  • 接著存入 48,由于 h(48) = 48 % 7 = 6,此時6號單元已被占據(jù),所以進行線性再散列 H? = (6+1)%7 = 0,但0號單元也被占據(jù)了,所以再次散列 H? = (6+2)%7 = 1,1號單元為空,所以把48存入1號單元

    0123456
    634838257452
  • 如果一個元素進行了N次散列,相應(yīng)的查找次數(shù)也是N,所以 38,25,63 這三個元素查找長度為1,74查找長度為2,48查找長度為3,52查找長度為4,平均查找長度為 (1+1+1+2+3+4)/6 = 2。

    題28

    設(shè)某算法的計算時間可用遞推關(guān)系式 T(n) = 2T(n/2) + n 表示,則該算法的時間復(fù)雜度為()。

    A. O(lgn)

    B. O(nlgn)

    C. O(n)

    D. O(n2)

    答案 B

    遞推關(guān)系式 T(n) = 2T(n/2) + n 其實是在給n個元素進行快速排序時最好情況(每次分割都恰好將記錄分為兩個長度相等的子序列)下的時間遞推關(guān)系式,其中T(n/2) 是一個子表需要的處理時間,n為當(dāng)次分割需要的時間。注意,這里實際上是用比較次數(shù)來度量時間。可以對此表達(dá)式進行變形得

    ? T(n)/n - (2/n)*T(n/2) = T(n)/n - T(n/2)/(n/2) = 1

    用 n/2 代替上式中的n可得

    ? T(n/2)/(n/2) - T(n/4)/(n/4) = 1

    繼續(xù)用 n/2 代替上式中的n可得

    ? T(n/4)/(n/4) - T(n/8)/(n/8) = 1

    ? ...

    ? T(2)/2 - T(1)/1 = 1

    算法共需要進行 log?n 次分割,將上述 log?n 個式子相加,刪除互相抵消的部分,得

    ? T(n)/n - T(1)/1 = log?n,而T(1) = 1

    那么上式可轉(zhuǎn)化為

    ? T(n)/n = log?n + 1 => T(n) = nlog?n + n

    而在求時間復(fù)雜度時關(guān)注“大頭”,那么

    ? T(n) = O(n log?n) = O(n lgn)

    題29

    ()算法策略與遞歸技術(shù)的聯(lián)系最弱。

    A.分治

    B.動態(tài)規(guī)劃

    C.貪心

    D.回溯

    答案 C

    • 分治法:對于一個規(guī)模為n的問題,若該問題可以容易地解決(如說規(guī)模n較小)則直接解決;否則將其分解為k個規(guī)模較小的子問題,這些子問題互相獨立且與原問題形式相同,遞歸地解這些子問題,然后將各個子問題的解藕餅到原問題的解。
    • 動態(tài)規(guī)劃法:這種算法也用到了分治思想,它的做法是將問題實例分解為更小、相似的子問題,并存儲子問題的解而避免計算重復(fù)的子問題。
    • 貪心算法:它是一種不追求最優(yōu)解,只希望得到較為滿意解的方法。貪心算法一般可以快速得到滿意的解,因為它省去了為找到最優(yōu)解而窮盡所有可能所必須耗費的大量時間。貪心算法常以當(dāng)前情況為基礎(chǔ)做最優(yōu)選擇,而不考慮各種可能的整體情況,所以貪心法不要回溯。
    • 回溯算法(試探法):它是一種系統(tǒng)地搜索問題的解的方法。回溯算法的基本思想是:從一條路往前走,能進則進,不能進則退回來,換一條路再試。其實現(xiàn)一般要用到遞歸和堆棧。

    以上算法中的分治法和動態(tài)規(guī)劃法通常要用到回溯算法,而回溯算法又一般要用到遞歸,所以只有貪心算法與遞歸技術(shù)聯(lián)系最弱。

    題30★

    對于具有 n 個元素的一個數(shù)據(jù)序列,若只需要得到其中第k個元素之前的部分排序,最好采用()。

    A.直接插入排序

    B.希爾排序

    C.快速排序

    D.堆排序

    答案 D

    此題考察的是場景的內(nèi)部排序算法。

    • 直接插入排序的基本思想:每步將一個待排序的記錄按其排序碼值的大小,插入到前面已經(jīng)拍好的文件中的適當(dāng)位置,直到全部插入為止。
    • 希爾排序的基本思想:先取一個小n的整數(shù)d1作為第一個增量,把文件的全部記錄分成 d1 個組,所有距離為 d1 的倍數(shù)記錄放在同一個組中。先在各組內(nèi)進行直接插入排序;然后取第二個增量d2 < d1,重復(fù)上述的分組和排序,直至所有的增量 dt=1(dt < dt-1 < O < d2 < d1),即所有記錄放在同一組中進行直接插入排序為止。該方法實質(zhì)上是一種分組插入方法。
    • 直接選擇排序:首先在所有記錄中選出排序碼最小的記錄,把它與第一個記錄交換,然后在其余的記錄內(nèi)選出排序碼最小的記錄,與第2個記錄交換......依次類推,直到所有記錄排完為止。
    • 堆排序:堆排序是一種樹形選擇排序,是對直接選擇排序的有效改進。它通過建立初始堆和不斷地重建堆,逐個將排序關(guān)鍵字按順序輸出,從而達(dá)到排序的目的。(從小到大排序:大頂推,每次取出根元素放在數(shù)組最后)
    • 冒泡排序:被排序的記錄數(shù)組R[1,...,n]垂直排列,每個記錄R[i]看做是重量為 ki 的氣泡。根據(jù)輕氣泡不能在重氣泡之下的原則,從下往上掃碼數(shù)組 R ,凡掃描到違反本原則的輕氣泡,就使其向上“漂浮”。如此反復(fù)進行,知道最后任何兩個氣泡都是在輕者在上,重者在下為止。
    • 快速排序:采用了一種分治的策略,將原問題分解為若干個規(guī)模更小但結(jié)構(gòu)與原問題相似的子問題。遞歸地解這些子問題,然后將這些子問題的解組合為原問題的解。
    • 歸并排序:將兩個或兩個以上的有序子表合并程一個新的有序表,初始時,把含有n個結(jié)點的待排序序列看作由n個長度都為1的有序子表所組成,將他們依次兩兩歸并得到長度為2的若干有序子表,再對他們兩兩合并,知道得到長度為n的有序表為止,排序結(jié)束。
    • 基數(shù)排序:從低位到高位依次對待排序的關(guān)鍵碼進行分配和收集,經(jīng)過d趟分配和收集,就可以得到一個有序序列。

    了解這些算法思想后,解題就容易了。現(xiàn)在看題目具體要求,題目中“若只需得到其中第k個元素之前的部分排序”有歧義。例如,現(xiàn)在待排序列(15,8,9,2,23,69,5)。現(xiàn)在要求得到其中第三個元素之前的部分排序。

    第一種理解:得到 (15,8,9)的排序

    第二種理解:得到排序之后的序列(2,5,8,9,15,23,69)的(2,5,8,9);得到排序后第三個元素之前的部分排序,即(2,5,8)。

    但綜合題義,第一種理解可以排除。對于第二種理解,只有堆排序合適,因為希爾排序、直接插入排序和快速排序都不能實現(xiàn)部分排序。若要達(dá)到題目要求,只能把所有元素排序完成,在從結(jié)果集中把需要的數(shù)據(jù)列截取出來,這樣效率遠(yuǎn)遠(yuǎn)不及堆排序。所以本體選擇D.

    總結(jié)

    以上是生活随笔為你收集整理的软件设计师-3.数据结构与算法基础的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。

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