freeCodeCamp/curriculum/challenges/portuguese/10-coding-interview-prep/rosetta-code/closest-pair-problem.md

10 KiB

id title challengeType forumTopicId dashedName
5951a53863c8a34f02bf1bdc Problema do par mais próximo 5 302232 closest-pair-problem

--description--

Forneça uma função para encontrar os dois pontos mais próximos entre um conjunto de pontos dados em duas dimensões.

A solução simples é um algoritmo O(n^2) (que podemos chamar de algoritmo de força bruta). O pseudocódigo (usando índices) poderia ser, simplesmente:

bruteForceClosestPair de P(1), P(2), ... P(N)
se N < 2 então
  retornesenão
  minDistance ← |P(1) - P(2)|
  minPoints ← { P(1), P(2) }
  paraCada i ∈ [1, N-1]
    paraCada j ∈ [i+1, N]
      se |P(i) - P(j)| < minDistance então
        minDistance ← |P(i) - P(j)|
        minPoints ← { P(i), P(j) }
      fimSe
    fimPara
  fimPara
  retorne minDistance, minPoints
fimSe

Um algoritmo melhor com base na abordagem recursiva de dividir e conquistar, com complexidade O(n\log n), teria, como pseudocódigo:

closestPair de (xP, yP)
  onde xP é P(1) .. P(N) ordenado pela coordenada x, e
  yP é P(1) .. P(N) ordenado pela coordenada y (ordem ascendente)
se N ≤ 3 então
  retorne pontos mais próximos de xP usando o algoritmo de força bruta
senão
  xL ← pontos de xP de 1 a ⌈N/2⌉
  xR ← pontos de xP de ⌈N/2⌉+1 a N
  xm ← xP(⌈N/2⌉)x
  yL ← { p ∈ yP : px ≤ xm }
  yR ← { p ∈ yP : px > xm }
  (dL, pairL) ← closestPair de (xL, yL)
  (dR, pairR) ← closestPair de (xR, yR)
  (dmin, pairMin) ← (dR, pairR)
  se dL < dR então
    (dmin, pairMin) ← (dL, pairL)
  fimSe
  yS ← { p ∈ yP : |xm - px| < dmin }
  nS ← número de pontos em yS
  (closest, closestPair) ← (dmin, pairMin)
  para i de 1 a nS - 1
    k ← i + 1
    enquanto k ≤ nS e yS(k)y - yS(i)y < dmin
      se |yS(k) - yS(i)| < closest então
        (closest, closestPair) ← (|yS(k) - yS(i)|, {yS(k), yS(i)})
      fimSe
      k ← k + 1
    fimEnquanto
  fimPara
  retorne closest, closestPair
fimSe

Para a entrada, espere que o argumento seja um array de objetos Point com membros x e y definidos como números. Retorna um objeto que contém os pares chave-valor de distance e pair (o par com os dois pontos mais próximos).

Por exemplo, getClosestPair com o array de entrada points:

const points = [
  new Point(1, 2),
  new Point(3, 3),
  new Point(2, 2)
];

Retornaria:

{
  distance: 1,
  pair: [
    {
      x: 1,
      y: 2
    },
    {
      x: 2,
      y: 2
    }
  ]
}

Observação: ordene o array de pair por seus valores em x na ordem de incrementação.

--hints--

getClosestPair deve ser uma função.

assert(typeof getClosestPair === 'function');

getClosestPair(points1).distance deve ser 0.0894096443343775.

assert.equal(getClosestPair(points1).distance, answer1.distance);

getClosestPair(points1).pair deve ser [ { x: 7.46489, y: 4.6268 }, { x: 7.46911, y: 4.71611 } ].

assert.deepEqual(
  JSON.parse(JSON.stringify(getClosestPair(points1))).pair,
  answer1.pair
);

getClosestPair(points2).distance deve ser 65.06919393998976.

assert.equal(getClosestPair(points2).distance, answer2.distance);

getClosestPair(points2).pair deve ser [ { x: 37134, y: 1963 }, { x: 37181, y: 2008 } ].

assert.deepEqual(
  JSON.parse(JSON.stringify(getClosestPair(points2))).pair,
  answer2.pair
);

getClosestPair(points3).distance deve ser 6754.625082119658.

assert.equal(getClosestPair(points3).distance, answer3.distance);

getClosestPair(points3).pair deve ser [ { x: 46817, y: 64975 }, { x: 48953, y: 58567 } ].

assert.deepEqual(
  JSON.parse(JSON.stringify(getClosestPair(points3))).pair,
  answer3.pair
);

--seed--

--after-user-code--

const points1 = [
    new Point(0.748501, 4.09624),
    new Point(3.00302, 5.26164),
    new Point(3.61878,  9.52232),
    new Point(7.46911,  4.71611),
    new Point(5.7819,   2.69367),
    new Point(2.34709,  8.74782),
    new Point(2.87169,  5.97774),
    new Point(6.33101,  0.463131),
    new Point(7.46489,  4.6268),
    new Point(1.45428,  0.087596)
];

const answer1 = {
  distance: 0.0894096443343775,
  pair: [
    {
      x: 7.46489,
      y: 4.6268
    },
    {
      x: 7.46911,
      y: 4.71611
    }
  ]
};

const points2 = [
  new Point(37100, 13118),
  new Point(37134, 1963),
  new Point(37181, 2008),
  new Point(37276, 21611),
  new Point(37307, 9320)
];

const answer2 = {
  distance: 65.06919393998976,
  pair: [
    {
      x: 37134,
      y: 1963
    },
    {
      x: 37181,
      y: 2008
    }
  ]
};

const points3 = [
  new Point(16910, 54699),
  new Point(14773, 61107),
  new Point(95547, 45344),
  new Point(95951, 17573),
  new Point(5824, 41072),
  new Point(8769, 52562),
  new Point(21182, 41881),
  new Point(53226, 45749),
  new Point(68180, 887),
  new Point(29322, 44017),
  new Point(46817, 64975),
  new Point(10501, 483),
  new Point(57094, 60703),
  new Point(23318, 35472),
  new Point(72452, 88070),
  new Point(67775, 28659),
  new Point(19450, 20518),
  new Point(17314, 26927),
  new Point(98088, 11164),
  new Point(25050, 56835),
  new Point(8364, 6892),
  new Point(37868, 18382),
  new Point(23723, 7701),
  new Point(55767, 11569),
  new Point(70721, 66707),
  new Point(31863, 9837),
  new Point(49358, 30795),
  new Point(13041, 39744),
  new Point(59635, 26523),
  new Point(25859, 1292),
  new Point(1551, 53890),
  new Point(70316, 94479),
  new Point(48549, 86338),
  new Point(46413, 92747),
  new Point(27186, 50426),
  new Point(27591, 22655),
  new Point(10905, 46153),
  new Point(40408, 84202),
  new Point(52821, 73520),
  new Point(84865, 77388),
  new Point(99819, 32527),
  new Point(34404, 75657),
  new Point(78457, 96615),
  new Point(42140, 5564),
  new Point(62175, 92342),
  new Point(54958, 67112),
  new Point(4092, 19709),
  new Point(99415, 60298),
  new Point(51090, 52158),
  new Point(48953, 58567)
];

const answer3 = {
  distance: 6754.625082119658,
  pair: [
    {
      x: 46817,
      y: 64975
    },
    {
      x: 48953,
      y: 58567
    }
  ]
}

--seed-contents--

const Point = function(x, y) {
  this.x = x;
  this.y = y;
};
Point.prototype.getX = function() {
  return this.x;
};
Point.prototype.getY = function() {
  return this.y;
};

function getClosestPair(pointsArr) {

  return true;
}

--solutions--

const Point = function(x, y) {
  this.x = x;
  this.y = y;
};
Point.prototype.getX = function() {
  return this.x;
};
Point.prototype.getY = function() {
  return this.y;
};

const mergeSort = function mergeSort(points, comp) {
    if(points.length < 2) return points;

    var n = points.length,
        i = 0,
        j = 0,
        leftN = Math.floor(n / 2),
        rightN = leftN;

    var leftPart = mergeSort( points.slice(0, leftN), comp),
        rightPart = mergeSort( points.slice(rightN), comp );

    var sortedPart = [];

    while((i < leftPart.length) && (j < rightPart.length)) {
        if(comp(leftPart[i], rightPart[j]) < 0) {
            sortedPart.push(leftPart[i]);
            i += 1;
        }
        else {
            sortedPart.push(rightPart[j]);
            j += 1;
        }
    }
    while(i < leftPart.length) {
        sortedPart.push(leftPart[i]);
        i += 1;
    }
    while(j < rightPart.length) {
        sortedPart.push(rightPart[j]);
        j += 1;
    }
    return sortedPart;
};

const closestPair = function _closestPair(Px, Py) {
    if(Px.length < 2) return { distance: Infinity, pair: [ new Point(0, 0), new Point(0, 0) ] };
    if(Px.length < 3) {
        //find euclid distance
        var d = Math.sqrt( Math.pow(Math.abs(Px[1].x - Px[0].x), 2) + Math.pow(Math.abs(Px[1].y - Px[0].y), 2) );
        return {
            distance: d,
            pair: [ Px[0], Px[1] ]
        };
    }

    var n = Px.length,
        leftN = Math.floor(n / 2),
        rightN = leftN;

    var Xl = Px.slice(0, leftN),
        Xr = Px.slice(rightN),
        Xm = Xl[leftN - 1],
        Yl = [],
        Yr = [];
    //separate Py
    for(var i = 0; i < Py.length; i += 1) {
        if(Py[i].x <= Xm.x)
            Yl.push(Py[i]);
        else
            Yr.push(Py[i]);
    }

    var dLeft = _closestPair(Xl, Yl),
        dRight = _closestPair(Xr, Yr);

    var minDelta = dLeft.distance,
        closestPair = dLeft.pair;
    if(dLeft.distance > dRight.distance) {
        minDelta = dRight.distance;
        closestPair = dRight.pair;
    }

    //filter points around Xm within delta (minDelta)
    var closeY = [];
    for(i = 0; i < Py.length; i += 1) {
        if(Math.abs(Py[i].x - Xm.x) < minDelta) closeY.push(Py[i]);
    }
    //find min within delta. 8 steps max
    for(i = 0; i < closeY.length; i += 1) {
        for(var j = i + 1; j < Math.min( (i + 8), closeY.length ); j += 1) {
            var d = Math.sqrt( Math.pow(Math.abs(closeY[j].x - closeY[i].x), 2) + Math.pow(Math.abs(closeY[j].y - closeY[i].y), 2) );
            if(d < minDelta) {
                minDelta = d;
                closestPair = [ closeY[i], closeY[j] ]
            }
        }
    }

    return {
        distance: minDelta,
        pair: closestPair.sort((pointA, pointB) => pointA.x - pointB.x)
    };
};

function getClosestPair(points) {
  const sortX = function(a, b) { return (a.x < b.x) ? -1 : ((a.x > b.x) ? 1 : 0); }
  const sortY = function(a, b) { return (a.y < b.y) ? -1 : ((a.y > b.y) ? 1 : 0); }

  const Px = mergeSort(points, sortX);
  const Py = mergeSort(points, sortY);

  return closestPair(Px, Py);
}