web-dev-qa-db-ja.com

並べ替えられた行を持つ行列の中央値

次の問題を最適に解決できず、どこにでもこれを行うためのアプローチを見つけることができません。

各行が並べ替えられているN×M行列を指定して、行列の全体の中央値を求めます。 N * Mが奇数であると仮定します。

例えば、

マトリックス=
[1、3、5]
[2、6、9]
[3、6、9]

A = [1、2、3、3、5、6、6、9、9]

中央値は5なので、5を返します。
注:追加のメモリは許可されていません。

どんな助けでもありがたいです。

14
hatellla

次のプロセスを検討してください。

  • N * M行列を1次元配列と見なす場合、中央値は1+N*M/2番目の要素です。

  • 次に、xが行列の要素であり、行列要素の数≤xが1 + N*M/2に等しい場合、xが中央値になると考えます。

  • 各行の行列要素が並べ替えられているので、各行の要素の数less than or equals xを簡単に見つけることができます。マトリックス全体を見つける場合、複雑さはバイナリ検索でN*log Mです。

  • 次に、最初にN * M行列から最小要素と最大要素を見つけます。その範囲にバイナリ検索を適用し、xごとに上記の関数を実行します。

  • 行列≤ xの要素数が1 + N*M/2であり、xがその行列に含まれている場合、xは中央値です。

あなたはC++コードの下でこれを考えることができます:

int median(vector<vector<int> > &A) {
    int min = A[0][0], max = A[0][0];
    int n = A.size(), m = A[0].size();
    for (int i = 0; i < n; ++i) {
        if (A[i][0] < min) min = A[i][0];
        if (A[i][m-1] > max) max = A[i][m-1];
    }

    int element = (n * m + 1) / 2;
    while (min < max) {
        int mid = min + (max - min) / 2;
        int cnt = 0;
        for (int i = 0; i < n; ++i)
            cnt += upper_bound(&A[i][0], &A[i][m], mid) - &A[i][0];
        if (cnt < element)
            min = mid + 1;
        else
            max = mid;
    }
    return min;
}
12
sunkuet02

行列要素が整数の場合、行列範囲から始まる中央値の高低をバイナリ検索できます。 O(n log m log(hi-low))。

それ以外の場合、O(n²log²m)の最悪の場合の時間の複雑さを持つ1つの方法は、各行のバイナリ検索O(log m)であるO(n)です。これは、左から全体の行列中央値に最も近い要素であり、右から最も近いO(n log m)、これまでで最高の更新。全体の中央値には、厳密にそれより少ないfloor(m * n / 2)要素しかなく、それより少ない要素の数とそれが発生する回数を追加すると、floor(m * n / 2) + 1以上になることがあります。行では標準のバイナリ検索を使用し、グレイベアーが指摘したように、「最良の」範囲外の要素のテストはスキップします。要素が全体の中央値にどれだけ近いかをテストするには、各行の要素の数が厳密にそれよりも少ないか、等しいかをカウントします。これは、nバイナリ検索を使用してO(n log m)時間で達成されます。行が並べ替えられているため、全体の中央値に対して、要素が大きいほど「右」になり、要素が小さいほど「左」になります。

行列の再配置が許可されている場合、O(mn log(mn))時間の複雑さは、行列を所定の場所に並べ替え(たとえば、ブロックの並べ替えを使用)、中央の要素を返すことで可能です。

1

この問題をO(n(log n)(log m))時間で解決するランダム化アルゴリズムがあります。 ラスベガスアルゴリズム です。つまり、常に正しい結果が得られますが、予想よりも時間がかかる場合があります。この場合、予想以上に時間がかかる確率は非常に小さいです。

M = 1の場合、この問題は、定数空間を使用して読み取り専用配列の中央値を見つける問題に還元されます。その問題には既知の最適な解決策がありません。 "整数入力の読み取り専用メモリで中央値を見つける、Chan et al。" を参照してください。

M = 1の場合の問題のこの削減についての奇妙なことの1つは、このsubケースもsuperケース。m= 1のアルゴリズムは、m> 1のケースに適用できます。アイデアは、配列の行がソートされていることを忘れて、ストレージ領域全体をサイズn * mのソートされていない配列として扱うことです。したがって、たとえば、各要素が中央値かどうかを確認するために各要素がチェックされるm = 1の場合の自明なアルゴリズムは、O(n2)時間。 m> 1の場合に適用すると、O(n2メートル2)時間。

比較モデル(配列の項目は整数、文字列、実数、または不等号演算子「<」、「>」と比較できるその他のもの)であるm = 1の場合に戻ります。スペースs(ここでsは定数、つまりO(1))は時間has(2ss!n1 + 1 /秒)、stackoverflowで説明されている通常のアルゴリズムよりも複雑です(ただし、 https://cstheory.stackexchange.com または https://cs.stackexchange.com ではありません) =)。アルゴリズムの連鎖シーケンスを使用しますAs、As-1、...、A1、As + 1 Aを呼び出すs。 MunroとRamanによる "読み取り専用メモリからの選択と最小限のデータ移動による並べ替え")で読み取ることができます

高い確率で実行時間が短い単純なランダム化アルゴリズムがあります。任意の定数cについて、このアルゴリズムは確率1-O(nで時間O(n log n)で実行されます-c)。配列がO(n m log(n m))となるサイズn * mの行列である場合。

このアルゴリズムは、パーティショニング中に要素を再配置しないクイックセレクトによく似ています。

import random

def index_range(needle, haystack):
  """The index range' of a value over an array is a pair
  consisting of the number of elements in the array less
  than that value and the number of elements in the array
  less than or equal to the value.
  """
  less = same = 0
  for x in haystack:
    if x < needle: less += 1
    Elif x == needle: same += 1
  return less, less + same

def median(xs):
  """Finds the median of xs using O(1) extra space. Does not
  alter xs.
  """
  if not xs: return None
  # First, find the minimum and maximum of the array and
  # their index ranges:
  lo, hi = min(xs), max(xs)
  lo_begin, lo_end = index_range(lo, xs)
  hi_begin, hi_end = index_range(hi, xs)
  # Gradually we will move the lo and hi index ranges closer
  # to the median.
  mid_idx = len(xs)//2
  while True:
    print "range size", hi_begin - lo_end
    if lo_begin <= mid_idx < lo_end:
      return lo
    if hi_begin <= mid_idx < hi_end:
      return hi
    assert hi_begin - lo_end > 0
    # Loop over the array, inspecting each item between lo
    # and hi. This loops sole purpose is to reservoir sample
    # from that set. This makes res a randomly selected
    # element from among those strictly between lo and hi in
    # xs:
    res_size = 0
    res = None
    for x in xs:
      if lo < x < hi:
        res_size += 1
        if 1 == random.randint(1, res_size):
          res = x
    assert res is not None
    assert hi_begin - lo_end == res_size
    # Now find which size of the median res is on and
    # continue the search on the smaller region:
    res_begin, res_end = index_range(res, xs)
    if res_end > mid_idx:
      hi, hi_begin, hi_end = res, res_begin, res_end
    else:
      lo, lo_begin, lo_end = res, res_begin, res_end

中央値の上限と下限を維持することで機能します。次に、配列をループし、境界の間の値をランダムに選択します。その値が境界の1つを置き換え、プロセスが再び開始されます。

境界にはインデックス範囲が伴います。これは、配列がソートされた場合に境界が表示されるインデックスの指標です。境界の1つがインデックス⌊n/ 2 atに現れると、それが中央値になり、アルゴリズムが終了します。

要素が境界間のギャップでランダムに選択されると、これにより、ギャップが予想の50%縮小されます。アルゴリズムは、ギャップが0になると(遅くとも)終了します。これを一連のランダムで独立した一様分布変数Xとしてモデル化できます。 (0,1)からYk = X1 * バツ2 * ... * バツk ここでX ラウンドiの後に残るギャップの比率です。たとえば、10回目のラウンドの後、lohiのインデックス範囲間のギャップが120であり、11回目のラウンドの後、ギャップが90である場合、X11 = 0.75。アルゴリズムはYのときに終了しますk <1/n。ギャップが1未満になるため。

定数の正の整数kを選択します。 Yの確率を制限しましょうkログ2 > = 1/n、チャーノフ境界を使用。私たちはYを持っていますkログ2 = X1 * バツ2 * ... バツkログ2なので、ln Ykログ2 = ln X1 + ln X2 + ... + ln Xkログ2。チャーノフ限界はPr(ln X1 + ln X2 + ... + ln Xkログ2 > = ln(1/n))<=分t> 0 e-t ln(1/n) (E [et ln X1] * E [et ln X2] * ... * E [et ln Xkログ2 ん])。いくつかの単純化の後、右側はminですt> 0 んt (E [X1t] * E [X2t] * ... * E [Xkログ2 んt])。これは最小値であり、上限を探しているため、t = 1に特化することでこれを弱めることができます。1-k、E [X] = 1/2。

たとえば、k = 6を選択した場合、これは6つの対数がある確率を制限します。2n回以上n回-5。したがって、確率1-O(n-5)アルゴリズムは6ログを実行します2n-1ラウンド以下。これが、上記の「高い確率で」という意味です。

各ラウンドは配列のすべてのメンバーを一定回数検査するので、各ラウンドはO(n log n)の合計実行時間に対して高い確率で線形時間をとります。配列が単なる配列ではなく、O(n m log(n m))となるサイズn * mの行列である場合。

ただし、行の並べ替えを利用することで、大幅に改善できます。単一の並べ替えられていない配列で作業していたときに、上記で参照したギャップ内の要素を見つけるには、配列の各要素を検査する必要がありました。並べ替えられた行のある行列では、ギャップの要素は各行の隣接するセグメントに配置されます。各セグメントは、バイナリ検索を使用してO(log m)時間で識別できるため、すべてO(n log m)時間で配置できます。貯水池のサンプリングは、ループの反復ごとにO(n log m)時間かかります。

ループで行われる他の主な作業は、ランダムに選択されたギャップから要素のインデックス範囲を識別することです。この場合も、各行がソートされているため、行内のランダムに選択された要素のインデックス範囲はO(log m)時間で決定できます。各行のインデックス範囲の合計は、配列全体のインデックス範囲を構成するため、各ループ反復のこの部分もO(n log m)時間しかかかりません。

チャーノフ限界を使用した上記と同じ引数により、確率が少なくとも1-O(n-k)定数kの場合。したがって、アルゴリズム全体はO(n(log n)(log m))時間かかります。

import bisect
import random

def matrix_index_range(needle, haystack):
  """matrix_index_range calculates the index range of needle
  in a haystack that is a matrix (stored in row-major order)
  in which each row is sorted"""
  n, m = len(haystack), len(haystack[0])
  begin = end = 0;
  for x in haystack:
    begin += bisect.bisect_left(x, needle)
    end += bisect.bisect_right(x, needle)
  return begin, end

def matrix_median(xs):
  print "Starting"
  if not xs or not xs[0]: return None
  n, m = len(xs), len(xs[0])
  lo, hi = xs[0][0], xs[0][m-1]
  for x in xs:
    lo, hi = min(lo, x[0]), max(hi, x[m-1])
  lo_begin, lo_end = matrix_index_range(lo, xs)
  hi_begin, hi_end = matrix_index_range(hi, xs)
  mid_idx = (n * m) // 2
  while True:
    print "range size", hi_begin - lo_end
    if lo_begin <= mid_idx < lo_end:
      return lo
    if hi_begin <= mid_idx < hi_end:
      return hi
    assert hi_begin - lo_end > 0
    mid = None
    midth = random.randint(0, hi_begin - lo_end - 1)
    for x in xs:
      gap_begin = bisect.bisect_right(x, lo)
      gap_end = bisect.bisect_left(x, hi)
      gap_size = gap_end - gap_begin
      if midth < gap_size:
        mid = x[gap_begin + midth]
        break
      midth -= gap_size
    assert mid is not None
    mid_begin, mid_end = matrix_index_range(mid, xs)
    assert lo_end <= mid_begin and mid_end <= hi_begin
    if mid_end > mid_idx:
      hi, hi_begin, hi_end = mid, mid_begin, mid_end
    else:
      lo, lo_begin, lo_end = mid, mid_begin, mid_end

このソリューションは、mが定数でない場合、最初のソリューションよりもかなり高速です。

1
jbapple

単純なO(1)メモリソリューションは、個々の要素zが中央値であるかどうかを確認することです。これを行うには、zすべての行で、zより小さい要素の数を累積するだけです。これは、各行が並べ替えられるという事実を使用しませんzの位置をO(log M)時間で各行に見つけることを除いて、各要素に対してN * log M比較であり、N * M要素があるため、N²Mlog M

1
Saeed Amiri

O(n2 ログ2 m)גלעדברקןの時間解ですが、彼らは答えにコードを追加しないように私に頼んだので、ここでそれは別の答えです:

import bisect

def MedianDistance(key, matrix):
  lo = hi = 0
  for row in matrix:
    lo += bisect.bisect_left(row, key)
    hi += bisect.bisect_right(row, key)
  mid = len(matrix) * len(matrix[0]) // 2;
  if hi - 1 < mid: return hi - 1 - mid
  if lo > mid: return lo - mid
  return 0

def ZeroInSorted(row, measure):
  lo, hi = -1, len(row)
  while hi - lo > 1:
    mid = (lo + hi) // 2
    ans = measure(row[mid])
    if ans < 0: lo = mid
    Elif ans == 0: return mid
    else: hi = mid

def MatrixMedian(matrix):
  measure = lambda x: MedianDistance(x, matrix)
  for idx, row in enumerate(matrix):
    if not idx & idx-1: print(idx)
    ans = ZeroInSorted(row, measure)
    if ans is not None: return row[ans]
1
jbapple

sunkuet02の回答 改良とpythonコード:
N×Mマトリックスの各行Aはソートされ、中央値である中央の要素を持っています。
これらの中央値の最大値hi以下の少なくともN *(M + 1)/ 2個の要素があり、少なくともN *(M + 1)/ 2は最小値以上lo
Aのすべての要素の中央値は、lohiの間でなければなりませんを含みます。
要素の半分以上が現在の候補よりも低いことが判明するとすぐに、後者は高いことが判明します。現在の候補よりも少ない要素の数に対して行が少なすぎて合計の半分に達するとすぐに、候補は低いことがわかります。どちらの場合も、すぐに次の候補に進みます。

_from bisect import bisect

def median(A):
    """ returns the median of all elements in A.
        Each row of A needs to be in ascending order. """
    # overall median is between min and max row median
    lo, hi = minimax(A)
    n = len(A)
    middle_row = n // 2
    columns = len(A[0])
    half = (n * columns + 1) // 2
    while lo < hi:
        mid = lo + (hi - lo) // 2
        lower = 0
        # first half can't decide median
        for a in A[:middle_row]:
            lower += bisect(a, mid)
        # break as soon as mid is known to be too high or low
        for r, a in enumerate(A[middle_row:n-1]):
            lower += bisect(a, mid)
            if half <= lower:
                hi = mid
                break
            if lower < r*columns:
                lo = mid + 1
                break
        else: # decision in last row
            lower += bisect(A[n-1], mid)
            if half <= lower:
                hi = mid
            else:
                lo = mid + 1

    return lo


def minmax(x, y):
    """return min(x, y), max(x, y)"""
    if x < y:
        return x, y
    return y, x


def minimax(A):
    """ return min(A[0..m][n//2]), max(A[0..m][n//2]):
        minimum and maximum of medians if A is a
        row major matrix with sorted rows."""
    n = len(A)
    half = n // 2
    if n % 2:
        lo = hi = A[0][half]
    else:
        lo, hi = minmax(A[0][half], A[1][half])
    for i in range(2-n % 2, len(A[0]), 2):
        l, h = minmax(A[i][half], A[i+1][half])
        if l < lo:
            lo = l
        if hi< h:
            hi = h
    return lo, hi


if __name__ =='__main__':
    print(median( [[1, 3, 5], [2, 6, 9], [3, 6, 9]] ))
_

std::upper_bound()bisect.bisect()は同等と見なします(bisect_right()はエイリアスです)。
2番目の候補中央値の場合、最後に処理された行が最初の反復よりも低くなる可能性があります。次の反復では、その行番号は決して減少してはなりません-thatを因数分解するには遅延が必要です((名前の変更と)必要に応じて_middle_row_を増加します)。

0
greybeard

ラスベガスアルゴリズムの使用:

from random import randint

def findMedian(matrix):
    #getting the length of columns and rows
     N = len(matrix)
     M = len(matrix[0])
     while True:
           counter = 0
           #select a row randomly
           u = randint(0,len(matrix)-1)
           #select a column randomly
           v = randint(0,len(matrix[0])-1)
           #random index
           x = matrix[u][v]
          for i in range(len(matrix)):
             for j in range(len(matrix[0])):
                 if matrix[i][j] < x:
                        counter+=1
          #finding median
          if counter == (N*M-1)//2:
     return (x)



 arr = [[1,3,5],
        [2,6,9],
        [3,6,9]]

 findMedian(arr)  
0
user7983445