1. /* 2. Name: 向量旋转算法集锦 3. Copyright: 始发于goal00001111的专栏;允许自由转载,但必须注明作者和出处 4. Author: goal00001111 5. Date: 28-12-08 23:28 6. Description: 7. 向量旋转算法:将具有n个元素的向量a向左旋转r个位置。 8. 例如 :将字符串"abcdefghij"旋转成"defghjabc",其中n=10,r=3。 9. 其实就是将 AB 转换成 BA 的过程,这里A ="abc", B="defghij"。 10. 本文总共采用了四种方法来解决向量旋转问题,依次是: 11. 方法一:最简单的直接移动向量旋转算法; 12. 方法二:简明的的逆置数组旋转算法; 13. 方法三:传说中的杂耍旋转算法; 14. 方法四:一个类似于欧几里得算法的旋转算法; 15. 其中方法一需要一个辅助数组,空间复杂度较高;方法二每个元素都要移动两次,效率相对较低; 16. 方法三和方法四都是极牛的算法,空间和时间复杂度都很低。 17. 这是牛书《编程珠玑》中的一个例题,在书的网站上有详细的源码,我把它改成了我所熟悉的样子。 18. 源码的网站是:http://www.cs.bell-labs.com/cm/cs/pearls/code.html 19. */ 20. #include<iostream> 21. #include <time.h> 22. 23. using namespace std; 24. 25. template <class T> //最简单的直接移动向量旋转算法 26. void SlideRotate(T a[], int n, int r); 27. 28. template <class T> //逆置数组的原子操作 29. void Reverse(T a[], int left, int right); 30. 31. template <class T> //简明的的逆置数组旋转算法 32. void ReverseRotate(T a[], int n, int r); 33. 34. int Gcd(int m, int n); //欧几里德算法求最大公约数 35. 36. template <class T> //传说中的杂耍旋转算法 37. void JuggleRotate(T a[], int n, int r); 38. 39. template <class T> //交换两个长度均为len的向量块a[left_1..left_1+len) 和 a[left_2..left_2+len) 40. void Swap(T a[], int left_1, int left_2, int len); 41. 42. template <class T> //一个类似于欧几里得算法的旋转算法 43. void GcdRotate(T a[], int n, int r); 44. 45. template <class T> //创建一个向量 46. void InitVector(T a[], int n); 47. 48. template <class T> //输出一个向量 49. void PrintVector(const T a[], int n); 50. 51. template <class T> //判断向量旋转是否成功 52. bool CheckRotate(const T a[], int n, int r); 53. 54. int main() 55. { 56. const int N = 601; //测试次数 57. const int MAX = 500000; //向量长度 58. int a[MAX] = {0}; 59. int rotateDistance = 100000; 60. time_t startTime, endTime; 61. 62. 最简单的直接移动向量旋转算法/// 63. time(&startTime); 64. InitVector(a, MAX); 65. // PrintVector(a, MAX); 66. for (int i=0; i<N; i++) 67. SlideRotate(a, MAX, rotateDistance); 68. // PrintVector(a, MAX); 69. if (CheckRotate(a, MAX, rotateDistance)) 70. cout << "True" << endl; 71. else 72. cout << "False" << endl; 73. 74. time(&endTime); 75. cout << "time = " << difftime(endTime, startTime) << endl << endl; 76. /// 77. //简明的的逆置数组旋转算法// 78. time(&startTime); 79. InitVector(a, MAX); 80. // PrintVector(a, MAX); 81. for (int i=0; i<N; i++) 82. ReverseRotate(a, MAX, rotateDistance); 83. // PrintVector(a, MAX); 84. if (CheckRotate(a, MAX, rotateDistance)) 85. cout << "True" << endl; 86. else 87. cout << "False" << endl; 88. 89. time(&endTime); 90. cout << "time = " << difftime(endTime, startTime) << endl << endl; 91. /// 92. 传说中的杂耍旋转算法 // 93. time(&startTime); 94. InitVector(a, MAX); 95. // PrintVector(a, MAX); 96. for (int i=0; i<N; i++) 97. JuggleRotate(a, MAX, rotateDistance); 98. // PrintVector(a, MAX); 99. if (CheckRotate(a, MAX, rotateDistance)) 100. cout << "True" << endl; 101. else 102. cout << "False" << endl; 103. 104. time(&endTime); 105. cout << "time = " << difftime(endTime, startTime) << endl << endl; 106. /// 107. /一个类似于欧几里得算法的旋转算法/// 108. time(&startTime); 109. InitVector(a, MAX); 110. // PrintVector(a, MAX); 111. for (int i=0; i<N; i++) 112. GcdRotate(a, MAX, rotateDistance); 113. // PrintVector(a, MAX); 114. if (CheckRotate(a, MAX, rotateDistance)) 115. cout << "True" << endl; 116. else 117. cout << "False" << endl; 118. 119. time(&endTime); 120. cout << "time = " << difftime(endTime, startTime) << endl << endl; 121. /// 122. 123. system("pause"); 124. return 0; 125. } 126. 127. 方法一:创建一个长度为min{r, n-r)的辅助数组,以帮助完成旋转任务// 128. /* 129. 函数名称:SlideRotate 130. 函数功能:最简单的直接移动向量旋转算法:先利用一个辅助数组将较短的那一段元素存储起来, 131. 再移动原向量中未另外存储的那部分元素,最后将辅助数组中的元素再复制到正确位置 132. 输入参数:T a[]:需要被旋转的向量 133. int n:向量的长度 134. int r:向量左半段长度 135. 输出参数:T a[]:旋转后的向量 136. 返回值:无 137. */ 138. template <class T> 139. void SlideRotate(T a[], int n, int r) 140. { 141. if (r < n - r) //如果左半段小于右半段,存储左半段的元素 142. { 143. T *buf = new T[r]; 144. for (int i=0; i<r; i++)//存储左半段的元素 145. buf[i] = a[i]; 146. 147. for (int i=r; i<n; i++)//移动右半段的元素 148. a[i-r] = a[i]; 149. 150. for (int i=0; i<r; i++)//复制左半段的元素到右半段 151. a[n-r+i] = buf[i]; 152. 153. delete []buf; 154. } 155. else //否则存储右半段的元素 156. { 157. T *buf = new T[n-r]; 158. for (int i=r; i<n; i++)//存储右半段的元素 159. buf[i-r] = a[i]; 160. 161. for (int i=r-1; i>=0; i--)//移动左半段的元素 162. a[i+n-r] = a[i]; 163. 164. for (int i=0; i<n-r; i++)//复制右半段的元素到左半段 165. a[i] = buf[i]; 166. 167. delete []buf; 168. } 169. } 170. 171. //方法二:使用一个逆置数组的原子操作 172. /* 173. 函数名称:Reverse 174. 函数功能:逆置数组的原子操作 175. 输入参数:T a[]:需要被逆置的向量 176. int left: 数组的左界 177. int right:数组的右界 178. 输出参数:T a[]:逆置后的数组 179. 返回值:无 180. */ 181. template <class T> 182. void Reverse(T a[], int left, int right) 183. { 184. T t; 185. while (left < right) 186. { 187. t = a[left]; 188. a[left] = a[right]; 189. a[right] = t; 190. left++; 191. right--; 192. } 193. } 194. 195. /* 196. 函数名称:ReverseRotate 197. 函数功能:简明的的逆置数组旋转算法:构造一个逆置数组的原子操作子函数,然后设置不同的数组左右界, 198. 分三次调用子函数就行了。因为每个元素都需要移动两次,所以效率不是很高。 199. 输入参数:T a[]:需要被旋转的向量 200. int n:向量的长度 201. int r:向量左半段长度 202. 输出参数:T a[]:旋转后的向量 203. 返回值:无 204. */ 205. template <class T> 206. void ReverseRotate(T a[], int n, int r) 207. { 208. Reverse(a, 0, r-1); //逆置左半段数组 209. Reverse(a, r, n-1); //逆置右半段数组 210. Reverse(a, 0, n-1); //逆置整段数组 211. } 212. 213. //方法三:传说中的杂耍旋转算法 214. 215. /* 216. 函数名称:Gcd 217. 函数功能:欧几里德算法求最大公约数 218. 输入参数:int m:正整数之一 219. int n:正整数之二 220. 输出参数:无 221. 返回值:正整数m和n的最大公约数 222. */ 223. int Gcd(int m, int n) 224. { 225. int t; 226. while (m > 0) 227. { 228. t = n % m; 229. n = m; 230. m = t; 231. } 232. return n; 233. } 234. 235. /* 236. 函数名称:JuggleRotate 237. 函数功能:传说中的杂耍旋转算法: 238. 先将a[0]存储到临时变量t中,然后将a[r]移动到a[0],将a[2r] 移动到 a[r], 239. 依此类推(数组中所有的下标都要对数组的长度n取模),直到(k*r)%n == 0, 240. 此时我们不能在a[0]中取数,而是在临时变量t中取数,然后结束该过程。 241. 如果该过程不能移动所有的元素,那么我们从a[1]开始移动,一直依次下去, 242. 直到移动了所有的元素为止。 243. 那么总共要重复开始移动几次呢?数学证明是Gcd(r, n)次。 244. 此算法每个元素只需移动一次就可以到达正确位置,理论上效率是最高的, 245. 但由于要做求最大公约数和求余运算等准备工作,所以没有显示出优势。 246. 输入参数:T a[]:需要被旋转的向量 247. int n:向量的长度 248. int r:向量左半段长度 249. 输出参数:T a[]:旋转后的向量 250. 返回值:无 251. */ 252. template <class T> 253. void JuggleRotate(T a[], int n, int r) 254. { 255. int i, j, k; 256. int cyc = Gcd(r, n); //用r和n的最大公约数作为循环周期 257. 258. for (i=0; i<cyc; i++) //总共需要重复开始移动cyc次,才能使得所有的元素都移动到正确位置 259. { 260. T t = a[i]; //临时变量t存储a[i] 261. j = i; 262. while (1)//依次移动元素,直到 (k*r)%n == 0 263. { 264. k = j + r; 265. if (k >= n) //用除法运算替代模运算 266. k -= n; 267. 268. if (k == i) 269. break; 270. a[j] = a[k]; 271. j = k; 272. } 273. a[j] = t; 274. } 275. } 276. 277. //方法四:一个类似于欧几里得辗转相除算法的旋转算法/ 278. /* 279. 函数名称:Swap 280. 函数功能:交换两个长度均为len的向量块a[left_1..left_1+len) 和 a[left_2..left_2+len) 281. 输入参数:T a[]:需要被处理的向量 282. int left_1:向量块a[left_1..left_1+len)的左界 283. int left_2:向量块a[left_2..left_2+len)的左界 284. int len: 两个向量块的长度 285. 输出参数:T a[]:交换了部分元素的向量 286. 返回值:无 287. */ 288. template <class T> 289. void Swap(T a[], int left_1, int left_2, int len) 290. { 291. T t; 292. while (len > 0) 293. { 294. t = a[left_1]; 295. a[left_1] = a[left_2]; 296. a[left_2] = t; 297. left_1++; 298. left_2++; 299. len--; 300. } 301. } 302. 303. /* 304. 函数名称:JuggleRotate 305. 函数功能:一个类似于欧几里得辗转相除算法的旋转算法: 306. 就像一个做阻尼振动的弹簧振子一样,按照由两边到中间的顺序,整段的交换向量块, 307. 并且被交换的向量块长度不断缩减,直到lLen == rLen。 308. 由于重复移动的元素较少,所以效率比逆置数组旋转算法要高。 309. 输入参数:T a[]:需要被旋转的向量 310. int n:向量的长度 311. int r:向量左半段长度 312. 输出参数:T a[]:旋转后的向量 313. 返回值:无 314. */ 315. template <class T> 316. void GcdRotate(T a[], int n, int r) 317. { 318. if (r == 0 || r == n) //特殊情况不用旋转 319. return; 320. 321. int lLen, rLen, pos; 322. lLen = pos = r; 323. rLen = n - r; 324. while (lLen != rLen) 325. { 326. if (lLen > rLen) //左半段大于右半段,移动右半段 327. { 328. Swap(a, pos-lLen, pos, rLen); 329. lLen -= rLen; //减少移动范围 330. } 331. else 332. { 333. Swap(a, pos-lLen, pos+rLen-lLen, lLen); 334. rLen -= lLen; 335. } 336. } 337. Swap(a, pos-lLen, pos, lLen); //最后交换中间段 338. } 339. 340. /* 341. 函数名称:InitVector 342. 函数功能:创建一个向量 343. 输入参数:T a[]:需要被赋值的向量 344. int n:向量的长度 345. 输出参数:T a[]:创建好的向量 346. 返回值:无 347. */ 348. template <class T> 349. void InitVector(T a[], int n) 350. { 351. for (int i=0; i<n; i++) //创建一个向量 352. a[i] = i; 353. } 354. 355. /* 356. 函数名称:PrintVector 357. 函数功能:输出一个向量 358. 输入参数:const T a[]:需要被输出的向量 359. int n:向量的长度 360. 输出参数:无 361. 返回值:无 362. */ 363. template <class T> 364. void PrintVector(const T a[], int n) 365. { 366. for (int i=0; i<n; i++) 367. cout << a[i] << ' '; 368. cout << endl; 369. } 370. 371. /* 372. 函数名称:CheckRotate 373. 函数功能:判断向量旋转是否成功 374. 输入参数:T a[]:需要被旋转的向量 375. int n:向量的长度 376. int r:向量左半段长度 377. 输出参数:无 378. 返回值:旋转成功返回true,否则返回false 379. */ 380. template <class T> 381. bool CheckRotate(const T a[], int n, int r) 382. { 383. for (int i=0; i<n-r; i++) //判断左半段 384. { 385. if (a[i] != i+r) 386. return false; 387. } 388. 389. for (int i=0; i<r; i++)//判断右半段 390. { 391. if (a[n-r+i] != i) 392. return false; 393. } 394. 395. return true; 396. }