緒論
爲甚要學習數據結構?
首先來了解什麼是數據:
- 數據是表徵客觀事物的可記錄可識別的符號集合。
- 數據是信息處理的核心基礎。
早期計算機主要是進行數值計算。
現在的計算機主要進行 非數值計算。
這裏的非數值,包括處理字符、表格、圖像等等
它們都是具有一定結構的數據。
數據的內容存在聯繫。
因此,要想設計出高效的算法,必須要分清楚數據的的內在聯繫,合理的組織數據。
而數據結構主要研究的問題就是:如何合理的組織數據?如何有效的處理數據?
所以,學習數據結構是十分有必要的。數據結構和算法是計算機科學的基石。
數據結構研究的內容
計算機進行數值計算的研究過程
- 從具體問題中抽象出數學模型
- 分析問題並提出操作對象
- 找出操作對象之間的關係
- 用數學語言描述這種關係,並建立數學方程
- 建立求解該數學模型的算法
- 編寫程序,測試、調試程序,
計算機如何進行非數值運算
非數值運算無法用數學方程建立數學模型,而數據結構則能解決這個問題。
學生管理系統類
學生的信息,包括學生的學號、姓名、性別、籍貫、專業等。
每個學生的基本情況按照不同的順序號,依次存在學生信息表中。
每個學生的基本信息記錄按順序號排列,形成了學生信息記錄的線性序列,呈一種線性關係。
學號 | 姓名 | 性別 | 籍貫 | 專業 |
---|---|---|---|---|
1812050001 | 張三 | 男 | 河南省南陽市 | 計算機科學與技術 |
1812050002 | 李四 | 男 | 河南省開封市 | 信息安全 |
諸如此類的線性表結構還有圖書館的數目管理系統,庫房管理系統。
這類問題,計算機處理的對象是各種表,元素之間存在簡單一對一的線性關係。
因此這類問題的數學模型就是各種線性表,
可對線性表進行查找、插入、和刪除操作。
這種數學模型成爲“線性”數據結構。
人機對弈問題
人機對弈的數學模型就是如何用樹結構來表示棋盤和棋子等。
算法是博弈的規則和策略
諸如此類的結構還有計算機的文件系統,一個單位的組織機構。
這類問題中,計算機處理的對象是樹結構,元素之間是一對多的層次關係。
可對對象進行查找、插入、和刪除操作。
這類數學模型稱爲“樹”的數據結構。
最短路徑問題
最短路徑問題的數學模型是圖結構,算法是求解兩點之間的最短路徑。
諸如此類的還有網絡工程圖和網絡通信圖,
這類問題中,元素之間是多對對的網狀關係
可對對象進行查找、插入、和刪除操作。
這類數學模型稱爲圖的數據結構。
由以上三個實例,自然就可以看出,非數值計算問題的數學模型不再是數學方程,而是,線性表、樹、圖的數據結構.
簡單的講,數據結構是一門研究非數值計算程序設計中的操作對象以及這些對象之間的關係和操作的學科。數據結構是計算機專業的核心課程,分爲基礎、結構和技術三大塊。
基本概念和術語
數據
數據(Data)是客觀事物的符號表示,是所有能輸入到計算機中並能被計算機處理的符號的總稱。
數據是描述客觀事物的數值、字符以及一切能輸入到計算機且 能被處理的符號集合。
如:進行數學計算是用到的整數與實數,
文本編輯中用到的字符串,
多媒體程序處理的圖形、圖像、聲音及動畫等(通過特殊編碼定義後的數據)。
數據元素
**數據元素(Data Element)**是數據的基本單位,在計算機在通常作爲一個整體進行考慮和處理。 在某些情況下,數據元素也稱爲元素、記錄。
數據元素是組成數據的基本單位,是數據集合的個體,用學籍表裏的一條學生記錄理解,雖然學生信息中有姓名、班級多個屬性,通常作爲一個整體考慮和處理,是一個數據元。
數據項
數據項(Data Item)是組成數據元素的、有獨立含義的、不可分割的最小單位。
如學生信息表中的學號、姓名、性別等都是數據項。
數據對象
數據對象(Data Object)是性質相同的數據元素的集合,是數據的一個子集。
例如:
學生信息表也是一個數據對象,
由此可看,不論數據元素的集合是無限集(整數集),還是有限集(如字母字符集),
還是由多個數據項組成的複合數據元素(學生表)的集合,只要集合內元素性質均相同,都可稱爲一個數據對象。
數據構成
以上四種概念的關係
數據>數據對象>數據元素>數據項
- 數據子集 - - - 數據對象
- 數據個體 - - - 數據元素
數據結構
數據結構(Data Structure)是相互之間存在一種或多種特定關係的數據元素的集合。也就是帶有**“結構”的數據元素的集合。“結構”**就是數據元素之間的關係即數據的組織形式。
如學生信息表的表結構,圖書館中的圖書按照索引分類。
數據類型
數據類型(Data Type) 是高級程序設計語言中的一個基本概念,是一組性質相同的值集合以及定義其上的一組操作的集合。也可以說成是高級語言中已經實現的數據結構。
如C語言
- 基本數據類型: char int float double void
- 構造數據類型:數組、結構體、共用體
明顯或隱含地規定了在程序執行期間變量或表達式所有可能取值的範圍,以及在這個值上允許的操作。
如C語言中,整型類型的取值範圍可爲:-32767~+32768;
運算符集合爲:+、-、*、/、%。
抽象數據類型
數據的抽象抽象就是抽取實際問題的本質。
如在計算機中使用二進制來表示數據,而在彙編語言中則可以給出各種數據的十進制表示,它們是二進制的抽象。
在高級語言中,則給出了更高一級的抽象,出現了數據類型,如整形,實型,字符型等;
可以進一步利用這些類型構造出線性表、棧、隊列、樹、圖等複雜的抽象數據類型。
抽象數據類型(Abstract Data Type,ADT)
一個數學模型以及定義在該模型上的一組操作。
- 由用戶定義,從問題抽象出數據模型(邏輯結構)
- 還包括定義在數據模型上的一組抽象運算(相關操作)
- 不考慮計算機內的具體存儲結構與運算的具體實現算法
ADT定義了一個數據對象,數據對象中各個元素之間的結構關係,以及一組處理數據的操作。
ADT的特點是數據抽象與信息隱蔽
ADT包括定義和實現兩個方面,其中定義是獨立於實現的。
ADT兩個重要特徵
數據抽象
- 通過數據抽象,將一個數據對象的規格說明與其實現分離,對外提供簡潔、清晰的接口。比如:手機。
數據封裝
- 通過數據封裝,將一個數據對象的內部結構和實現細節對外屏蔽
如書庫管理
用戶通過服務界面實現查書、借書和還書,用戶只使用功能,具體處理過程對用戶是封裝的,用戶也不必關心具體書在哪個書架上的具體實現問題。
抽象數據類型的表示
可以用三元組進行表示
ADT = (D,R,P)
- D:數據對象
- R :D上的關係集
- P: D上的操作集
ADT常見的定義格式
ADT抽象數據類型名{
數據對象:<數據對象的定義>
數據關係:<數據關係的定義>
基本操作 :<基本操作的定義>
} ADT抽象數據類型名
基本操作的定義格式
基本操作名(參數表)
初始條件:〈初始條件描述〉
操作結果:〈操作結果描述〉
- 賦值參數:只爲操作提供輸入值
- 引用參數:以&開頭,除提供輸入值外,還將返回操作結果
- 初始條件:描述了操作執行前數據結構和參數應滿足的條件,若不滿足,操作失敗,並返回相應出錯信息。若初始條件爲空,則省略之。
- 操作結果:說明操作正常完成之後,數據結構的變化情況和應返回的結果。
ADT定義實例
ADT Complex {
數據對象:D={e1,e2|e1,e2均爲實數}
數據關係:R={<e1,e2>| e1是複數的實部,e2 是複數的虛部 }
基本操作:
AssignComplex(&Z,v1,v2)
操作結果:構造複數Z,其實部和虛部分別賦以參數v1和v2的值。
DestroyComplex(&Z)
操作結果: 複數Z被銷燬。
GetReal(Z,&real)
初始條件:複數已存在。
操作結果: 用real返回複數Z的實部值。
GetImag(Z,&imag)
初始條件:複數已存在。
操作結果: 用imag返回複數Z的虛部值。
Add(z1,z2,&sum)
初始條件:z1,z2是複數。
操作結果: 用sum返回兩個複數z1,z2的和值
} ADT Complex
ADT Circle {
數據對象:D={r,x,y| r,x,y 均爲實數}
數據關係:R={< r,x,y >| r是半徑,<x,y>是圓心座標 }
基本操作:
Circle(&C,r,x,y)
操作結果:構造一個圓。
double Area(C)
初始條件:圓已存在。
操作結果:計算面積。
double Circumference (C)
初始條件:圓已存在。
操作結果: 計算周長。
……
} ADT Circle
類比接口的定義
接口:裏面內容都是抽象的,怎麼實現取決於具體應用
抽象數據類型的實現
ADT實現的含義
- 將ADT轉換成程序設計語言的說明語句,加上對應於該ADT中的每個操作的函數。
- 用適當的數據結構來表示ADT中的數學模型,並用一組函數來實現該模型上的各種操作。
抽象數據類型如何實現
抽象數據類型可以通過固有的數據類型(如整型、實型、字符型等)來表示和實現。即利用處理器中已存在的數據類型來說明新的結構,用已經實現的操作來組合新的操作。
數據結構的內容
邏輯結構
定義
數據的邏輯結構是指數據元素之間的邏輯關係的描述。
形式化描述
數據結構是一個二元組Data_Structure=(D,R)
其中D指數據元素的有限集,R是D上關係的有限集。
四類基本的邏輯結構
集合結構
數據元素之間除了“屬於同一集合外”的關係外,別無其他關係。
例如:確定一名學生是否爲班級成員,只需將班級看作爲一個集合結構。
線性結構
數據元素之間存在一對一的關係。
如將學生入學信息按照入學報道的時間順序進行排列,將組成一個線性結構。
樹形結構
數據元素之間存在一對多的關係。
如,在班級的管理體系中,班長管理多個組長,每位組長管理多名組員,從而形成樹狀結構。
圖結構或網狀結構
數據元素之間存在多對多的關係。
例如,多爲同學之間的朋友關係,任何兩位同學都可以是朋友,從而構成圖結構或網狀結構。
線性結構與否
線性結構
- 線性表(典型的線性結構,如學生基本信息表)
- 棧和隊列(具有特殊限制的線性表,數據操作只能在表的一端或兩端進行)
- 字符串(特殊的線性表,特殊性表現在他的數據元素僅有一個字符組成)
- 數組(是線性表的推廣,他的數據元素是一個線性表)
- 廣義表(線性表的推廣,他的數據元素是一個線性表,但不同構,即或者是單元素,或者是線性表)
非線性結構
- 樹(具有多個分支的層次結構)
- 二叉樹(具有兩個分支的層次結構)
- 有向圖(一種圖結構,邊是頂點的有序對)
- 無向圖(一種圖結構,邊是頂點的無序對)
邏輯結構圖示
存儲結構
定義
數據對象在計算機中的存儲表示稱爲存儲結構。
存儲結構又稱物理結構。
把數據對象存儲到計算機時,既要存儲各數據元素的數據,又要存儲各數據元素之間的關係。
是邏輯結構在計算機中的存儲映像,是邏輯結構在計算機中的實現。包含數據元素的表示和關係的表示。
形式化描述
D要存入計算機中,建立一從D的數據元素到存儲空間M單元的映像S,D–>M,即對每一個d,d屬於D,都有唯一的z屬於M使S(D)=Z,同時這個映像必須明顯或隱含的顯示關係R。
邏輯結構與存儲結構的關係
邏輯結構是邏輯關係的映像和元素本身的映像。
邏輯結構是數據結構的抽象,存儲結構是數據結構的實現。
兩者綜合起來建立了數據元素之間的結構關係。
數據元素之間的關係在計算機中的表示方法。
-
順序映像(順序存儲結構)
-
非順序結構(非順序存儲結構)
順序存儲結構
順序存儲結構是藉助元素在存儲器的相對位置來表示數據元素之間的元素關係。
通常藉助程序設計語言中的數組來描述。
Lo(元素i)=L0+(i-1)*m
鏈式存儲結構
順序存儲結構要求各數據元素依次存放在一片連續的存儲空間,而鏈式存儲結構則沒有這樣要求。它無需佔用一整塊的存儲空間。它在每個節點後面附加指針字段,用於存放後續的數據元素的存儲地址。通常藉助程序設計語言的指針來描述。
運算集合
討論數據的目的是爲了在計算機中實現操作,因此在結構上運算集合是很重要的一部分。數據結構就是研究一類數據的表示及其相關的運算操作。
數據的運算
在數據的邏輯結構上定義操作算法,而在存儲結構上進行實現。
最常用的5種運算:
- 插入、刪除、修改、查找、排序
邏輯結構和存儲結構相同, 但運算不同, 則數據結構不同。例如, 棧與隊列
數據結構的精確定義
按照一定的邏輯關係組織起的數據;
按照一定的映像關係 ,存放在計算機中;
並在其上定義一個運算集合。
(1)數據的邏輯結構是數據的機外表示,數據的存儲結構是數據的機內表示。
(2) 一種數據的邏輯結構可以用多種存儲結構來存儲。
(3) 任何一個算法的設計取決於選定的邏輯結構,而算法的實現依賴於採用的存儲結構。
(4) 採用不同的存儲結構,其數據處理的效率往往是不同的。
研究數據結構的方法
數據結構與算法之間存在着聯繫,而且在有些類型的數據結構中,我們總是要涉及到在這類數據結構上施加的運算。我們需要對這些運算進行研究,才能夠理解這類數據結構的定義和作用。
所以,算法是研究數據結構的主要途徑。我們在研究數據結構的過程,由於算法聯繫着數據在計算中的組織方式,爲了描述和實現某種操作,常常需要設計算法。
算法
定義
A finite set of rules which gives a sequence of operation for solving a specific type of problem.
翻譯過來就是:算法是規則的有限集合,是爲解決特定問題而規定的一系列操作。換句話就是算法是對特定問題求解步驟的一種描述,是指令的有限序列。
特性
一個算法必須滿足五種特性:
-
有窮性
一個算法必須在執行有窮的步驟之後結束,且每一步都要在有窮的時間內完成。
也就是有限步驟之內結束,不能形成死循環 -
確定性
算法中的每一條指令必須有確切的含義,在任何條件下,只有唯一的一條執行路徑,且無二義性。即對於相同的輸入只能得到相同的輸出。
這樣每種情況下所執行的操作,在算法中都有明確的規定。無論是閱讀者還是執行者,都能明確其含義和作用。 -
可行性
原則上可以精確的運行,算法描述的操作可以通過已經實現的基本操作運算執行有限次來實現。 -
輸入
一個算法有零個或多個輸入。
若用函數來描述算法時,往往藉助形參,在函數被調用時,從主調函數來獲取輸入值。 -
輸出
一個算法有一個或多個輸出,無輸出的算法是沒有意義的。當用函數來描繪算法時,輸出往往是函數的返回值或引用類型的形參。
算法設計的標準
-
正確性
在合理的數據輸入下,能夠在有限的運行時間下,得到正確的結果;
正確性有四個層次:- 所設計的程序沒有語法錯誤
- 程序能夠對幾組輸入的數據都能夠得到滿足的結果;
- 所設計的程序對於精心選擇的典型,苛刻而帶有刁難性的幾組輸入數據能夠得到滿足要求的結果。
- 程序對一切合法的輸入數據都能夠產生滿足要求的結果。
-
可讀性
一個好的算法首先應該便於人們理解和相互交流。其次纔是機器的可執行。
可讀性好的算法有助於加強我們對算法的理解,難懂的算法容易隱藏錯誤並且難以調試和修改。 -
健壯性
即對非法數據的抵抗性。
它主要強調,如果輸入非法數據,算法應能加以識別並作出處理,而不是產生誤動作輸出莫名奇妙的結果或者陷入癱瘓。 -
高效率和低存儲量
算法的效率通常是指算法的執行時間。
對於一個具體問題的解決通常可以通過多種算法,對於執行時間短的算法其效率就高。所謂的存儲量要求,是指算法在執行過程中所需要的最大的存儲空間。
前者用時間複雜度來衡量,後者用空間複雜度來衡量。兩者都與問題的規模有關。
算法描述的工具
算法+數據結構=程序
算法、語言、程序的關係
算法:算法是規則的有限集合,是爲解決特定問題而規定的一系列操作。
**描述算法的工具:**可以用自然語言,框圖或高級程序設計語言來描述。
**程序:**程序是算法在計算機中的實現。
類描述法的語言選擇
類語言
類語言是接近與高級語言而又不是嚴格的高級語言。
具有高級語言的一般語句設施,但是撇去了高級語言中的細節,而是把注意力集中在算法處理步驟本身的描述上。
算法性能評價的標準
性能評價
算法描述機器性能主要表現在時間代價(T和空間代價(S上。
算法的效率與**問題規模(N)**有關。算法效率是問題規模的函數。
算法效率的度量
算法效率可以用依據該算法編制的程序在計算機上執行所消耗的時間來度量。
兩種度量方法:
-
事後統計
- 將算法實現,測算其時間和空間開銷。
缺點:- 編寫程序實現算法將花費較多的時間和精力;
- 所得實驗結果依賴於計算機的軟硬件等環境因素
- 將算法實現,測算其時間和空間開銷。
-
事前分析
對算法所消耗資源的一種估算方法。
一個高級語言程序在計算機上運行所消耗的時間取決於:- 算法選用的策略
- 問題的規模
- 編寫程序的語言
- 編譯程序產生的機器代碼質量
- 機器執行指令的速度
同一個算法用不同的語言、不同的編譯程序、在不同的計算機上運行,效率均不同。因此,使用絕對時間單位衡量算法效率是不合適的。
關於算法的執行時間
由於算法的實際執行時間和機器硬件和系統軟件等多種環境因素有關,所以算法的執行時間是針對算法中的語句執行次數做出估計,從中得到算法執行時間的本質信息。
定義:
一個算法的執行時間大致上等於其所有語句執行時間的總和。
對於語句的執行時間是指該條語句的執行次數和執行一次所需時間的乘積。
語句頻度:
語句頻度是指該語句在一個算法中重複執行的次數。
例如求兩個n階矩陣乘積的算法
for(int i=1;i<=n;i++)..............................n+1
{
for(int j=1;j<=n;j++ )........................n*(n+1)
{
c[i][j]=0;.................................n^2
for(k=1;k<=n;k++)..........................n^2(n+1)
{
c[i][j]=c[i][j]+a[i][k]* b[k][j];.......n^3
}
}
}
注:循環執行n次,還要加上一次跳出循環的判斷,故爲n+1。
總執行次數爲$ T_{(n)}=2n3+3n2+n+1$;
問題規模(N)
反映問題大小的本質數目,對於不同的問題其含義不同。
如:
- 對矩陣是階數
- 對多項式運算是多項式的項數
- 對圖是頂點的個數
- 對集合運算是集合中的元素的個數
算法的時間複雜度
即算法的時間量度,以語句頻度爲量度。記作
最壞時間複雜度:討論算法在最壞情況下的時間複雜度,即分析最壞情況下估計出算法執行時間的上界。
漸進符號(O)的定義
- 如果存在兩個正常數c和,對於所有的n ≥ ,有
- 則記作 ;
- f(n)是參照物,通常是一些常用的標準函數。
常用的時間複雜度的頻率基數
- O(1)…常數型
- O(n)…線性型
- O()…平方型
- O()…立方型
- O()…指數型
- O()…對數型
- O(n)…二維型
n | $ n^3$ | $ 2^ n$ | |||
---|---|---|---|---|---|
0 | 1 | 0 | 1 | 1 | 2 |
1 | 2 | 2 | 4 | 8 | 4 |
2 | 4 | 8 | 16 | 64 | 16 |
3 | 8 | 24 | 64 | 512 | 256 |
4 | 16 | 64 | 256 | 5096 | 65536 |
5 | 32 | 160 | 1024 | 32728 | 2147483648 |
一般來說,前三種可以實現,後三種理論上可實現,但實際上只有把N限制在很小的範圍纔有意義,當N較大的時候不可能實現。
定理:
若是一個m次多項式,則 。
-
在計算算法時間複雜度時,可以忽略所有低次冪和最高次冪的係數。
- , c是正整數
對定理的一個證明:
分析算法時間複雜度的基本方法
- 找出語句頻度最大的那條語句作爲基本語句
- 計算基本語句的頻度得到問題規模n的某個函數
- 取其數量級用符號表示
分析時間複雜度的例子
for (int i=1; i<=n; i++)
x=x+1;
時間複雜度爲。
for(int i=1; i<=n; i++)
for(int j=1; j<=n; j++)
x=x+1;
時間複雜度爲。
void exam ( float x[ ][ ], int m, int n ) {
float sum [ ];.......................1
for ( int i = 0; i < m; i++ ) { .....m+1
sum[i] = 0.0;....................m
for ( int j = 0; j < n; j++ ) ...m*(n+1)
sum[i] += x[i][j];...........m*n--------------最深層的語句
}
for ( i = 0; i < m; i++ )............m+1
cout << i << “ : ” <<sum [i] << endl;...m
}
時間複雜度爲。
i=1; ①
while(i<=n)
i=i*2; ②
對於循環語句;
,可得。
故時間複雜度。
for (int i=2;i<=n;++i)
for (int j=2;j<=i-1;++j)
{
++x;
a[i][j]=x;......................分析最深層的語句
}
當i=2時,頻度爲0
當i=3時,頻度爲1
…
當i=n時,頻度爲n-2
故
故時間複雜度。
for( i=1; i<=n; i++)
for (j=1; j<=i; j++)
for (k=1; k<=j; k++)
x=x+1;........................f(n)
i=1…1
i=2…1+2
i=3…1+2+3
…
i=n…1+2+…+n
所以時間複雜度爲。
算法的空間複雜度
算法開始運行直至結束過程中所需要的最大存儲資源開銷的一種度量。
表示隨着問題規模 n 的增大,算法運行所需存儲量的增長率與 $f(n) $的增長率相同。
算法佔據的存儲空間
- 輸入數據所佔空間;
- 程序本身所佔空間;
- 輔助變量所佔空間。
一維數組的空間複雜度是O(n)
矩陣的空間複雜度爲
若輸入數據所佔空間只取決於問題本身,和算法無關,則只需要分析除輸入和程序之外的輔助變量所佔額外空間。
原地工作:若額外空間相對於輸入數據量來說是常數,則稱此算法爲原地工作。
實例
將一維數組a中的n個數逆序存放到原數組中。
【算法1】
for(i=0;i<n/2;i++)
{ t=a[i];
a[i]=a[n-i-1];
a[n-i-1]=t;
}
【算法2】
for(i=0;i<n;i++)
b[i]=a[n-i-1];
for(i=0;i<n;i++)
a[i]=b[i];