أسرع خوارزمية لمصفوفة ذات حجم N للإزاحة الدائرية للموضع M
-
22-08-2019 - |
سؤال
ما هي أسرع خوارزمية لمصفوفة تبديل الدائرة للمواضع M؟
على سبيل المثال، [3 4 5 2 3 1 4]
التحول M = ينبغي أن يكون موقفين [1 4 3 4 5 2 3]
.
شكرًا جزيلاً.
المحلول
إذا كنت تريد O (ن) وقت وليس استخدام الذاكرة اضافية (منذ تم تحديد مجموعة)، استخدام خوارزمية من كتاب جون بنتلي، "برمجة اللؤلؤ 2 الطبعة". انها مقايضة جميع العناصر مرتين. ليست سريعة مثل استخدام القوائم المرتبطة ولكن يستخدم ذاكرة أقل وبسيطة من الناحية النظرية.
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) عكس ترتيب العناصر من startIndex إلى endIndex، ضمنا.
نصائح أخرى
وانها مجرد مسألة التمثيل. إبقاء المؤشر الحالي كما متغير عدد صحيح وعندما يجتاز مجموعة المشغل استخدام مودولو أن تعرف متى التفاف حولها. تحول بعد ذلك فقط تغيير قيمة المؤشر الحالي، التفاف حول حجم المصفوفة. وهذا بالطبع O (1).
وعلى سبيل المثال:
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;
}
حل مثالي
السؤال المطروح للأسرع.يعد العكس ثلاث مرات هو الأسهل ولكنه يحرك كل عنصر مرتين بالضبط، ويستغرق وقت O(N) ومسافة O(1).من الممكن إجراء إزاحة دائرية لمصفوفة تتحرك كل عنصر مرة واحدة بالضبط أيضًا في وقت O(N) ومساحة O(1).
فكرة
يمكننا دائرة التحول مجموعة من الطول N=9
بواسطة M=1
مع دورة واحدة:
tmp = arr[0]; arr[0] = arr[1]; ... arr[7] = arr[8]; arr[8] = tmp;
و إذا N=9
, M=3
يمكننا دائرة التحول بثلاث دورات:
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;
لاحظ أن كل عنصر تتم قراءته مرة واحدة وكتابته مرة واحدة.
رسم تخطيطي للتحول N=9, M=3
تظهر الدورة الأولى باللون الأسود مع أرقام تشير إلى ترتيب العمليات.وتظهر الدورتين الثانية والثالثة باللون الرمادي.
عدد الدورات المطلوبة هو القاسم المشترك الأكبر (جي سي دي) من N
و M
.إذا كان GCD هو 3، نبدأ دورة في كل منها {0,1,2}
.حساب GCD سريع مع خوارزمية GCD الثنائية.
رمز المثال:
// 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;
}
}
الكود في C لأي نوع صفيف:
// 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;
}
يحرر:قد تتمتع هذه الخوارزمية أيضًا بأداء أفضل مقابل عكس المصفوفة (متى N
كبير و M
صغير) نظرًا لموقع ذاكرة التخزين المؤقت، نظرًا لأننا نقوم بالتكرار فوق المصفوفة بخطوات صغيرة.
ملاحظة أخيرة: إذا كانت مصفوفتك صغيرة، فإن العكس الثلاثي يكون بسيطًا.إذا كان لديك مصفوفة كبيرة، فمن المفيد العمل على GCD لتقليل عدد الحركات بعامل 2.المرجع: http://www.geeksforgeeks.org/array-rotation/
وإعداده مع مؤشرات، ويستغرق وقتا لا تقريبا. كل نقطة عنصر إلى آخر، و "آخر" (لا يوجد آخر، وبعد كل شيء، وقال لكم انه كان التعميم) نقطة للأول. مؤشر واحد إلى "بداية" (العنصر الأول)، وربما طول، وكان لديك مجموعة الخاصة بك. الآن، على أن تفعل التحول الخاص بك، فإنك مجرد المشي المؤشر بداية على طول الدائرة.
واسأل عن خوارزمية جيدة، ويمكنك الحصول على أفكار معقولة. طلب <م> أسرع م>، ويمكنك الحصول على أفكار غريبة!
وهذه الخوارزمية يعمل في O (ن) الوقت وO (1) الفضاء.
والفكرة هي أن تتبع كل مجموعة دوري في التحول (مرقمة من قبل متغير 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
وهذا الرمز يعمل بشكل جيد حتى على ك تحول سلبي
وظيفة C arrayShiftRight. إذا تحول سلبي تركت مجموعة التحولات وظيفة. هو الأمثل لأقل من استخدام الذاكرة. دوران وقت O (ن).
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];
}
}
}
وهناك حل بسيط جدا. هذا هو وسيلة سريعة جدا، وهنا يمكنني استخدام مجموعة ودرجة الحرارة مع نفس الحجم أو الأصلي ونعلق على المتغير الأصلي في نهاية المطاف. هذا الاستخدام طريقة O (ن) التعقيد الزمني وO (ن) مساحة التعقيد وبسيط جدا لتنفيذ.
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;
واعتمادا على بنية البيانات التي تستخدمها، يمكنك أن تفعل ذلك في O (1). أعتقد أن أسرع وسيلة لعقد مجموعة في شكل قائمة مرتبطة، ويكون لها جدول التجزئة التي يمكن أن تترجم بين "مؤشر" في مجموعة ل"مؤشر" للدخول. وبهذه الطريقة يمكنك العثور على رؤوس وذيول ذات الصلة في O (1)، والقيام إعادة الاتصال في O (1) (وتحديث جدول التجزئة بعد التبديل في O (1)). وهذا من شأنه بالطبع أن يكون "فوضوي" حل جدا، ولكن إذا كان كل ما كنت ترغب في هو سرعة التحول، من شأنها أن تفعل (على حساب الإدراج أطول والبحث في مجموعة، ولكنه سوف لا تزال O ( 1))
إذا كان لديك البيانات في مجموعة نقية، لا أعتقد يمكنك تجنب O (ن).
والترميز الحكيم، فإنه يعتمد على ما هي اللغة التي تستخدمها.
في بيثون على سبيل المثال، هل يمكن أن "شريحة" عليه (يفترض n هو حجم التحول):
result = original[-n:]+original[:-n]
(وأنا أعلم أن تجزئة بحث في نظرية لا O (1) ولكن نحن عملي هنا وليس النظري، على الأقل آمل ذلك ...)
وهذا يجب أن تعمل لتحويل لدائري arry: المدخلات: {1، 2، 3، 5، 6، 7، 8}؛ قيمة الانتاج الحالية في مجموعة بعد 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];
}
}
}
إليك وظيفة تدوير عامة بسيطة وفعالة في لغة C++، أقل من 10 أسطر.
وهو مقتطف من إجابتي على سؤال آخر. كيفية تدوير مجموعة؟
#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';
}
وحافظ على مؤشرين للمجموعة، يبدأ مؤشر واحد من بداية مجموعة إلى نهاية مجموعة. يبدأ مؤشر آخر من موقع MTH من الماضي، وحلقات عبر العناصر M مشاركة أي عدد من المرات. يأخذ O (ن) في جميع الأوقات. لا مساحة إضافية مطلوبة.
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;
}
}
وانظر هذا إذا كنت ترغب في تطبيق جافا:
<وأ href = "http://programmingpearls.blogspot.com/search؟updated-min=2011-01-01T00٪3A00٪3A00-08٪3A00&updated-max=2012-01-01T00٪3A00٪3A00- 08٪ 3A00 & ماكس-النتائج = 2 "يختلط =" نوفولو "> البرمجة لآلئ: التعميم يسار / يمين التحول عملية
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;
}
وروبي سبيل المثال:
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
في نظرية، أسرع واحد هو حلقة من هذا القبيل:
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; }
}
}
في الواقع، يجب أن ملف ونرى.
وهنا هو نوثر واحد (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()] );
}
وبطبيعة الحال فإنه لا يكاد أنيقة مثل حل العكسية ثلاث مرات الشهير، ولكن اعتمادا على الجهاز يمكن أن يكون similary بسرعة.
وcircleArray
لديه بعض الأخطاء ولا يعمل في جميع الحالات!
وحلقة يجب أن تستمر 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;
}
}
وهناك صديق لي في حين يمزح طلب مني كيفية تحويل صفيف، خطرت لي هذه الحلول (انظر الرابط ideone)، لك الآن رأيت، شخص يبدو مقصور على فئة معينة قليلا.
ونلقي نظرة هنا .
#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;
}
وهذه الطريقة سوف تفعل هذا العمل:
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;
}
وعلى غرارIsaacTurner وليس أنيقة بسبب النسخ غير الضرورية، ولكن التنفيذ قصيرة جدا.
والفكرة - تبادل العنصر A على المؤشر 0 مع العنصر B التي تقع على جهة A. B الآن هو أولا. مبادلة مع العنصر C التي تقع على جهة B. تستمر حتى الوجهة ليست في 0.
إذا القاسم المشترك الأكبر ليست 1 ثم كنت لم تنته بعد - تحتاج إلى مواصلة مبادلة، ولكن الآن باستخدام مؤشر 1 في بلدكم البداية والنهاية نقطة
وتستمر حتى نقطة الانطلاق ليست هي 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;
}
وهنا هو الحل بلدي في جاوة التي حصلت لي نتيجة العمل 100٪ و 100٪ الصواب في Codility:
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];
}
}
ملحوظة أنه على الرغم من رؤية اثنين من الحلقات for
، التكرار على مجموعة كاملة ويتم مرة واحدة فقط.
وسويفت 4 نسخة لتحويل مجموعة اليسار.
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
}
وعلى سبيل المثال، إذا إدخال مجموعة هي a = [1, 2, 3, 4, 5]
، وتحول اليسار الإزاحة d = 4
، ثم ستكون النتيجة [5, 1, 2, 3, 4]