演算法 - 空間複雜度 時間複雜度
關於演算法空間複雜度的問題
‘演算法空間複雜度’,別以為這個東西多麼高大上,我保證你看完這篇文章就能明白。
最近在啃演算法,發現非常有趣。在我學習的過程中發現了一個問題,那就是空間複雜度的問題,它絕對是效率的殺手。
關於空間複雜度的介紹(摘自百度)
空間複雜度(Space Complexity)是對一個演算法在執行過程中臨時佔用儲存空間大小的量度,記做S(n)=O(f(n))。比如直接插入排序的時間複雜度是O(n^2),空間複雜度是O(1) 。而一般的遞迴演算法就要有O(n)的空間複雜度了,因為每次遞迴都要儲存返回資訊。一個演算法的優劣主要從演算法的執行時間和所需要佔用的
拿插入排序來說。插入排序和我們現實中打撲克牌時理清牌的那一步很像。拿鬥地主來說,我們經常會把順子理出來,回想下我們是怎麼理的?比如我們有這麼5張牌9、8、10、7、6。過程應該是這樣的:
9、8、10、7、6
從8開始,8發現9比它大,然後8插到9前面。
8、9、10、7、6
然後到10,10發現它比前面2個都大,所以不用動。
8、9、10、7、6
然後到7,7發現10比它大,然後跟10換位置。
8、9、7、10、6
然後7又發現9也比它大,於是又跟9換位置
8、7、9、10、6
然後7又發現8也比它大,於是又跟8換位置
7、8、9、10、6
等等,好像有點不對。到牌‘7’的那一步好像太麻煩了,我們平時是把7拿起來,直接插到8前面就完事了。簡單快捷,絕對比一個個插要快。沒錯!這就是空間複雜度的問題。下面直接上2組程式碼來校驗一下。
public static void InsertSort(List<int> list) { for (int i = 0; i < list.Count; i++) { for (int j = i; j - 1 >= 0; j--) { if (list[j - 1] > list[j]) { int temp = list[j - 1]; list[j - 1] = list[j]; list[j] = temp; Console.WriteLine(string.Join(",", list)); } else break; } } } static void Main(string[] args) { List<int> list = new List<int>() { 9,8,10,7,6 }; InsertSort(list); Console.ReadKey(); }
我們可以看到,這種方法真是很笨。。就是一個一個往前插。。這當然不是我們想要的。。我們再改進下
public static void InsertSort2(List<int> list)
{
for (int i = 0; i < list.Count; i++)
{
int j = i;
int baseNumber = list[j];//先把牌抽出來
for (; j - 1 >= 0; j--)
{
if (list[j - 1] > baseNumber)
{
list[j] = list[j - 1];//後面的往前推
}
else
break;
}
list[j] = baseNumber;//結束後把牌插入到空位
}
}
static void Main(string[] args)
{
List<int> list = new List<int>()
{
9,8,10,7,6
};
InsertSort2(list);
}
其實思路就是先抽出1張牌(比如抽出的那張牌的位置為3,注意:現在3是空出來的),如果前一張牌(位置2)比它大,就把2移到3上面去。2就空出來了。
接著再前面那張(位置1)如果比抽出來的牌大,繼續往前移。因為2空出來了,1移到2上。現在1空出來了。
然後把抽出來的牌放到1上,完成。
過程如下
8、9、10、7、6
7
8、9、10、 、6
8、9、 、10、6
8、 、9 、10、6
、8、9 、10、6
7、8、9 、10、6
再來看看執行效率方面到底差了多遠
public static void InsertSort(List<int> list)
{
for (int i = 0; i < list.Count; i++)
{
for (int j = i; j - 1 >= 0; j--)
{
if (list[j - 1] > list[j])
{
int temp = list[j - 1];
list[j - 1] = list[j];
list[j] = temp;
}
else
break;
}
}
}
public static void InsertSort2(List<int> list)
{
for (int i = 0; i < list.Count; i++)
{
int j = i;
int baseNumber = list[j];//先把牌抽出來
for (; j - 1 >= 0; j--)
{
if (list[j - 1] > baseNumber)
{
list[j] = list[j - 1];//後面的往前推
}
else
break;
}
list[j] = baseNumber;//結束後把牌插入到空位
}
}
static void Main(string[] args)
{
List<int> list = new List<int>();
List<int> list2 = new List<int>();
Random random = new Random();
for (int i = 0; i < 50000; i++)
{
var temp = random.Next();
list.Add(temp);
list2.Add(temp);
}
Stopwatch watch = new Stopwatch();
watch.Start();
InsertSort(list);
watch.Stop();
Console.WriteLine(watch.ElapsedMilliseconds);
watch.Reset();
watch.Start();
InsertSort2(list2);
watch.Stop();
Console.WriteLine(watch.ElapsedMilliseconds);
Console.ReadKey();
}
執行結果
快了將近1倍吧
第一種方法需要不短的交換2個元素。因為需要交換2個元素,所以我們還需要用1個臨時變數來儲存其中1個元素的值
int temp = list[j - 1];
list[j - 1] = list[j];
list[j] = temp;
第二種方法則是直接將後面的元素往前移。
list[j] = list[j - 1];
如果說第一個種方法元素交換的次數為n,那第二種方法交換的次數則為 n/2+1。
堆排,快排時很多時候都會運用到這種思想。不知道大家有沒得到一些幫助呢?平時程式設計的時候是否也要注意到呢?
分類: 演算法&資料結構
0
0
« 上一篇:構建屬於自己的ORM框架之二--IQueryable的奧祕
» 下一篇:解析雜湊表
https://www.cnblogs.com/irenebbkiss/p/4243715.html
隨筆- 70 文章- 0 評論- 1
演算法的效能評價------空間複雜度和時間複雜度
一個演算法的優劣往往通過演算法複雜度來衡量,演算法複雜度包括時間複雜度和空間複雜度。
時間複雜度是演算法的所需要消耗的時間,時間越短,演算法越好。可以對演算法的程式碼進行估計,而得到演算法的時間複雜度。
一般來說,演算法程式碼簡短精悍可以用來減少演算法的時間複雜度!
空間複雜度指的是演算法程式在執行時所需要的儲存空間。空間複雜度可以分為以下兩個方面!
1.程式的儲存所需要的儲存空間資源。即程式的大小;
2.程式在執行過程中所需要消耗的儲存空間資源,如中間變數等;
一般來說,程式的大小越小,執行過程中消耗的資源越少,這個程式就越好!
下面為時間複雜度和空間複雜度的計算
時間複雜度是總運算次數表示式中受n的變化影響最大的那一項(不含係數) 比如:一般總運算次數表示式類似於這樣: a*2n+b*n3+c*n2+d*n*lg(n)+e*n+f a ! =0時,時間複雜度就是O(2n); a=0,b<>0 =>O(n3); a,b=0,c<>0 =>O(n2)依此類推 例子: (1) for(i=1;i<=n;i++) //迴圈了n*n次,當然是O(n2) for(j=1;j<=n;j++) s++; (2) for(i=1;i<=n;i++)//迴圈了(n+n-1+n-2+...+1)≈(n2)/2,因為時間複雜度是不考慮係數的,所以也是O(n2) for(j=i;j<=n;j++) s++; (3) for(i=1;i<=n;i++)//迴圈了(1+2+3+...+n)≈(n^2)/2,當然也是O(n2) for(j=1;j<=i;j++) s++; (4) i=1;k=0;//迴圈了n-1≈n次,所以是O(n) while(i<=n-1){ k+=10*i; i++; } (5) for(i=1;i<=n;i++) for(j=1;j<=i;j++) for(k=1;k<=j;k++) x=x+1; // 迴圈了(1
2
+2
2
+3
2
+...+n
2
)=n(n+1)(2n+1)/6(這個公式要記住哦)≈(n
3
)/3,不考慮係數,自然是O(n
3
) 另外,在時間複雜度中,log
2
n與lg(n)(同lg
10
(n))是等價的,因為對數換底公式: log
a
b=log
c
b/log
c
a 所以,log
2
n=log
2
10 * lg(n),忽略掉係數,二者當然是等價的 二、計算方法 1.一個演算法執行所耗費的時間,從理論上是不能算出來的,必須上機執行測試才能知道。 但我們不可能也沒有必要對每個演算法都上機測試,只需知道哪個演算法花費的時間多,哪個演算法花費的時間少就可以了。 並且一個演算法花費的時間與演算法中語句的執行次數成正比例,哪個演算法中語句執行次數多,它花費時間就多。 一個演算法中的語句執行次數稱為語句頻度或時間頻度。記為T(n)。 2.一般情況下,演算法的基本操作重複執行的次數是模組n的某一個函式f(n), 因此,演算法的時間複雜度記做:T(n)=O(f(n))。隨著模組n的增大,演算法執行的時間的增長率和f(n)的增長率成正比, 所以f(n)越小,演算法的時間複雜度越低,演算法的效率越高。 在計算時間複雜度的時候,先找出演算法的基本操作,然後根據相應的各語句確定它的執行次數, 再找出T(n)的同數量級(它的同數量級有以下:1,Log
2
n ,n ,nLog
2
n ,n的平方,n的三次方,2的n次方,n!), 找出後,f(n)=該數量級,若T(n)/f(n)求極限可得到一常數c,則時間複雜度T(n)=O(f(n))。 3.常見的時間複雜度 按數量級遞增排列,常見的時間複雜度有: 常數階O(1), 對數階O(log
2
n), 線性階O(n), 線性對數階O(nlog
2
n), 平方階O(n
2
), 立方階O(n
3
),..., k次方階O(n
k
), 指數階O(2
n
) 。 其中, 1.O(n),O(n
2
), 立方階O(n
3
),..., k次方階O(n
k
) 為多項式階時間複雜度,分別稱為一階時間複雜度,二階時間複雜度。。。。 2.O(2
n
),指數階時間複雜度,該種不實用 3.對數階O(log
2
n), 線性對數階O(nlog
2
n),除了常數階以外,該種效率最高 例:演算法: for(i=1;i<=n;++i) { for(j=1;j<=n;++j) { c[ i ][ j ]=0; //該步驟屬於基本操作 執行次數:n
2
for(k=1;k<=n;++k) c[ i ][ j ]+=a[ i ][ k ]*b[ k ][ j ]; //該步驟屬於基本操作 執行次數:n
3
} } 則有 T(n)= n
2
+n
3
,根據上面括號裡的同數量級,我們可以確定 n
3
為T(n)的同數量級 則有f(n)= n
3
,然後根據T(n)/f(n)求極限可得到常數c 則該演算法的 時間複雜度:T(n)=O(n
3
) 四、定義: 如果一個問題的規模是n,解這一問題的某一演算法所需要的時間為T(n),它是n的某一函式 T(n)稱為這一演算法的“時間複雜性”。 當輸入量n逐漸加大時,時間複雜性的極限情形稱為演算法的“漸近時間複雜性”。 我們常用大O表示法表示時間複雜性,注意它是某一個演算法的時間複雜性。大O表示只是說有上界,由定義如果f(n)=O(n),那顯然成立f(n)=O(n^2),它給你一個上界,但並不是上確界,但人們在表示的時候一般都習慣表示前者。 此外,一個問題本身也有它的複雜性,如果某個演算法的複雜性到達了這個問題複雜性的下界,那就稱這樣的演算法是最佳演算法。 “大O記法”:在這種描述中使用的基本引數是 n,即問題例項的規模,把複雜性或執行時間表達為n的函式。這裡的“O”表示量級 (order),比如說“二分檢索是 O(logn)的”,也就是說它需要“通過logn量級的步驟去檢索一個規模為n的陣列”記法 O ( f(n) )表示當 n增大時,執行時間至多將以正比於 f(n)的速度增長。 這種漸進估計對演算法的理論分析和大致比較是非常有價值的,但在實踐中細節也可能造成差異。 例如,一個低附加代價的O(n2)演算法在n較小的情況下可能比一個高附加代價的 O(nlogn)演算法執行得更快。 當然,隨著n足夠大以後,具有較慢上升函式的演算法必然工作得更快。 O(1) Temp=i;i=j;j=temp; 以上三條單個語句的頻度均為1,該程式段的執行時間是一個與問題規模n無關的常數。 演算法的時間複雜度為常數階,記作T(n)=O(1)。如果演算法的執行時間不隨著問題規模n的增加而增長, 即使演算法中有上千條語句,其執行時間也不過是一個較大的常數。此類演算法的時間複雜度是O(1)。 O(n
2
) 1) 交換i和j的內容 sum=0; (一次) for(i=1;i<=n;i++) (n次 ) for(j=1;j<=n;j++) (n
2
次 ) sum++; (n^2次 ) 解:T(n)=2n^2+n+1 =O(n^2) 2) for (i=1;i<n;i++) { y=y+1; //頻度是n-1 for (j=0;j<=(2*n);j++) x++; //頻度是(n-1)*(2n+1)=2n
2
-n-1 } f(n)=2n
2
-n-1+(n-1)=2n
2
-2 該程式的時間複雜度T(n)=O(n
2
). O(n) 3) a=0; b=1; //頻度:2 for (i=1;i<=n;i++) //頻度: n { s=a+b; //頻度: n-1 b=a; //頻度:n-1 a=s; //頻度:n-1 } T(n)=2+n+3(n-1)=4n-1=O(n). O(log
2
n ) 4) i=1; //頻度是1 while (i<=n) i=i*2; //頻度是f(n), 則:2
f(n)
<=n;f(n)<=log
2
n 取最大值f(n)= log
2
n, T(n)=O(log
2
n ) O(n
3
) 5) for(i=0;i<n;i++) { for(j=0;j<i;j++) { for(k=0;k<j;k++) x=x+2; } } 解:當i=m, j=k的時候,內層迴圈的次數為k當i=m時, j 可以取 0,1,...,m-1 , 所以這裡最內迴圈共 進行了0+1+...+m-1=(m-1)m/2次所以,i從0取到n, 則迴圈共進行了: 0+(1-1)*1/2+...+(n-1)n/2=n(n+1)(n-1)/6 所以時間複雜度為O(n
3
). 我們還應該區分演算法的最壞情況的行為和期望行為。如快速排序的最壞情況執行時間是 O(n
2
), 但期望時間是 O(nlogn)。通過每次都仔細 地選擇基準值,我們有可能把平方情況 (即O(n
2
)情況) 的概率減小到幾乎等於 0。在實際中,精心實現的快速排序一般都能以 (O(nlogn)時間執行。 下面是一些常用的記法: 訪問陣列中的元素是常數時間操作,或說O(1)操作。一個演算法如 果能在每個步驟去掉一半資料元素, 如二分檢索,通常它就取 O(logn)時間。用strcmp比較兩個具有n個字元的串需要O(n)時間。 常規的矩陣乘演算法是O(n
3
),因為算出每個元素都需要將n對 元素相乘並加到一起,所有元素的個數是n
2
。 指數時間演算法通常來源於需要求出所有可能結果。例如,n個元 素的集合共有2n個子集,所以要求出 所有子集的演算法將是O(2n)的。指數演算法一般說來是太複雜了,除非n的值非常小,因為,在 這個問題 中增加一個元素就導致執行時間加倍。不幸的是,確實有許多問題 (如著名的“巡迴售貨員問題” ), 到目前為止找到的演算法都是指數的。如果我們真的遇到這種情況,通常應該用尋找近似最佳結果的演算法替代之。
二.空間複雜度
空間複雜度(Space Complexity)是對一個演算法在執行過程中臨時佔用儲存空間大小的量度。 一個演算法在計算機儲存器上所佔用的儲存空間, 包括程式程式碼所佔用的空間,輸入資料所佔用的空間和輔助變數所佔用的空間這三個方面。 演算法的輸入輸出資料所佔用的儲存空間是由要解決的問題決定的,是通過引數表由呼叫函式傳遞而來的, 它不隨本演算法的不同而改變。儲存演算法本身所佔用的儲存空間與演算法書寫的長短成正比,要壓縮這方面的儲存空間, 就必須編寫出較短的演算法。演算法在執行過程中臨時佔用的儲存空間隨演算法的不同而異,有的演算法只需要佔用少量的臨時工作單元, 而且不隨問題規模的大小而改變,我們稱這種演算法是“就地"進行的,是節省儲存的演算法,如這些介紹過的幾個演算法都是如此; 有的演算法需要佔用的臨時工作單元數與解決問題的規模n有關,它隨著n的增大而增大,當n較大時,將佔用較多的儲存單元, 例如將在第九章介紹的快速排序和歸併排序演算法就屬於這種情況。 分析一個演算法所佔用的儲存空間要從各方面綜合考慮。如對於遞迴演算法來說,一般都比較簡短,演算法本身所佔用的儲存空間較少,但執行時需要一個附加堆疊,從而佔用較多的臨時工作單元;若寫成非遞迴演算法,一般可能比較長,演算法本身佔用的儲存空間較多,但執行時將可能需要較少的儲存單元。 一個演算法的空間複雜度只考慮在執行過程中為區域性變數分配的儲存空間的大小,它包括為引數表中形參變數分配的儲存空間和為在函式體中定義的區域性變數分配的儲存空間兩個部分。若一個演算法為遞迴演算法,其空間複雜度為遞迴所使用的堆疊空間的大小,它等於一次呼叫所分配的臨時儲存空間的大小乘以被呼叫的次數(即為遞迴呼叫的次數加1,這個1表不開始進行的一次非遞迴呼叫)。演算法的空間複雜度一般也以數量級的形式給出。如當一個演算法的空間複雜度為一個常量,即不隨被處理資料量n的大小而改變時,可表示為O(1);當一個演算法的空間複雜度與以2為底的n的對數成正比時,可表示為0(log
2
n);當一個演算法的空I司複雜度與n成線性比例關係時,可表示為0(n).若形參為陣列,則只需要為它分配一個儲存由實參傳送來的一個地址指標的空間,即一個機器字長空間;若形參為引用方式,則也只需要為其分配儲存一個地址的空間,用它來儲存對應實參變數的地址,以便由系統自動引用實參變數。 對於一個演算法,其時間複雜度和空間複雜度往往是相互影響的。當追求一個較好的時間複雜度時,可能會使空間複雜度的效能變差,即可能導致佔用較多的儲存空間;反之,當=i自求一個較好的空間複雜度時,可能會使時間複雜度的效能變差,即可能導致佔用較長的執行時間。另外,演算法的所有效能之間都存在著或多或少的相互影響。因此,當設計一個演算法(特別是大型演算法)時,要綜合考慮演算法的各項效能,演算法的使用頻率,演算法處理的資料量的大小,演算法描述語言的特性,演算法執行的機器系統環境等各方面因素,才能夠設計出比較好的演算法。 空間複雜度是程式執行所以需要的額外消耗儲存空間,也用o()來表示 比如插入排序的時間複雜度是o(n
2
),空間複雜度是o(1) 而一般的遞迴演算法就要有o(n)的空間複雜度了,因為每次遞迴都要儲存返回資訊 一個演算法的優劣主要從演算法的執行時間和所需要佔用的儲存空間兩個方面衡量,演算法執行時間的度量不是採用演算法執行的絕對時間來計算的,因為一個演算法在不同的機器上執行所花的時間不一樣,在不同時刻也會由於計算機資源佔用情況的不同,使得演算法在同一臺計算機上執行的時間也不一樣,所以對於演算法的時間複雜性,採用演算法執行過程中其基本操作的執行次數,稱為計算量來度量。 演算法中基本操作的執行次數一般是與問題規模有關的,對於結點個數為n的資料處理問題,用T(n)表示演算法基本操作的執行次數.在評價演算法的時間複雜性時,不考慮兩演算法執行次數之間的細小區別,而只關心演算法的本質差別: 為此,引入一個所謂的O() 記號,則T1(n)=2n=O(n),T2(n)=n+1=O(n)。一個函式f(n)是O(g(n))的,則一定存在正常數c和m,使對所有的n>m,都滿足f(n)<c*g(n)。
0
0
« 上一篇:idea 快捷鍵大全
» 下一篇:mysql中的自增列和預設欄位值為系統時間
posted @ 2017-07-06 15:38 尹福建 閱讀(2350) 評論(0) 編輯 收藏
註冊使用者登入後才能發表評論,請 登入 或 註冊,訪問網站首頁。
【推薦】超50萬VC++原始碼: 大型組態工控、電力模擬CAD與GIS原始碼庫!
【福利】華為雲4核8G雲主機免費試用
【活動】華為雲會員節雲服務特惠1折起
【活動】騰訊雲+社群開發者大會12月15日首都北京盛大起航!
相關博文:
· 2018年9月全國計算機公共基礎題庫
· 演算法的效能評價
· 演算法(1)
· 時間複雜度
· 時間複雜度和空間複雜度
https://www.cnblogs.com/yinfj/p/7126643.html