web-dev-qa-db-fra.com

Médiane d'une matrice avec des lignes triées

Je ne suis pas en mesure de résoudre le problème suivant de manière optimale ni de trouver une approche pour le faire n'importe où.

Étant donné une matrice N × M dans laquelle chaque ligne est triée, trouvez la médiane globale de la matrice. Supposons que N * M est impair.

Par exemple, 

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

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

La médiane est 5. Nous retournons donc 5.
Remarque: Aucune mémoire supplémentaire n'est autorisée.

Toute aide serait appréciée.

10
hatellla

Considérez le processus suivant.

  • Si nous considérons la matrice N * M comme un tableau 1-D, la médiane est l'élément de 1+N*M/2 ème élément.

  • Considérons alors que x sera la médiane si x est un élément de la matrice et que le nombre d’éléments de la matrice ≤ x est égal à 1 + N*M/2.

  • Comme les éléments de la matrice dans chaque ligne sont triés, vous pouvez facilement trouver le nombre d’éléments dans chaque ligne less than or equals x. Pour trouver dans toute la matrice, la complexité est N*log M avec recherche binaire.

  • Ensuite, commencez par rechercher les éléments minimum et maximum dans la matrice N * M. Appliquez la recherche binaire sur cette plage et exécutez la fonction ci-dessus pour chaque x.

  • Si le nombre d'éléments dans la matrice ≤ x est 1 + N*M/2 et que x contient dans cette matrice, alors x est la médiane.

Vous pouvez considérer ceci sous le code 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;
}
11
sunkuet02

Si les éléments de la matrice sont des entiers, on peut effectuer une recherche binaire dans la médiane en commençant par la plage de la matrice pour hi et low. O (n log m log (salut-bas)).

Sinon, la recherche binaire, O (log m), pour chaque rangée tour à tour, O (n), élément le plus proche de la médiane globale de la matrice à partir de la gauche et de la plus proche de la droite, O (n log m), mettant à jour le meilleur jusqu'à présent. Nous savons que la médiane globale ne contient pas plus de floor(m * n / 2) éléments strictement inférieurs, et que l’ajout du nombre d’éléments inférieurs et le nombre de fois où cela se produit ne peuvent être inférieurs à floor(m * n / 2) + 1. Nous utilisons la recherche binaire standard sur la ligne et, comme le souligne Greybeard, nous ignorons le test des éléments extérieurs à notre "meilleure" plage. Pour déterminer la proximité d'un élément par rapport à la médiane globale, il faut compter combien d'éléments dans chaque ligne sont strictement inférieurs à lui et combien sont égaux, ce qui est obtenu en O(n log m)time avec n binary search. Puisque la rangée est triée, nous savons que les éléments les plus grands seraient plus "à droite" et les éléments plus petits plus "à gauche" par rapport à la médiane globale.

Si on est autorisé à réorganiser la matrice, la complexité temporelle en O (mn log (mn)) est possible en triant la matrice en place (en utilisant le tri par bloc, par exemple) et en renvoyant l'élément du milieu.

1
גלעד ברקן

Il existe un algorithme aléatoire qui résout ce problème en temps O (n (log n) (log m)). Il s'agit d'un algorithme de Las Vegas , ce qui signifie qu'il donne toujours des résultats corrects mais peut prendre plus de temps que prévu. Dans ce cas, la probabilité que cela prenne beaucoup plus de temps que prévu est extrêmement faible.

Lorsque m = 1, ce problème se réduit au problème de la recherche de la médiane dans un tableau en lecture seule utilisant un espace constant. Ce problème n’a pas de solution optimale connue: voir "Recherche de la médiane en mémoire en lecture seule sur une entrée entière, Chan et al."

Une chose étrange à propos de cette réduction du problème lorsque m = 1 est que ce cas sub est aussi un cas super, dans la mesure où un algorithme pour m = 1 peut être appliqué au cas m> 1. L'idée est simplement d'oublier que les lignes du tableau sont triées et traitent toute la zone de stockage comme un tableau non trié de taille n * m. Ainsi, par exemple, l’algorithme trivial pour le cas m = 1, dans lequel chaque élément est vérifié pour voir s’il s’agit de la médiane, prend O (n2) temps. L’appliquer lorsque m> 1 prend O (n2m2) temps.

Pour revenir au cas m = 1, dans le modèle de comparaison (dans lequel les éléments du tableau peuvent être des entiers, des chaînes, des nombres réels ou tout autre élément pouvant être comparé aux opérateurs d'inégalité "<", ">"), la solution déterministe la plus connue utilisant l'espace s (où s est une constante, c'est-à-dire que dans O(1)) a le temps (2ss! n1 + 1/s), et il est plus complexe que les algorithmes habituels décrits dans stackoverflow (mais pas dans https://cstheory.stackexchange.com ou https://cs.stackexchange.com ). Il utilise une séquence chaînée d'algorithmes As, UNEs-1, ..., UNE1, où uns + 1 appelle uns. Vous pouvez le lire dans "Sélection à partir de la mémoire en lecture seule et tri avec un minimum de mouvement de données", par Munro et Raman .

Il existe un algorithme aléatoire simple avec une durée d'exécution plus courte et une probabilité élevée. Pour toute constante c, cet algorithme s'exécute dans le temps O (n log n) avec une probabilité 1 - O (n-c). Lorsque le tableau est la matrice de taille n * m, cela donne O (n m log (n m)).

Cet algorithme ressemble beaucoup à la sélection rapide sans réorganisation des éléments lors du partitionnement.

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

Cela fonctionne en maintenant les limites supérieure et inférieure de la valeur de la médiane. Il boucle ensuite sur le tableau et sélectionne de manière aléatoire une valeur entre les limites. Cette valeur remplace l'une des limites et le processus recommence.

Les bornes sont accompagnées de leur plage d'index, une mesure des index sur lesquels la limite apparaîtrait si le tableau était trié. Une fois que l’une des bornes apparaît à l’indice ⌊n/2⌋, c’est la médiane et l’algorithme se termine.

Lorsqu'un élément est sélectionné de manière aléatoire dans l'écart entre les limites, cela réduit cet écart de 50%. L'algorithme se termine (au plus tard) lorsque l'écart est égal à 0. Nous pouvons le modéliser sous la forme d'une série de variables aléatoires indépendantes et uniformément réparties Xje à partir de (0,1) tel que Yk = X1 * X2 * ... * Xk où Xje est le rapport de l'écart qui reste après le tour i. Par exemple, si après le dixième tour, l’écart entre les plages d’indice de lo et hi est de 120, et après le onzième tour, l’écart est de 90, alors X11 = 0,75. L'algorithme se termine lorsque Yk <1/n, car l’écart est alors inférieur à 1.

Choisissez un entier positif constant k. Lions la probabilité que Yk log2n > = 1/n en utilisant les limites de Chernoff. Nous avons Yk log2n = X1 * X2 * ... Xk log2n, alors lnk log2n = ln X1 + ln X2 + ... + ln Xk log2n. La borne de Chernoff liée donne alors Pr (ln X1 + ln X2 + ... + ln Xk log2n > = ln (1/n)) <= mint> 0 e-t ln (1/n) (E [et ln X1] * E [et ln X2] * ... * E [et ln Xk log2 n]). Après quelques simplifications, le côté droit est mint> 0 nt (EX1t] * E [X2t] * ... * E [Xk log2 nt]). Comme il s’agit d’un minimum et que nous cherchons une limite supérieure, nous pouvons l’affaiblir en nous spécialisant à t = 1. Il se simplifie ensuite à n1-k, puisque E [Xje] = 1/2.

Si nous prenons, par exemple, k = 6, alors cela limite la probabilité qu'il y ait 6 log2n tours ou plus par n-5. Donc, avec probabilité 1 - O (n-5) l'algorithme effectue 6 log2n - 1 round ou moins. C'est ce que je veux dire par "avec une probabilité élevée" ci-dessus.

Étant donné que chaque tour inspecte chaque membre du tableau un nombre constant de fois, chaque tour prend un temps linéaire, pour un temps total de fonctionnement de O (n log n) avec une probabilité élevée. Lorsque le tableau n'est pas simplement un tableau, mais une matrice de taille n * m qui donne O (n m log (n m)).Nous pouvons toutefois faire beaucoup mieux en tirant parti du tri des rangs. Lorsque nous travaillions dans un seul tableau non trié, il était nécessaire d'inspecter chaque élément du tableau pour trouver les éléments dans l'espace que j'ai référencé ci-dessus. Dans une matrice avec des lignes triées, les éléments de l’espace sont situés dans un segment contigu de chaque ligne. Chaque segment peut être identifié en temps O (log m) à l'aide de la recherche binaire, de sorte qu'ils peuvent tous être situés en temps O (n log m). L'échantillonnage de réservoir prend maintenant un temps O (n log m) par itération de la boucle.

L’autre travail principal effectué dans la boucle consiste à identifier la plage d’index de l’élément à partir de l’espace sélectionné de manière aléatoire. De nouveau, comme chaque ligne est triée, la plage d'index pour l'élément choisi de manière aléatoire dans une ligne peut être déterminée en temps O (log m). Les sommes des plages d'index pour chaque ligne constituent la plage d'index sur l'ensemble du tableau. Par conséquent, cette partie de chaque itération de boucle prend également uniquement O (n log m).

.

-k) pour toute constante k. Ainsi, l’ensemble de l’algorithme prend O (n (log n) (log m)) avec une probabilité élevée.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

This solution is substantially faster than the first one when m is non-constant.

1
jbapple

Une solution simple O(1) en mémoire consiste à vérifier si chaque élément individuel z est la médiane. Pour ce faire, nous trouvons la position de z dans toutes les lignes, en accumulant simplement le nombre d’éléments inférieurs à z . Cela n'utilise pas le fait que chaque ligne est triée sauf la recherche de la position de z dans chaque ligne de O (log M) heure. Pour chaque élément, nous devons faire N * log M des comparaisons, et il y a N * M éléments, donc c'est N²M log M .

1
Saeed Amiri

J'ai codé le O (nbûche2 m) la solution temporelle de גלעד ברקן, mais ils m'ont demandé de ne pas ajouter le code à leur réponse, la voici donc sous forme de réponse séparée:

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

Réponse de sunkuet02 avec des améliorations et un code python:
Chaque ligne de la matrice N × M A est triée et comporte un élément central, qui est sa médiane.
Il y a au moins N * (M + 1)/2 éléments non plus grands que le maximum hi de ces médianes, et au moins N * (M + 1)/2 pas plus petits que le minimum lo:
la médiane de tous les éléments de A doit être comprise entre lo et hi, inclus.
.__ Dès que plus de la moitié des éléments sont connus pour être inférieurs au candidat actuel, ce dernier est connu pour être élevé. Dès qu'il ne reste que trop peu de lignes pour que le nombre d'éléments inférieurs au candidat actuel atteigne la moitié du total, le candidat est connu pour être faible: dans les deux cas, passez immédiatement au candidat suivant.

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 __=='__main__':
    print(median( [[1, 3, 5], [2, 6, 9], [3, 6, 9]] ))

(Je considère que std::upper_bound() et bisect.bisect() sont équivalents (bisect_right() est un alias).)
Pour la seconde médiane candidate, la dernière ligne traitée peut être inférieure à celle de la première itération. Lors des itérations suivantes, ce nombre ne devrait jamais diminuer - trop paresseux pour prendre en compte que dans ((renommer et) augmenter middle_row selon le cas).

0
greybeard

Utilisation de l'algorithme de Las Vegas:

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
Eye Sun