找出矩陣中含有0最多的一行(find the longest row of zero)
阿新 • • 發佈:2019-01-05
對於一個n*n的矩陣,其中只包含有0,1兩種元素且,所有的0都在1之前,請找出矩陣中0最多的一行。(Given an N-by-N matrix of 0s and 1s such that in each row no 0 comes before a 1, find the row with the most 0s in O(N) time.)
初看這題,想到的演算法就是每一行都設定一個計數器,記錄每行的0的個數,然後找出最大值即可(暴力解法)。
演算法實現:
演算法中有兩層迴圈,因此演算法的時間複雜度為o(n^2)。這樣的演算法是否還能改進呢?呵呵,當然可以。int* find_the_longest_row_force(int **a, int n) { int* res = new int[2]; res[0] = res[1] = -1; for(int i=0; i<n; i++) { int max = 0; for(int j=0;j<n;j++) { if(a[i][j] == 0) max++; } if(max > res[1] && max!=0) { res[1] = max; res[0] = i+1; } } if(res[0] == -1) res[1] = -1; return res; }
首先由於矩陣中只含有0和1兩個元素,且0在1之前,因此每一行都是排好序的,為此每行都使用二分查詢,找到每行的最後一個0所在的位置,這個位置即為每行0的長度,如此迴圈查詢n行,即可得到最長的一行的位置以及相應的0的長度。
演算法實現
int* find_the_longest_row_search(int** a, int n) { int* res = new int[2]; res[0] = res[1] = -1; for(int i=0; i<n; i++) { int low = 0; int high = n-1; int temp_loc = -1; while(low <= high) { int mid = (low + high)/2; if(a[i][mid] > 0) high = mid -1; else { if(mid > temp_loc) temp_loc = mid; low = mid + 1; } } if(temp_loc > res[1]) { res[1] = temp_loc; res[0] = i+1; } } if(res[0] != -1) res[1] = res[1]+1; return res; }
上述演算法實現的過程中,有一個迴圈,迴圈裡面執行的是二分查詢,時間複雜度為O(logn),總共迴圈n次,因此總的演算法的時間複雜度為O(nlogn)。與暴力演算法相比,有了一定的提高。那這是不是最優的演算法呢?
其實是有的,仔細想想,我們對每一行都進行二分查詢似乎是多餘的,比如說第一行的0的長度為10,那麼我只要看第二行在10的位置是否為0,如果不為0,則這一行的0的個數就小於第一個行,如果大於0,那就繼續在這行往後查詢0。由此可得一個新的演算法。
首先從左上角的第一個元素開始檢視,如果為0,則向右檢視,否則向下檢視,如此迴圈,直到到達矩陣的邊界為止。
演算法實現:
上述演算法實現的過程中,從矩陣的左上角已知查詢到矩陣的右下角,中途只能往右或者往下查詢,因此最多查詢2n次即可找到含有0元素最多的行。為此演算法的時間複雜度為O(n)。int* find_the_longest_row_0(int** a, int n) { int* res = new int[2]; res[0]=-1; res[1] =-1; int i=0,j=0; while (i<n && j<n) { if(a[i][j] == 0) { j++; res[0] = i+1; } else { i++; } } if(res[0] != -1) res[1] = j; return res; }
測試程式碼:
#include <iostream>
#include<stdlib.h>
#include <time.h>
#include <string>
using namespace std;
int* find_the_longest_row_force(int **a, int n);
int* find_the_longest_row_search(int** a, int n);
int** Create_matrix(const int n);
void print(int** a, const int n);
int* find_the_longest_row_0(int** a, int n);
void main()
{
int n;
cout<<"Input the n: (exit 0)";
cin>>n;
while(n>0)
{
int** a = Create_matrix(n);
print(a,n);
const string name[3] = {"Force","Search","Best"};
int* (*(p[3]))(int**,int) = {find_the_longest_row_force,find_the_longest_row_search,find_the_longest_row_0};
int* res;
for(int i=0; i<3; i++)
{
res = p[i](a,n);
cout<<name[i]<<": "<<endl;
cout<<"The longest row is :"<<res[0]<<endl
<<"The length is :"<<res[1]<<endl;
}
delete[] res;
for(int i=0; i<n; i++)
delete[] a[i];
cout<<"Input the n: (exit 0)";
cin>>n;
}
}
輔助函式
int** Create_matrix(const int n)
{
int** a = new int*[n];
for(int i=0; i<n; i++)
{
a[i] = new int[n];
srand(i);
int index = rand()%n;
for(int j=0; j<n; j++)
{
if(j<index)
a[i][j] = 0;
else
a[i][j] = 1;
}
}
return a;
}
void print(int** a, const int n)
{
for(int i=0; i<n; i++)
{
for(int j=0; j<n; j++)
cout<<a[i][j]<<" ";
cout<<endl;
}
}
上述演算法優化的過程中發現,在設計一個問題的解題演算法時,可以先設計出一個暴力演算法,然後再暴力演算法的基礎上看是否存在可以改進的地方(本題主要的改進就是0這個元素的位置查詢方式)。對本題而言,主要耗時就在0的位置的查詢,第一中方法是挨個挨行的查詢,其中存在較多的冗餘,第二種方法減少了沒有必要的查詢,使用了二分查詢,雖說在每行查詢的時間減少了,但是由於沒有充分利用上一次查詢的資訊,造成了一定的冗餘查詢。最後一種方法充分利用了上一行查詢的資訊,使得查詢的效率大幅度提高。