排序算法总结

==============================
冒泡排序算法
==============================
1.算法稳定性
稳定

2.复杂度
空间复杂度O(1); 平均时间复杂度O(n2)

3.极限情况分析
最好情况:向量本来就是有序的,则一趟扫描即可结束,共比较n-1次,无交换。
最坏情况:向量是逆序的,则一共需要做n-1次扫描,每次扫描都必须比较n-i次,由等差数列求和公式知道,一共需做n(n-1)/2次比较和交换。

4.算法实现

  1. <SPAN style="FONT-SIZE: 13px"><SPAN style="FONT-SIZE: 13px"></SPAN></SPAN><PREclass=cpp name="code">template<typename T>
  2. void bubble_sort(T * array, const int size)
  3. {
  4. if(size < 2 || array == NULL)
  5. {
  6. cout << "illegal input!" << endl;
  7. return;
  8. }
  9. bool exchanged = false;
  10. T tmp;
  11. for(int i=0; i<size-1; ++i)
  12. {
  13. exchanged = false;
  14. for(int j=size-1; j>i; --j)
  15. {
  16. if(array[j] < array[j-1])
  17. {
  18. tmp = array[j];
  19. array[j] = array[j-1];
  20. array[j-1] = tmp;
  21. exchanged = true;
  22. }
  23. }
  24. if(!exchanged)
  25. {
  26. return;
  27. }
  28. }
  29. }
  30. </PRE>
  31. <PRE></PRE>
  32. <P><SPAN style="FONT-SIZE: 13px">5. 算法改进<BR>
  33. (1) 奇偶交换排序<BR>
  34. 稳定性? 稳定 <SPAN style="COLOR: #ff0000">//TODO need verifying<BR>
  35. </SPAN> 空间复杂度O(1), 时间复杂度O(n2)<BR>
  36. 极限情况分析:正向有序:n-1次比较;逆向有序((n+1)/2)*n+(n/2-1)次比较<BR>
  37. 综合而言,性能和冒泡排序相当,所以一般不用此种排序方法。<BR>
  38. 算法实现:</SPAN></P>
  39. <PRE class=cpp name="code"><SPAN style="FONT-SIZE: 13px">template<typename T>
  40. void oe_sort(T * array, const int size)
  41. {
  42. if(size < 2 || array == NULL)
  43. {
  44. cout << "illegal input!" << endl;
  45. return;
  46. }
  47. T tmp;
  48. bool exchanged =true;
  49. while(exchanged)
  50. {
  51. exchanged = false;
  52. //scan odd index
  53. for(int i=1; i<size-1; i+=2)
  54. {
  55. if(array[i] > array[i+1])
  56. {
  57. tmp = array[i];
  58. array[i] = array[i+1];
  59. array[i+1] = tmp;
  60. exchanged = true;
  61. }
  62. }
  63. //scan even index
  64. for(int j=0; j<size-1; j+=2)
  65. {
  66. if(array[j] > array[j+1])
  67. {
  68. tmp = array[j];
  69. array[j] = array[j+1];
  70. array[j+1] = tmp;
  71. exchanged = true;
  72. }
  73. }
  74. }
  75. }
  76. </SPAN></PRE>
  77. <P><BR>
  78. <BR>
  79. <SPAN style="FONT-SIZE: 13px"> <BR>
  80. (2) 每次扫描记住最后一次扫描发生的位置lastExchange<BR>
  81. 如此一样,下一趟排序时区间[0, lastExchange-1]是有序区, [lastExchange, size-1]是无序区。如果不记录,则下一趟排序时区间[0, i]有序区间,i为上次排序的趟数。自然的,lastExchange的最小可能数值就是i,所以此种改性是有效的。<BR>
  82. 算法性能和普通冒泡一样。<BR>
  83. 算法实现: </SPAN><SPAN style="FONT-SIZE: 13px"><SPAN style="COLOR: #ff0000">//TODO<BR>
  84. </SPAN> <BR>
  85. (3) 改进冒泡算法的不对称性<BR>
  86. 什么是不对称性? 比如上面的冒泡排序算法,如果数组中的最大值位于a[0],理论上一遍扫描就可以完成排序,但实际仍旧需要n-1遍扫描。而如果改冒泡为沉底,则一遍即可。<BR>
  87. 所以引出了解决不对称性的方法:冒泡和沉底交替进行。<BR>
  88. 算法性能和普通冒泡一样。<BR>
  89. 算法实现: </SPAN><SPAN style="FONT-SIZE: 13px"><SPAN style="COLOR: #ff0000">//TODO<BR>
  90. </SPAN> <BR>
  91. <BR>
  92. ==========================<BR>
  93. 快速排序算法<BR>
  94. ==========================<BR>
  95. 1. 基本思想<BR>
  96. 分治法:将原问题分解为若干个规模更小但结构与原问题相似的子问题。递归地解这些子问题,然后将这些子问题的解组合为原问题的解。</SPAN></P>
  97. <P><SPAN style="FONT-SIZE: 13px">2. 算法描述<BR>
  98. 核心是:选取哨兵结点,划分区域,并找到哨兵结点所在的位置。<BR>
  99. (1) 选取一个哨兵结点,通常取首结点,或者末结点,或者中间结点。但最合理的应该是随机选择一个结点。<BR>
  100. (2) 根据哨兵结点,将原数组划分为左右2个部分,并决定哨兵结点的位置。<BR>
  101. (3) 分别对左右部分递归地进行(1)和(2),递归结束条件是分出的子数组长度小于等于1</SPAN></P>
  102. <P><SPAN style="FONT-SIZE: 13px">3. 算法稳定性<BR>
  103. 不稳定</SPAN></P>
  104. <P><SPAN style="FONT-SIZE: 13px">4. 复杂度<BR>
  105. 平均空间复杂度是O(lgn), 注意是以2为底的对数。<BR>
  106. 平均时间复杂度是O(nlgn)。</SPAN></P>
  107. <P><SPAN style="FONT-SIZE: 13px">5. 极限情况分析<BR>
  108. 最好情况下,空间复杂度是O(lgn), 时间复杂度是O(nlgn)。此处的空间复杂度主要是递归产生的栈空间。<BR>
  109. 最坏情况下,空间复杂度是O(n), 时间复杂度是O{n2}。最坏情况是每次哨兵结点都是整个数组的最大值,或者最小值。<BR>
  110. <BR>
  111. 6. 算法实现<BR>
  112. </SPAN></P>
  113. <PRE class=cpp name="code">template<typename T>
  114. int partition(T * array,constint low,constint high)
  115. {
  116. //select the first elem as pivot. Here use random index for pivot will be better
  117. T pivot = array[low];
  118. for(int i=low, j=high; i<j;)
  119. {
  120. //scan for the first elem that smaller than pivot
  121. while(j>i && array[j]>=pivot)
  122. j--;
  123. if(i<j)
  124. {
  125. //since array[i] is already be array[j] which is smaller than pivot, array[i+1] will firstly checkde in next scan
  126. array[i++] = array[j];
  127. }
  128. //scan for the first elem that bigger than pivot
  129. while(i<j && array[i]<=pivot)
  130. i++;
  131. if(i<j)
  132. {
  133. array[j--] = array[i];
  134. }
  135. }
  136. //put the pivot to right position, pls notice that i will be equal with j at this point
  137. array[i] = pivot;
  138. return i;
  139. }
  140. template<typename T>
  141. quick_sort(T * array, const int low, constint high)
  142. {
  143. int pivotPos = 0;
  144. if(low < high)
  145. {
  146. //the most important is locating index for pivot, then partition will be possible
  147. pivotPos = partition(array, low, high);
  148. quick_sort(array, low, pivotPos-1);
  149. quick_sort(array, pivotPos+1, high);
  150. }
  151. }
  152. </PRE>
  153. <P><SPAN style="FONT-SIZE: 13px">注意:冒泡排序和快速排序均属于交换排序算法。</SPAN></P>
  154. <P><SPAN style="FONT-SIZE: 13px"> </SPAN></P>
  155. <P><SPAN style="FONT-SIZE: 13px">========================<BR>
  156. 直接插入排序算法<BR>
  157. ========================<BR>
  158. 1.算法稳定性<BR>
  159. 稳定<BR>
  160. <BR>
  161. 2.复杂度<BR>
  162. 空间复杂度O(1); 时间复杂度O(n2)<BR>
  163. <BR>
  164. 3.极限情况分析<BR>
  165. 最好情况:向量是正序的,共比较关键字n-1次,无交换。<BR>
  166. 最坏情况:向量是逆序的,则一共比较关键字│(n+2)(n-1)/2次,记录移动次数n-1)(n+4)/2次<BR>
  167. <BR>
  168. 4. 算法实现<BR>
  169. </SPAN></P>
  170. <PRE class=cpp name="code">template<typename T>
  171. void insert_sort(T * array,constint size)
  172. {
  173. if(size < 2 || array == NULL)
  174. {
  175. cout << "illegal input!" << endl;
  176. return;
  177. }
  178. T pivot;
  179. for(int i=1; i<size; ++i)
  180. {
  181. if(array[i] >= array[i-1])
  182. {
  183. continue;
  184. }
  185. pivot = array[i];
  186. int j = i-1;
  187. do
  188. {
  189. array[j+1] = array[j];
  190. --j;
  191. }while(pivot < array[j] && j>=0);
  192. array[j+1] = pivot;
  193. }
  194. }
  195. </PRE>
  196. <P> </P>
  197. <P><SPAN style="FONT-SIZE: 13px">========================<BR>
  198. 希尔排序算法<BR>
  199. ========================<BR>
  200. 1.算法稳定性<BR>
  201. 不稳定<BR>
  202. <BR>
  203. 2.算法描述<BR>
  204. (1)直接插入排序的变体,取一个小于数组长度n的整数d作为初始增量,将整个数组分为d个小组。<BR>
  205. 比如说整个数组的长度为7,选择初始增量d为5, 则a[0], a[5]为第一组。a[1]和a[6]为第二组,a[2]和a[7]为第三组,a[3]为第四组,a[4]为第五组<BR>
  206. 然后在每个小组内进行直接插入排序。<BR>
  207. (2)按照一定的减少d, 比如说第二次改变为3,再次进行上述的(1)和(2)。直到d减少到为1,并最后一次执行直接排序为止。<BR>
  208. <BR>
  209. 3.复杂度<BR>
  210. 空间复杂度O(1); 时间复杂度O(n(lgn)2) 译为n乘以lgn的平方。其复杂度收到增量递减方式的影响。最好的步长序列由Marcin Ciura设计为{1, 4, 10, 23, 57, 132, 301, 701, 1750, ...}<BR>
  211. <BR>
  212. 4.极限情况分析<BR>
  213. 最好情况:向量是正序的,共比较关键字n-1次,无交换。<BR>
  214. 最坏情况:向量是逆序的,则一共比较关键字│(n+2)(n-1)/2次,记录移动次数n-1)(n+4)/2次<BR>
  215. <BR>
  216. 5.算法实现</SPAN></P>
  217. <PRE class=cpp name="code">template<typename T>
  218. void shellInteration(T * array,constint size,constint increment)
  219. {
  220. T pivot;
  221. for(int i=increment; i<size; ++i)//此算法中,将所有的increment改成1,就退化成了直接插入排序算法
  222. {
  223. if(array[i] >= array[i-increment])
  224. {
  225. continue;
  226. }
  227. pivot = array[i];
  228. int j = i-increment;
  229. do
  230. {
  231. array[j+increment] = array[j];
  232. j -= increment;
  233. }while(pivot < array[j] && j>=0);
  234. array[j+increment] = pivot;
  235. }
  236. }
  237. template<typename T>
  238. void shellSort(T * array, const int size)
  239. {
  240. if(size < 2 || array == NULL)
  241. {
  242. cout << "illegal input!" << endl;
  243. return;
  244. }
  245. int increaArray[3] = {5, 3, 1};//增量序列,必须以1结束
  246. for(int i=0; i<sizeof(increaArray)/sizeof(int); ++i)
  247. {
  248. shellInteration(array, size, increaArray[i]);
  249. }
  250. }
  251. </PRE>
  252. <P> </P>
  253. <P><SPAN style="FONT-SIZE: 13px">注意:直接插入排序和希尔排序都是插入排序。</SPAN></P>
  254. <P><SPAN style="FONT-SIZE: 13px"> </SPAN></P>
  255. <P><SPAN style="FONT-SIZE: 13px">========================<BR>
  256. 直接选择排序算法<BR>
  257. ========================<BR>
  258. 1. 基本思想<BR>
  259. 进行n-1次扫描,每次从无序区中选择关键字最小的记录,并移动到有序区中。</SPAN></P>
  260. <P><SPAN style="FONT-SIZE: 13px">2. 算法稳定性<BR>
  261. 不稳定</SPAN></P>
  262. <P><SPAN style="FONT-SIZE: 13px">3. 复杂度<BR>
  263. 平均空间复杂度是O(l)。<BR>
  264. 平均时间复杂度是O(n2)。</SPAN></P>
  265. <P><SPAN style="FONT-SIZE: 13px">4. 极限情况分析<BR>
  266. 最好情况下,空间复杂度是O(1), 时间复杂度是O(n2)。此处的空间复杂度,即便在正向有序的情况下,第i趟都需要比较关键字n-i次。<BR>
  267. 最坏情况下,空间复杂度是O(1), 时间复杂度是O{n2}。</SPAN></P>
  268. <P><SPAN style="FONT-SIZE: 13px">5. 算法实现</SPAN></P>
  269. <PRE class=cpp name="code">template<typename T>
  270. void select_sort(T * array, const int size)
  271. {
  272. if(size < 2 || array == NULL)
  273. {
  274. cout << "illegal input!" << endl;
  275. return;
  276. }
  277. //pivotIndex index to monitor the smallest elem in every scan
  278. int pivotIndex = 0;
  279. for(int i=0; i<size; ++i)
  280. {
  281. pivotIndex = i;
  282. for(int j=i+1; j<size; ++j)
  283. {
  284. if(array[j] < array[pivotIndex])
  285. pivotIndex=j;
  286. }
  287. if(pivotIndex!=i)
  288. {
  289. T tmp = array[pivotIndex];
  290. array[pivotIndex] = array[i];
  291. array[i] = tmp;
  292. }
  293. }
  294. }
  295. </PRE>
  296. <P><BR>
  297. <SPAN style="FONT-SIZE: 13px">========================<BR>
  298. 堆排序算法<BR>
  299. ========================<BR>
  300. 1. 基本思想<BR>
  301. 在排序过程中,将R[l..n]看成是一棵完全二叉树的顺序存储结构,利用完全二叉树中双亲结点和孩子结点之间的内在关系,在当前无序区中选择关键字最大(或最小)的记录。</SPAN></P>
  302. <P><SPAN style="FONT-SIZE: 13px">2. 算法稳定性<BR>
  303. 不稳定</SPAN></P>
  304. <P><SPAN style="FONT-SIZE: 13px">3. 复杂度<BR>
  305. 平均空间复杂度是O(l)。<BR>
  306. 平均时间复杂度是O(nlogn)。</SPAN></P>
  307. <P><SPAN style="FONT-SIZE: 13px">4. 极限情况分析<BR>
  308. 最坏情况下,时间复杂度是O(nlogn)。</SPAN></P>
  309. <P><SPAN style="FONT-SIZE: 13px">5. 算法实现</SPAN></P>
  310. <PRE class=cpp name="code"><SPAN style="FONT-SIZE: 13px">template<typename T>
  311. void heapAdjust(T * array, const int startIndex,constint length)
  312. {
  313. //注意此函数的功能不是建立大根堆,只是做有限的调整
  314. int pivotIndex = startIndex;
  315. while(2*pivotIndex+1 < length)
  316. {
  317. int lChildIndex = 2*pivotIndex+1, rChildIndex = 2*pivotIndex+2;
  318. //取得左右孩子中较大值的下标
  319. int maxChildIndex = lChildIndex;
  320. if(rChildIndex < length)
  321. {
  322. maxChildIndex = array[lChildIndex]<array[rChildIndex]?rChildIndex:lChildIndex;
  323. }
  324. //如果孩子结点的值大于父亲结点的值,则交换2个结点值
  325. if(array[maxChildIndex] > array[pivotIndex])
  326. {
  327. T tmp = array[pivotIndex];
  328. array[pivotIndex] = array[maxChildIndex];
  329. array[maxChildIndex] = tmp;
  330. //交换后,子堆被破坏,继续调整子堆
  331. pivotIndex = maxChildIndex;
  332. }
  333. else
  334. {
  335. break; //如果已经符合了大根堆的规则,则直接退出
  336. }
  337. }
  338. }
  339. template<typename T>
  340. void heap_sort(T * array,constint size)
  341. {
  342. if(size < 2 || array == NULL)
  343. {
  344. cout << "illegal input!" << endl;
  345. return;
  346. }
  347. //第一步,把数组看成是完全二叉树的层次遍历存储,并将整个数组建成一个大根二叉堆
  348. //size/2-1所标记的结点,完全二叉树的最后一个叶子结点的父结点
  349. for(int i=size/2-1; i>=0; --i)
  350. {
  351. heapAdjust(array, i, size);
  352. //test
  353. //printResult(cout, array, 10);
  354. }
  355. //经过第一步后,array[0]存储的是此数组中最大值,将array[0]和array[size-1]交换,这样无序区就变成了
  356. //array[0, size-2],再对无序区进行大根堆的调整。
  357. //这里需要注意一点:array[0, size-2]成为不符合大根堆原则的区间,仅仅因为调整后的根结点array[0]位置不对,
  358. //所以只需要将array[0]“下沉交换”到合适的层次,使得整个区间再次成为大根堆时,array[0]变再次成为子区间中的最大值。依次类推。
  359. for(int j=size-1; j>0; --j)
  360. {
  361. T tmp = array[0];
  362. array[0] = array[j];
  363. array[j] = tmp;
  364. heapAdjust(array, 0, j); //TODO: I suspect that it should be j-1 here
  365. }
  366. }
  367. </SPAN></PRE>
  368. <P> </P>
  369. <P><SPAN style="FONT-SIZE: 13px">注意:直接选择排序和堆排序都属于选择排序的范畴。</SPAN></P>
  370. <P><BR>
  371. <SPAN style="FONT-SIZE: 13px">========================<BR>
  372. 箱排序算法<BR>
  373. ========================<BR>
  374. 1. 基本思想<BR>
  375. 箱排序也称桶排序(Bucket Sort),其基本思想是:设置若干个箱子,依次扫描待排序的记录R[0],R[1],…,R[n-1],把关键字等于k的记录全都装入到第k个箱子里(分配),然后按序号依次将各非空的箱子首尾连接起来(收集)。</SPAN></P>
  376. <P><SPAN style="FONT-SIZE: 13px">2. 算法稳定性<BR>
  377. 如果保证元素入箱和出箱时都遵守FIFO,则箱排序是稳定的。<BR>
  378. <BR>
  379. 3. 复杂度<BR>
  380. 平均空间复杂度是O(n)。<BR>
  381. 平均时间复杂度是O(m+n)。其中,m是箱子的个数。如果箱子个数的数量级是O{n},则时间复杂度是O{n}<BR>
  382. <BR>
  383. 4. 极限情况分析<BR>
  384. 最坏情况下,时间复杂度是o{n2}。比如说一个有10个元素的数组排序,元素的取值范围是1-100, 用100个箱子进行排序,此时m=n2=100, 时间复杂度也变成了O(n2).</SPAN></P>
  385. <P><SPAN style="FONT-SIZE: 13px">5. 算法实现<BR>
  386. <SPAN style="COLOR: #ff0000">//TODO</SPAN></SPAN></P>
  387. <P><SPAN style="COLOR: #ff0000; FONT-SIZE: 13px"></SPAN> </P>
  388. <P><SPAN style="FONT-SIZE: 13px">========================<BR>
  389. 基数排序算法<BR>
  390. ========================<BR>
  391. 1. 基本思想<BR>
  392. 定义比较抽象,以十进制的整数数组为例说明。一个十进制整数的每一位的取值范围是0-9,可能的取值个数是10,这个可能的取值个数成为基数。<BR>
  393. 再比如是英文小写字符数组,则取值范围是'a''z',一共26个可能的取值,基数则为26。<BR>
  394. 首先设置基数个箱子,比如整数数组排序,则设置10个箱子,序号从0到9。然后从低位到高位对每个元素进行箱排序。排序所需要进行的趟数,是最大的元素位数。比如说一个整数数组,最大元素是1000,其它元素都小于1000,则需要进行4趟扫描。</SPAN></P>
  395. <P><SPAN style="FONT-SIZE: 13px">2. 算法稳定性<BR>
  396. 要保证基数排序的正确性,必须保证除开第一趟外的每趟箱排序是稳定的。这点保证了基数排序本身也是稳定的。<BR>
  397. <BR>
  398. 3. 复杂度<BR>
  399. 平均时间复杂度 O{n}<BR>
  400. 平均空间复杂度 O(n+基数)</SPAN></P>
  401. <P><SPAN style="FONT-SIZE: 13px">4. 算法实现<BR>
  402. <SPAN style="COLOR: #ff0000"> //TODO</SPAN><BR>
  403. </SPAN></P>
  404. <P><SPAN style="FONT-SIZE: 13px"></SPAN><SPAN style="FONT-SIZE: 13px"><BR>
  405. ========================<BR>
  406. 归并排序算法<BR>
  407. ========================<BR>
  408. 1. 基本思想<BR>
  409. 算法导论上第一章中谈分治法时的例子,基于二路归并操作(对2个有序数组进行合并,形成新的有序序列)。首先将整个数组不断二分直至只有一个元素(已然有序),然后对子数组进行二路归并操作,最后合并操作结果。</SPAN></P>
  410. <P><SPAN style="FONT-SIZE: 13px">2. 算法的稳定性<BR>
  411. 是稳定的。</SPAN></P>
  412. <P><SPAN style="FONT-SIZE: 13px">3. 复杂度<BR>
  413. 平均时间复杂度 O{nlgn}, 空间复杂度O{n}。就时间复杂度为言,和快速排序一样,但快速排序是就地排序。<BR>
  414. <BR>
  415. 4. 算法实现</SPAN></P>
  416. <PRE class=cpp name="code">//merge [left, middle] and [middle+1, right], then copy back to array
  417. template<typename T>
  418. void mergeAndCopy(T * array, const int left,int middle,int right)
  419. {
  420. T * buff = new T[right-left+1];
  421. //merge two part of array
  422. int i=left, j=middle+1;
  423. int buff_index=0;
  424. while(i<=middle && j<=right)
  425. {
  426. buff[buff_index++] = array[i]<=array[j]?array[i++]:array[j++];
  427. }
  428. //merge the rest part
  429. while(i<=middle)
  430. {
  431. buff[buff_index++] = array[i++];
  432. }
  433. while(j<=right)
  434. {
  435. buff[buff_index++] = array[j++];
  436. }
  437. //copy buff data back to original array
  438. for(int k=left, index=0; k<=right; ++k, ++index)
  439. {
  440. array[k] = buff[index];
  441. }
  442. }
  443. template<typename T>
  444. void mergeSort(T * array, const int left,constint right)
  445. {
  446. if(NULL == array)
  447. {
  448. cout << "illegal input" << endl;
  449. return;
  450. }
  451. if(left < right)
  452. {
  453. int middle = (left + right)/2;
  454. mergeSort(array, left, middle);
  455. mergeSort(array, middle+1, right);
  456. mergeAndCopy(array,left,middle,right);
  457. }
  458. }
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值