数组左旋转k位

来源:互联网 发布:互联网电商公关 知乎 编辑:程序博客网 时间:2024/06/07 01:44

数组左旋转k位 

 

要将一个数组的所有元素向左旋转k位,通常有三种算法:

 

算法1(分组交换):

若a长度大于b,将ab分成a0a1b,交换a0和b,得ba1a0,只需再交换a1和a0

若a长度小于b,将ab分成ab0b1,交换a和b0,得b0ab1,只需再交换a和b1

不断将数组划分和交换,直到不能再划分为止。分组过程与求最大公约数很相似。

 读写内存各 n到2*n次

 

算法2 (三次反转)

利用ba=(br)r(ar)r=(arbr)r,先分别反转a、b,最后再对所有元素进行一次反转。

读写内存各约2*n次

 

算法3 (使用循环链)

假设 n、k的最大公约数为M,则所有序号为 (i + j*k) % n (0<= i < M, 0 <= j < n/M)的元素,构成M个循环链(i值相同的在同一个循环链上), 每个循环链上的元素移动到前一个元素的元素,就可以交换到最终结果上的位置,因而总共只要读写内存各n次。(比如: 1 2 3 4 5 6,左移2位, 1 3 5 和 2 4 6分别构成两个循环链。)

 

事实上C++标准算法库提供了现成的函数:rotate函数。按理说,几种算法都比较简单,编译器的库函数又是经过时间检验的,效率即使比手写的差,也不会差太多。但如果对rotate函数进行测试的话,可能会发现标准库的版本慢得可不是一点点。

VC 2010,运行后面的测试程序,自定义函数(采用算法2)要用99ms,而std::rotate却要1656ms。是库的实现者不懂得用这个简单的算法吗?检查下库的源代码,就会发现:标准算法库中,对C++的三种迭代器(前向迭代器、双向迭代器,随机访问迭代器),分别采用了上面三种算法。直接调用其内部的实现(std::_Rotat函数),重新测试下,可得到下面结果:

 

迭代器

前向(算法1

双向(算法2

随机访问(算法3

时间(ms

46

99

1651

                                                              (使用GCC的,请用版本号低于4.5的进行测试)

 

    从结果可以看出,效率是:算法1 >算法2 >>>算法3 

 

从理论上讲,算法3只要读写内存各n次,应该是效率最高的算法。这在每次内存读写的开销相差不大时成立。但实际上,由于硬件限制,CPU对内存的访问采用分级缓存机制:一级缓存容量很小但访问速度最快,存放程序的指令和最常用的数据,而二、三级缓存容量较大但访问速度要慢很多。CPU是无法绕过缓存直接访问内存数据(某些特殊指令可以不用一二三级缓存,但它也要用到其它专用缓存),对不在缓存中的数据,必须先载入到缓存中,这个操作是相当昂贵的。对大数组来说,不可能将所有数据都存放在缓存中,而对内存的不连续访问,CPU对内存定位的开销(各级缓存间数据的调整,反复移入或移出数据到缓存)是巨大的,这就造成了算法3的性能在该情况下非常差。测试发现,k = 3时,该算法的效率就已经相当差了。对小数组,尽管该算法读写次数少,但由于各种算法所用时间都很小,这种优势很难体现出来。可以说,算法3在数学上是非常优美的,但是在实际应用中,是一种相当差的算法。

对算法的选择,不应该忽视内存因素。在对随机访问迭代器版本的roate实现上犯这个错误的,可不仅仅是VC,还有著名的STL PortGCCGCC4.5开始libstdc++改用算法1,并做了些优化),以及新兴的libc++。(其它的编译器/库没用过,也就没有测试。)

 

另外,测试时发现VC 2010的一个bug:前向迭代器的实现版本,当k = 0时,程序直接挂了。

 

 

测试代码:

 

复制代码
 1 // www.cnblogs.com/flyinghearts
 2 
 3 #include <vector>
 4 #include <algorithm>
 5 #include <iterator>
 6 #include <ctime>
 7 
 8 #if __GNUC__
 9   #define ROTATE std::__rotate
10 #elif _MSC_VER
11   #define ROTATE std::_Rotate
12 #else
13   #error "You should use GCC or VC"
14 #endif
15 
16 
17 
18 template<unsigned Count, bool Show, typename T, typename Iterator_tag>
19 void test(T beg, T mid, T end, const Iterator_tag& iterator_tag, const char *str = "")
20 {
21   unsigned sum = 0;
22   for (unsigned i = 0; i != Count; ++i) {
23     unsigned ta = clock();
24     ROTATE(beg, mid, end, iterator_tag);
25     ta = clock() - ta;
26     sum += ta;
27     if (Show) printf("%s  %u ms\n", str, ta);
28   }
29   if (Show) printf("aveg: %u ms\n\n", sum / Count);
30   else printf(" %s  total: %u ms\n", str, sum);
31 }
32 
33 
34 template<unsigned Count, bool is_std, typename T>
35 void test2(T beg, T mid, T end,const char *str = "")
36 {
37   unsigned sum = 0;
38   for (unsigned i = 0; i != Count; ++i) {
39     unsigned ta = clock();
40     if (is_std) std::rotate(beg, mid, end);
41     else {
42       std::reverse(beg, mid);
43       std::reverse(mid, end);
44       std::reverse(beg, end);
45     }
46     ta = clock() - ta;
47     sum += ta;
48     printf("%s  %u ms\n", str, ta);
49   }
50   printf("aveg: %u ms\n\n", sum / Count);
51 }
52 
53 template<unsigned Count, bool Show, typename T>
54 inline void test3(T beg, T mid, T end)
55 {
56   test<Count, Show>(beg, mid, end, std::forward_iterator_tag(), "forward");
57   test<Count, Show>(beg, mid, end, std::bidirectional_iterator_tag(), "bidirectional");
58   test<Count, Show>(beg, mid, end, std::random_access_iterator_tag(), "random");
59 }
60 
61 int main()
62 {
63   const int N = 1e7;
64   const int M = 1024;
65   //const int M = 777;
66   std::vector<int> vec(N);
67   std::vector<int>::iterator beg(vec.begin()), mid(beg + M), end(vec.end());
68 
69   printf("------\n");
70   test2<3,false>(beg, mid, end, " 3_reverse");
71   test2<3true>(beg, mid, end, " std::rotate");
72   
73   test3<3true>(beg, mid, end);
74   
75   for (int i = 1; i < 5++i) {
76     printf ("\n%d\n", i);
77     test3<3false>(beg, beg + i, end);
78   }
79   
80 }
复制代码
0 0
原创粉丝点击
热门问题 老师的惩罚 人脸识别 我在镇武司摸鱼那些年 重生之率土为王 我在大康的咸鱼生活 盘龙之生命进化 天生仙种 凡人之先天五行 春回大明朝 姑娘不必设防,我是瞎子 驾照被扣了12分怎么办? c1驾照记满12分怎么办 不交钱不让验房怎么办 中考考号忘了怎么办 高考考生号忘了怎么办 1岁宝宝吞了硬币怎么办 小孩吞了5角硬币怎么办 10小孩吞了硬币怎么办 小孩吞了5毛硬币怎么办 小孩把硬币吞了怎么办 高三数学成绩差怎么办 没交社保的工龄怎么办 购置税证明丢了怎么办 车登记证书丢了怎么办 车辆登记书丢了怎么办 车贷分期还完了怎么办 住宅70年到期后怎么办 在外地扣了12分怎么办 英语6级证书丢了怎么办 孩子腺样体肥大鼻子堵塞怎么办 孩子初中成绩太差怎么办 交违章罚单丢了怎么办 违章缴费单丢了,怎么办 违章处理单掉了怎么办 驾驶证被扣9分后怎么办 车子违章扣50分怎么办 车子扣了12分怎么办 大学把档案丢了怎么办 学校把档案丢了怎么办 高考考了200多分怎么办 高考报名号忘了怎么办 中考只考500分怎么办 档案自提了之后怎么办 冬天衣服上的毛怎么办 四维预约不上怎么办 交了订金后悔了怎么办 信而富认证失败怎么办 南京市民卡坏了怎么办 南京市民卡断了怎么办 义乌市民卡丢了怎么办 常熟市民卡丢了怎么办