C++ 算法

C++ 算法

 

算法概念

算法是特定問題求解步驟的描述ios

在計算機中表現爲指令的有限序列算法

算法是獨立存在的一種解決問題的方法和思想。數組

對於算法而言,語言並不重要,重要的是思想。緩存

 

算法和數據結構區別

數據結構只是靜態的描述了數據元素之間的關係數據結構

高效的程序須要在數據結構的基礎上設計和選擇算法函數

程序=數據結構+算法 學習

總結:測試

      算法是爲了解決實際問題而設計的spa

      數據結構是算法須要處理的問題載體設計

      數據結構與算法相輔相成

 

算法特性

輸入

            算法具備0個或多個輸入

輸出

            算法至少有1個或多個輸出

有窮性

            算法在有限的步驟以後會自動結束而不會無限循環

肯定性

            算法中的每一步都有肯定的含義,不會出現二義性

可行性

            算法的每一步都是可行的

 

算法效率的度量

一、過後統計法

       比較不一樣算法對同一組輸入數據的運行處理時間

缺陷

       爲了得到不一樣算法的運行時間必須編寫相應程序

       運行時間嚴重依賴硬件以及運行時的環境因素

       算法的測試數據的選取至關困難

過後統計法雖然直觀,可是實施困難且缺陷多。

 

二、事前分析估算

       依據統計的方法對算法效率進行估算

       影響算法效率的主要因素

       算法採用的策略和方法

              問題的輸入規模

              編譯器所產生的代碼

              計算機執行速度

 

#define _CRT_SECURE_NO_WARNINGS

#include <iostream>
#include <string>

//算法最終編譯成具體的計算機指令
//每個指令,在具體的計算機上運行速度固定
//經過具體的n的步驟,就能夠推導出算法的複雜度

long sum1(int n)
{
    long ret = 0;                         
    int* array = (int*)malloc(n * sizeof(int)); 
    int i = 0;  

    for(i=0; i<n; i++)   
    {
        array[i] = i + 1;
    }

    for(i=0; i<n; i++) 
    {
        ret += array[i];
    }

    free(array); 

    return ret; 
}

long sum2(int n)
{
    long ret = 0;
    int i = 0;

    for(i=1; i<=n; i++)
    {
        ret += i;
    }

    return ret;
}

long sum3(int n)
{
    long ret = 0;

    if( n > 0 )
    {
        ret = (1 + n) * n / 2; 
    }

    return ret;
}


void mytest()
{
    printf("%d\n", sum1(100));
    printf("%d\n", sum2(100));
    printf("%d\n", sum3(100));

    return;
}

int main()
{
    mytest();

    system("pause");
    return 0;
}

 

int func(int a[], int len)
{
    int i = 0;
    int j = 0;
    int s = 0;

    for(i=0; i<len; i++) n
    {
        for(j=0; j<len; j++) n
        {
            s += i*j;  //n*n
        }
    }
    return s; 
}
//n*n

 

注意1:判斷一個算法的效率時,每每只須要關注操做數量的最高次項,其它次要項和常數項能夠忽略。

注意2:在沒有特殊說明時,咱們所分析的算法的時間複雜度都是指最壞時間複雜度。

 

二、大O表示法

算法效率嚴重依賴於操做(Operation)數量

在判斷時首先關注操做數量的最高次項

操做數量的估算能夠做爲時間複雜度的估算

O(5) = O(1)

O(2n + 1) = O(2n) = O(n) 

O(n2+ n + 1) = O(n2)

O(3n3+1) = O(3n3) = O(n3)

 

常見時間複雜度

 

關係

 

三、算法的空間複雜度

算法的空間複雜度經過計算算法的存儲空間實現

S(n) = O(f(n))

其中,n爲問題規模,f(n))爲在問題規模爲n時所佔用存儲空間的函數

大O表示法一樣適用於算法的空間複雜度

當算法執行時所須要的空間是常數時,空間複雜度爲O(1)

 

空間與時間的策略

           多數狀況下,算法執行時所用的時間更使人關注

           若是有必要,能夠經過增長空間複雜度來下降時間複雜度

           同理,也能夠經過增長時間複雜度來下降空間複雜度

 

#define _CRT_SECURE_NO_WARNINGS

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

/*
    問題: 
    在一個由天然數1-1000中某些數字所組成的數組中,每一個數字可能出現零次或者屢次。
    設計一個算法,找出出現次數最多的數字。
*/

// 方法1: 排序,而後找出出現次數最多的數字
// 排序,而後找出出現次數最多的數字

// 方法2: 把每一個數字出現的次數的中間結果,緩存下來;在緩存的結果中求最大值
void search(int a[], int len)
{
    int sp[1000] = {0};
    int i = 0;
    int max = 0;

    for (i = 0; i < len; i++)
    {
        int index = a[i] - 1;
        sp[index]++;
    }
    for (i = 0; i < 1000; i++)
    {
        if (max < sp[i])
        {
            max = sp[i];
        }
    }
    for (i = 0; i < 1000; i++)
    {
        if (max == sp[i])
        {
            printf("%d\n", i + 1);
        }
    }
}


void mytest()
{
    int array[] = {1, 1, 3, 4, 5, 6, 6, 6, 2, 3};
    search(array, sizeof(array)/sizeof(array[0]));

    return;
}

int main()
{
    mytest();

    system("pause");
    return 0;
}

 

 

1  大部分程序的大部分指令之執行一次,或者最多幾回。若是一個程序的全部指令都具備這樣的性質,咱們說這個程序的執行時間是常數。
 logN   若是一個程序的運行時間是對數級的,則隨着N的增大程序會漸漸慢下來,若是一個程序將一個大的問題分解成一系列更小的問題,每一步都將問題的規 模縮減成幾分之一 ,通常就會出現這樣的運行時間函數。在咱們所關心的範圍內,能夠認爲運行時間小於一個大的常數。對數的基數會影響這個常數,但改變不會太 大:當N=1000時,若是基數是10,logN等於3;若是基數是2,logN約等於10.當N=1 00 000,logN只是前值的兩倍。當N時原來的兩倍,logN只增加了一個常數因子:僅當從N增加到N平方時,logN纔會增加到原來的兩倍。
 N  若是程序的運行時間的線性的,極可能是這樣的狀況:對每一個輸入的元素都作了少許的處理。當N=1 000 000時,運行時間大概也就是這個數值;當N增加到原來的兩倍時,運行時間大概也增加到原來的兩倍。若是一個算法必須處理N個輸入(或者產生N個輸出), 那麼這種狀況是最優的。
 NlogN  若是某個算法將問題分解成更小的子問題,獨立地解決各個子問題,最後將結果綜合起來 ,運行時間通常就是NlogN。咱們找不到一個更好的形容, 就暫且將這樣的算法運行時間叫作NlogN。當N=1 000 000時,NlogN大約是20 000 000。當N增加到原來的兩倍,運行時間超過原來的兩倍,但超過不是太多。
 
N平方
 若是一個算法的運行時間是二次的(quadratic),那麼它通常只能用於一些規模較小的問題。這樣的運行時間一般存在於須要處理每一對輸入 數據項的算法(在程序中極可能表現爲一個嵌套循環)中,當N=1000時,運行時間是1 000 000;若是N增加到原來的兩倍,則運行時間將增加到原來的四倍。
 N三次方  相似的,若是一個算法須要處理輸入數據想的三元組(極可能表現爲三重嵌套循環),其運行時間通常就是三次的,只能用於一些規模較小的問題。當N=100時,運行時間就是1 000 000;若是N增加到原來的兩倍,運行時間將會增加到原來的八倍。
 2的N次方  若是一個算法的運行時間是指數級的(exponential),通常它很難在實踐中使用,即便這樣的算法一般是對問題的直接求解。當N=20時,運行時間是1 000 000;若是增加到原來的兩倍時,運行時間將是原時間的平方!

log log N 能夠看做是一個常數:即便N不少,兩次去對數以後也會變得很小

 

之前光看了n多排序算法,知道僅經過比較的排序算法一共兩種複雜度

O(N2)或O(N*lgN),

因爲高數學的很差,以前一看到後者就放棄了思考,沒有真正研究爲何會有個lgN,

這兩天工做不是很忙,看了一下基礎知識,有了必定的認識,算是初步搞清楚了緣由.

寫在這裏算是一個記錄,若是有問題也請你們指正.

 

說到N*lgN的算法大體上有幾種:堆排序,歸併排序,快速排序.

因爲學習數據結構的時候老師講過快速排序,(其實各類都講過,我只記住了這種)我如今仍是很是有印象的,

這幾種排序實際都有一個共同點,這個共同點讓他們有了lgN的特性.

 

都是使用了分治算法,把大集合經過分治,造成小集合,小集合的排序再次遞歸更小集合,直到1個(還多是2個或3個)元素爲止.

這樣對於整個集合來說,每次遞歸都是處理2個元素數量是1/2當前元素數量的新集合,

f(x) = f(x/2) + a (有限極小數)

這個特色在堆排序和歸併排序中尤其突出,他們是絕對的遵循2分法的.而快速排序結果是隨機的,若是點背,可能出現O(N*N)的可能性

 

下面用堆排序爲例說明一下NlgN:

爲了讓更多人明白,我簡單把堆排序說一下:

堆排序就是把原來輸入的值串,當成一棵徹底2叉樹,每次找最大值的時候,都是把數的左右子節點比較,把大於本身的最大的一個跟本身互換,找到一個後,從新找剩下的n-1個,一直到最終找完全部節點.

因爲遞歸使用的是深度優先,每次都會從最底層往上找起,每次找的次數假設是F(x),則其須要找兩個子樹F(x/2)而且等兩個子樹處理完後,比較2次(子樹的根比較一次,跟本身比較一次)若是連移動都算上,是3次操做

值的注意的是,因爲最初排列過了之後,找子樹的時候只要找那顆被破壞了的子樹便可,

另外一顆排過序了的不須要再找了. (這個我本身都感受說的不明白,若是實在不行,你們再去看看相關資料吧)

這樣分析下來,找第x個節點的操做次數爲: f(x) <= f((x-1)/2) +3 (固然,我理解算成 + 2也行,x-1是把根去掉)

因爲當x爲2的整次方倍 + 1 的時候,正好是這個數值,當其餘的狀況 也不大於這個值

因此咱們能夠就使用最大值的狀況 f(x) = f((x-1)/2) + 3; 爲了計算更容易

直接 f(x) = f(x/2) + 3

 f(x/2) = f(x/4) + 3

......

 f(x/2m-1) = f(x/2m) + 3   (2m>=x)  <= f(1)+3 < 3;

因爲一共m個算式 加起來是 f(x) = 3m 而 m = log(2)(x)

f(x)=3log(2)(x);

而計算f(1)+f(2) + ... + f(n)的時候,咱們把他分城 m段(m= log(2)(n)) (分別爲1,2,4,8,...2m-1)個元素(固然最後一端可能沒有那麼多)

求他們的和的話就是

2m-1(m-1) + 2m-2(m-2) + ....<2m-1m + 2m-2m + ... < 2mm 而m = log(2)(N)

 2mm =  2log(2)(n)*log(2)(n) = N * log(2)(N)

得證 哈哈

相關文章
相關標籤/搜索