從零開始 學 資料結構(一) 演算法的基本概念

2021-09-23 23:00:29 字數 4486 閱讀 8426

從零開始_學_資料結構(一)——演算法

演算法的定義:

解決問題的方法。

對於同乙個問題,乙個好的演算法比乙個差的演算法,效率更高,更節約資源。

for computer:演算法是解決特定問題的求解步驟的描述,在計算機中,表示指令的有限序列,每條指令表示乙個或者多個操作。

簡單來說,演算法就是輸入**,告訴計算機,你應該怎麼解決這個問題

演算法的特性:

(1)輸入和輸出。

光算出結果但不輸出結果,跟沒算沒區別;要計算,總得有資料,不然沒法計算。

(2)有窮性:

能出結果,並且在接受時間之內出結果。如果時間太長,這個演算法往往就沒什麼意義了。

(3)確定性:

同樣的資料,同乙個演算法,出同樣的結果。

(4)可行性;

如果乙個演算法太複雜,都沒辦法變成**給計算機,那肯定不行。

乙個好的演算法的要求:

(1)正確。

對於計算的資料,能得到預期的結果。

①首先得做到程式沒問題,能執行;

②其次得對於計算的資料,能得到符合要求的結果;

③更好的演算法,對於非法資料,能得出滿足要求的結果(比如說提示有問題);

④對於專門用於為難人的資料,也能有滿足要求的輸出結果;

至少要做到第②步

(2)可讀性

**和演算法,起碼讓人能讀懂,不然寫**的人都不明白自己寫的**,如果**出問題,那麼也沒辦法鑑別出來。

(3)健壯性

面對不符合要求(非法)的資料,能夠適當的處理。

例如,我們正常處理的一組資料裡,只有正數,比如說我們使用unsigned int型別。那麼假如有負數輸入,如果沒有**去鑑別這些,那麼就容易出現出乎意料的結果。

(4)速度快,用記憶體小

假如計算一組1gb大小的資料,同樣的處理器

①假如乙個演算法用1分鐘,另乙個演算法用1小時,顯然前者更好;

②假如乙個演算法用10mb記憶體,另乙個演算法用1gb記憶體,顯然前者更好。

在實際中,有時候會出現對於較少的資料來說,某一種演算法更好,對於較多的資料來說,另一種演算法更好。因此,選擇哪種演算法,要根據實際情況而決定。

演算法效率的度量方法:

(1)事後統計:

簡單直接,執行一遍就知道。

他到底用1秒還是100秒,用10mb記憶體還是500mb記憶體,一目了然。

缺點:假如需要1天甚至10天難道也要這麼做?肯定是不靠譜的。因此,一般是不會使用這種方法的。

(2)事前分析估算方法:

這個方法,簡單來說,就是:

①預估資料量(比如100萬個資料)

②根據演算法計算其執行次數(比如說3行**,一般是運算3次的。但若涉及到迴圈,那麼就是迴圈**的行數*迴圈次數)

③機器執行**的速度(計算求和與在一串字串中插入乙個字元,其執行速度自然是有區別的。而且,機器配置不同,執行同樣指令其時間也是有區別的)

由於③是不可控的,因此一般不考慮。

而①是我們要為之服務的物件,因此是需要考慮,但無法選擇的;

只有②,是我們最需要關心的內容。

以迴圈為例:

int total;

for(int i=0; i < n; i ++)

cin>>data[i];

data[i]*=2;

data[i]+=5;

total += data[i];

cout<< total;

//

假如cin

是讀取某個檔案的

int型別的資料(實際上不能這麼寫)

首先,迴圈範圍內有4條指令。因此,當data陣列裡有n個元素時,這個**將執行

4*n + 2

,4*n是迴圈,1是int total,另乙個1是cout《如果我們使用另外乙個方法:

int total;

for(int i=0; icin>>data[i];

total+=data[i];

}total=total*2+5*n;

cout<//

注意,這裡的

cin只是用來意會,實際應用

ifstream

類變數來讀取檔案

與上面相比,這個演算法需要執行的次數是:2*n+3次。

假如n=100萬時,第乙個演算法需要執行的次數是400萬+2次,第二個演算法執行的次數是200萬+3次,節約了幾乎50%的時間。

這樣的話,哪個演算法好,通過簡單的預估執行**的次數,一目了然。

當然了,實際中,會因為**不同,不同的**的效率略有差異,但是顯然執行次數更少的**,更剩時間,效率也更高。

除了以上兩種以外,還有雙重for迴圈(即外層是乙個for迴圈,執行n次;然後在外層的for迴圈內部還有乙個for迴圈,執行m次,如**);

for (int i=0; i < n ; i++)

for(int j=0; jtotal=total+m+n;

這樣一段**,其執行次數將為m*n次(假如m=n的話,可以認為其執行次數為n的乘方)。

總結一下:

不同的演算法(假設**行數為m),針對不同的資料量(n),將執行不同的次數:

有的次數為n,有的次數為m,有的為m*n,有的為n*n,也有其他的。

一般來說,需要特別設計演算法的,資料量都比較大(比如說幾萬甚至更多),**行數相對就偏少(也許只有幾行或者幾十行)。

因此,m次最好,n次其次,m*n次再次,n*n次最次。

四個字:避免乘方

如下表:

資料量n

演算法a執行**(n2)次

記作f(a)=

演算法b執行**(3n)次

f(b)=11

3246

39910

10030

100一萬

三百10000

一億三萬

假如需要特別的演算法,一般來說,原因是資料量都很大(幾百mb,幾gb,幾十幾百gb,甚至tb級別),只有乙個好的演算法,才能做到高效率的完成任務。

因此,需要盡量避免那種執行的**次數,為資料量的乘方甚至n次方這樣的情況。

以下概念有印象就行,反正知道怎麼用最重要,單純背概念毫無意義。

概念:函式的漸進增長

如上表,當n<3時,f(a)>f(b);

當n=3時,f(a)=f(b);

當n>3時,f(a)>f(b);

假如對於給定的函式,存在乙個整數n,使得對於所有n>n的情況,f(a)總是大於f(b),那麼我們就說,f(a)的漸進增長快於f(b)

假如兩個函式,都存在冪的情況(比如說n的2次方或者3次方)。

那麼主要關心其最高端的部分(因為假如n=10,其2次方的值,僅僅只有3次方的十分之一,n越大,差距越大。即使乙個是n的平方+一萬,另乙個是n的3次方,只需要n=100時,前者的效率便能輕鬆高於後者)。

因此:避免使用需要執行n的

m次冪次**的演算法。

除此之外,乙個演算法的需要執行**的次數還有對數的情況、或者是指數的情況。

個人意見是,這裡有個概念就可以,暫時不需要深究,畢竟這裡學的是資料結構。

演算法需要考慮最壞的情況:

例如我們設計乙個演算法,然後通過這個演算法去試圖去尋找乙個檔案裡的資料。

那麼①這個資料可能存在於檔案的開始,也可能存在於資料的結尾;

②如果這個演算法是逐個位元組尋找,那麼假如在開始,就會立刻找到,但若假如在結尾,那麼可能需要找很久很久。而這個 最壞的情況(需要很多時間),是需要我們考慮的。

③特別是當這個檔案很大很大時(遍歷整個檔案需要很多時間),那麼考慮如何解決這個問題,是很重要的(因此可能需要優化演算法)。比如說,假如這個資料存在的開始位址,是0***xx0這樣,我們就可以考慮一次讀取十六個位元組,然後先對比第乙個,符合後在繼續對比,這樣的話,就相對節約了很多時間。

④除了優化演算法外,我們還應該考慮平均時間。例如,當演算法無法優化時,那麼最好的情況下,這個演算法需要多久,最差的情況下,這個演算法需要多久,二者平均起來,往往就是普通的情況下,需要的時間。

而這個平均時間,是這個演算法的期望執行時間,是需要我們關注的。

演算法的儲存空間:

當使用演算法時,除了需要花時間外,可能還需要使用一定的儲存空間。

例如:①使用乙個10個元素的int陣列來儲存計算的內容,由於乙個int是4位元組,因此需要使用40位元組來儲存這些內容;

②可能還要建立乙個臨時的變數(堆或者棧)用於儲存演算法中建立的臨時物件,而在提高效率方面,建立的臨時物件是有一定意義的;

③有時候,對於演算法,要求其最多只能使用多少空間,演算法在執行過程中,不能超出這個界限。

因此,對於儲存空間的使用,也是有必要考慮的。

第二章:樹

從零開始學資料結構與演算法 4 鍊錶

建立單個結點 連線點,相當於是車廂 public class node 顯示方法 public void display 建立鍊錶,實際上就是建立乙個火車頭,指著下一輛 鍊錶,相當於火車 public class linklist 插入乙個結點,在頭結點後進行插入 public void inser...

從零開始學習 從零開始學習資料結構 雜湊桶

雜湊桶的本質是雜湊表,雜湊表的本質是 k v,k v 不就是 map,那麼這樣一層一層學習下來,就能理解的更為透徹,學習程式設計一定要有追根刨底的好奇心,這樣你的進步會非常快。1 桶 就是可以存放資料的結構 在這裡我認為桶就是結構體!在雜湊表的改進之上,雜湊表當時自己的做法是 表中存放的是指標,而不...

從零開始學資料結構與演算法 3 棧和佇列

模仿棧結構寫乙個陣列 主要模仿棧的先進後出和後進先出 public class mystack 帶引數的構造方法,引數為陣列初始化大小 public mystack int maxsize 新增資料 public void push int value 移除資料 public long pop 檢視...