本文首發於 我的博客算法
程序 = 數據結構 + 算法數組
其實不少同窗知道數據結構與算法很重要,可是卻不明覺厲。 這裏咱們看一個簡單的題:markdown
對天然數從1到100的求和數據結構
最簡單的設計無非是:函數
void addNum () {
int total = 0;
for (int i = 1; i <= 100; i ++) {
total += i;
}
printf("total is %d \n",total);// 5050
}
複製代碼
沒毛病,可是哥根廷的數學家 高斯
在其9歲的時候就發明了一個快速計算等差數列求和的小技巧 (1+100,2+99,3+98.....),總共50 對101,結果5050。其公式能夠概括爲:oop
不論n多大,咱們只用算一次就能夠得出結果,而上面的循環卻要循環n次,n很小無所謂,若是幾萬幾十萬這個時間消耗不言而喻,因而可知數據結構與算法確實很重要。spa
數據結構中最基本的5個概念:數據、數據元素、數據項、數據對象,他們總體構成數據結構。設計
比較抽象,咱們用代碼來展現以下:指針
struct Teacher {
char *name;
char *sex;
int age;
};
int main(int argc, const char * argv[]) {
struct Teacher t1; // 數據元素
struct Teacher tArray[10]; // 數據對象( 一組數據元素組成 )
t1.name = "Mary"; // 數據項
t1.sex = "female"; // 數據項
t1.age = 23; // 數據項
return 0;
}
複製代碼
數據元素之間不是獨立的,存在特定的關係,這些關係即結構,數據結構指的就是數據對象中數據元素之間的關係。code
從視角的不一樣做以區分,咱們將數據結構分爲兩種類型:邏輯結構 和 物理結構
邏輯結構分爲:集合結構 、線性結構、樹形結構 、圖形結構。
集合結構
集合結構中的數據元素除了同屬一個集合外,彼此之間沒有其餘關係。
線性結構
線性結構中的數據元素之間都是一對一的關係,從圖中也能夠看出他們像一條線同樣把各個元素連起來,經常使用的線性結構有:鏈表 ,棧,隊列 ,數組 等等。
樹形結構
樹形結構中的元素是呈現一對多的關係,常見的樹有:二叉樹,B樹,哈夫曼樹 等等。
圖形結構
圖形結構之間的元素是多對多的關係,常見的圖形結構有: 矩陣 等。
物理結構其實就是存儲結構,就是存儲到計算機中的形式。數據存儲的結構才真正反映了數據存在的樣式,也反映了數據元素之間的邏輯關係,如何設計數據的存儲以及相互之間的關係纔是數據結構的關鍵。
順序存儲
咱們知道計算機中的內存都是連續的,如上圖所述,1-6個元素按照順序存儲的方式存到內存裏,好比第一個元素的內存地址是 0x000001
假設咱們這個順序表中每一個元素佔1個位置,那麼很容易獲得第二個元素的地址是 0x000002
,以此類推很容易知道第六個元素的地址是 0x000006
鏈式存儲
這裏我畫了一個簡單的圖,大概描述了一下鏈式存儲在內存中是如何體現的。當咱們的元素在內存中是散亂的,也就是他們的地址之間沒有必定的規律,這個時候就要靠咱們的指針去標記位置了,好比咱們把 元素2
的物理地址 0x000019
存儲到 元素1
中去,那麼每兩個元素之間就會有必定的相互綁定的關係,這就是鏈式存儲的基本邏輯。
經過上述咱們會發現,順序存儲的結構查找會很容易,由於直接按照順序對應的索引就能對應找到相應的內存地址,而鏈式存儲則不行,不過鏈式存儲的結構對於數據的增刪就特別快了,由於他們之間的關係靠的是指針,而不是內存地址的偏移。
算法是解決特定問題步驟的描述,在計算機中表現爲解決特定問題的一系列代碼
數據結構脫離算法,或者算法脫離數據結構都是沒法進行的,由於算法是基於數據結構進行的,只有數據結構而沒有算法那麼數據結構存在就沒有意義了。
程序 = 數據結構 + 算法
一般咱們用程序代碼執行的次數做爲算法時間複雜度的衡量,一般咱們用 大O
法來進行標記。
n+2n+log(n)+n^2
-----> n^2
O(5logn) -----> O(logn)
一般用O(1)
來表示
//1+1+1 = 3
void testSum1(int n){
int sum = 0; //執行1次
sum = (1+n)*n/2; //執行1次
printf("testSum2:%d\n",sum);//執行1次
}
複製代碼
無論n是多少,這個地方都只用執行1次,因此n不會影響其時間複雜度,O(1)
void add2(int x,int n){
for (int i = 0; i < n; i++) {
x = x+1;
}
}
複製代碼
能夠發現這個for循環會執行n次,因此其時間複雜度爲 O(n)
int count = 1;
while(count < n){
count = count * 2;
}
複製代碼
上述算法換個寫法就是2^x = n
就能夠獲得 x = log2n
,咱們說過常數若是不是1均可以去掉,最終結果就是 O(logn)
// x=x+1; 執行n*n次
void add3(int x,int n){
for (int i = 0; i< n; i++) {
for (int j = 0; j < n ; j++) {
x=x+1;
}
}
}
複製代碼
外層循環n次,內層循環n次,加在一塊兒就是 n*n = n^2
次,因此其時間複雜度爲 O(n^2)
O(1)<O(logn)< O(n) < O(nlogn) < O(n^2) < O(n^3) < O(2^n) < O(n!) < O(n^n)
其實咱們只用關注前面的複雜度就好了,若是你的算法時間複雜度像後面的那幾位靠齊,也就沒啥好說的了,指數級的增加仍是蠻恐怖的。
算法設計有一個重要原則: 空間 時間 權衡
算法的空間負責度是經過計算算法所需的存儲空間實現,算法空間複雜度的計算公式是:s(n) = n(f(n)) 其中n爲問題的規模,f(n) 爲語句關於n所佔存儲空間的函數。
咱們經過一個簡單的例子大概瞭解一下空間複雜度:
int n = 5;
int a[10] = {1,2,3,4,5,6,7,8,9,10};
//算法實現(1)
/*
算法(1),僅僅經過藉助一個臨時變量temp,與問題規模n大小無關,因此其空間複雜度爲O(1);
*/
int temp;
for(int i = 0; i < n/2 ; i++){
temp = a[i];
a[i] = a[n-i-1];
a[n-i-1] = temp;
}
for(int i = 0;i < 10;i++)
{
printf("%d\n",a[i]);
}
//算法實現(2)
/*
算法(2),藉助一個大小爲n的輔助數組b,因此其空間複雜度爲O(n).
*/
int b[10] = {0};
for(int i = 0; i < n;i++){
b[i] = a[n-i-1];
}
for(int i = 0; i < n; i++){
a[i] = b[i];
}
for(int i = 0;i < 10;i++)
{
printf("%d\n",a[i]);
}
複製代碼
須要注意的是:算法的空間複雜度並非整個算法佔用內存的空間,而是該算法在實現的時候所需的輔助空間。
研究算法的最終目的是要提升程序運行的效率,咱們還想下降程序運行所佔用的內存等等,這兩個一個是時間維度,一個是空間惟獨。一個好的算法並非說必定要時間複雜度低,也不必定要空間佔用小,這些東西要根據項目實際狀況去均衡。但願這篇文章可以將數據結構與算法的基礎知識講清楚。