1. 程式人生 > >中位數(第k大數)快速求法

中位數(第k大數)快速求法

本文為twenz根據個人經驗整理,轉載請註明來源,謝謝!

中位數即為一系列數中的大小在中間位置的數,快速找中位數的有效方法有:

1.排序法:先對陣列進行排序,時間複雜度為O(nlogn),然後選擇中間的數

2.快排的篩選法(類似於找第k大的數):思想是選定一個數,找比它大的數和小的數,然後根據數量再在大的部分或者小的部分迴圈遞迴找,時間複雜度應該為O(n)

如果給定兩個有序的系列,需要查詢他們共同的中位數,時間複雜度又會是多少呢?

1. 直接對兩個有序系列進行逐個元素比較,時間複雜度為O(m+n)

2. 採用二分查詢(類似於找第k大點),先以一個序列的基準點(二分來確定)來在另外一個系列中二分查詢,再不斷更新兩系列的上下界限,時間複雜度應該在O(logm*logn). 下面是一個程式的基本框架,不一定正確,只是提供架構思想,待以後碰到類似問題我將更新該程式碼:

double findMid(int pm,int m)
{
    if(pm == 0 || pm == m){
        if(m%2)return a[m/2]*1.0;
        else return (a[m/2]+a[m/2-1])/2.0;
    }
    int low1=0,up1=pm-1,low2=pm,up2=m-1,mid1,mid2,num;
    while(low2<=up2){
        mid2=(low2+up2)/2;
        while(low1<=up1){
            mid1=(low1+up1)/2;
            if(a[mid1]<=a[mid2])low1=mid1+1;
            else up1=mid1-1;
        }
        num = mid1+(mid2-pm);
        if(a[mid1]<=a[mid2]){
            num +=1;
            if(num == (m-1)/2)break;
            else if(num < (m-1)/2){
                low2=mid2+1;
                up2=m-1;
                low1=mid1+1;
                up1=pm-1;
            }
            else{
                low2=pm;
                up2=mid2-1;
                low1=0;
                up1=mid1-1;
            }
        }
        else{
            if(num == (m-1)/2)break;
            else if(num < (m-1)/2){
                low2=mid2+1;
                up2=m-1;
                low1=mid1;
                up1=pm-1;
            }
            else{
                low2=pm;
                up2=mid2-1;
                low1=0;
                up1=mid1-1;
            }
        }
    }
    int ans,x;
    if(num != (m-1)/2){
        if(num > (m-1)/2)mid2--;
        ans = (m-1)/2-(mid2-pm+1);
    }
    else ans = mid2;
    if(m%2)return a[ans]*1.0;
    else {
        if(ans<pm){
            if(mid2<m-1&&ans<pm-1)x=a[ans+1]>a[mid2+1]?a[mid2+1]:a[ans+1];
            else if(mid2<m-1)x=a[mid2=1];
            else if(ans<pm-1)x=a[ans+1];
            return (x+a[ans])/2.0;    
        }
        else{
            if(ans<m-1&&a[mid1]>a[ans])x=a[ans+1]>a[mid1]?a[mid1]:a[ans+1];
            else if(ans<m-1&&mid1<pm-1&&a[mid1]<=a[ans])x=a[ans+1]>a[mid1+1]?a[mid1+1]:a[ans+1];
            else if(ans<m-1)x=a[ans+1];
            else if(a[mid1]>a[ans])x=a[mid1];
            else if(mid1<pm-1&&a[mid1]<=a[ans])x=a[mid1+1];
            return (x+a[ans])/2.0;    
        }
    }
}

但是如果給定的是一個動態的不斷增加的系列,期間需要不斷地查詢中位數,該怎麼辦呢?

1. 採用插入排序,這樣每次查詢的都是有序的系列。時間複雜度O(n*n)

2. 利用上面的兩個有序序列的思想,以一區間段M作為標準將系列分成兩個有序系列,其中第二個系列採用插入排序(M*M*n/M),如果達到M就和1系列歸併(n*n/M),查詢中位數則採用上面的方法(n*logn*logM)。總時間複雜度為O(nM+n*n/M+nlogn*logM),則M取n的平方根可能較好

3. 還有一種思路就是仍然採用插入排序,但是使用雙向連結串列插入排序,此外以M作為區間記錄M倍數的位置的節點,這樣插入過程只需找區間(n/M)以及在區間中找(M),則每次的時間複雜度為(n/M+M),而一箇中位數指標維護指向中間節點,插入大的考慮右移小的左移O(1)。這樣總的時間複雜度為O(n*(n/M+M)+1)及為O(n^1.5)

6. 如果查詢的第k大數的k呈現非遞減關係,則可以採用維護最大堆和最小堆。最小堆k個數。時間複雜度O(nlogn)

下面這道題即是:http://acm.scs.bupt.cn/onlinejudge/newoj/Statistic/Statistic.php?problem_id=67 採用方法3的程式碼:

#include<iostream>
#include<stdio.h>
using namespace std;
#define M 200
#define N 100001
struct Node{
    int v;
    Node *prev,*next;
}node[N];
Node *inde[N/M+2],*mid,*last,*start;
void insert(Node *add,int lm,int m)
{
    if(last == NULL){
        start = last = mid = add;
        return;
    }
    if(last->v <= add->v){
        last->next = add;
        add->prev = last;
        last = add;
    }
    else if(start->v > add->v){
        add->next = start;
        start->prev = add;
        start = add;
        for(int i = lm-1;i >= 0;i --)inde[i] = inde[i]->prev;
    }
    else{
        int i = lm-1;
        while(i>=0 && inde[i]->v>add->v){
            inde[i] = inde[i]->prev;
            i --;
        }
        Node *p;
        if(i == lm-1)p=last->prev;
        else{
            p=inde[i+1];
            if(p->v<=add->v)inde[i+1]=add;
        }
        while(p->v>add->v)p=p->prev;
        add->prev = p;
        add->next = p->next;
        p->next->prev = add;
        p->next = add;
    }
    if(mid->v<=add->v){
        if(m%2)mid=mid->next;
    }
    else{
        if(m%2==0)mid=mid->prev;
    }
}
int main()
{
  int n,m,lm;
  while(scanf("%d",&n)!=EOF)
  {
     int i,x,tt;
     m = 0;
     lm = 0;
     start=mid=last=NULL;
     for(i=0;i<n;i++)
     {
      scanf("%d",&x);
      if(x == 1){
          scanf("%d",&tt);
          Node *add = &node[m++];
          add->v = tt;
          add->next=add->prev=NULL;
          insert(add,lm,m);
          if(m%M == 0)
              inde[lm++]=last;
      }
      else if(x == 2){
          double ans = mid->v;
          if(m%2==0)ans=(ans+mid->next->v)/2.0;
          printf("%.1lf\n",ans);
      }
    }
  }
  return 0;
}

---------------------------------方法4------------------------

#include <algorithm>
#include <iostream>
using namespace std;
#define N 100001
int a[N],b[N],tag[N],lm;
struct Node{
    int v;
    int count;
    bool tag;
    Node *l,*r;
}node[N];
Node * makeTree(int low,int up){
    int mid = (low+up)/2;
    while(mid>low&&a[mid]==a[mid-1])mid--;
    Node *root = &node[lm++];
    root->count = 0;
    root->tag = false;
    root->v=a[mid];
    if(low<=mid-1)root->l=makeTree(low,mid-1);
    else root->l=NULL;
    if(mid+1<=up)root->r=makeTree(mid+1,up);
    else root->r=NULL;
    return root;
}
void insert(Node *root,int xx){
    if(root->v>xx){
        root->count++;
        insert(root->l,xx);
    }
    else if(root->v==xx&&root->tag==false)
        root->tag=true;
    else
        insert(root->r,xx);
}
int look(Node *root,int middle)
{
    if(root->count >= middle)return look(root->l,middle);
    else if(root->count == middle-1 && root->tag)return root->v;
    if(root->tag)return look(root->r,middle-1-root->count);
    return look(root->r,middle-root->count);
}
int main()
{
    int i,j,x,n,m,resn;
    while(scanf("%d",&n)!=EOF)
    {
        m = 0;
        resn = 0;
        lm = 0;
        for(i=0;i<n;i++)
        {
            scanf("%d",&x);
            if(x == 1){
                scanf("%d",&a[m]);
                b[m]=a[m];
                m ++;
            }
            else if(x == 2){
                tag[resn++]=m;
            }
        }
        sort(a,a+m);
        Node *root = makeTree(0,m-1);
        j = 0;
        for(i=0;i<m;i++){
            insert(root,b[i]);
            while(j < resn && tag[j] == i+1){
                double ans = look(root,i/2+1);
                if(i%2)ans = (ans+look(root,i/2+2))/2;
                printf("%.1lf\n",ans);
                j ++;
            }
        }
    }
    return 0;
}