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.
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.
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;
}
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).
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:
tmp = arr[0]; arr[0] = arr[3]; arr[3] = tmp;
tmp = arr[1]; arr[1] = arr[4]; arr[4] = tmp;
tmp = arr[2]; arr[2] = arr[5]; arr[5] = tmp;
Notez que chaque élément est lu une fois et écrit une fois.
N=9, M=3
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;
}
}
// 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/
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!
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;
}
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
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];
}
}
}
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;
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';
}
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 ...)
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];
}
}
}
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 .
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;
}
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.
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;
}
}
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
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.
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;
}
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;
}
Voir ceci si vous êtes intéressé par une implémentation Java:
Programmation des perles: fonctionnement circulaire gauche/droit
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]
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;
}
}
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;
}