1. 程式人生 > >找出矩陣中含有0最多的一行(find the longest row of zero)

找出矩陣中含有0最多的一行(find the longest row of zero)

對於一個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的個數,然後找出最大值即可(暴力解法)

演算法實現:

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;
}
演算法中有兩層迴圈,因此演算法的時間複雜度為o(n^2)。這樣的演算法是否還能改進呢?呵呵,當然可以。

首先由於矩陣中只含有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,則向右檢視,否則向下檢視,如此迴圈,直到到達矩陣的邊界為止。

演算法實現:

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;
}
上述演算法實現的過程中,從矩陣的左上角已知查詢到矩陣的右下角,中途只能往右或者往下查詢,因此最多查詢2n次即可找到含有0元素最多的行。為此演算法的時間複雜度為O(n)。

測試程式碼:

#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的位置的查詢,第一中方法是挨個挨行的查詢,其中存在較多的冗餘,第二種方法減少了沒有必要的查詢,使用了二分查詢,雖說在每行查詢的時間減少了,但是由於沒有充分利用上一次查詢的資訊,造成了一定的冗餘查詢。最後一種方法充分利用了上一行查詢的資訊,使得查詢的效率大幅度提高。