資料結構與演算法(緒論)

2021-07-25 04:30:30 字數 4435 閱讀 6732

作為乙個準程式設計師,在讀本科的時候,就一直被老師不停的灌輸資料機構與演算法的重要性,但是好像我們從來就沒遇到過真正棘手的需要通過資料結構去解決的問題,可能唯一再聽到它的時候就是找工作實習的時候會被面試官提問,因此我們似乎覺得資料結構沒那麼重要,而且又因為各種程式語言,尤其是python,各種內建的資料結構好像是萬能的,在運算量、記憶體占用等都沒到一定程度的時候,好像所有的資料結構都能完成要求的功能。

但是當我們真正去了解這些資料結構背後的實現的時候,我們才會發現那些看似完美的資料結構,也未必是那麼完美。網上有人提問有了python是不是不用再學資料結構了?這裡很贊同第二個人的答案。

"雖然當下大部分流行的高階語言都自帶了對常見資料結構的支援,而且多半你無法給出更加優秀的實現,但是繼續學習資料結構的動力在於:它讓你學會選擇乙個正確且合適的資料結構去解決乙個具體的問題」

或者到更具體一點的問題: 

1、python的list是如何實現的,為什麼會有近乎無限大小的空間?為什麼專門有乙個固定長度且不能修改的資料結構tuple而不全用list?

3、python 的 dict 是怎麼回事,為什麼可以用字串數字等等東西來索引?是怎麼搜尋的?在 dict 中找乙個元素,和在 list 裡面找乙個元素有什麼區別?

所有的資料結構書籍開始之前都會介紹一大堆演算法的概念,甚至是不厭其煩的介紹從實際問題到完成程式的過程,當然為了知識的完整性,這樣顯然是沒有問題的,但是博主盡量省略一些繁瑣的過程。

這裡只是簡單的通過實際的例子來介紹演算法到程式的實現過程。

現在假設乙個實際的問題:求出任一非負實數的平方根?

下面我們來看一下:從計算的角度來看,這種問題假設是有一定的缺陷的:對於給定的數值,即使它只包含有窮位小數,其平方根通常也是無理數,不能寫成數字的有窮表示形式,計算都需要在有窮步內完成,應該是乙個有窮過程。因此一般而言,通過計算只能得到實數的平方根的近似值,熟悉高等數學的應該知道,高數第一章就是關於極限的定義,而這裡的近似值估計的過程可以看做為如下問題:對任意非負實數x,設法找到乙個實數y,使得|y*y-x|

人們提出一些求平方根的演算法,這裡採用一種牛頓迭代法的演算法,描述如下:

1、對給定正實數x和允許誤差e,令變數y取任意正實數值,如令y=x;

2、如果y*y與x足夠接近,即|y*y-x|

3、取z=(y+x/y)/2;

4、將z作為y的新值,回到步驟2。

演算法的具體推倒過程用到均值不等式和高數中的極限,通過這個演算法我們可以寫出如下的實現**:

def sqrt(x):

y = 1.0

while abs(y * y - x) > 1e-10:

y = (y + x/y)/2

return y

不去深究牛頓迭代法與其他求平方根演算法的優劣,通過這個問題,可以感受到演算法到程式的實現過程以及平時工作中我們沒有注意到的一些點。

既然提到不同演算法之間的比較,那就需要對不同的演算法提出統一的評價標準,而在實際過程中一般會從時間複雜度和空間複雜度來做考量。人們通過某個問題的某種規模n為變數,反映出這個演算法在處理規模n的問題例項時需要付出的時間(或空間)代價。同時在度量演算法的代價(比如時間複雜度)時,最常關注的是最壞情況的時間複雜度和平均時間複雜度。

對於實際的問題時,人們可能有具體的評價標準,但是對於抽象或者資料量龐大的演算法,人們無法做出精確度量。在這種情況下只能退而求其次,設法估計演算法複雜度的量級。

對應於高等數學中高階和低階的度量,一般採用相同的記法。在演算法和資料結構領域,人們常用如下幾組函式來反映複雜度:

o(1),o(logn),o(n),o(nlogn),o(n^2),o(n^3),o(2^n)

在實際學習中,我們可能限於運算的場景和規模,對於演算法複雜度沒有那麼明顯的感知。通常可以把演算法複雜度分級理解為高數中對於無窮大的階,反映了規模在n趨向於無窮大的過程中,演算法代價增長的速度。舉兩個例子:

1、做天氣預報的程式,必須今天下午完成對明天上午的天氣預報計算。如果計算時間超過明天,那麼這個演算法則是毫無意義的。

2、假設解決某個問題具體問題的基本操作每秒鐘可以玩完成10^6次,需要處理的問題規模n是100。那麼問題複雜度是o(n)和o(2^n)的不同演算法帶來的時間將是天差地別。

例子1中表明由於場景的需求,對於演算法常量級的改變也是很有意義的。而例子2則表明不同時間複雜度的演算法帶來的結果是相差很大的。更為日常的場景,我們在使用手機時,攝像頭對焦的速度和人臉識別的速度在不同手機間也是有差別的,而這也會帶來使用者體驗上的差異。

下面具體到python程式的複雜度,以一些常見資料結構的時間開銷和空間開銷來稍作舉例,其中的n均是指有關結構的元素個數。

1、構造操作,如構造新的list,set等。構造新的空結構(空表,空集合等)是常量時間,但是構造乙個包含n個元素的結構,則至少需要o(n)時間。統計說明,分配長度為n個元素的**塊的時間代價為o(n)。

2、一些list操作的效率:列表元素的訪問和元素修改(基於索引)是常量時間操作,但是一般的加入/刪除操作(列表的操作是保序的,需要移動元素)都是o(n)時間操作,尾端的加入/刪除是o(1)操作。

3、字典dict的效率:針對字典的操作一般是加入新的鍵值對和基於鍵查詢值。它們的最壞的情況複雜度是o(n),平均時間複雜度是o(1)。

4、相對而言,列表和元組是比較簡單的資料結構。集合和字典由於支援快速查詢等操作,其空間結構更加複雜,空間開銷也更高。包含n個元素的集合和字典,至少需要占用o(n)的儲存空間。

需要格外注意的是,python中的各種組合資料物件都沒有預設的最大元素個數(例如list的實現就是採用分離式技術實現的動態順序表)。在實際使用中,這些結構能根據元素個數的增長自動擴充儲存空間。從空間占用的角度來看,其實際開銷在存續期間可能更大,但通常是不會自動縮小(即使後來元素變少了)。舉個例子,假設程式裡建了乙個表,而後又不斷加入元素導致表變得很大,而後又不斷的刪除元素,後來表的元素變得很少,但是占用的儲存空間並不會減少。

在電腦程式中,演算法和資料結構是緊密相連的,演算法的實現離不開資料的組織方式,而資料結構的高效組織則能支援處理它們的高效演算法,資料結構上的操作也需要通過演算法實現。在計算中一般將資料結構劃分為:

1、結構性的資料結構,如線性結構、樹結構和圖結構,這些資料結構的最重要的特徵是它們的結構,即對其資料元素之間的關係都做了一些規定,元素之間確實存在某種關係;

2、功能性資料結構,如棧、佇列等,它們並沒有對其元素的相互關係提出任何結構性的規定,而是要求實現某種計算中非常有用的功能。作為可以包含一批資料元素的結構,最基本的要求就是支援元素的儲存和使用(即訪問)。而且這些不同的資料結構各有不同的功能方面的特點,比如棧和佇列是使用最多的緩衝儲存結構。

資料結構研究的就是資料之間的關聯和組合的形式,在計算機記憶體裡表示資料元素之間的聯絡通常採用如下兩種基本技術(具體的在後面會講到):

1、利用資料元素的儲存位置隱式的表示,也稱為元素的順序表示。由於記憶體是單元的線性序列,知道了前乙個元素的位置及大小(儲存佔用量),就能計算下乙個元素的位置。如果儲存的是一系列大小相同的元素,就可以利用公式直接計算出序列中任何乙個元素的位置;

2、把資料元素之間的聯絡也看做一種資料,顯式的儲存在記憶體中。用這種方式可以表示資料元素之間任意複雜的關係,因此這種技術的功能也更強大。

這裡對python語言中與資料表示有關的內容做一些介紹:

高階語言中的變數(全域性變數、函式的區域性變數和引數)是記憶體及其位址的抽象。變數本身也需要在記憶體中安排位置,每個變數占用若干儲存單元。

在python程式裡,可以通過初始化(或提供引數)給變數(或函式引數)約束乙個值,還可以通過賦值修改變數的值。這裡的值就是物件,給變數約束乙個物件,就是把該物件的標識(記憶體位置)存入該變數,例如a =1,就是把1這個物件在記憶體的位址賦值給a,在呼叫的時候,是通過a裡面儲存的1的記憶體位址找到物件1。

可以理解為:把該值的記憶體位址賦值給該變數,在變數中儲存值(物件)的引用(即位址)。採用這種方式,變數所需要的儲存空間大小一致,因為其中只需要儲存乙個引用(位址),這種方式被稱為引用語義。

有些語言採用的不是這種方式,它們把變數的值直接儲存在變數的儲存區中,稱為值語義,這樣乙個整數型別的變數就需要儲存在乙個整數所需的空間,乙個浮點數變數就需要足夠的空間儲存乙個浮點數。如果乙個變數中需要儲存很大的資料物件,它就需要佔據更大的儲存空間,例如c語言就是採用的值語義。

python語言的實現是基於一套精心設計的鏈結結構。變數與值物件的關聯通過鏈結的方式實現,物件之間的聯絡同樣也通過鏈結。

最後簡單介紹一下python的幾個標準資料型別,具體的在之後會詳細的講解:

list(列表):列表是使用最多的組合資料型別、list物件可以包含任意多個任意型別的元素,元素訪問和修改都是常量時間操作。此外,list物件是可變物件,在物件的存在期間可以任意的加入和刪除元素。因此程式裡經常需要從空表開始,通過逐步加入元素的方法構造任意大的表。

tuple(元組):在儲存元素和元素訪問方面的性質和列表類似,但其物件是不可變物件,只能在建立的時候構造出來,不能逐步構造,由於這種資料型別的性質,因此使用較少。

dict(字典):支援基於鍵的資料儲存和檢索,這裡的鍵只能是不變物件(例如:元組、字串,但不能是列表)。如果鍵是組合物件,其元素必須是不變物件。在乙個字典裡面可以容納任意多的鍵值對,支援高效檢索(平均時間為o(1))。

參考資料:

資料結構與演算法 緒論

一般法則 法則1 for迴圈 乙個for迴圈的執行時間至多是該for迴圈內部那些語句的執行時間乘以迭代的次數。法則2 巢狀的for迴圈 從裡向外分析這些迴圈。在一組巢狀迴圈內部的一些語句總的執行時間為該語句的執行時間乘以該組所有的for迴圈的大小的乘積 例如,下列程式片斷為o n2 for i 0 ...

資料結構與演算法 緒論

重要性 使用者資訊表usersid name 001 bigsai man002 smallsai man003 菜虛鯤woman users的pojo物件 class users list和woman是資料 listlist 資料物件list listwoman 資料物件woman list.ad...

演算法與資料結構緒論

資料結構是一門研究非數值計算的程式設計問題中的操作物件,以及它們之間的關係和操作等相關問題的學科 程式設計 資料結構 演算法 傳統上,我們把資料結構分為邏輯結構和物理結構 邏輯結構 是指資料物件中資料元素之間的相互關係,也是我們今後最需要關注和討論的問題。物理結構 是指資料的邏輯結構在計算機中的儲存...