算法初級(二)

上一篇:算法初級(一)

問題:

問題:給定序列A1,A2,A3,A4,…,An,求A1An,求A1An的一個任意子序列AiAj,使得AiAj的和最大。例如:整數序列【-2,11,-4,13,-5,2,-5,-3,12,-9的最大子序列之和爲21(A2~A9)】

上一篇給出的是窮舉法,我的簡單理解是裏面包含了三層for循環,每個循環遍歷爲N,因此算法的時間複雜度爲O(n³)。

專業說法:

從第十行代碼得出:

i=1ni(ni+1)\sum_{i=1}^{n} i*(n-i+1) = O(n3n^3)

算法二:
窮舉法裏的每個子序列不需要每次都重新計算一次的,假設sum(i,j) 是AiA_iAjA_j的和,那麼sum(i,j+1)= sum(i,j) + Aj+1A_{j+1}
代碼:

public static int maxSub_2(int[] seq) {
		int max = 0;
		int n = seq.length;
		int sum = 0;
		for (int i = 0; i < n; i++) {
			sum = 0;
			for (int j = i; j < n; j++) {
				sum += seq[j];
				if(sum > max)
	            	max = sum;
			}
		}
		return max;
	}

算法複雜度
專業說法:

從第8,9行代碼得出:

i=0n1(ni)\sum_{i=0}^{n-1} (n-i) = n(n+1)2\frac{n(n+1)}{2} = O(n2n^2)
我的簡單理解是裏面包含了兩層for循環,每個循環遍歷爲N,因此算法的時間複雜度爲O(n2n^2)。

算法三(分治法):
把當前數組分開成2部分,前半部分【-2, 11, -4, 13, -5】,後半部分【2, -5, -3, 12, -9】。這裏可以看出前半部分最大子序列之和是20(11-4+13),後半部分最大子序列之和是12(12)。這時候再合併起來看,最大子序列之和應該在這些元素之間產生,那就把中間元素加進去,前半部分爲(20-5)15,後半部分爲(2-5-3+12)6,那最大子序列之和就是15+6=21.

public static int maxSum(int[] seq,int left,int right) {
		if(left == right) 
			if(seq[left] > 0)
				return seq[left];
			else
				return 0;
			
		int mid = (left+right)/2;
		int maxLeftSum = maxSum(seq,left,mid);
		int maxRightSum = maxSum(seq,mid+1,right);
		int maxLeftBorderSum = 0,leftBorderSum = 0;
		for (int i = mid; i >= left; i--) {
			leftBorderSum +=  seq[i];
			if(leftBorderSum > maxLeftBorderSum) {
				maxLeftBorderSum = leftBorderSum;
			}
		}
		int maxRightBorderSum = 0,rightBorderSum = 0;
		for (int i = mid+1; i < right; i++) {
			rightBorderSum +=  seq[i];
			if(rightBorderSum > maxRightBorderSum) {
				maxRightBorderSum = rightBorderSum;
			}
		}
		return max3(maxLeftSum, maxRightSum, maxLeftBorderSum+maxRightBorderSum);
		
	}
	
	public static int max3(int a,int b ,int c ) {
        int max = a>b?a:b;
        max = max > c? max:c;
        return max;
	}
	
	public static int maxSub_3(int[] seq) {
		return maxSum(seq, 0, seq.length-1);
	}

算法複雜度:
假設T(n)是求解大小爲n的最大子序列和問題所花費的時間,那麼如果當n=1時,T(1)只執行:

if(left == right) 
		if(seq[left] > 0)
			return seq[left];
		else
			return 0;

那麼T(1) = 1.否則就是調用後面的兩個遞歸:

		int maxLeftSum = maxSum(seq,left,mid);
		int maxRightSum = maxSum(seq,mid+1,right);

分別求前後兩部分。這裏我們特殊一下,當n爲偶數,那麼問題就變成了求兩個序列的最大子序列和的問題且每個序列的長度爲n/2,故總時間爲2T(n/2);其中兩個for循環:

for (int i = mid; i >= left; i--) {
			leftBorderSum +=  seq[i];
			if(leftBorderSum > maxLeftBorderSum) {
				maxLeftBorderSum = leftBorderSum;
			}
		}
for (int i = mid+1; i < right; i++) {
			rightBorderSum +=  seq[i];
			if(rightBorderSum > maxRightBorderSum) {
				maxRightBorderSum = rightBorderSum;
			}
		}

循環共執行n次,因此時間複雜度爲O(n)。因此整體時間複雜度:2T(n/2)+O(n)得到方程組:
T(1)=1;T(n)=2T(n/2)+O(n).
爲了簡化計算,用n代替O(n),由於最終T(n)最終還是要用大O表示,因此並不影響最終的答案。
這樣T(n)=2T(n/2)+n且T(1)=1;則:T(2)=4=22,T(4)=12=43,T(8)=32=84,T(16)=80=165…用類推法,當n=2k2^k,則:
T(n)=n*(K+1)=nlog2n\log_2n+n=O(nlog2n\log_2n)
因此算法三的時間複雜度爲:O(nlog2n\log_2n)。
附:該算法實現代碼比較多,當時當n較大時,與前兩個相比,彼此代碼的運行速度是不一樣的。如下:

	public static int[] randomCommon(int min, int max, int n){  
	    if (n > (max - min + 1) || max < min) {  
	           return null;  
	       }  
	    int[] result = new int[n];  
	    int count = 0;  
	    while(count < n) {  
	        int num = (int) (Math.random() * (max - min)) + min;  
	        boolean flag = true;  
	        for (int j = 0; j < n; j++) {  
	            if(num == result[j]){  
	                flag = false;  
	                break;  
	            }  
	        }  
	        if(flag){  
	            result[count] = num;  
	            count++;  
	        }  
	    }  
	    return result;  
	}  
	
	public static void main(String[] args) {
		int[] reult1 = randomCommon(-1000,1000,1000);  
//	    for (int i : reult1) {  
//	        System.out.println(i);  
//	    }  
		long record = System.currentTimeMillis();
		System.out.println(maxSub_1(reult1));
		System.out.println(System.currentTimeMillis()-record);
		record = System.currentTimeMillis();
		System.out.println(maxSub_2(reult1));
		System.out.println(System.currentTimeMillis()-record);
		record = System.currentTimeMillis();
		System.out.println(maxSub_3(reult1));
		System.out.println(System.currentTimeMillis()-record);
	}
}

randomCommon是一個隨機生成不重複數組的方法,不打印我都不知道它的順序,不過我們可以知道它的最大子序列之和和運行時間。

21162
519
21162
5
21162
1
22359
520
22359
6
22359
0

這是我運行兩次的結果。可以看到當n=1000時,O(n3n^3)的結果是519和520,O(n2n^2)是5和6,O(nlog2n\log_2n)的結果是1和0。
算法四:
動態規劃的思想【百度百科】:動態規劃(dynamic programming)是運籌學的一個分支,是求解決策過程(decision process)最優化的數學方法。20世紀50年代初美國數學家R.E.Bellman等人在研究多階段決策過程(multistep decision process)的優化問題時,提出了著名的最優化原理(principle of optimality),把多階段過程轉化爲一系列單階段問題,利用各階段之間的關係,逐個求解,創立了解決這類過程優化問題的新方法——動態規劃。1957年出版了他的名著《Dynamic Programming》,這是該領域的第一本著作。
代碼:

	public static int maxSub_4(int[] seq) {
		int max = 0;
		int n = seq.length;
		int sum = 0;
		for (int i = 0; i < n; i++) {
			sum+=seq[i];
			if(sum > max)
				max = sum;
			else if(sum < 0)
				sum = 0;
		}
		return max;
	}

因爲只對整個數組只循環一遍因此時間複雜度爲O(n)。
先調整n=10000,看下結果:

	public static void main(String[] args) {
		int n = 10000;
		int[] reult1 = randomCommon(-n,n,n);  
//	    for (int i : reult1) {  
//	        System.out.println(i);  
//	    }  
		long record = System.currentTimeMillis();
		System.out.println(maxSub_1(reult1));
		System.out.println(System.currentTimeMillis()-record);
		record = System.currentTimeMillis();
		System.out.println(maxSub_2(reult1));
		System.out.println(System.currentTimeMillis()-record);
		record = System.currentTimeMillis();
		System.out.println(maxSub_3(reult1));
		System.out.println(System.currentTimeMillis()-record);
		record = System.currentTimeMillis();
		System.out.println(maxSub_4(reult1));
		System.out.println(System.currentTimeMillis()-record);
	}

運行時間稍微有點長。。。

749415
607700
749415
29
749415
1
749415
1

當n=100000時(去掉算法一,時間太長了):

14804002
2047
14804002
17
14804002
2

可以更清楚的看到各代碼的時間差異。
到此,結束了,告辭!
附知識點:

  1. 利用計算機求解問題的實現過程:
    (1)確定問題求解的數學模型(或者邏輯結構):對問題進行深入分析,確定處理的數據對象是什麼,再考慮根據處理對象的邏輯關係給出其數學模型。
    (2)確定存儲結構:根據數據對象的邏輯結果及其所需完成的功能,選擇一個合適的組織形式將數據對象映射到計算機存儲器中。
    (3)設計算法(程序):討論要解決問題的策略,即算法(程序)的確定步驟。
    (4)編程並測試結果
  2. 數據和數據結構
    (1)數據:數據是信息的載體
    (2)數據元素:數據元素是數據中的一個“個體”,是數據的基本組織單位
    (3)數據項:數據項是數據元素的組成部分
    (4)數據對象::數據對象是性質相同的數據元素的集合;一般來說,數據對象的數據元素不會是孤立的,而是彼此關聯的,這種彼此之間的關係稱之爲“結構”。
  3. 數據結構
    (1)邏輯結構:集合結構[除了屬於一個集合外的特性,數據元素之間毫無關係]、線性結構[一對一關係]、樹形結構[一對多關係]、圖形結構[多對多關係]…
    在這裏插入圖片描述
    (2)存儲結構:順序存儲、鏈式存儲、索引存儲、散列存儲…
    (3)數據操作:創建、銷燬、插入、刪除、查找、修改、遍歷…
  4. 數據類型
    一個數據的數據類型描述了三個方面的內容:存儲區域的大小(存儲結構)、取值範圍(數據集合)和允許的操作。
  5. 算法的性質:有窮性、確定性、有效性、輸入、輸出
  6. 算法的目標:正確性、可讀性、健壯性、高效率
  7. 算法時間複雜度排序:
    O(1)<O(log2n\log_2n)<O(n)<O(nlog2n\log_2n)<O(n2n^2)<O(nnn^n)
    指數級:O(ana^n),常見的有O(2n2^n),O(n!n!),O(nnn^n),之間的關係:
    O(2n2^n)<O(n!n!)<O(nnn^n)
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章