整數數組的最大等分組數

問題描述:一個整數數組,長度爲n,將其分爲m 份,使各份的和相等,求m 的最大值。
比如{3,2,4,3,6} 可以分成{3,2,4,3,6} m=1;
{3,6}{2,4,3} m=2

{3,3}{2,4}{6} m=3 所以m 的最大值爲3。

      通過調研,找到網上的解決方法主要是如下兩種:

1. http://my.oschina.net/dapengking/blog/92183

“算法原理的思想是將大問題轉換成小問題。就{3,2,4,3,6}的操作步驟:
      第一步:想將數組遞減排序得{6,4,3,3,2},求出數組中所有數的和m=18,第一個最大的數b=6, m/b=3餘數爲0,當除數爲1,餘數爲0時終止。當餘數不爲0時,轉到第三步。當餘數爲0時將數組劃分爲{6},{4,3,3,2}兩個。把{4,3,3,2}看成一個新的數組。
      第二步:先用{4,3,3,2}中的最大數與b=6比較,即4<b,所以再將4與最右邊的數即2相加與b比較,結果相等,則將這兩個數從該數組中除去生成新的數組,轉到第一步,現在的結果是{6},{4,2},{3,3},把{3,3}看成一個新的數組繼續重複第二步。
      第三步,將數組中最大的數與最小的數取出構成一個新數組Z,剩餘的構成一個數組,然後,判斷m/Z中數字之和看是否餘數爲0,若爲0,把b替換爲Z中數字之和轉第二步,若不爲0,繼續從剩餘的數字中取出最小值加入到Z中,再判斷m/Z中數字之和看是否餘數爲0,直到爲0,轉第二步爲止。最後得到的結果是{6},{4,2},{3,3} 這時可以計算出m爲3,也可以在程序中作記載。在第二步工程過,若出現兩個數相加大於上一次的b,則將程序轉到第三步。”

      具體代碼爲:

#include <iostream>
#include <queue>
 
using namespace std;
 
int partition(int *index,int begin,int end){
    int i = begin;
    int j = end;
    int temp = 0;
    while(i < j){
        while((i < j) && (index[i] >= index[j])){
            --j;
        }
        if(i < j){
            temp = index[i];
            index[i] = index[j];
            index[j] = temp;
        }
        while((i < j) && (index[i] > index[j])){
            ++i;
        }
        if(i < j){
            temp = index[i];
            index[i] = index[j];
            index[j] = temp;
        }
    }
    return i;
}
 
//快速排序,按遞減順序
void quickSort(int *index,int begin,int end){
    if(begin >= end){
        return;
    }
    int p = partition(index,begin,end);
    quickSort(index,begin,p-1);
    quickSort(index,p+1,end);
}
 
bool findPartition(int *index,int length){
    quickSort(index,0,length-1);//快速排序,按遞減順序
    int sum = 0;
    for(int i = 0 ; i < length ; ++i){
        cout << index[i] << " ";
        sum += index[i];
    }
    cout << endl;
    int mMax = length;
    for(int i = mMax ; i > 1 ; --i){
        if(0 == sum % i){
            int aSum = sum / i;
            int pBegin = 0;//初始化數組索引,指向頭
            int pEnd = length - 1;//初始化數組索引,指向尾
            queue<int> q;
            int j = 0;
            for(j = i ; j > 0 ; --j){//找到mMax個組中,每個組的元素組成情況
                if(aSum == index[pBegin]){
                    q.push(index[pBegin]);
                    q.push(-1);
                    ++pBegin;
                }else if(aSum > index[pBegin]){
                    q.push(index[pBegin]);
                    int temp = index[pBegin];
                    ++pBegin;
                    while(temp < aSum){
                        temp += index[pEnd];
                        q.push(index[pEnd]);
                        --pEnd;
                    }
                    if(temp == aSum){
                        q.push(-1);
                    }else{
                        break;
                    }
                }else{
                    //不確定是否應該清空queue
                    break;
                }
            }
            if(j == 0){
                while(!q.empty()){
                    if(-1 != q.front()){
                        cout << q.front() << " ";
                        q.pop();
                    }else{
                        cout << endl;
                        q.pop();
                    }
                }
                cout << "mMax:" << i << endl;
                return true;
            }
        }
    }
    return false;
}
 
int main(){
    int index[] = {3,2,4,3,6};
    //int index[] = {3,3,3,3,3};
    findPartition(index,sizeof(index)/sizeof(int));
    return 1;
}

2. http://blog.csdn.net/v_july_v/article/details/6870251

“初始值m從n開始,依次遞減測試;數組的和爲sum,若sum%m的值不爲0,則直接跳過
對於符合sum%m = 0的每個m,掃描數組中每個元素,若該元素的狀態爲未選,將其分配到相應組
(1) 若當前組元素的和大於 sum/m,表明當前元素不適合該組,將其狀態(aux[i])置爲0
(2) 若當前組元素的和等於 sum/m, 將組號加1,繼續進行下一組的判斷
(3) 若當前組元素的和小於 sum/m,將當前加入的元素置爲已選狀態(aux[i]的值設爲當前組號),繼續判斷下一個元素加入加入當前組的情況”

      具體代碼爲:

    #include <cstdio>  
    #include <cstdlib>  
      
    #define NUM 10  
      
    int maxShares(int a[], int n);  
      
    //aux[i]的值表示數組a中第i個元素分在哪個組,值爲0表示未分配  
    //當前處理的組的現有和 + goal的值 = groupsum  
    int testShares(int a[], int n, int m, int sum, int groupsum, int aux[], int goal, int groupId);  
      
    int main()  
    {  
        int a[] = {2, 6, 4, 1, 3, 9, 7, 5, 8, 10};  
      
        //打印數組值  
        printf("數組的值:");  
        for (int i = 0; i < NUM; i++)  
            printf(" %d ", a[i]);  
      
        printf("\n可以分配的最大組數爲:%d\n", maxShares(a, NUM));  
      
        system("pause");  
        return 0;  
    }  
      
    int testShares(int a[], int n, int m, int sum, int groupsum, int aux[], int goal, int groupId)   
    {  
        if (goal < 0)  
            return 0;  
      
        if (goal == 0)  
        {  
            groupId++;  
            goal = groupsum;  
      
            if (groupId == m+1)       
                return 1;  
        }  
      
        for (int i = 0; i < n; i++)   
        {  
            if (aux[i] != 0)  
                continue;  
      
            aux[i] = groupId;  
            if (testShares(a, n, m, sum, groupsum, aux, goal-a[i], groupId))   
                return 1;  
      
            aux[i] = 0;             //a[i]分配失敗,將其置爲未分配狀態  
        }  
      
        return 0;  
    }  
    int maxShares(int a[], int n)  
    {  
        int sum = 0;  
        int *aux = (int *)malloc(sizeof(int) * n);            
      
        for (int i = 0; i < n; i++)   
            sum += a[i];  
      
        for (int m = n; m >= 2; m--)   
        {  
            if (sum%m != 0)   
                continue;  
      
            for (int i = 0; i < n; i++)   
                aux[i] = 0;  
      
            if (testShares(a, n, m, sum, sum/m, aux, sum/m, 1))  
            {  
                //打印分組情況  
                printf("\n分組情況:");  
                for (int i = 0; i < NUM; i++)  
                    printf(" %d ", aux[i]);  
      
                free(aux);  
                aux = NULL;  
                return m;  
            }  
        }  
      
        free(aux);  
        aux = NULL;  
        return 1;  
    }  

       通過分析這兩種算法的代碼,可以看到第一種方法有點像貪心的思想,每次都將最小的整數加入當前組,直到找到解,或者在找不到解時便將組數減1後重新尋找。而第二種方法則窮舉遍歷,無解時則回溯。

       實際上,第一種方法是錯誤的。因爲貪心找不到解並不代表沒有解。比如輸入“6,4,1,2,3,2”時,運行第一種方法的代碼,得到的m=2,而實際上很明顯m=3([6],[4,2],[1,3,2])。我們看一下第一種方法執行的過程:

       首先排序得到“6,4,3,2,2,1”。取m=3時,首先6直接成一組。然後取4,再從右邊最小的數開始取1,再取2的時候已經超過6,所以認爲不成立,m--。但是,4並非一定要從最小的整數開始組合,而可以直接取2組成6。所以這種方法是錯的。

       第二種方法中的組數其實並不用從NUM開始取,可以從sum/max開始取,可以部分減小計算量。基於此,我也試着寫了一下整數數組最大等分組數的代碼:

#include <vector>
#include <iostream>
#include <string>
using namespace std;
bool verify(vector<int> &vc, int used[], int group_num, int group_size, int rest_size, int remain, int group_id){   // group_num表示組數,group_size表示每組的和,rest_size表示每組的剩餘空間,remain表示整個數組的剩餘空間,group_id表示當前組的編號
	if(group_id == group_num+1 && remain == 0){   // 當前組編號=組數+1,並且整個數組剩餘空間爲0,迭代完畢,找到解(迭代的終止條件)
		return true;
	}
	for(vector<int>::size_type i=0; i<vc.size(); i++){
		if(used[i] == 0){   // 遍歷未使用的數字
			if(vc[i] ==rest_size){   // 當前數字等於當前組剩餘空間
				used[i] = group_id;
				group_id++;   // 當前組分配完畢,group_id加1
				if(verify(vc, used, group_num, group_size, group_size, remain-vc[i], group_id)){   // 繼續往後分配,進入下一組,因此當前組剩餘空間置爲group_size,整個數組剩餘空間remain-vc[i]
					return true;   //   後面分配成功,則當前步也正確
				}
				used[i] = 0;   //    後面分配不成功,則當前步也不正確,重置爲0
			}
			else if(vc[i] <rest_size){   // 當前數字小於當前組剩餘空間
				used[i] = group_id;
				if(verify(vc, used, group_num, group_size, rest_size-vc[i], remain-vc[i], group_id)){   // // 繼續往後分配,仍在當前組,當前組剩餘空間置爲rest_size-vc[i],整個數組剩餘空間remain-vc[i]
					return true;    //   後面分配成功,則當前步也正確
				}
				used[i] = 0;   //    後面分配不成功,則當前步也不正確,重置爲0
			}
		}
	}
	if(group_id <= group_num){   //   當所有未使用的數字都大於當前組的剩餘空間時,沒有解,終止當前迭代
		return false;
	}
}

int FindMax(vector<int> &vc, int sum, int max){
	int group_num;
	int *used = new int[vc.size()];   // 記錄數組中的每個數的狀態,0表示還沒有使用,其他數字表示分到組的編號
	memset(used, 0, vc.size()*4);   // 初始化
	for(group_num=sum/max; group_num>0; group_num--){   // 組數不用從vc.size()開始計算,實際上從sum/max即可
		if(sum%group_num == 0){
			if(verify(vc, used, group_num, sum/group_num, sum/group_num, sum, 1)){   // 迭代
				for(vector<int>::size_type i=0; i<vc.size(); i++){   // 成功,則輸出結果
					cout<<used[i]<<'\t';
				}
				cout<<endl;
				delete []used;
				return group_num;
			}
		}
	}
}

int main()
{
	int val;
	int sum = 0, max = -1;
	vector<int> vc;
	while(cin>>val){
		vc.push_back(val);
	}
	for(vector<int>::size_type i=0; i<vc.size(); i++){
		sum += vc[i];   // 數組和
		if(vc[i]>max){
			max = vc[i];   // 數組中最大的數
		}
	}
	for(vector<int>::iterator it=vc.begin(); it!=vc.end(); it++){
		cout<<*it<<'\t';
	}
	cout<<endl;
	cout<<FindMax(vc, sum ,max);   // 找到最大的組數
	return 0;
}


發佈了9 篇原創文章 · 獲贊 32 · 訪問量 12萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章