演算法進化歷程之「水壺問題」

2021-06-26 13:48:41 字數 3996 閱讀 5908

演算法進化歷程之「水壺問題」

問題描述:假設給定了n個紅色的水壺和n個藍色的水壺,它們的形狀和尺寸都不相同。所有紅色水壺中所盛水的量都不一樣,藍色水壺也是一樣。此外,對於每個紅色的水壺,都有乙個對應的藍色水壺,兩者所盛的水量是一樣的。反之亦然。

你的任務是將所盛水量一樣的紅色水壺和藍色水壺找出來。為了達到這一目的,可以執行如下操作:挑選出一對水壺,其中乙個是紅色的,另乙個是藍色的:將紅色水壺中倒滿水;再將水倒入到藍色的水壺中。通過這個操作,可以判斷出來這兩隻水壺的容量哪乙個大,或者是一樣大。假設這樣的比較需要乙個時間單位。你的目標是找出乙個演算法,它通過執行最少次數的比較,來確定分組和配對問題。記住不能直接比較兩個紅色的或兩個藍色的水壺。

思路分析:

為了驗證配對是否正確,我在設計水壺的資料結構時安排了兩個屬性:水壺的儲水量和水壺的編號。配對結束後,表示紅藍水壺的陣列中下標相同的水壺儲水量相同,但編號不一定相同。

資料結構如下:

typedef structkettle

int value;//水壺的儲水量

int number; //水壺的編號

} kettle;

為了確保每個紅色水壺中所盛水的量都不一樣,並且都有乙個對應的藍色水壺,我設計了乙個函式為紅壺和藍壺隨機初始化各不相同的儲水量。**如下:

void creatkettle(kettle bluek, kettleredk, int n)

inti, j, pos, temp;

for(i=0; ibluek[i].value = redk[i].value =bluek[i].number = redk[i].number = i + 1;

for (i=0; ipos = rand() % n;

temp = bluek[0].value;

for (j=0; jbluek[j].value = bluek[j+1].value;

bluek[pos].value = temp;

pos = rand() % n;

temp = redk[0].value;

for (j=0; jredk[j].value = redk[j+1].value;

redk[pos].value = temp;

版本一:簡明易懂的版本

先介紹最簡單的方法,拿出第1個紅色水壺,然後從n個藍色水壺中去配對,需要θ(n)次比較,找到配對的藍水壺後,將其交換到最左邊,使其下標與對應紅水壺的下標相同。

重複上述操作,直到所有的紅色水壺都完成了配對。總的時間複雜度為θ(n^2)。

**如下:

void match_1(kettle bluek, kettle redk,int n)//最簡單的配對演算法,時間複雜度為 θ(n^2)

inti, j;

for(i=0; ifor(j=i; jif(redk[i].value == bluek[j].value)

swap(&bluek[j],&bluek[i]);

break;

void swap(kettle *a, kettle *b)

kettletemp = *a;

*a= *b;

*b= temp;

void match_2(kettle bluek, kettle redk,int n)

inti, pos;

pos= partition(bluek, redk[0].value, 0, n-1);//先配對第乙個紅壺

swap(&bluek[0],&bluek[pos]);//將已配對水壺交換到最左邊

for(i=1; iif(redk[i].value < bluek[i-1].value)

pos= partition(bluek, redk[i].value, i, pos);//在[i,pos]範圍內尋找配對

else

pos= partition(bluek, redk[i].value, pos+1, n-1);//在[pos+1, n-1]範圍內尋找配對

swap(&bluek[i],&bluek[pos]);//將已配對水壺交換到最左邊

match_2()用到乙個分割函式partition(),這是乙個類似快速排序的分割函式,可以以值為x的元素為樞紐元,將陣列k分成兩部分,並返回樞紐元的位置。**如下:

int partition(kettle k, int x, int left,int right)

while(left < right)

while(k[left].value < x)

left++;

while(k[right].value > x)

right--;

swap(&k[left],&k[right]);

returnleft;

版本三:類快速排序演算法

由於每個紅色水壺中所盛水的量都不一樣,並且都有乙個對應的藍色水壺,因此我們只需分別將其進行排序即可。但是不能直接比較同種顏色的水壺,只有顏色不一樣的水壺才能進行比較,因此需要交叉比較,互為樞紐元素。

整個排序過程中利用快速排序的思想,呼叫了版本二中的分割函式partition(),採用交叉分割的方法,具體描述如下:

1. 從紅色水壺序列中隨機選擇乙個作為樞軸元素

2. 利用紅色水壺樞軸元素redk[posredk]對藍色水壺序列進行分割,並返回對應藍色水壺的編號posbluek。

3. 利用bluek[posbluek]對紅色水壺序列進行進行分割,並返回對應紅色水壺的編號posredk,很顯然此時posredk ==posbluek。

4.遞迴呼叫函式quickmatch(),對分割好的序列進行配對。

演算法的時間複雜度為o(nlgn),最壞的情況下時間複雜度為o(n^2)。

**如下:

void match_3(kettle bluek, kettle redk,int n)//快速配對演算法的驅動函式

quickmatch(bluek,redk, 0, n-1);

void quickmatch(kettle bluek, kettleredk, int left, int right)//類似快速排序的快速配對演算法

intposbluek, posredk;

if(left < right)

posredk= rand() % (right-left+1) + left; //從紅色水壺中隨機選擇乙個作為樞軸元素

posbluek= partition(bluek, redk[posredk].value, left, right);//將藍壺分成兩堆,並返回配對藍壺的編號

posredk= partition(redk, bluek[posbluek].value, left, right); //將紅壺分成兩堆,並返回配對紅壺的編號

//遞迴呼叫函式quickmatch(),對分割好的序列進行配對

quickmatch(bluek,redk, left, posredk-1);

quickmatch(bluek,redk, posredk+1, right);

測試主函式:

int main(void)

kettle bluek[maxsize], redk[maxsize];

int i, n = 6;

creatkettle(bluek, redk, n);

print(bluek, redk, n);

match_1(bluek,redk, n);

// match_2(bluek,redk, n);

// match_3(bluek,redk, n);

print(bluek, redk, n);

return 0;

輸出資料函式:

void print(kettle bluek, kettle redk,int n)

inti;

for(i=0; iprintf("bluek[%d]: %d, %d  redk[%d]: %d,%d\n", i, bluek[i].number, bluek[i].value, i, redk[i].number, redk[i].value);

printf("\n");

演算法進化歷程之剪刀石頭布

演算法進化歷程之剪刀石頭布 小美 阿福,你玩過剪刀石頭布遊戲嗎?阿福 這算什麼問題?誰還能沒玩過剪刀石頭布?要知道它可是一種世界聞名的猜拳遊戲。它起源於中國,然後傳到日本 朝鮮等地,隨著亞歐 的不斷發展它傳到了歐洲,到了近現代逐漸風靡世界。簡單明瞭的規則 石頭打剪刀,布包石頭,剪刀剪布 使得剪刀石頭...

最大m子段和問題演算法進化歷程

name author date 23 03 17 08 08 description 問題描述 最大m子段和問題 給定由 n個整數 可能為負整數 組成的序列a1,a2,a3,an,以及乙個正整數 m,要求確定序列 a1,a2,a3,an的 m個不相交子段,使這m個子段的總和達到最大,求出最大和。演...

最大連續子串行之和演算法進化歷程

name author date 23 03 17 08 08 description 題目描述 給定k個整數的序列,其任意連續子串行可表示為,其中 1 i j k。最大連續子串行是所有連續子串行中元素和最大的乙個,例如給定序列,其最大連續子串行為,最大和為20。演算法1 我們用乙個備忘錄陣列s i...