數據結構與算法
什麽是數據結構?
指數據元素之間的關系。這些關系可以分為:
集合
線性結構
樹形結構
網狀結構。
邏輯結構分為: 線性結構 和 非線性結構。
集合:除了同屬一個對象外不存在相互關系。如:汽車上的人除了同輛車彼此間無其他關系。
線性結構:元素間為嚴格的一對一關系,即一個元素有且只有一個前驅。如:成績表中一個學生一個成績
樹形結構:元素之間為嚴格的一對多關系,即一個元素有且只有一個前驅,但可以多個後繼。如家譜,行政組織
網狀結構:元素之間存在多對多的關系,比較復雜。如:微信朋友圈
如何描述數據結構?
使用二元組來描述數據結構
Data_structure = (D,R)
D 是元素的有限集
R 是D上所有元素的關系有限集
下面舉一些例子
線性結構可以用二元組表示為:
linear = (D,R)
D = {A,B,C,D,E,F,G,H}
R = {r}
r = {<A,B>,<B,C>,<C,D>,<D,E>,<E,F>,<F,G>,<G,H>}
樹形結構可以用二元組表示為:
tree = (D,R)
D = {A,B,C,D,E,F,G,H,I,J}
R = {r}
r = {<A,B>,<A,C>,<A,D>,<B,E>,<B,F>,<C,G>,<C,H>,<C,I>,<D,G>}
網狀結構可以用二元組表示為:
nets = (D,R)
D = {1,2,3,4,5}
R = {r}
r = {(1,2),(1,3),(1,4),(2,3),(2,4),(2,5),(3,4),(3,5),(4,5)}
尖括號表示有向線,圓括號表示無向線。
將上面的尖括號或圓括號的元素用線連一下便是對應的形狀了。
數據結構如何存儲?
順序存儲結構:數據元素依次存儲在一組連續存儲單元當中,數據邏輯關系由存儲單元的位置直接體現。
鏈式存儲結構:數據元素存儲在任意存儲單元當中,而附加的指針域表示元素之間的邏輯關系
程序 = 算法 + 數據結構
數據結構離不開算法,下面說說算法
什麽是算法?
算法具有五個特性:
有窮性:一個算法必須在有窮的執行步驟後結束,每步都可以在有窮的時間內完成。
確定性:算法中每條指令必須明確,任何情況下對於相同輸入都有相同的輸出。
可行性:算法中描述的操作均可以用基本運算的有限執行次數來實現。
輸入:一個算法有0個或者多個輸入,這些輸入是某個特定對象的合集
輸出:一個算法至少有一個輸出,與輸入有著某些特定關系的量。
如何設計算法?
正確性:除了語法正確,邏輯上也正確,能達到預期的目,最好可以驗證結果是否正確。
可讀性:方便閱讀,交流,改進
健壯性:輸入非法參數,算法能及時給出錯誤答案,並輸出錯誤提示,同時終止程序。
效率與存儲:執行的時間越短越好,使用的空間越少越好,雖然時間和空間總算矛盾的
算法的時間復雜度
事後統計:將不同的算法編制成程序,然後比較這些程序執行的時間。
事前估算:用數學方法對算法效率直接分析,常用此方法來判斷時間復雜度。
事前估算需要考慮的因數:
1.問題的規模,排序10個數字比排序100個數字時間短
2.編寫的語言,越高級的語言執行的效率越低。
3.機器的速度,80386和i7的速度沒得比
4.算法本身的策略。
由於2,3因數需要考慮到硬件,軟件(編譯器)等因數,因此用絕對的機器運行時間來衡量算法
是不科學的,所以需要拋開這些因數,主要研究時間效率與算法所處理的問題規模n的函數關系
當隨著問題規模n增大時,算法執行時間的增長率與f(n)的增長率相同,稱為算法的漸進時間復雜度。記作:
T(n) = O(f(n))
執行時間 == 執行次數(假設執行一條語句所用的時間相同),另外下面的時間復雜度都以最壞的情況考慮
下面是常見的時間復雜度:
常數階 O(1)
對數階 O(logn)
線性階 O(n)
平方階 O(n^2)
立方階 O(n^3)
指數階 O(2^n)
階數階 O(n!)
爆炸階 O(n^n)
下面兩段代碼是累加求和
1 int sum1(){
2 int i,sum=0,n=100; #執行了1次
3 for(i=0;i<n;i++) #執行了n+1次
4 sum += i; #執行了n次
5 }
高斯算法
1 int sum2(){
2 int n,sum=0; #執行了1次
3 sum = n(n+1)/2; #執行了1次
4 }
上面兩段代碼中 n 是問題的規模。
第一個算法執行次數為:1 + n+1 + n = 2n+2
隨著問題規模n的增加,執行次數以線性增加,所以時間復雜度為O(n)
第二個算法執行次數為:1 + 1 = 2
隨著問題規模n的增加,執行次數始終為2,所以時間復雜度為O(1)
所以時間復雜度計算規則為:
1.如果執行的次數是常數,與規模n無關,則為O(1)
2.如果執行的次數是多次多項式,取最高項,如:n^3 + n^2 則為O(n^3)
3.如果執行的次數是含有系數和常數,可以忽略系數和常數,如: 2n^2 + 3 則為O(n^2)
有了上面的法則,來分析下下面這些算法的時間復雜度,n均代表問題規模。
1 #define n 10
2 int multiMatrix(int a[n][n],b[n][n],c[n][n]){
3 for(i=0;i<=n;i++) #執行了n+1次
4 for(j=0;j<n;j++){ #執行了(n+1)*n次
5 c[i][j] = 0; #執行了n^2
6 for(k=0;k<n;k++) #執行了n^2*(n+1)次
7 c[i][j] = c[i][j] +a[i][k] * b[k][i]; #執行了n^3
8
9 }
10 }
T(n) = 2n^3 + 3n^2 + 2n + 1 所以時間復雜度為O(n^3)
1 int fun(){
2 i=1;n=100;
3 while(i<n){
4 i = i * 2;
5 }
6 }
假設上面的算法執行了x次後停止。則有 2^x >= n,所以 x = log(2)n,所以時間復雜度為O(logn)
1 void prime(n){
2 i=2;
3 while( (n%i)!=0 && i*1.0<sqrt(n) ) #如果不能被整除,且小於根號n,則繼續執行
4 i++;
5
6 if(i*1.0>sqrt(n))
7 printf("%d是個素數\n",n);
8 else
9 printf("%d不是素數\n",n);
10
11 }
函數作用判斷n是否為素數,原理就是遍歷開區間(2,n-1)間的數,然後看看遍歷的數能否被整除。
事實上如果 n^1/2 (根號n)前不存在一個數能被n整除,那麽n^1/2(根號n)之後的數也不會被n整除
這是因為對稱性。因此只需要判斷 n^1/2(根號n)之前的數字是否存在一個能被n整除的數即可。
所以隨著問題規模n的增加,執行的次數增長率與 f(n)=n^1/2 的漸進增長率相同,所以T(n)=O(n^1/2)
後面還有幾道具有挑戰的題目,這裏先講講空間復雜度:
算法的空間復雜度
指算法對運算所需要的輔助工作單元和存儲為實現計算所需信息輔助性的空間大小,記作:
S(n) = O(f(n));
這個大小不包括輸入數據占用的空間,大小是由具體的實際問題來決定的。
下面一個例子:判斷三個數字中,哪個數最大
1 max(int a,b,c){
2 if(a>b){
3 if(a>c)return a;
4 else return c;
5 }
6 else{
7 if(b>c)return b;
8 else return c;
9 }
10 }
隨著問題規模增大,執行的次數也增大,時間復雜度為O(n)
1 max(int a[3]){
2 c=a[0];
3 for(i=0;i<3;i++)
4 if(a[i]>c)c=a[i];
5
6 return c;
7 }
隨著問題規模增大,執行的次數也增大,時間復雜度也為O(n)
上面兩個例子時間復雜度相同都為O(n),但第一個算法無需額外的空間,而第二個算法需要兩個變量輔助。
所以第一個算法空間復雜度優於第二個算法,但是當問題規模不是3 而是100的時候,相信沒人會用第一個
算法,因為會寫到手殘。
歡迎各位裝載,但請指明出處:http://www.cnblogs.com/demonxian3/p/7075441.html
最後來幾個難度高的時間復雜度題,有幾個我沒有做出來,做出來的大神留個言發表下答案,再此謝謝各位了。
選擇題
1 int fun(int n){
2 i=1,s=1;
3 while(s<n)
4 s += ++i;
5
6 return i;
7 }
A. O(n/2) B. O(logn) C. O(n) D.O(n^1/2)
for(int i=0;i<m;i++)
for(int j=0;j<n;i++)
a[i][j] = i*j;
A. O(m^3) B. O(n^2) C. O(m*n) D.O(m+n)
計算題,設n為整數求下面的時間復雜度
1.
1 i=1,j=0;
2 while(i+j<n)
3 if(i>j) j=j+1;
4 else i=i+1;
2.
1 x=91,y=100;
2 while(y>0)
3 if(x>100){
4 x=x-10;
5 y=y-1;
6 }
7 else
8 x=x+1;
3.
1 x=n
2 while(x>=(y+1)*(y+1))
3 y=y+1;
數據結構與算法