ขั้นตอนวิธีค่ามัธยฐานแบบโรลลิ่งใน C


114

ฉันกำลังทำงานกับอัลกอริทึมเพื่อใช้ตัวกรองค่ามัธยฐานแบบกลิ้ง (คล้ายกับตัวกรองค่าเฉลี่ยแบบกลิ้ง) ใน C. จากการค้นหาวรรณกรรมของฉันดูเหมือนว่าจะมีสองวิธีที่มีประสิทธิภาพพอสมควร อันดับแรกคือการจัดเรียงหน้าต่างเริ่มต้นของค่าจากนั้นทำการค้นหาแบบไบนารีเพื่อแทรกค่าใหม่และลบค่าที่มีอยู่ออกในการวนซ้ำแต่ละครั้ง

ประการที่สอง (จาก Hardle and Steiger, 1995, JRSS-C, Algorithm 296) สร้างโครงสร้างฮีปแบบปลายคู่โดยมี Maxheap ที่ปลายด้านหนึ่งมินฮ็อปอีกด้านหนึ่งและค่ามัธยฐานอยู่ตรงกลาง สิ่งนี้ให้ผลอัลกอริทึมเวลาเชิงเส้นแทนที่จะเป็นหนึ่งที่เป็น O (n log n)

นี่คือปัญหาของฉัน: การนำแบบเดิมมาใช้นั้นทำได้ แต่ฉันต้องเรียกใช้สิ่งนี้กับอนุกรมเวลานับล้านดังนั้นประสิทธิภาพจึงมีความสำคัญมาก ข้อหลังนี้พิสูจน์ได้ยากมากที่จะนำไปใช้ ฉันพบรหัสในไฟล์ Trunmed.c ของรหัสสำหรับแพ็คเกจสถิติของ R แต่มันค่อนข้างจะอ่านไม่ออก

มีใครรู้จักการใช้งาน C ที่เขียนมาอย่างดีสำหรับอัลกอริธึมค่ามัธยฐานการหมุนเวลาเชิงเส้นหรือไม่?

แก้ไข: ลิงก์ไปยังโค้ด Trunmed.c http://google.com/codesearch/p?hl=th&sa=N&cd=1&ct=rc#mYw3h_Lb_e0/R-2.2.0/src/library/stats/src/Trunmed.c


เพิ่งใช้ค่าเฉลี่ยเคลื่อนที่ ... ค่ามัธยฐานการเคลื่อนที่ค่อนข้างยุ่งยากกว่า ลองใช้ค่ามัธยฐานการเคลื่อนที่ของ googling
Matt

พยายามค้นหารหัส google และ google มันเปิดโค้ด Trunmed.c และการนำไปใช้งานในภาษาอื่นสำหรับพอร์ต SGI ของโค้ด Trunmed (จากสิ่งที่ฉันบอกได้) นอกจากนี้อัลกอริทึม JRSS ที่ฉันอ้างถึงดูเหมือนจะเป็นเพียงหนึ่งเดียวในซีรีส์ของวารสารที่รหัสต้นฉบับไม่ได้ถูกเก็บถาวร
AWB

คุณมีตัวเลขกี่ตัวในอนุกรมเวลา? แม้จะมีเป็นล้าน แต่หากคุณมีตัวเลขเพียงไม่กี่พันหมายเลขก็อาจใช้เวลาไม่เกินหนึ่งหรือสองนาทีในการรัน (หากโค้ดของคุณเขียนได้อย่างมีประสิทธิภาพ)
Dana the Sane

16
วิธีแก้ปัญหาสองฮีปเป็นเส้นตรงอย่างไร? มันคือ O (n log k) โดยที่ k คือขนาดหน้าต่างเนื่องจากการลบของฮีปคือ O (log k)
yairchu

3
การใช้งานและการเปรียบเทียบบางอย่าง: github.com/suomela/median-filter
Jukka Suomela

คำตอบ:


28

ฉันได้ดู R src/library/stats/src/Trunmed.cสองสามครั้งเพราะฉันต้องการบางสิ่งที่คล้ายกันเช่นกันในรูทีนย่อยคลาส C ++ / C แบบสแตนด์อโลน โปรดทราบว่านี่เป็นการใช้งานสองอย่างในหนึ่งเดียวโปรดดูsrc/library/stats/man/runmed.Rd(ที่มาของไฟล์วิธีใช้) ซึ่งระบุว่า

\details{
  Apart from the end values, the result \code{y = runmed(x, k)} simply has
  \code{y[j] = median(x[(j-k2):(j+k2)])} (k = 2*k2+1), computed very
  efficiently.

  The two algorithms are internally entirely different:
  \describe{
    \item{"Turlach"}{is the Härdle-Steiger
      algorithm (see Ref.) as implemented by Berwin Turlach.
      A tree algorithm is used, ensuring performance \eqn{O(n \log
        k)}{O(n * log(k))} where \code{n <- length(x)} which is
      asymptotically optimal.}
    \item{"Stuetzle"}{is the (older) Stuetzle-Friedman implementation
      which makes use of median \emph{updating} when one observation
      enters and one leaves the smoothing window.  While this performs as
      \eqn{O(n \times k)}{O(n * k)} which is slower asymptotically, it is
      considerably faster for small \eqn{k} or \eqn{n}.}
  }
}

คงจะดีไม่น้อยหากได้เห็นสิ่งนี้ถูกนำมาใช้ใหม่ในรูปแบบสแตนด์อโลนมากขึ้น คุณเป็นอาสาสมัครหรือไม่? ฉันสามารถช่วยเกี่ยวกับบิต R ได้บ้าง

แก้ไข 1 : นอกจากลิงก์ไปยัง Trunmed.c เวอร์ชันเก่าด้านบนแล้วนี่คือสำเนา SVN ปัจจุบันของไฟล์

  • Srunmed.c (สำหรับเวอร์ชัน Stuetzle)
  • Trunmed.c (สำหรับรุ่น Turlach)
  • runmed.R สำหรับฟังก์ชัน R ที่เรียกสิ่งเหล่านี้

แก้ไข 2 : Ryan Tibshirani มีรหัส C และ Fortran บางตัวในการหาค่ามัธยฐานอย่างรวดเร็วซึ่งอาจเป็นจุดเริ่มต้นที่เหมาะสมสำหรับแนวทางแบบหน้าต่าง


ขอบคุณ Dirk เมื่อฉันได้รับโซลูชันที่สะอาดแล้วฉันกำลังวางแผนที่จะปล่อยภายใต้ GPL ฉันสนใจที่จะตั้งค่าอินเตอร์เฟส R และ Python ด้วย
AWB

9
@AWB เกิดอะไรขึ้นกับความคิดนี้? คุณรวมโซลูชันของคุณไว้ในแพ็คเกจหรือไม่?
Xu Wang

20

ฉันไม่พบการใช้โครงสร้างข้อมูล c ++ ที่ทันสมัยพร้อมสถิติคำสั่งซื้อดังนั้นจึงลงเอยด้วยการนำแนวคิดทั้งสองไปใช้ในลิงก์ตัวเข้ารหัสยอดนิยมที่ MAK แนะนำ ( Match Editorial : เลื่อนลงไปที่ FloatingMedian)

มัลติเซ็ตสองชุด

แนวคิดแรกแบ่งข้อมูลออกเป็นสองโครงสร้างข้อมูล (heaps, multisets ฯลฯ ) ด้วย O (ln N) ต่อการแทรก / ลบไม่อนุญาตให้เปลี่ยนแปลง quantile แบบไดนามิกโดยไม่มีค่าใช้จ่ายจำนวนมาก กล่าวคือเราสามารถมีค่ามัธยฐานการหมุนหรือ 75% แต่ไม่ใช่ทั้งสองอย่างในเวลาเดียวกัน

ต้นไม้กลุ่ม

แนวคิดที่สองใช้โครงสร้างส่วนซึ่งเป็น O (ln N) สำหรับการแทรก / ลบ / การสืบค้น แต่มีความยืดหยุ่นมากกว่า เหนือสิ่งอื่นใด "N" คือขนาดของช่วงข้อมูลของคุณ ดังนั้นหากค่ามัธยฐานการหมุนของคุณมีหน้าต่างเป็นล้านรายการ แต่ข้อมูลของคุณแตกต่างกันไปตั้งแต่ 1..65536 จำเป็นต้องมีการดำเนินการ 16 ครั้งต่อการเคลื่อนที่ของหน้าต่างการหมุน 1 ล้านรายการเท่านั้น !!

โค้ด c ++ คล้ายกับที่เดนิสโพสต์ไว้ด้านบน ("นี่คืออัลกอริทึมง่ายๆสำหรับข้อมูลเชิงปริมาณ")

GNU สั่งซื้อต้นไม้สถิติ

ก่อนที่จะยอมแพ้ฉันพบว่า stdlibc ++ มีต้นไม้สถิติคำสั่งซื้อ !!!

สิ่งเหล่านี้มีการดำเนินการที่สำคัญสองประการ:

iter = tree.find_by_order(value)
order = tree.order_of_key(value)

โปรดดูlibstdc ++ manual policy_based_data_structures_test (ค้นหา "split and join")

ฉันได้ห่อต้นไม้เพื่อใช้ในส่วนหัวที่สะดวกสำหรับคอมไพเลอร์ที่รองรับ c ++ 0x / c ++ 11 style typedef บางส่วน:

#if !defined(GNU_ORDER_STATISTIC_SET_H)
#define GNU_ORDER_STATISTIC_SET_H
#include <ext/pb_ds/assoc_container.hpp>
#include <ext/pb_ds/tree_policy.hpp>

// A red-black tree table storing ints and their order
// statistics. Note that since the tree uses
// tree_order_statistics_node_update as its update policy, then it
// includes its methods by_order and order_of_key.
template <typename T>
using t_order_statistic_set = __gnu_pbds::tree<
                                  T,
                                  __gnu_pbds::null_type,
                                  std::less<T>,
                                  __gnu_pbds::rb_tree_tag,
                                  // This policy updates nodes'  metadata for order statistics.
                                  __gnu_pbds::tree_order_statistics_node_update>;

#endif //GNU_ORDER_STATISTIC_SET_H

จริงๆแล้วคอนเทนเนอร์ส่วนขยาย libstdc ++ ไม่อนุญาตให้มีหลายค่า! โดยการออกแบบ! ตามที่แนะนำโดยชื่อของฉันด้านบน (t_order_statistic_set) ค่าหลายค่าจะถูกรวมเข้าด้วยกัน ดังนั้นพวกเขาต้องทำงานอีกเล็กน้อยเพื่อจุดประสงค์ของเรา :-(
Leo Goodstadt

เราจำเป็นต้อง 1) สร้างแผนที่ของค่าเพื่อนับ (แทนที่จะเป็นชุด) 2) ขนาดสาขาควรสะท้อนจำนวนคีย์ (libstdc ++ - v3 / include / ext / pb_ds / detail / tree_policy / order_statistics_imp.hpp) สืบทอดมาจาก ต้นไม้และ 3) overload insert () เพื่อเพิ่มจำนวน / เรียก update_to_top () หากค่ามีอยู่แล้ว 4) overload ลบ () เพื่อลดจำนวน / เรียก update_to_top () หากค่าไม่ซ้ำกัน (ดู libstdc ++ - v3 / include / ext / pb_ds / detail / rb_tree_map_ / rb_tree_.hpp) อาสาสมัครคนใด ??
Leo Goodstadt

15

ฉันได้ทำการติดตั้ง C ที่นี่แล้ว รายละเอียดอีกไม่กี่อยู่ในคำถามนี้: Rolling เฉลี่ยใน C - การดำเนินงาน

ตัวอย่างการใช้งาน:

int main(int argc, char* argv[])
{
   int i,v;
   Mediator* m = MediatorNew(15);

   for (i=0;i<30;i++)
   {
      v = rand()&127;
      printf("Inserting %3d \n",v);
      MediatorInsert(m,v);
      v=MediatorMedian(m);
      printf("Median = %3d.\n\n",v);
      ShowTree(m);
   }
}

6
การใช้งานที่ยอดเยี่ยมรวดเร็วและชัดเจนโดยพิจารณาจากฮีปขั้นต่ำค่ามัธยฐานสูงสุด งานดีมาก.
Johannes Rudolph

ฉันจะหาโซลูชันนี้ในเวอร์ชัน Java ได้อย่างไร
Hengameh

10

ฉันใช้ตัวประมาณค่ามัธยฐานที่เพิ่มขึ้นนี้:

median += eta * sgn(sample - median)

ซึ่งมีรูปแบบเดียวกับตัวประมาณค่าเฉลี่ยทั่วไป:

mean += eta * (sample - mean)

นี่คือการทางพิเศษแห่งประเทศไทยเป็นพารามิเตอร์ที่อัตราการเรียนรู้ขนาดเล็ก (เช่น0.001) และsgn()เป็นฟังก์ชัน Signum {-1, 0, 1}ซึ่งจะส่งกลับอย่างใดอย่างหนึ่ง (ใช้ค่าคงที่etaเช่นนี้หากข้อมูลไม่อยู่นิ่งและคุณต้องการติดตามการเปลี่ยนแปลงเมื่อเวลาผ่านไปมิฉะนั้นสำหรับแหล่งข้อมูลที่อยู่กับที่ให้ใช้สิ่งที่ต้องการeta = 1 / nมาบรรจบกันโดยที่nจำนวนตัวอย่างที่เห็นจนถึงขณะนี้)

นอกจากนี้ฉันยังแก้ไขตัวประมาณค่ามัธยฐานเพื่อให้สามารถใช้งานได้กับจำนวนตามอำเภอใจ โดยทั่วไปฟังก์ชัน quantileบอกคุณค่าที่แบ่งข้อมูลออกเป็นสองเศษส่วน: และp 1 - pข้อมูลต่อไปนี้จะประมาณค่านี้ทีละน้อย:

quantile += eta * (sgn(sample - quantile) + 2.0 * p - 1.0)

ค่าที่ควรจะอยู่ในp [0, 1]โดยพื้นฐานแล้วจะเปลี่ยนsgn()เอาต์พุตสมมาตรของฟังก์ชัน{-1, 0, 1}ให้เอนเอียงไปทางด้านใดด้านหนึ่งโดยแบ่งตัวอย่างข้อมูลออกเป็นสองถังขยะที่มีขนาดไม่เท่ากัน (เศษส่วนpและ1 - pข้อมูลน้อยกว่า / มากกว่าค่าประมาณเชิงปริมาณตามลำดับ) โปรดทราบว่าสำหรับp = 0.5สิ่งนี้จะลดลงเป็นตัวประมาณค่ามัธยฐาน


2
เจ๋งนี่คือการปรับเปลี่ยนที่ปรับ 'eta' ตามค่าเฉลี่ยที่กำลังทำงาน ... เช่น eta ถูกปรับโดยอัตโนมัติ stackoverflow.com/questions/11482529/…
Jeff McClintock

3
สำหรับเทคนิคที่คล้ายกันโปรดดูบทความนี้เกี่ยวกับการสตรีมแบบประหยัด: arxiv.org/pdf/1407.1121v1.pdf สามารถประมาณควอไทล์และปรับให้เข้ากับการเปลี่ยนแปลงของค่าเฉลี่ย คุณต้องจัดเก็บค่าสองค่าเท่านั้น: ค่าประมาณสุดท้ายและทิศทางของการปรับครั้งล่าสุด (+1 หรือ -1) อัลกอริทึมนั้นใช้งานง่าย ฉันพบว่าข้อผิดพลาดอยู่ภายใน 5% ประมาณ 97% ของเวลา
Paul Chernoch

9

นี่คืออัลกอริทึมง่ายๆสำหรับข้อมูลเชิงปริมาณ (หลายเดือนต่อมา):

""" median1.py: moving median 1d for quantized, e.g. 8-bit data

Method: cache the median, so that wider windows are faster.
    The code is simple -- no heaps, no trees.

Keywords: median filter, moving median, running median, numpy, scipy

See Perreault + Hebert, Median Filtering in Constant Time, 2007,
    http://nomis80.org/ctmf.html: nice 6-page paper and C code,
    mainly for 2d images

Example:
    y = medians( x, window=window, nlevel=nlevel )
    uses:
    med = Median1( nlevel, window, counts=np.bincount( x[0:window] ))
    med.addsub( +, - )  -- see the picture in Perreault
    m = med.median()  -- using cached m, summ

How it works:
    picture nlevel=8, window=3 -- 3 1s in an array of 8 counters:
        counts: . 1 . . 1 . 1 .
        sums:   0 1 1 1 2 2 3 3
                        ^ sums[3] < 2 <= sums[4] <=> median 4
        addsub( 0, 1 )  m, summ stay the same
        addsub( 5, 1 )  slide right
        addsub( 5, 6 )  slide left

Updating `counts` in an `addsub` is trivial, updating `sums` is not.
But we can cache the previous median `m` and the sum to m `summ`.
The less often the median changes, the faster;
so fewer levels or *wider* windows are faster.
(Like any cache, run time varies a lot, depending on the input.)

See also:
    scipy.signal.medfilt -- runtime roughly ~ window size
    http://stackoverflow.com/questions/1309263/rolling-median-algorithm-in-c

"""

from __future__ import division
import numpy as np  # bincount, pad0

__date__ = "2009-10-27 oct"
__author_email__ = "denis-bz-py at t-online dot de"


#...............................................................................
class Median1:
    """ moving median 1d for quantized, e.g. 8-bit data """

    def __init__( s, nlevel, window, counts ):
        s.nlevel = nlevel  # >= len(counts)
        s.window = window  # == sum(counts)
        s.half = (window // 2) + 1  # odd or even
        s.setcounts( counts )

    def median( s ):
        """ step up or down until sum cnt to m-1 < half <= sum to m """
        if s.summ - s.cnt[s.m] < s.half <= s.summ:
            return s.m
        j, sumj = s.m, s.summ
        if sumj <= s.half:
            while j < s.nlevel - 1:
                j += 1
                sumj += s.cnt[j]
                # print "j sumj:", j, sumj
                if sumj - s.cnt[j] < s.half <= sumj:  break
        else:
            while j > 0:
                sumj -= s.cnt[j]
                j -= 1
                # print "j sumj:", j, sumj
                if sumj - s.cnt[j] < s.half <= sumj:  break
        s.m, s.summ = j, sumj
        return s.m

    def addsub( s, add, sub ):
        s.cnt[add] += 1
        s.cnt[sub] -= 1
        assert s.cnt[sub] >= 0, (add, sub)
        if add <= s.m:
            s.summ += 1
        if sub <= s.m:
            s.summ -= 1

    def setcounts( s, counts ):
        assert len(counts) <= s.nlevel, (len(counts), s.nlevel)
        if len(counts) < s.nlevel:
            counts = pad0__( counts, s.nlevel )  # numpy array / list
        sumcounts = sum(counts)
        assert sumcounts == s.window, (sumcounts, s.window)
        s.cnt = counts
        s.slowmedian()

    def slowmedian( s ):
        j, sumj = -1, 0
        while sumj < s.half:
            j += 1
            sumj += s.cnt[j]
        s.m, s.summ = j, sumj

    def __str__( s ):
        return ("median %d: " % s.m) + \
            "".join([ (" ." if c == 0 else "%2d" % c) for c in s.cnt ])

#...............................................................................
def medianfilter( x, window, nlevel=256 ):
    """ moving medians, y[j] = median( x[j:j+window] )
        -> a shorter list, len(y) = len(x) - window + 1
    """
    assert len(x) >= window, (len(x), window)
    # np.clip( x, 0, nlevel-1, out=x )
        # cf http://scipy.org/Cookbook/Rebinning
    cnt = np.bincount( x[0:window] )
    med = Median1( nlevel=nlevel, window=window, counts=cnt )
    y = (len(x) - window + 1) * [0]
    y[0] = med.median()
    for j in xrange( len(x) - window ):
        med.addsub( x[j+window], x[j] )
        y[j+1] = med.median()
    return y  # list
    # return np.array( y )

def pad0__( x, tolen ):
    """ pad x with 0 s, numpy array or list """
    n = tolen - len(x)
    if n > 0:
        try:
            x = np.r_[ x, np.zeros( n, dtype=x[0].dtype )]
        except NameError:
            x += n * [0]
    return x

#...............................................................................
if __name__ == "__main__":
    Len = 10000
    window = 3
    nlevel = 256
    period = 100

    np.set_printoptions( 2, threshold=100, edgeitems=10 )
    # print medians( np.arange(3), 3 )

    sinwave = (np.sin( 2 * np.pi * np.arange(Len) / period )
        + 1) * (nlevel-1) / 2
    x = np.asarray( sinwave, int )
    print "x:", x
    for window in ( 3, 31, 63, 127, 255 ):
        if window > Len:  continue
        print "medianfilter: Len=%d window=%d nlevel=%d:" % (Len, window, nlevel)
            y = medianfilter( x, window=window, nlevel=nlevel )
        print np.array( y )

# end median1.py

4

ค่ามัธยฐานแบบโรลลิ่งสามารถพบได้โดยการรักษาสองพาร์ติชันของตัวเลข

สำหรับการดูแลพาร์ติชั่นให้ใช้ Min Heap และ Max Heap

Max Heap จะมีตัวเลขที่น้อยกว่าค่ามัธยฐาน

Min Heap จะมีตัวเลขที่มากกว่าค่ามัธยฐาน

ข้อ จำกัด การปรับสมดุล: หากจำนวนองค์ประกอบทั้งหมดเท่ากันฮีปทั้งสองควรมีองค์ประกอบเท่ากัน

หากจำนวนองค์ประกอบทั้งหมดเป็นเลขคี่ Max Heap จะมีองค์ประกอบมากกว่า Min Heap หนึ่งรายการ

องค์ประกอบค่ามัธยฐาน:ถ้าทั้งสองพาร์ติชันมีจำนวนองค์ประกอบเท่ากันค่ามัธยฐานจะเป็นครึ่งหนึ่งของผลรวมขององค์ประกอบสูงสุดจากพาร์ติชันแรกและองค์ประกอบขั้นต่ำจากพาร์ติชันที่สอง

มิฉะนั้นค่ามัธยฐานจะเป็นองค์ประกอบสูงสุดจากพาร์ติชันแรก

Algorithm-
1- รับสองฮีป (1 นาทีฮีปและ 1 สูงสุดฮีป)
   Max Heap จะมีจำนวนองค์ประกอบครึ่งแรก
   Min Heap จะมีจำนวนองค์ประกอบครึ่งหลัง

2- เปรียบเทียบหมายเลขใหม่จากสตรีมกับด้านบนของ Max Heap 
   ถ้ามันน้อยกว่าหรือเท่ากันให้เพิ่มจำนวนนั้นในฮีปสูงสุด 
   มิฉะนั้นให้เพิ่มหมายเลขใน Min Heap

3- หากฮีปขั้นต่ำมีองค์ประกอบมากกว่า Max Heap 
   จากนั้นลบองค์ประกอบด้านบนของ Min Heap และเพิ่มใน Max Heap
   หากฮีปสูงสุดมีองค์ประกอบมากกว่าหนึ่งองค์ประกอบมากกว่าในฮีปขั้นต่ำ 
   จากนั้นลบองค์ประกอบสูงสุดของ Max Heap และเพิ่มใน Min Heap

4- ถ้าฮีปทั้งสองมีจำนวนองค์ประกอบเท่ากันแล้ว
   ค่ามัธยฐานจะเป็นครึ่งหนึ่งของผลรวมขององค์ประกอบสูงสุดจาก Max Heap และองค์ประกอบขั้นต่ำจาก Min Heap
   มิฉะนั้นค่ามัธยฐานจะเป็นองค์ประกอบสูงสุดจากพาร์ติชันแรก
public class Solution {

    public static void main(String[] args) {
        Scanner in = new Scanner(System.in);
        RunningMedianHeaps s = new RunningMedianHeaps();
        int n = in.nextInt();
        for(int a_i=0; a_i < n; a_i++){
            printMedian(s,in.nextInt());
        }
        in.close();       
    }

    public static void printMedian(RunningMedianHeaps s, int nextNum){
            s.addNumberInHeap(nextNum);
            System.out.printf("%.1f\n",s.getMedian());
    }
}

class RunningMedianHeaps{
    PriorityQueue<Integer> minHeap = new PriorityQueue<Integer>();
    PriorityQueue<Integer> maxHeap = new PriorityQueue<Integer>(Comparator.reverseOrder());

    public double getMedian() {

        int size = minHeap.size() + maxHeap.size();     
        if(size % 2 == 0)
            return (maxHeap.peek()+minHeap.peek())/2.0;
        return maxHeap.peek()*1.0;
    }

    private void balanceHeaps() {
        if(maxHeap.size() < minHeap.size())
        {
            maxHeap.add(minHeap.poll());
        }   
        else if(maxHeap.size() > 1+minHeap.size())
        {
            minHeap.add(maxHeap.poll());
        }
    }

    public void addNumberInHeap(int num) {
        if(maxHeap.size()==0 || num <= maxHeap.peek())
        {
            maxHeap.add(num);
        }
        else
        {
            minHeap.add(num);
        }
        balanceHeaps();
    }
}

ยังไม่ชัดเจนสำหรับฉันว่าคำตอบ Java ที่สามมีประโยชน์ต่อคำถาม C มากแค่ไหน คุณควรถามคำถามใหม่จากนั้นให้คำตอบ Java ของคุณในคำถามนั้น
jww

ตรรกะตายหลังจากอ่านสิ่งนี้ 'จากนั้นลบองค์ประกอบด้านบนของ Min Heap และเพิ่มใน Min Heap' อย่างน้อยก็มีความเอื้อเฟื้อในการอ่านอัลโกก่อนโพสต์
Cyclotron3x3

4
อัลกอริทึมนี้ไม่ได้มีไว้สำหรับค่ามัธยฐานแบบหมุน แต่เป็นค่ามัธยฐานขององค์ประกอบจำนวนมากขึ้นเรื่อย ๆ สำหรับค่ามัธยฐานการหมุนเราต้องลบองค์ประกอบออกจากฮีปซึ่งจะต้องพบก่อน
Walter

2

อาจเป็นเรื่องที่ควรค่าแก่การชี้ให้เห็นว่ามีกรณีพิเศษซึ่งมีวิธีแก้ปัญหาที่แน่นอนง่ายๆ: เมื่อค่าทั้งหมดในสตรีมเป็นจำนวนเต็มภายในช่วงที่กำหนดไว้ (ค่อนข้างเล็ก) ตัวอย่างเช่นสมมติว่าพวกเขาทั้งหมดต้องอยู่ระหว่าง 0 ถึง 1023 ในกรณีนี้ให้กำหนดอาร์เรย์ของ 1024 องค์ประกอบและจำนวนและล้างค่าเหล่านี้ทั้งหมด สำหรับแต่ละค่าในสตรีมให้เพิ่ม bin และจำนวนที่เกี่ยวข้อง หลังจากสตรีมสิ้นสุดลงให้ค้นหา bin ที่มีค่าสูงสุด count / 2 - ทำได้อย่างง่ายดายโดยการเพิ่ม bins ต่อเนื่องโดยเริ่มจาก 0 ใช้วิธีการเดียวกันนี้อาจพบค่าของลำดับอันดับที่กำหนดเอง (มีความซับซ้อนเล็กน้อยหากตรวจพบความอิ่มตัวของถังขยะและ "อัปเกรด" ขนาดของถังเก็บข้อมูลให้เป็นประเภทที่ใหญ่ขึ้นในระหว่างการเรียกใช้)

กรณีพิเศษนี้อาจดูเหมือนเทียม แต่ในทางปฏิบัติเป็นเรื่องปกติมาก นอกจากนี้ยังสามารถใช้เป็นค่าประมาณสำหรับจำนวนจริงได้หากพวกเขาอยู่ในช่วงและทราบระดับความแม่นยำที่ "ดีพอ" สิ่งนี้จะถือเป็นชุดของการวัดในกลุ่มของวัตถุ "โลกแห่งความจริง" ตัวอย่างเช่นความสูงหรือน้ำหนักของกลุ่มคน ชุดใหญ่ไม่พอ? มันจะใช้งานได้ดีสำหรับความยาวหรือน้ำหนักของแบคทีเรีย (แต่ละตัว) ทั้งหมดบนโลกโดยสมมติว่ามีคนให้ข้อมูลได้!

ดูเหมือนว่าฉันจะอ่านต้นฉบับผิด - ซึ่งดูเหมือนว่ามันต้องการค่ามัธยฐานของหน้าต่างบานเลื่อนแทนที่จะเป็นค่ามัธยฐานของสตรีมที่ยาวมาก วิธีนี้ยังคงใช้ได้ผล โหลดค่าสตรีม N แรกสำหรับหน้าต่างเริ่มต้นจากนั้นสำหรับค่าสตรีม N + 1 จะเพิ่มถังที่สอดคล้องกันในขณะที่ลดถังที่สอดคล้องกับค่าสตรีม 0 ในกรณีนี้จำเป็นต้องเก็บค่า N สุดท้ายไว้เพื่อให้การลดลงซึ่งสามารถทำได้อย่างมีประสิทธิภาพโดยการกำหนดอาร์เรย์ขนาด N แบบวนรอบเนื่องจากตำแหน่งของค่ามัธยฐานสามารถเปลี่ยนแปลงได้เพียง -2, -1,0,1 เท่านั้น 2 ในแต่ละขั้นตอนของหน้าต่างบานเลื่อนไม่จำเป็นต้องรวมถังขยะทั้งหมดจนถึงค่ามัธยฐานในแต่ละขั้นตอนเพียงแค่ปรับ "ตัวชี้ค่ามัธยฐาน" ขึ้นอยู่กับว่ามีการแก้ไขถังขยะด้านใด ตัวอย่างเช่น หากทั้งค่าใหม่และค่าที่ถูกลบออกต่ำกว่าค่ามัธยฐานปัจจุบันก็จะไม่เปลี่ยนแปลง (offset = 0) วิธีการนี้จะหยุดลงเมื่อ N มีขนาดใหญ่เกินไปที่จะเก็บไว้ในหน่วยความจำได้อย่างสะดวก


1

หากคุณมีความสามารถในการอ้างอิงค่าเป็นฟังก์ชันของจุดในเวลาคุณสามารถสุ่มตัวอย่างค่าด้วยการแทนที่โดยใช้bootstrappingเพื่อสร้างค่ามัธยฐาน bootstrapped ภายในช่วงความเชื่อมั่น วิธีนี้อาจช่วยให้คุณคำนวณค่ามัธยฐานโดยประมาณได้อย่างมีประสิทธิภาพมากกว่าการเรียงลำดับค่าที่เข้ามาอย่างต่อเนื่องในโครงสร้างข้อมูล


1

สำหรับผู้ที่ต้องการค่ามัธยฐานในการทำงานใน Java ... PriorityQueue คือเพื่อนของคุณ ใส่ O (log N), O (1) ค่ามัธยฐานปัจจุบันและ O (N) ลบ หากคุณรู้จักการกระจายข้อมูลของคุณคุณสามารถทำได้ดีกว่านี้มาก

public class RunningMedian {
  // Two priority queues, one of reversed order.
  PriorityQueue<Integer> lower = new PriorityQueue<Integer>(10,
          new Comparator<Integer>() {
              public int compare(Integer arg0, Integer arg1) {
                  return (arg0 < arg1) ? 1 : arg0 == arg1 ? 0 : -1;
              }
          }), higher = new PriorityQueue<Integer>();

  public void insert(Integer n) {
      if (lower.isEmpty() && higher.isEmpty())
          lower.add(n);
      else {
          if (n <= lower.peek())
              lower.add(n);
          else
              higher.add(n);
          rebalance();
      }
  }

  void rebalance() {
      if (lower.size() < higher.size() - 1)
          lower.add(higher.remove());
      else if (higher.size() < lower.size() - 1)
          higher.add(lower.remove());
  }

  public Integer getMedian() {
      if (lower.isEmpty() && higher.isEmpty())
          return null;
      else if (lower.size() == higher.size())
          return (lower.peek() + higher.peek()) / 2;
      else
          return (lower.size() < higher.size()) ? higher.peek() : lower
                  .peek();
  }

  public void remove(Integer n) {
      if (lower.remove(n) || higher.remove(n))
          rebalance();
  }
}

c ++ มีลำดับทรีสถิติจาก gnu ในส่วนขยายไปยังไลบรารีมาตรฐาน ดูโพสต์ของฉันด้านล่าง
Leo Goodstadt

ฉันคิดว่ารหัสของคุณวางไม่ถูกต้อง มีบางส่วนที่ไม่สมบูรณ์เช่น: }), higher = new PriorityQueue<Integer>();หรือnew PriorityQueue<Integer>(10, . ฉันไม่สามารถเรียกใช้รหัสได้
Hengameh

@Hengameh Java สิ้นสุดคำสั่งด้วยอัฒภาค - การแบ่งบรรทัดไม่สำคัญเลย คุณต้องคัดลอกอย่างไม่ถูกต้อง
Matthew อ่าน

คุณควรถามคำถามใหม่จากนั้นให้คำตอบ Java ของคุณในคำถามนั้น
jww

0

นี่คือสิ่งที่สามารถใช้ได้เมื่อเอาต์พุตที่แน่นอนไม่สำคัญ (เพื่อวัตถุประสงค์ในการแสดงผลเป็นต้น) คุณต้องมี totalcount และ lastmedian รวมถึงค่า newvalue

{
totalcount++;
newmedian=lastmedian+(newvalue>lastmedian?1:-1)*(lastmedian==0?newvalue: lastmedian/totalcount*2);
}

ให้ผลลัพธ์ที่ค่อนข้างแน่นอนสำหรับสิ่งต่างๆเช่น page_display_time

กฎ: สตรีมอินพุตต้องเรียบตามลำดับเวลาในการแสดงเพจจำนวนมาก (> 30 เป็นต้น) และมีค่ามัธยฐานที่ไม่ใช่ศูนย์

ตัวอย่าง: เวลาโหลดหน้า 800 รายการ 10ms ... 3000ms เฉลี่ย 90ms ค่ามัธยฐานจริง: 11ms

หลังจาก 30 อินพุตค่ามัธยฐานข้อผิดพลาดโดยทั่วไปคือ <= 20% (9ms..12ms) และน้อยลงเรื่อย ๆ หลังจาก 800 อินพุตข้อผิดพลาดคือ + -2%

นักคิดอีกคนที่มีวิธีแก้ปัญหาคล้ายกันอยู่ที่นี่: การใช้งานตัวกรองค่ามัธยฐานที่มีประสิทธิภาพสูง


-1

นี่คือการใช้งาน java

package MedianOfIntegerStream;

import java.util.Comparator;
import java.util.HashSet;
import java.util.Iterator;
import java.util.Set;
import java.util.TreeSet;


public class MedianOfIntegerStream {

    public Set<Integer> rightMinSet;
    public Set<Integer> leftMaxSet;
    public int numOfElements;

    public MedianOfIntegerStream() {
        rightMinSet = new TreeSet<Integer>();
        leftMaxSet = new TreeSet<Integer>(new DescendingComparator());
        numOfElements = 0;
    }

    public void addNumberToStream(Integer num) {
        leftMaxSet.add(num);

        Iterator<Integer> iterMax = leftMaxSet.iterator();
        Iterator<Integer> iterMin = rightMinSet.iterator();
        int maxEl = iterMax.next();
        int minEl = 0;
        if (iterMin.hasNext()) {
            minEl = iterMin.next();
        }

        if (numOfElements % 2 == 0) {
            if (numOfElements == 0) {
                numOfElements++;
                return;
            } else if (maxEl > minEl) {
                iterMax.remove();

                if (minEl != 0) {
                    iterMin.remove();
                }
                leftMaxSet.add(minEl);
                rightMinSet.add(maxEl);
            }
        } else {

            if (maxEl != 0) {
                iterMax.remove();
            }

            rightMinSet.add(maxEl);
        }
        numOfElements++;
    }

    public Double getMedian() {
        if (numOfElements % 2 != 0)
            return new Double(leftMaxSet.iterator().next());
        else
            return (leftMaxSet.iterator().next() + rightMinSet.iterator().next()) / 2.0;
    }

    private class DescendingComparator implements Comparator<Integer> {
        @Override
        public int compare(Integer o1, Integer o2) {
            return o2 - o1;
        }
    }

    public static void main(String[] args) {
        MedianOfIntegerStream streamMedian = new MedianOfIntegerStream();

        streamMedian.addNumberToStream(1);
        System.out.println(streamMedian.getMedian()); // should be 1

        streamMedian.addNumberToStream(5);
        streamMedian.addNumberToStream(10);
        streamMedian.addNumberToStream(12);
        streamMedian.addNumberToStream(2);
        System.out.println(streamMedian.getMedian()); // should be 5

        streamMedian.addNumberToStream(3);
        streamMedian.addNumberToStream(8);
        streamMedian.addNumberToStream(9);
        System.out.println(streamMedian.getMedian()); // should be 6.5
    }
}

คุณควรถามคำถามใหม่จากนั้นให้คำตอบ Java ของคุณในคำถามนั้น
jww

-4

หากคุณต้องการค่าเฉลี่ยแบบเรียบวิธีที่ง่าย / รวดเร็วคือคูณค่าล่าสุดด้วย x และค่าเฉลี่ยด้วย (1-x) จากนั้นเพิ่มค่าเหล่านี้ สิ่งนี้จะกลายเป็นค่าเฉลี่ยใหม่

แก้ไข: ไม่ใช่สิ่งที่ผู้ใช้ขอและไม่ถูกต้องตามสถิติ แต่ดีพอสำหรับการใช้งานจำนวนมาก
ฉันจะทิ้งมันไว้ที่นี่ (ทั้งๆที่มีคนโหวตต่ำ) เพื่อค้นหา!


2
ซึ่งจะคำนวณค่าเฉลี่ย เขาต้องการค่ามัธยฐาน นอกจากนี้เขากำลังคำนวณค่ามัธยฐานของหน้าต่างบานเลื่อนค่าไม่ใช่ของทั้งชุด
อ. เลวี่

1
สิ่งนี้จะคำนวณค่าเฉลี่ยของหน้าต่างค่าที่มีค่าคงที่การสลายตัวขึ้นอยู่กับ X ซึ่งมีประโยชน์มากเมื่อประสิทธิภาพมีความสำคัญและคุณจะไม่ต้องกังวลกับการใช้ตัวกรองคาลแมน ฉันใส่ไว้เพื่อให้ค้นหาได้
Martin Beckett

นี่คือสิ่งที่ฉันคิดทันทีด้วยการติดตั้งตัวกรองดังกล่าวเป็นตัวกรอง lowpass พื้นฐานและราคาถูกสำหรับแอปเสียง
James Morris
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.