第四章:多維陣列和矩陣 ------------- 4.4 找出邊界為1的最大子方陣

Curtis_發表於2019-03-17

找出邊界為1的最大子方陣:

1、未優化的題解:(複雜度較高)

//虛擬碼
max(A,N){
	int n=N;
	while(n>0){
		for(i from 0 to N-1){
			if(i+n>N) break;
			
			l3: 
			for(j from 0 to N-1){
				if(j+n>N) break; 
				
				//i,j就是頂點
				r=i;
				c=j;
				
				//上面的邊 
				while(c<j+n){
					if(A[r][c]==0) continue l3;
					c++;
				}
				c--;
				//右邊的邊 
				while(r<i+n){
					if(A[r][c]==0) continue l3;
					r++;
				} 
				r--;
				//下邊的邊
				while(c>=j){
					if(A[r][c]==0) continue l3;
					c--;
				} 
				c++;
				//左邊的邊
				while(r>=i){
					if(A[r][c]==0) continue l3;
					r--;
				} 
				//r++;--------- 無需恢復 
				return n;
			}	 	
		}		
		n--;
	}
	return 0;
}

程式碼:

#include<iostream>
#include<vector>
using namespace std;

int solve(vector<vector<int> > A){
	int N=A.size();
	int n=N;
	while(n>0)
	{
		for(int i=0;i<N;i++)
		{
			if(i+n>N) break;			
			//第二層迴圈: 
			int j=0;
			l3:
			while(j++<N){
				if(j+n>N) break; 
				
				//檢查四條邊 
				
				//i,j就是頂點
				int r=i,c=j;
				
				//上面的邊 
				while(c<j+n)
				{
					if(A[r][c++]==0) goto l3;
				}
				c--;
				//右邊的邊 
				while(r<i+n)
				{
					if(A[r++][c]==0) goto l3;
				} 
				r--;
				//下邊的邊
				while(c>=j)
				{
					if(A[r][c--]==0) goto l3;
				} 
				c++;
				//左邊的邊
				while(r>=i)
				{
					if(A[r--][c]==0) goto l3;
				} 
				//r++;--------- 無需恢復 
				return n;				
			}				
		}		
		n--;
	}
	return 0;
}


int main()
{
	vector<vector<int> > arr;
	int row=5,col=5;
	//二維向量賦初值 
	int i=0,j=0;
	//賦初值 
	vector<int> temp;
	//第一行 
	temp.push_back(0);
	temp.push_back(1);
	temp.push_back(1);
	temp.push_back(1);
	temp.push_back(1);
	arr.push_back(temp);
	temp.clear();
	//第二行 
	temp.push_back(0);
	temp.push_back(1);
	temp.push_back(0);
	temp.push_back(1);
	temp.push_back(0);
	arr.push_back(temp);
	temp.clear();
	//第三行 
	temp.push_back(0);
	temp.push_back(1);
	temp.push_back(1);
	temp.push_back(1);
	temp.push_back(1);
	arr.push_back(temp);
	temp.clear();
	//第四行 
	temp.push_back(0);
	temp.push_back(1);
	temp.push_back(1);
	temp.push_back(1);
	temp.push_back(1);
	arr.push_back(temp);
	temp.clear();
	//第五行 
	temp.push_back(0);
	temp.push_back(1);
	temp.push_back(0);
	temp.push_back(1);
	temp.push_back(1);
	arr.push_back(temp);
	temp.clear();
	
	//列印二維向量	
	for(int i=0;i<row;i++)
	{
		for(int j=0;j<col;j++)
		{
			cout<<arr[i][j]<<" ";		
		}		
		cout<<endl;
	}	
	cout<<endl;
	
	cout<<solve(arr);
	return 0; 
} 

continue:結束本次迴圈,不執行迴圈體內continue下面的語句,繼續下次迴圈。

break:終止整個迴圈體,無論多少層。

goto:與標誌聯用,容易造成程式的混亂。 

 結果:

2、邊界為1的最大子方陣優化:

①、可以通過對原有矩陣進行預處理的方式,這個思路類似於動態規劃中的打表法

②、我們可以構建一個三維陣列,二維平面空間和矩陣大小相同,第三維儲存兩個元素,第一個元素儲存包括自己以及自

己右方連續的1的個數,如果自己為0,那麼自己就儲存為0。第二個元素儲存包括自己以及自己下方連續的1的個數,如果

自己為0,那麼自己也儲存為0。

③、通過生成這個輔助表,就可以在判斷是否是最大方陣的時候加快判斷速度,可以將4個while迴圈去除,而通過這個輔助

表只需判斷一次即可,時間複雜度為O(1),那麼整個演算法的時間複雜度就為N*N*N了,為O(N3)。

假如矩陣為:
-----------------------------
{ 1, 1, 0, 1 }
{ 1, 1, 1, 1 }
{ 1, 1, 0, 1 }
{ 1, 1, 1, 1 }
-----------------------------
生成的輔助陣列為:
-----------------------------
2,4    1,4    0,0    1,4    
4,3    3,3    2,1    1,3    
2,2    1,2    0,0    1,2    
4,1    3,1    2,1    1,1    
----------------------------- 

程式碼: 

public class MaxSquare_1 {
    public static void main(String[] args) {
//        int[][] A = { 
//                { 0, 1, 1, 1, 1 },
//                { 0, 1, 0, 1, 0 }, 
//                { 0, 1, 1, 1, 1 }, 
//                { 0, 1, 1, 1, 1 }, 
//                { 0, 1, 0, 1, 1 } 
//            };
        int [][]A = new int[][] { 
            { 1, 1, 0, 1 },
            { 1, 1, 1, 1 }, 
            { 1, 1, 0, 1 }, 
            { 1, 1, 1, 1 }, 
        };
        generateHelpRec(A);
        System.out.println("生成的輔助陣列為:");
        System.out.println("=============================");
        print(rec, A.length);
        System.out.println("=============================");
        System.out.println("最大子方陣的邊長為:"+solve(A));
    }
    
    private static void print(int[][][] rec, int N) {
        for (int i = 0; i < N; i++) {
          for (int j = 0; j < N; j++) {
            System.out.print(rec[i][j][0] + "," + rec[i][j][1] + "\t");
          }
          System.out.println();
        }
      }
    
    static int [][][]rec;
    /**
     * 記錄每個元素往右和往下有多少個連續的1
     * @param A
     */
    private static void generateHelpRec(int[][] A) {
        int N = A.length;
        rec = new int[N][N][2];
        int row = N-1;
        // 初始化最後一行,因為記錄的是往右和往下的,所以必須從最後一行向上初始化,而且從右向左初始化
        for(int j=N-1;j>=0;j--){
            int value = A[row][j];
            if (value==1) {
                if (j==N-1) { // 避免陣列越界
                    rec[row][j][0] = 1; // 右邊界
                }else {
                    // A的元素值為1,rec在這個位置的連續1的個數 = 右邊位置的連續的1的個數+1
                    rec[row][j][0] = rec[row][j+1][0] + 1;
                }
                // 最後一行的下方的1的連續數==1
                rec[row][j][1] = 1;
            }
        }
        row--; //開始初始化倒數第二行到第一行
        for(int i = row;i>=0;i--){
            for (int j = N-1; j>=0; j--) {
                int value = A[i][j];
                // 利用右邊和下邊已經生產的資料來推出現在這個位置上右側和下方有多少個1
                if(value==1){
                    if (j==N-1) {
                        rec[i][j][0] = 1;// 右側連續1的個數
                    }else {
                        rec[i][j][0] = rec[i][j+1][0]+1;
                    }
                    
                    rec[i][j][1] = rec[i+1][j][1] + 1; // 向下連續1的個數
                }
            }
        }
    }

    // O(n3)
    static int solve(int [][]A){
        int N = A.length;
        int n = N;
        while(n>0){
            for (int i = 0; i < N; i++) {
                if (i+n>N) break;
                l3:  // 這個語言相當於給第二層for迴圈命令  下面程式碼中的continue繼續的是第二層的for迴圈,不是while迴圈
                for (int j = 0; j < N; j++) {
                    if (j+n>N) break;
                    if (check(i, j, n))
                        return n;
                }
            }
            n--;
        }
        return 0;
    }

    // O(1)
    private static boolean check(int i, int j, int n) {
        // 左上角那個點往右數的1的數目要大於等於n
        // 左上角那個點往下數的1的數目要大於等於n
        // 右上角那個點往下數的1的個數要大於等於n
        // 左下角那個點往右數的1的個數要大於等於n
        if (rec[i][j][0]>=n&&rec[i][j][1]>=n&&rec[i][j+n-1][1]>=n&&rec[i+n-1][j][0]>=n) {
            return true;
        }
        return false;
    }
    
    
}

結果:

小結:

計算優化過後的時間複雜度為: 生成輔助陣列所花時間N2+後面迴圈遍歷時間N3,加起來整個演算法的時間複雜度就

為O(N3)了,那這個演算法的效率就比優化前的演算法的效率好太多了。 

第二種方法轉自:https://www.cnblogs.com/xiaoyh/p/10293686.html 

相關文章