1 中位数
中位数:将数组大小为n的数据,从大到小,或者是从小到大排列,那么当n为奇数的时候,中位数就是(n+1)/2的这个数,当n为偶数的时候,中位数就是n/2和(n+1)/2这二个数据的平均数。
中位数:也就是选取中间的数。一种衡量集中趋势的方法。
要找中位数,首先需要从小到大排序,例如这组数据:23、29、20、32、23、21、33、25;
我们将数据排序20、21、23、23、25、29、32、33;排序后发现有8个数怎么办?
若有n个数,则选择第(n+1)/2个(当n为奇数),或选择(n/2+1)个(当n为偶数)
此例中选择25为中位数
2 带权中位数
带权中位数:首先也是将这个数组的数据按一定的顺序排列, 带权中位数(Weighted Median)对于n个互不相同的元素集合x1、x2……xn,其权重依次为w1、w2……wn。令W = sigma(wi),则带权中位数xk满足:(这里的权重可以用这个数据出现的频率来表示,或者这个数据的重要性)
sigma(wi)(xi<xk)<=W/2
sigma(wi)(xi>xk)<=W/2
其中sigma表示求和。
带权中位数满足:sigma(|xi-xk|*wi)最小
百度的证明:
D[I]为权重,DIST为距离
若最优点在T
则有:
∑
{D[I]*DIST(I,T)}(I<>T)<=∑
{D[I]*DIST(I,T+1)}(I<>T+1)
将此式化为:
∑
{D[L]}*DIST(L,T)}+∑
{D[R]*DIST(R,T)}+D[T+1]*DIST(T+1,T)
<=∑
{D[L]}*DIST(L,T+1)}+∑
{D[R]*DIST(R,T+1)}+D[T]*DIST(T,T+1) (L<T&R>T+1)
即:
∑
{D[L]*DIST(L,T+1)}-∑
{D[L]*DIST(L,T)}(L<T)+D[T]*(DIST(T,T+1))>=∑
{D[R]*DIST(R,T)}-∑
(D[R]*DIST(R,T+1))(R>T+1)+D[T+1]*(DIST(T,T+1))进一步化简为:
∑
{D[L]*(DIST(L,T)-DIST[L,T+1])}(L<=T)<=∑
{D[R]*(DIST(R,T+1)-DIST(R,T))}(R>=T+1)∵
DIST(L,T)-DIST(L,T+1)=DIST(T,T+1)
DIST(R,T+1)-DIST(R,T)=DIST(T+1,T)
OBVIOUSLY : DIST(T,T+1)=DIST(T+1,T)
因此:
∑
D[L](L<=T)>=∑
(D[R])(R>=T+1)
即:∑
D[L](L<T)+D[T]>=∑
(D[R])(R>T)
因此我们发现,若
T是最优点,则必有其左边的权值和加上
D[T]后大于右边的权值和
而类似的,我们可以证明其右边的权值和加上
D[T]后大于左边的权值和
因此我们要找的点也就是满足以上条件的点。注意到此时我们的选择已经和具体的位置(坐标)没有关系了,而成为主要考虑因素的仅仅是各点上的权值。
因为左边的权值和数+
D[T]>=右边的权值和,那么:
LEFTSUM+D[T]>=
RIGHTSUM=
SUMALL-(LEFTSUM+D[T])
=>2*(LEFTSUM+D[T])>=
SUMALL
=>2*RIGHTSUM<=
SUMALL
同理可得:
RIGHTSUM+D[T]>=
LEFTSUM=
SUMALL-(RIGHTSUM+D[T])
=>2*(RIGHTSUM+D[T])>=
SUMALL
=>2*LEFTSUM<=
SUMALL
此时我们发现:
2*LEFTSUM<=
SUMALL 而
2*(LEFTSUM+D[T])>=
SUMALL
也即是说当前的位置
T上的数包含了第[(
SUMALL)/2]个数,由开篇的简述可知,这第[(
SUMALL)/2]个数,就是这个序列中的带权中位数。所以这一类问题,实质上就是带权中位数问题。
3 例题及变形
3.1 距离之和最小
X轴上有N个点,求X轴上一点使它到这N个点的距离之和最小,输出这个最小的距离之和。
Input
<span id="Showjim86_bnbbbsbl_s35"></span>第1行:点的数量N。(2 <= N <= 10000) 第2 - N + 1行:点的位置。(-10^9 <= P[i] <= 10^9)代码:根据就是中位数
- #include <iostream>
- #include <algorithm>
- using namespace std;
- int a[10010];
- int main()
- {
- int N;
- while (cin >> N)
- {
- for (int i = 0; i < N; ++ i)
- {
- cin >> a[i];
- }
- sort(a,a+N);
- int l = 0,r = N - 1;
- __int64 sum = 0;
- while (l <= r)
- {
- sum += a[r --] - a[l ++];
- }
- cout << sum << endl;
- }
- return 0;
- }
3.2 距离之和最小V2
三维空间上有N个点, 求一个点使它到这N个点的曼哈顿距离之和最小,输出这个最小的距离之和。
点(x1,y1,z1)到(x2,y2,z2)的曼哈顿距离就是|x1-x2| + |y1-y2| + |z1-z2|。即3维坐标差的绝对值之和。
Input
<span id="Showjim86_bnbbbsbl_s216"></span>第1行:点的数量N。(2 <= N <= 10000) 第2 - N + 1行:每行3个整数,中间用空格分隔,表示点的位置。(-10^9 <= X[i], Y[i], Z[i] <= 10^9)<span id="Showjim86_bnbbbsbl_e216"></span>代码:3.1的变形,将x,y,z分开求,每一个就和3.1一样了,然后再求和即可。
- #include <iostream>
- #include <algorithm>
- using namespace std;
- __int64 getMinDis(int a[],int N)
- {
- sort(a,a+N);
- int l = 0,r = N - 1;
- __int64 sum = 0;
- while (l <= r)
- {
- sum += a[r --] - a[l ++];
- }
- return sum;
- }
- int main()
- {
- int N;
- while (cin >> N)
- {
- int x[10002],y[10002],z[10002];
- for (int i = 0; i < N; ++ i)
- {
- cin >> x[i] >> y[i] >> z[i];
- }
- cout << getMinDis(x,N)+getMinDis(y,N)+getMinDis(z,N) << endl;
- }
- return 0;
- }
-
3.2 距离之和最小V3
X轴上有N个点,每个点除了包括一个位置数据X[i],还包括一个权值W[i]。该点到其他点的带权距离 = 实际距离 * 权值。求X轴上一点使它到这N个点的带权距离之和最小,输出这个最小的带权距离之和。
Input
<span id="Showjim86_bnbbbsbl_s216"></span>第1行:点的数量N。(2 <= N <= 10000) 第2 - N + 1行:每行2个数,中间用空格分隔,分别是点的位置及权值。(-10^5 <= X[i] <= 10^5,1 <= W[i] <= 10^5)<span id="Showjim86_bnbbbsbl_e216"></span>代码:这个就是求出带权中位数,然后又和3.1是一样的。关键是带权中位数如何求,就是
1.1 按照从小到大的顺序给x1……xn排序
1.2 遍历数组,直至找到第一个xk,满足sigma(wi)(xi<xk)>=W/2,则xk就是要找的带权中位数。
1.2 遍历数组,直至找到第一个xk,满足sigma(wi)(xi<xk)>=W/2,则xk就是要找的带权中位数。
- #include <iostream>
- #include <algorithm>
- #include <cmath>
- using namespace std;
- const int MAXN = 10010;
- typedef struct NODE
- {
- __int64 x;
- __int64 w;
- }Node;
- Node node[MAXN];
- bool cmp(const Node &a, const Node &b)
- {
- return a.x < b.x;
- }
- int main()
- {
- int N;
- while (cin >> N)
- {
- __int64 wSum = 0;
- for (int i = 0; i < N; ++ i)
- {
- cin >> node[i].x >> node[i].w;
- wSum += node[i].w;
- }
- sort(node,node+N,cmp);
- wSum /= 2;
- __int64 tmp = 0;
- int pos = 0;
- for (int i = 0; i < N; ++ i)
- {
- tmp += node[i].w;
- if (tmp >= wSum)
- {
- pos = i;
- break;
- }
- }
- __int64 sum = 0;
- for (int i = 0; i < N; ++ i)
- {
- if (i <= pos)
- {
- sum += (node[pos].x - node[i].x)*node[i].w;
- }
- else
- {
- sum += (node[i].x - node[pos].x)*node[i].w;
- }
- }
- cout << sum << endl;
- }
- return 0;
- }
-
上面都是nlogn的解法,算法导论又最坏为O(N)的解法的伪代码: