分治法求眾數和重數(含檔案輸入輸出)

mie haha發表於2019-03-15

【mie haha的部落格】轉載請註明出處(萬分感謝!):
https://blog.csdn.net/qq_40315080/article/details/88580999

(可以列出所有眾數)
程式碼:

#include<iostream>
#include<algorithm> 
#include<vector> 
using namespace std;
int mode=0,multiplicate=0;
vector<int> allmode;
void get_mode_and_multiplicate(int a[],int start,int end)
{
	if(end==start)return;//遞迴結束條件:當待考察序列只有一個元素,終止檢視 
	int mid=(start+end)/2;
	int newmode=0,newmultiplicate=0,left=mid,right=mid,leftflag=1,rightflag=1;//次輪發現的眾數個重數
	for(int i=mid-1;i>=start;i--)
	{
		if(a[i]!=a[mid])
		{
			left=i;leftflag=0;break;//找到從中間數向左邊數第一個不相同的數 
		}
	}
	for(int i=mid+1;i<=end;i++)
	{
	    if(a[i]!=a[mid])
		{
			right=i;rightflag=0;break;//找到從中間數向右邊數第一個不相同的數  
		}	
	} 
	if(leftflag==1)left=start;
	if(rightflag==1)right=end;
	//cout<<"left:"<<left<<" "<<"right:"<<right<<" ";
	newmode=a[mid];newmultiplicate=right-left-1;//次輪得到的眾數候選重數候選
	if(leftflag==1&&rightflag==1)newmultiplicate+=2;
	else if(leftflag==1||rightflag==1)newmultiplicate+=1;
	//cout<<"nowmode:"<<newmode<<" "<<"nowmultiplicate:"<<newmultiplicate<<endl;
	if(newmultiplicate>multiplicate)//如果得到的新眾數重數更大,更新眾數和重數 
	{
		multiplicate=newmultiplicate;mode=newmode;allmode.push_back(mode);
	} 
	else if(newmultiplicate==multiplicate)allmode.push_back(newmode);
	get_mode_and_multiplicate(a,start,left);//繼續在左邊的待查陣列裡遞迴 
	get_mode_and_multiplicate(a,right,end);//繼續在右邊遞迴 
}
int main()
{
    int n;
	cin>>n;
	int a[n];
	for(int i=0;i<n;i++)cin>>a[i];
	sort(a,a+n);
	get_mode_and_multiplicate(a,0,n-1);
	cout<<"最終結果:"<<endl;
	if(multiplicate<=1)
	{
		cout<<"沒有眾數,所有元素都只出現了1次"<<endl;
		vector<int>().swap(allmode);//儲存的數並不是眾數已經沒有用了,當資料量過大,釋放vector記憶體 
	} 
	else if(multiplicate>1) 
	{
		cout<<"共有"<<allmode.size()<<"個眾數"<<endl;
		cout<<"為:";
		for(int i=0;i<allmode.size();i++)cout<<allmode[i]<<" "; 
	}
	return 0;	 
} 

執行結果:
在這裡插入圖片描述
在這裡插入圖片描述
思路:
首先對陣列進行排序,這樣相同值的元素就挨在了一起,通過對某個元素進行向左向右檢視是否有元素相同,就可判斷當前元素共出現了多少次。不斷地劃分陣列,每次都找到陣列中的1個元素作為候選眾數,以該元素出現次數為候選重數,把候選重數與當前儲存的重數比較,如果更大,則更新當前儲存的眾數和重數,如果一樣大說明目前有出現次數一樣的眾數,把它存入vector中儲存起來。每次劃分陣列長度都減半,要處理的陣列越來越短,直至陣列裡只有1個元素就結束排查了。(其實就是用了遞迴的方法)
(每次對半分讓子問題規模儘量相等時間複雜度最小,也體現了分治法的思想)

步驟:
1.使用sort函式平均情況下時間複雜度只有nlogn(最差情況就比較差了,為n²)
2.首先判斷一下當前待處理的陣列需不需要處理,如果只有1個元素,肯定不是眾數,不用再操作了直接結束。如果元素個數>1,則開始處理:
首先,找到當前待處理陣列的中間值a[(start+end)/2]作為此次排查中的候選眾數,該值出現的次數為候選重數。
左右兩個迴圈來找到兩邊最近的與它不同的元素,標記一下兩側的不同元素的位置,則從標記處分成了左右新的兩個短陣列。
然後,此時兩側標記相減(不是單純相減,有一點誤差,自己舉個例子算一下就可以調整了,不同情況下+1/-1/不變,程式碼中有註釋)就是此輪的候選重數。
最後,比較候選重數和當前儲存的重數(全域性變數)進行判斷:
(1)如果候選重數與其一樣大,把當前這個數儲存到vector中;
(2)如果候選重數更大,說明之前儲存的眾數都無效了,因為它們不是出現最多的數,∴清空vector,再將當前這個數儲存到vector中;
(3)如果候選重數小,不用管,當前儲存的數仍是目前出現最多的數;
3.對分出的左右兩個陣列繼續2的操作,即判斷陣列中間位置的數是不是眾數的操作,用遞迴來實現。

(檔案型輸入輸出)程式碼:

#include<iostream>
#include<algorithm> 
#include<vector> 
#include<fstream>//用於檔案輸入輸出 
using namespace std;
int mode=0,multiplicate=0;
vector<int> allmode;
void get_mode_and_multiplicate(int a[],int start,int end)
{
	if(end==start)return;//遞迴結束條件:當待考察序列只有一個元素,終止檢視 
	int mid=(start+end)/2;
	int newmode=0,newmultiplicate=0,left=mid,right=mid,leftflag=1,rightflag=1;//次輪發現的眾數個重數
	//找到從中間數向左邊數第一個不相同的數
	for(int i=mid-1;i>=start;i--)
	{
		if(a[i]!=a[mid])
		{
			left=i;leftflag=0;break; 
		}
	}
	//找到從中間數向右邊數第一個不相同的數 
	for(int i=mid+1;i<=end;i++)
	{
	    if(a[i]!=a[mid])
		{
			right=i;rightflag=0;break; 
		}	
	} 
	if(leftflag==1)left=start;//如果左邊找不到不相同的數,就只好賦值為當前陣列左起點 
	if(rightflag==1)right=end;//如果右邊找不到不相同的數,就只好賦值為當前陣列右終點
	newmode=a[mid];newmultiplicate=right-left-1;//次輪得到的眾數候選重數候選
	if(leftflag==1&&rightflag==1)newmultiplicate+=2;
	else if(leftflag==1||rightflag==1)newmultiplicate+=1;
	if(newmultiplicate>multiplicate)//如果得到的新眾數重數更大 
	{
		multiplicate=newmultiplicate;mode=newmode; //更新重數和眾數 
		allmode.clear();allmode.push_back(mode);//原來vector中儲存的眾數失效,清空存入新值 
	} 
	else if(newmultiplicate==multiplicate)allmode.push_back(newmode);
	get_mode_and_multiplicate(a,start,left);//繼續在左邊的待查陣列裡遞迴 
	get_mode_and_multiplicate(a,right,end);//繼續在右邊遞迴 
}
int main()
{
	ifstream infile("input.txt",ios::in);  
	
	//讀取元素個數n 
    int n;
	infile>>n;
	
	//讀取元素 
	int a[n];
	for(int i=0;i<n;i++)infile>>a[i];
	
	sort(a,a+n);
	get_mode_and_multiplicate(a,0,n-1);
	
	ofstream outfile("output.txt",ios::out);
	if(multiplicate<=1) 
	{
		outfile<<"沒有眾數,所有元素都只出現了1次"<<endl;
		vector<int>().swap(allmode);//儲存的數並不是眾數已經沒有用了,當資料量過大,釋放vector記憶體 
	} 
	else if(multiplicate>1) 
	{
		outfile<<"共有"<<allmode.size()<<"個眾數:";
		for(int i=0;i<allmode.size();i++)outfile<<allmode[i]<<" "; 
		outfile<<endl<<"出現次數為:"<<multiplicate;
		cout<<"結果請在輸出檔案output中檢視";
	}
	return 0;	 
} 

輸入:
在這裡插入圖片描述
執行結果:
在這裡插入圖片描述

注:
1.如果題目有說保證輸入的資料只有1個眾數,就不需要vector陣列來儲存了,可以把程式碼中有關的部分都刪掉,每次只要判斷一下候選重數是不是比儲存的重數大,如果是的話,更新一下重數和眾數就可以了,程式碼會短很多~
2.另外輸出部分當重數為1即沒有眾數的情況下我為了防止輸入的資料量太大,把vector的記憶體釋放了(swap()實現),也可以刪掉,程式碼又會短很多~
3.總體來說分治法和遞迴很相似,可以說分治是策略,遞迴是程式碼方法,分治用到遞迴

如有錯誤或不完善的地方,歡迎指出

相關文章