web-dev-qa-db-fra.com

Algorithme le plus rapide pour un tableau de taille N avec décalage de cercle pour position M

Quel est l'algorithme le plus rapide pour la matrice de décalage de cercle pour M positions?
Par exemple, [3 4 5 2 3 1 4] décalage M = 2 positions devrait être [1 4 3 4 5 2 3].

Merci beaucoup.

29
Arsen Mkrtchyan

Si vous voulez O(n) temps et aucune utilisation de mémoire supplémentaire (puisque le tableau a été spécifié), utilisez l'algorithme du livre de Jon Bentley, "Programming Pearls 2nd Edition". Il échange tous les éléments deux fois. Pas aussi vite que d'utiliser des listes chaînées, mais utilise moins de mémoire et est simple conceptuellement.

shiftArray( theArray, M ):
    size = len( theArray )
    assert( size > M )
    reverseArray( theArray, 0, size - 1 )
    reverseArray( theArray, 0, M - 1 )
    reverseArray( theArray, M, size - 1 )

reverseArray (anArray, startIndex, endIndex) inverse l'ordre des éléments de startIndex à endIndex, inclus.

48
Jerry Penner

C'est juste une question de représentation. Conservez l'index actuel sous forme de variable entière et utilisez l'opérateur modulo pour savoir quand boucler. Le décalage consiste alors uniquement à modifier la valeur de l'index actuel, en l'enroulant autour de la taille du tableau. C'est bien sûr O (1).

Par exemple:

int index = 0;
Array a = new Array[SIZE];

get_next_element() {
    index = (index + 1) % SIZE; 
    return a[index];
}

shift(int how_many) {
    index = (index+how_many) % SIZE;
}
22
Vinko Vrsalovic

Solution optimale

Question demandée pour le plus rapide. Inverser trois fois est plus simple, mais déplace chaque élément exactement deux fois, prend O(N) temps et O(1) espace. Il est possible de déplacer en cercle un tableau en déplaçant chaque élément exactement une fois également dans l'espace O(N) _ et O(1).

Idée

Nous pouvons déplacer en cercle un tableau de longueur N=9 de M=1 avec un cycle:

tmp = arr[0]; arr[0] = arr[1]; ... arr[7] = arr[8]; arr[8] = tmp;

Et si N=9, M=3 nous pouvons tourner en cercle avec trois cycles:

  1. tmp = arr[0]; arr[0] = arr[3]; arr[3] = tmp;
  2. tmp = arr[1]; arr[1] = arr[4]; arr[4] = tmp;
  3. tmp = arr[2]; arr[2] = arr[5]; arr[5] = tmp;

Notez que chaque élément est lu une fois et écrit une fois.

Diagramme de déplacement N=9, M=3

Diagram of cycle shift

Le premier cycle est indiqué en noir avec des chiffres indiquant l’ordre des opérations. Les deuxième et troisième cycles sont indiqués en gris.

Le nombre de cycles requis est le le plus grand commun diviseur (GCD) de N et M. Si le GCD est 3, nous commençons un cycle à chacun de {0,1,2}. Le calcul du GCD est rapide avec le algorithme GCD binaire .

Exemple de code:

// n is length(arr)
// shift is how many place to cycle shift left
void cycle_shift_left(int arr[], int n, int shift) {
  int i, j, k, tmp;
  if(n <= 1 || shift == 0) return;
  shift = shift % n; // make sure shift isn't >n
  int gcd = calc_GCD(n, shift);

  for(i = 0; i < gcd; i++) {
    // start cycle at i
    tmp = arr[i];
    for(j = i; 1; j = k) {
      k = j+shift;
      if(k >= n) k -= n; // wrap around if we go outside array
      if(k == i) break; // end of cycle
      arr[j] = arr[k];
    }
    arr[j] = tmp;
  }
}

Code en C pour tout type de tableau:

// circle shift an array left (towards index zero)
// - ptr    array to shift
// - n      number of elements
// - es     size of elements in bytes
// - shift  number of places to shift left
void array_cycle_left(void *_ptr, size_t n, size_t es, size_t shift)
{
  char *ptr = (char*)_ptr;
  if(n <= 1 || !shift) return; // cannot mod by zero
  shift = shift % n; // shift cannot be greater than n

  // Using GCD
  size_t i, j, k, gcd = calc_GCD(n, shift);
  char tmp[es];

  // i is initial starting position
  // Copy from k -> j, stop if k == i, since arr[i] already overwritten
  for(i = 0; i < gcd; i++) {
    memcpy(tmp, ptr+es*i, es); // tmp = arr[i]
    for(j = i; 1; j = k) {
      k = j+shift;
      if(k >= n) k -= n;
      if(k == i) break;
      memcpy(ptr+es*j, ptr+es*k, es); // arr[j] = arr[k];
    }
    memcpy(ptr+es*j, tmp, es); // arr[j] = tmp;
  }
}

// cycle right shifts away from zero
void array_cycle_right(void *_ptr, size_t n, size_t es, size_t shift)
{
  if(!n || !shift) return; // cannot mod by zero
  shift = shift % n; // shift cannot be greater than n
  // cycle right by `s` is equivalent to cycle left by `n - s`
  array_cycle_left(_ptr, n, es, n - shift);
}

// Get Greatest Common Divisor using binary GCD algorithm
// http://en.wikipedia.org/wiki/Binary_GCD_algorithm
unsigned int calc_GCD(unsigned int a, unsigned int b)
{
  unsigned int shift, tmp;

  if(a == 0) return b;
  if(b == 0) return a;

  // Find power of two divisor
  for(shift = 0; ((a | b) & 1) == 0; shift++) { a >>= 1; b >>= 1; }

  // Remove remaining factors of two from a - they are not common
  while((a & 1) == 0) a >>= 1;

  do
  {
    // Remove remaining factors of two from b - they are not common
    while((b & 1) == 0) b >>= 1;

    if(a > b) { tmp = a; a = b; b = tmp; } // swap a,b
    b = b - a;
  }
  while(b != 0);

  return a << shift;
}

Edit : Cet algorithme peut également avoir de meilleures performances que l'inversion de tableau (lorsque N est grand et M est petit) en raison de la localité du cache, puisque nous parcourons le tableau par petites étapes.

Note finale: si votre tableau est petit, le triple inverse est simple. Si vous avez un grand tableau, il est inutile de travailler avec GCD pour réduire le nombre de déplacements par un facteur de 2. Réf: http://www.geeksforgeeks.org/array-rotation/

14
Isaac Turner

Configurez-le avec des pointeurs, et cela ne prend presque pas de temps. Chaque élément pointe vers le suivant et le "dernier" (il n'y en a pas; après tout, vous avez dit qu'il était circulaire) pointe vers le premier. Un pointeur sur le "début" (premier élément), et peut-être une longueur, et vous avez votre tableau. Maintenant, pour faire votre quart de travail, il vous suffit de déplacer votre pointeur de départ le long du cercle.

Demandez un bon algorithme et vous obtenez des idées judicieuses. Demandez le plus rapide et vous aurez des idées bizarres!

7
gbarry

Cet algorithme s’exécute dans O(n) time et O(1) space. L'idée est de tracer chaque groupe cyclique dans le décalage (numéroté par la variable nextGroup).

var shiftLeft = function(list, m) {
    var from = 0;
    var val = list[from];
    var nextGroup = 1;
    for(var i = 0; i < list.length; i++) {
        var to = ((from - m) + list.length) % list.length;
        if(to == from)
            break;

        var temp = list[to];
        list[to] = val;
        from = to;
        val = temp;

        if(from < nextGroup) {
            from = nextGroup++;
            val = list[from];
        }
    }
    return list;
}
4
zhy2002
def shift(nelements, k):       
    result = []
    length = len(nelements)
    start = (length - k) % length
    for i in range(length):
        result.append(nelements[(start + i) % length])
    return result

Ce code fonctionne bien même en décalage négatif k

3
Ang

C fonction arrayShiftRight. Si shift est négatif, la fonction décale le tableau de gauche. Il est optimisé pour une utilisation moindre de la mémoire. Le temps d'exécution est O (n).

void arrayShiftRight(int array[], int size, int shift) {
    int len;

    //cut extra shift
    shift %= size;

    //if shift is less then 0 - redirect shifting left
    if ( shift < 0 ) {
        shift += size;
    }

    len = size - shift;

    //choosing the algorithm which needs less memory
    if ( shift < len ) {
        //creating temporary array
        int tmpArray[shift];

        //filling tmp array
        for ( int i = 0, j = len; i < shift; i++, j++ ) {
            tmpArray[i] = array[j];
        }

        //shifting array
        for ( int i = size - 1, j = i - shift; j >= 0; i--, j-- ) {
            array[i] = array[j];
        }

        //inserting lost values from tmp array
        for ( int i = 0; i < shift; i++ ) {
            array[i] = tmpArray[i];
        }
    } else {
        //creating temporary array
        int tmpArray[len];

        //filling tmp array
        for ( int i = 0; i < len; i++ ) {
            tmpArray[i] = array[i];
        }

        //shifting array
        for ( int i = 0, j = len; j < size; i++, j++ ) {
            array[i] = array[j];
        }

        //inserting lost values from tmp array
        for ( int i = shift, j = 0; i < size; i++, j++ ) {
            array[i] = tmpArray[j];
        }
    }
}
2
4ekistik

Une solution très simple. C'est un moyen très rapide, j'utilise ici un tableau temporaire avec la même taille ou le même original et l'attache à la variable d'origine à la fin. Cette méthode utilise O(n) complexité temporelle et O(n) complexité spatiale. Elle est très simple à mettre en œuvre.

int[] a  = {1,2,3,4,5,6};
    int k = 2;
    int[] queries = {2,3};

    int[] temp = new int[a.length];
    for (int i = 0; i<a.length; i++)
        temp[(i+k)%a.length] = a[i];

    a = temp;
2
DAme

Voici une fonction de rotation générale simple et efficace en C++, inférieure à 10 lignes.

qui est extrait de ma réponse à une autre question. Comment faire pivoter un tableau?

#include <iostream>
#include <vector>

// same logic with STL implementation, but simpler, since no return value needed.
template <typename Iterator>
void rotate_by_gcd_like_swap(Iterator first, Iterator mid, Iterator last) {
    if (first == mid) return;
    Iterator old = mid;
    for (; mid != last;) {
        std::iter_swap(first, mid);
        ++first, ++mid;
        if (first == old) old = mid; // left half exhausted
        else if (mid == last) mid = old;
    }
}

int main() {
    using std::cout;
    std::vector<int> v {0,1,2,3,4,5,6,7,8,9};
    cout << "before rotate: ";
    for (auto x: v) cout << x << ' '; cout << '\n';
    int k = 7;
    rotate_by_gcd_like_swap(v.begin(), v.begin() + k, v.end());
    cout << " after rotate: ";
    for (auto x: v) cout << x << ' '; cout << '\n';
    cout << "sz = " << v.size() << ", k = " << k << '\n';
}
1
qeatzy

En fonction de la structure de données que vous utilisez, vous pouvez le faire en O (1). Je pense que le moyen le plus rapide est de tenir le tableau sous la forme d'une liste chaînée et d'avoir une table de hachage pouvant traduire entre "index" dans le tableau et "pointeur" vers l'entrée. De cette façon, vous pouvez trouver les têtes et queues pertinentes dans O (1) et effectuer la reconnexion dans O(1) (et mettre à jour la table de hachage après le commutateur dans O (1)). Ce serait bien sûr une solution très "chaotique", mais si tout ce qui vous intéresse est la vitesse du changement, cela suffira (au détriment d'une insertion et d'une recherche plus longues dans le tableau, mais cela restera toujours O ( 1))

Si vous avez les données dans un tableau pur, je ne pense pas que vous puissiez éviter O (n).

En ce qui concerne le codage, cela dépend de la langue que vous utilisez.

En Python par exemple, vous pouvez le "découper" (en supposant que n est la taille du décalage):

result = original[-n:]+original[:-n]

(Je sais que la recherche de hachage n'est théoriquement pas O(1) mais nous sommes ici pratiques et non théoriques, du moins je l'espère ...)

1
Roee Adler

Cela devrait fonctionner pour déplacer un arbre de manière circulaire: Entrée: {1, 2, 3, 5, 6, 7, 8}; Valeur de sortie présente dans le tableau après les forloops: {8,7,1,2,3,5,6,8,7}

 class Program
    {
        static void Main(string[] args)
        {
            int[] array = { 1, 2, 3, 5, 6, 7, 8 };
            int index = 2;
            int[] tempArray = new int[array.Length];
            array.CopyTo(tempArray, 0);

            for (int i = 0; i < array.Length - index; i++)
            {
                array[index + i] = tempArray[i];
            }

            for (int i = 0; i < index; i++)
            {
                array[i] = tempArray[array.Length -1 - i];
            }            
        }
    }
1
Vikas

En voici un autre (C++):

void shift_vec(vector<int>& v, size_t a)
{
    size_t max_s = v.size() / a;
    for( size_t s = 1; s < max_s; ++s )
        for( size_t i = 0; i < a; ++i )
            swap( v[i], v[s*a+i] );
    for( size_t i = 0; i < a; ++i )
        swap( v[i], v[(max_s*a+i) % v.size()] );
}

Bien sûr, elle n’est pas aussi élégante que la fameuse solution inverse à trois reprises, mais elle peut être fonction de la machine similary fast .

0
Tobias Hermann
static int [] shift(int arr[], int index, int k, int rem)
{
    if(k <= 0 || arr == null || arr.length == 0 || rem == 0 || index >= arr.length)
    {
        return arr;
    }

    int temp = arr[index];

    arr = shift(arr, (index+k) % arr.length, k, rem - 1);

    arr[(index+k) % arr.length] = temp;

    return arr;
}
0
Swaranga Sarma

En théorie, le plus rapide est une boucle comme celle-ci:

if (begin != middle && middle != end)
{
    for (i = middle; ; )
    {
        swap(arr[begin++], arr[i++]);
        if (begin == middle && i == end) { break; }
        if (begin == middle) { middle = i; }
        else if (i == end) { i = middle; }
    }
}

En pratique, vous devriez le profiler et voir.

0
Mehrdad

circleArray a des erreurs et ne fonctionne pas dans tous les cas!

La boucle doit continuer while i1 < i2 NOT i1 < last - 1.

void Shift(int* _array, int _size, int _moves)
{
    _moves = _size - _moves;
    int i2 = _moves;
         int i1 = -1;
         while(++i1 < i2)
    {
        int tmp = _array[i2];
        _array[i2] = _array[i1];
        _array[i1] = tmp;
        if(++i2 == _size) i2 = _moves;
    }
}
0
0xDEAD BEEF

Exemple Ruby:

def move_cyclic2 array, move_cnt
  move_cnt = array.length - move_cnt % array.length 
  if !(move_cnt == 0 || move_cnt == array.length)            
    array.replace( array[move_cnt..-1] + array[0...move_cnt] )  
  end   
end
0
nub

Voici ma solution en Java qui m'a valu 100% de score de tâche et 100% de précision lors de la codilité:

class Solution {
    public int[] solution(int[] A, int K) {
        // write your code in Java SE 8
        if (A.length > 0)
        {
            int[] arr = new int[A.length];
            if (K > A.length)
                K = K % A.length;

            for (int i=0; i<A.length-K; i++)
                arr[i+K] = A[i];

            for (int j=A.length-K; j<A.length; j++)
                arr[j-(A.length-K)] = A[j];

            return arr;
        }
        else
            return new int[0];
    }
}

Notez que, malgré la présence de deux boucles for, l'itération sur l'ensemble du tableau n'est effectuée qu'une seule fois.

0
Very Objective

Cette méthode fera ce travail:

public static int[] solution1(int[] A, int K) {
    int temp[] = new int[A.length];

    int count = 0;

    int orignalItration = (K < A.length) ? K :(K%A.length); 


    for (int i = orignalItration; i < A.length; i++) {
        temp[i] = A[count++];
    }
    for (int i = 0; i < orignalItration; i++) {
        temp[i] = A[count++];
    }

    return temp;
}
0

Tout en plaisantant, un de mes amis m'a demandé comment passer d’un tableau à un autre. J’ai mis au point cette solution (voir lien idéone), maintenant que j’ai vu le vôtre, quelqu'un semble un peu ésotérique.

Jetez un oeil ici .

#include <iostream>

#include <assert.h>

#include <cstring>

using namespace std;

struct VeryElaboratedDataType
{
    int a;
    int b;
};

namespace amsoft
{
    namespace inutils
    {
        enum EShiftDirection
        {
            Left,
            Right
        };
template 
<typename T,size_t len>
void infernalShift(T infernalArray[],int positions,EShiftDirection direction = EShiftDirection::Right)
{
    //assert the dudes
    assert(len > 0 && "what dude?");
    assert(positions >= 0 && "what dude?");

    if(positions > 0)
    {
    ++positions;
    //let's make it fit the range
    positions %= len;

    //if y want to live as a forcio, i'l get y change direction by force
    if(!direction)
    {
        positions = len - positions;
    }

    // here I prepare a fine block of raw memory... allocate once per thread
    static unsigned char WORK_BUFFER[len * sizeof(T)];
    // std::memset (WORK_BUFFER,0,len * sizeof(T));
    // clean or not clean?, well
    // Hamlet is a prince, a prince does not clean

    //copy the first chunk of data to the 0 position
    std::memcpy(WORK_BUFFER,reinterpret_cast<unsigned char *>(infernalArray) + (positions)*sizeof(T),(len - positions)*sizeof(T));
    //copy the second chunk of data to the len - positions position
    std::memcpy(WORK_BUFFER+(len - positions)*sizeof(T),reinterpret_cast<unsigned char *>(infernalArray),positions * sizeof(T));

    //now bulk copy back to original one
    std::memcpy(reinterpret_cast<unsigned char *>(infernalArray),WORK_BUFFER,len * sizeof(T));

    }

}
template 
<typename T>
void printArray(T infernalArrayPrintable[],int len)
{
        for(int i=0;i<len;i++)
    {
        std::cout << infernalArrayPrintable[i] << " ";
    }
    std::cout << std::endl;

}
template 
<>
void printArray(VeryElaboratedDataType infernalArrayPrintable[],int len)
{
        for(int i=0;i<len;i++)
    {
        std::cout << infernalArrayPrintable[i].a << "," << infernalArrayPrintable[i].b << " ";
    }
    std::cout << std::endl;

}
}
}




int main() {
    // your code goes here
    int myInfernalArray[] = {1,2,3,4,5,6,7,8,9};

    VeryElaboratedDataType myInfernalArrayV[] = {{1,1},{2,2},{3,3},{4,4},{5,5},{6,6},{7,7},{8,8},{9,9}};
    amsoft::inutils::printArray(myInfernalArray,sizeof(myInfernalArray)/sizeof(int));
    amsoft::inutils::infernalShift<int,sizeof(myInfernalArray)/sizeof(int)>(myInfernalArray,4);
    amsoft::inutils::printArray(myInfernalArray,sizeof(myInfernalArray)/sizeof(int));
    amsoft::inutils::infernalShift<int,sizeof(myInfernalArray)/sizeof(int)>(myInfernalArray,4,amsoft::inutils::EShiftDirection::Left);
    amsoft::inutils::printArray(myInfernalArray,sizeof(myInfernalArray)/sizeof(int));
    amsoft::inutils::infernalShift<int,sizeof(myInfernalArray)/sizeof(int)>(myInfernalArray,10);
    amsoft::inutils::printArray(myInfernalArray,sizeof(myInfernalArray)/sizeof(int));


    amsoft::inutils::printArray(myInfernalArrayV,sizeof(myInfernalArrayV)/sizeof(VeryElaboratedDataType));
    amsoft::inutils::infernalShift<VeryElaboratedDataType,sizeof(myInfernalArrayV)/sizeof(VeryElaboratedDataType)>(myInfernalArrayV,4);
    amsoft::inutils::printArray(myInfernalArrayV,sizeof(myInfernalArrayV)/sizeof(VeryElaboratedDataType));
    amsoft::inutils::infernalShift<VeryElaboratedDataType,sizeof(myInfernalArrayV)/sizeof(VeryElaboratedDataType)>(myInfernalArrayV,4,amsoft::inutils::EShiftDirection::Left);
    amsoft::inutils::printArray(myInfernalArrayV,sizeof(myInfernalArrayV)/sizeof(VeryElaboratedDataType));
    amsoft::inutils::infernalShift<VeryElaboratedDataType,sizeof(myInfernalArrayV)/sizeof(VeryElaboratedDataType)>(myInfernalArrayV,10);
    amsoft::inutils::printArray(myInfernalArrayV,sizeof(myInfernalArrayV)/sizeof(VeryElaboratedDataType));

    return 0;
}
0
alessandro

Voir ceci si vous êtes intéressé par une implémentation Java:

Programmation des perles: fonctionnement circulaire gauche/droit

0
Sahil

Swift 4 version pour changer de tableau à gauche.

func rotLeft(a: [Int], d: Int) -> [Int] {

   var result = a
   func reverse(start: Int, end: Int) {
      var start = start
      var end = end
      while start < end {
         result.swapAt(start, end)
         start += 1
         end -= 1
      }
   }

   let lenght = a.count
   reverse(start: 0, end: lenght - 1)
   reverse(start: lenght - d, end: lenght - 1)
   reverse(start: 0, end: lenght - d - 1)
   return result
}

Par exemple, si le tableau d'entrée est a = [1, 2, 3, 4, 5] et que le décalage gauche est d = 4, le résultat sera [5, 1, 2, 3, 4]

0
Vlad

Conservez deux index dans le tableau, un index commençant du début du tableau à la fin du tableau. Un autre index part de la position Mth depuis la dernière position et parcourt les derniers M éléments autant de fois que nécessaire. Prend O(n) à tout moment. Aucun espace supplémentaire requis.

circleArray(Elements,M){
 int size=size-of(Elements);

 //first index
 int i1=0;

 assert(size>M)

 //second index starting from mth position from the last
 int i2=size-M;

 //until first index reaches the end
 while(i1<size-1){

  //swap the elements of the array pointed by both indexes
  swap(i1,i2,Elements);

  //increment first pointer by 1
  i1++;

  //increment second pointer. if it goes out of array, come back to
  //mth position from the last
  if(++i2==size) i2=size-M;

 }
}
0
sujatha

Similaire à @IsaacTurner et pas aussi élégant en raison de la copie inutile, mais la mise en œuvre est assez courte.

L'idée - permuter l'élément A sur l'index 0 avec l'élément B qui se trouve sur la destination de A. Maintenant, B est le premier. Échangez-le avec l'élément C qui se trouve sur la destination de B. Continuez jusqu'à ce que la destination ne soit pas à 0.

Si le plus grand commun diviseur n'est pas 1, vous n'avez pas encore terminé. Vous devez continuer à permuter, mais vous devez maintenant utiliser l'index 1 à vos points de départ et d'arrivée.

Continuez jusqu'à ce que votre position de départ ne soit pas le gcd.

int gcd(int a, int b) => b == 0 ? a : gcd(b, a % b);

public int[] solution(int[] A, int K)
{
    for (var i = 0; i < gcd(A.Length, K); i++)
    {
        for (var j = i; j < A.Length - 1; j++)
        {
            var destIndex = ((j-i) * K + K + i) % A.Length;
            if (destIndex == i) break;
            var destValue = A[destIndex];
            A[destIndex] = A[i];
            A[i] = destValue;
        }
    }

    return A;
}
0
Look