數據結構與算法 - 基礎

本文首發於 我的博客算法

程序 = 數據結構 + 算法數組

其實不少同窗知道數據結構與算法很重要,可是卻不明覺厲。 這裏咱們看一個簡單的題: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

1+2+3+....+n = n*(n+1)/2

不論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 法來進行標記。

  • 用常數1取代運行時間中的全部常數
  • 各類複雜度相加,只保留最高階 n+2n+log(n)+n^2 -----> n^2
  • 若是最高階存在且不是1,則去掉這個項相乘的常數 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]);

    }
複製代碼

須要注意的是:算法的空間複雜度並非整個算法佔用內存的空間,而是該算法在實現的時候所需的輔助空間。

研究算法的最終目的是要提升程序運行的效率,咱們還想下降程序運行所佔用的內存等等,這兩個一個是時間維度,一個是空間惟獨。一個好的算法並非說必定要時間複雜度低,也不必定要空間佔用小,這些東西要根據項目實際狀況去均衡。但願這篇文章可以將數據結構與算法的基礎知識講清楚。

相關文章
相關標籤/搜索