早期人們都把計算機理解爲數值計算工具,就是感受計算機固然是用來計算的,因此計算機解決問題,應該是先從具體問題中抽象出一個適當的數據模型,設計出一個解此數據類型的算法,而後再編寫程序,獲得一個實際的軟件。php
可現實中,咱們更多的不是解決數值計算的問題,而是須要一些更科學有效的手段(好比表、樹和圖等數據結構)等幫助,才能更好地解決問題。算法
因此數組
數據結構是一門研究非數值計算的程序設計問題中的操做對象,以及它們之間的關係和操做等相關問題的學科。數據結構
說到數據結構是什麼,咱們得先來談談什麼叫作數據。函數
數據結構中,有5個基本概念:數據、數據元素、數據項、數據對象和數據結構。工具
他們之間的關係以下圖所示:測試
具體到代碼上,參考以下代碼:ui
//聲明一個結構體類型
struct Teacher{ //一種數據結構
char *name; //數據項--名字
char *title; //數據項--職稱
int age; //數據項--年齡
};
int main(int argc, const char * argv[]) {
struct Teacher t1; //數據元素;
struct Teacher tArray[10]; //數據對象;
return 0;
}
複製代碼
是描述客觀事物的符號,是計算機中能夠操做的對象,是能被計算機識別,並輸入給計算機處理的符號集合。數據不只僅包括整型、實數等數值類型,還包括字符及聲音、圖像、視頻等非數值類型類型。spa
——《大話數據結構》設計
好比咱們平時使用搜索殷勤,有網頁、mp三、圖片、視頻等分類。MP3 就是聲音數據
數據的特色:
組成數據的、有必定意義的基本單位,在計算機中一般做爲總體處理,也被稱爲記錄。
好比,在人類中,人就是數據元素。
而在動物類中,牛、馬、羊、雞等動物就是動物類的數據元素了。
一個數據元素由若干數據項組成。
好比人這樣的數據元素,能夠有眼耳鼻舌口這些數據項,也有姓名、年齡、性別、出生地址、電話等數據項。
數據項上數據不可分割的最小單位。
性質相同的數據元素的集合,是數據的子集。
性質相同的意思,是指數據元素具備相同數量和類型的數據項,好比,人都有姓名、生日、性別等相同的數據項。
是相互之間存在一種或多種特定關係的數據元素的集合。
在現實世界中,不一樣數據元素之間不是獨立的,而是存在特定的關係,咱們將這些關係稱爲結構。而在計算機中,數據元素並非孤立、雜亂無序的,而是具備內在聯繫的數據集合。數據之間存在的一種或多種特定關係,也就是數據的組織形式。
按照觀點的不一樣,咱們把數據結構分爲邏輯結構和物理結構。
是指數據對象中數據元素之間的相互關係
邏輯關係按照類別分爲線性結構與非線性結構:
線性結構中的數據元素是一對一的關係
非線性結構中的數據元素是一對多或多對多的關係。
是指數據的邏輯結構在計算機中的存儲形式。
數據元素的存儲形式有兩種:順序存儲和鏈式存儲。
把數據元素存放在抵制連續的存儲單元裏,其數據間的邏輯關係和物理關係是一致的。
以下圖所示:
把數據元素存放在任意的存儲單元裏,這組存儲單元能夠是連續的,也能夠是不連續的。
數據元素的存儲關係並不能反映其邏輯關係,所以須要用一個指針存放數據元素的地址,這樣經過地址就能夠找到相關聯數據元素的位置,如圖所示:
數據類型:是指一組性質相同的值的集合及定義在此集合上的一些操做的總稱。
數據類型是按照值的不一樣進行劃分的。在高級語言中,每一個變量、常量的表達式都有各自的取值範圍。類型就用來講明變量或表達式的取值範圍和所能進行的操做。
在C語言中,按照趣致的不一樣,數據類型能夠分爲兩類:
抽象是指抽出事物具備的廣泛型的本質。咱們對已有的數據類型進行抽象,就有了抽象數據類型。
抽象數據類型(Abstract Data Type:ADT):
是指一個數學模型及定義在該模型上的一組操做。
抽象的意義在於數據類型的數字抽象特性。
抽象數據類型體現了程序設計中問題分解、抽象和信息隱藏的特性。
是解決特定問題對求解步驟的描述,在計算機中表現爲指令的有限序列,而且每條指令表示一個或多個操做。
什麼是算法?算法是描述解決問題的方法。
自唐代以來,歷代更有許多專門論述「算法」的專著:
而英文名稱「algorithm」來自於9世紀波斯數學家花拉子米(比阿勒·霍瓦里鬆,波斯語:خوارزمی ,拉丁轉寫:al-Khwarizmi),由於比阿勒·霍瓦里鬆在數學上提出了算法這個概念。「算法」原爲「algorism」,即「al-Khwarizmi」的音轉,意思是「花拉子米」的運算法則,在18世紀演變爲「algorithm」。
歐幾里得算法被人們認爲是史上第一個算法。
算法具備五個基本特徵:輸入、輸出、有窮性、肯定性和可行性。
算法的正確性是指算法至少應該具備輸入、輸出和加工處理無歧義性、能正確反應問題的需求、可以獲得問題的正確答案。
大概分爲如下四個層次:
以上這四層含義裏,層次1 要求最低,而層次4 時最困難的,實際開發中,咱們幾乎不可能逐一驗證全部的輸入都能獲得正確的結果。
算法設計的另外一目的是爲了便於閱讀、理解和交流。
可讀性時算法(也包括實現它的代碼)好壞很重要的標誌。
當輸入數據不合法時,算法也能作出相關處理,而不是產生異常或莫名其妙的結果。
設計算法應該儘可能知足時間效率高和存儲量低的特色。
在生活中,人們都但願花最少的錢,用最短的時間,辦最大的事,算法也是同樣的思想,最好用最少的存儲空間,辦成一樣的事——就是好的算法。
經過對算法的數據測試,利用計算機的計時功能,來計算不一樣算法的效率是高仍是低。
這種方法主要是經過設計好的測試程序和數據,利用計算機計時器對不一樣算法編織的程序的運行時間進行比較,從而肯定算法效率的高低。
在計算機程序編制前,依據統計方法對算法進行估算。
咱們發現,一個用高級程序語言編寫的程序在計算機上運行時所消耗的時間取決於下列因素:
拋開這些與計算機硬件、軟件有關的因素,一個程序的運行時間,依賴於算法的好壞和問題的輸入規模。所謂問題輸入規模是指輸入量的多少。
咱們看看兩種求和的算法:
第一種算法
int i, sum = 0 n = 100; /* 執行 1次*/
for(i = 1; i <= n; i++) /* 執行 n + 1 次*/
{
sum += i; /* 執行 n 次*/
}
print("%d", sum); /* 執行 1 次*/
複製代碼
第二種算法
int sum = 0, n = 100; /* 執行 1次*/
sum = (1 + n) * n/2; /* 執行 1次*/
printf("%d", sum); /* 執行 1次*/
複製代碼
顯然,第一種算法,執行了 1 + (n+1) + n + 1 次 = 2n + 3 次
而第二種算法是1+1+1 = 3 次。算法好壞顯而易見。
最終,在分析程序的運行時間時,最重要的是吧程序當作是獨立於程序設計語言的算法或一系列步驟。
在進行算法分析時,語句總的執行次數T(n)是關於問題規模n的函數,進而分析T(n) 隨n 的變化狀況並肯定T(n) 的數量級。
算法的時間複雜度,也就是算法的時間量度,記做:T(n) = O(f(n))。它表示隨問題規模 n 的增大,算法執行時間的增加率和 f(n) 的增加率相同,稱做算法的漸進時間複雜度,簡稱爲時間複雜度。其中f(n) 是問題規模 n 的某個函數。
大寫O() 來體現算法複雜度的激發,咱們稱之爲大O記法。
上面求和算法的時間複雜度,分別爲O(n) 和 O(1)
下面這個算法,就是剛剛的第二個算法(高斯算法)。
int sum = 0, n = 100; /* 執行 1次*/
sum = (1 + n) * n/2; /* 執行 1次*/
printf("%d", sum); /* 執行 1次*/
複製代碼
這個算法的運行次函數是 f(n) = 3。根據咱們推導大O階的方法,第一步就是把常數3 改成1,再加上它沒有最高階項,因此這個算法的時間複雜度爲O(1)
若是這裏的第二行 sum = (1 + n) * n / 2 有10句,會是怎麼樣?
int sum = 0, n = 100; /* 執行 1次*/
sum = (1 + n) * n/2; /* 執行 1次*/
sum = (1 + n) * n/2; /* 執行 1次*/
sum = (1 + n) * n/2; /* 執行 1次*/
sum = (1 + n) * n/2; /* 執行 1次*/
sum = (1 + n) * n/2; /* 執行 1次*/
sum = (1 + n) * n/2; /* 執行 1次*/
sum = (1 + n) * n/2; /* 執行 1次*/
sum = (1 + n) * n/2; /* 執行 1次*/
sum = (1 + n) * n/2; /* 執行 1次*/
sum = (1 + n) * n/2; /* 執行 1次*/
printf("%d", sum); /* 執行 1次*/
複製代碼
事實上,不管n 爲多少,上面的代碼就說3次和12次執行的差別。這種與問題的大小無關(n) 的多少,執行時間恆定的算法,咱們稱之爲具備 O(1) 的時間複雜度
咱們要分析算法的複雜度,關鍵就是要分析循環結構的運行狀況。
下面這段代碼,它的循環的時間複雜度爲O(n),由於循環體中的代碼需要執行 n 次
int i;
for(i = 0; i < n; i++)
{
/ *
時間複雜度爲O(1)的程序步驟序列
*/
}
複製代碼
int count = 1;
while (count < n)
{
count = count * 2;
/* 時間複雜度爲 O(1) 的程序步驟序列*/
}
複製代碼
上面這行代碼,因爲每次 count 乘以 2 之後,就距離 n 更近了一份。
也就是說,有多少個2 相乘後大於 n,則會推出循環。
由 2x= n 獲得 x = log2n 。因此這個循環的時間複雜度爲O(logn)。
下面的例子說一個循環嵌套,它的內循環時間複雜度爲O(n)
in i,j;
for(i = 0; i < n; i++)
{
for (j = 0; j < n; j++)
{
/ * 時間複雜度爲 O(1) 的程序步驟序列*/
}
}
複製代碼
而對於外層的循環,不過是內部這個時間複雜度 O(n) 的語句,再循環 n 次。因此這段代碼的時間複雜度爲 O(n2)。
常見的時間複雜度如表所示
執行次數函數 | 階 | 非正式術語 |
---|---|---|
12 | O(1) | 常數階 |
2n + 3 | O(n) | 線性階 |
3n2 + 2n + 1 | O(n2) | 平方階 |
5 log2n + 20 | O(logn) | 對數階 |
2n + 3n log2n + 19 | O(nlogn) | nlogn 階 |
6n3 + 2 n2 + 3n + 4 | O(n3) | 立方階 |
2 n | O(2n) | 指數階 |
經常使用的時間複雜度所消耗的時間從小到大依次是:
O(1) < O(logn) < O(n) < O(nlogn) < O(n2) < O(n3) < O(2n) < O(n!) < O(nn)
咱們查找一個由 n 個隨機數字數組中的某個數組,最好的狀況是第一個數字就是,那麼算法的時間複雜度爲O(1),但也有可能這個數字就在最後一個位置上待着,那麼算法的複雜度爲O(n),這是最壞的一種狀況了。
最壞狀況運行時間是一種保證,那就是運行時間將不會再壞了。在應用中,這是一種最重要的需求,一般,除非特別指定,咱們提到的運行時間都是最壞時間的運行時間。
平均運行時間是全部狀況中最有意義的,由於它是指望的運行時間。也就是說,咱們運行一段程序代碼時,實習完看到平均運行時間的。可現實中,平均運行時間很難經過分析獲得,通常都是經過運行必定數量的實驗數據後估算出來的。
對算法的分析,一種方法是計算全部狀況的平均值,這種時間複雜度的計算方法稱爲平均時間複雜度。
另外一種方法是計算最壞狀況下的時間複雜度,這種方法稱爲最壞時間複雜度。通常在沒有特殊說明的狀況下,都是指最壞時間複雜度。
算法的空間複雜度經過計算算法所需的存儲空間實現,算法空間複雜度的計算公式記做:S(n) = O(ƒ(n)),其中,n 爲問題的規模,ƒ(n) 爲語句關於 n 所佔存儲空間的函數。
一般,咱們都適用「時間複雜度」來指運行時間的需求,使用「空間複雜度」指空間需求。當不用限定詞地使用「複雜度」時,一般都是指時間複雜度。