在冼鏡光老師的 C 名題精選百則,問題 5-6 求中位數裡已經有詳細解答。但筆者在研究該解答時發現一件怪事,在大量測試下,有時得出的中位數與排序後得到的中位數不一樣。
下面為測試程式,與冼老師解答使用的演算法 100% 一樣(5-24頁),只是改用 C++ 實作:
#include <stdlib.h>
#include <time.h>
#include <vector>
#include <algorithm>
#include <stdio.h>
using namespace::std;
void swap(int& a, int& b)
{
const int tmp = a;
a = b;
b = tmp;
}
void split(vector<int>& v, int first, int last, int& split_point)
{
const int split_data = v[first];
split_point = first;
for(int i = first + 1; i <= last && split_point <= last; ++i)
{
if(v[i] < split_data)
{
++split_point;
swap(v[split_point], v[i]);
}
}
swap(v[first], v[split_point]);
}
int median(vector<int>& v)
{
int left = 0;
int right = v.size() - 1;
const int middle = (left+right)/2;
int split_point;
while(1)
{
split(v, left, right, split_point);
if(split_point == middle)
break;
else if(split_point > middle)
right = split_point - 1;
else
left = split_point + 1;
}
if((v.size()%2) != 0)
return v[middle];
else
return (v[middle]+v[middle+1])/2;
}
int main(int argc, char *argv[])
{
using namespace::std;
srand(time(0));
for(int i = 0; i < 10000; ++i)
{
vector<int> v;
while(v.size()==0)
v.resize(rand()%1000);
for(size_t i = 0; i < v.size(); ++i)
v[i] = rand();
vector<int> v2 = v;
int median1 = median(v);
sort(v2.begin(), v2.end());
const int middle = (v2.size() - 1)/2;
int median2;
if((v2.size()%2) != 0)
median2 = v2[middle];
else
median2 = (v2[middle]+v2[middle+1])/2;
if(median1 != median2)
{
printf("median1=%d,median2=%d\n", median1, median2);
printf("oops!\n");
break;
}
}
return 0;
}
程式的精髓是 Line 7. split(),以三步驟將陣列劃分:Step 1.
Step 2.
Step 3.
接著,Line 41-46 依據 < split_data 與 >= split_data 哪塊比較長來決定如何調整 left 或 right,如果 Line 40 取得的 split_point 等於 middle,則中斷 Line 38. while loop,得到答案。
後來找了很久,才知道要等 left 與 right 交會時中斷 Line 38. while loop 才是對的,程式修改如下:
int median(std::vector<int>& v)
{
//...
while(1)
{
split(v, left, right, split_point);
if(left >= right)
break;
else if(split_point > middle)
right = split_point - 1;
else
left = split_point + 1;
}
//...
}
至於「為什麼?」筆者也還在思考中,只能說演算法真的是很深奧的學問,寫出程式還不是最難的,要證明一隻程式 100% 正確無誤很難...



沒有留言:
張貼留言