算法复习笔记 | 排序算法比较

最近正好复习复习算法,于是从排序算法开始做一个总结。以下的代码均为原创,如果有任何问题,欢迎指正。简单来讲,排序算法的实质是将长度为n的数组中的数字按照从小到大或者从大到小的顺利排列。


简而言之,在不考虑算法的情况下,我们可以把排序抽象为如下的一个函数:array表示T类型的一个数组,num表示数组的长度。本文假设我们实现的排序算法都是按照从小到大的顺序排列;从大到小的排列类似。


template <class T>

void Order(T array[], int num)


1)选择算法:基本思想是每次从数组中选择最小的数,将这个数与已排序的数组最后一位交换。寻找第一个数时,我们需要遍历num个数才能判断出最小的数;寻找第i个数时,由于我们已经成功的将前i-1个数按序排列,我们只需要遍历num-i+1个数便能找到最小的数。

总而言之,时间复杂度为O(n2),而空间复杂度为O(1)。

[cpp]  view plain  copy
  1. template <class T>  
  2. void selectSort(T array[], int num)  
  3. {  
  4. T curItem;//哨兵元素,用于记录遍历时的较小值,将所以为排序数遍历后就是最小值  
  5. int cur;//记录较小值或最小值的位置  
  6.   
  7.   
  8. for(int i = 0; i < num; i++)//i代表当前有多少个元素已经成功排序  
  9. {  
  10. curItem = array[i];  
  11. cur = i;  
  12. //寻找未排序数的第一个数  
  13. for(int j = i + 1; j < num; j++)  
  14. {  
  15. if(curItem >= array[j])  
  16. {  
  17. curItem = array[j];  
  18. cur = j;  
  19. }  
  20. }  
  21. //从未排序数种找出最小值  
  22.   
  23.   
  24. if(cur != i)  
  25. {  
  26. T temp = array[cur];  
  27. array[cur] = array[i];  
  28. array[i] = temp;  
  29. }  
  30. //如果最小值不是未排序数种的第一个,则交换最小值与未排序数的第一个。  
  31. }  
  32. }  



2)简单插入算法:基本思想如同整理扑克牌,一开始手中有1张牌,每抽一张牌便将新抽的牌插入到手中有序的牌列种。

总而言之,时间复杂度为O(n2),而空间复杂度为O(1)。

[cpp]  view plain  copy
  1. template <class T>  
  2. void simpleInsertSort(T array[], int num)  
  3. {  
  4. for(int i = 1; i < num; i++)//初始时有一张牌,每抽取一张是一次循环  
  5. {  
  6. int cur = i;//当前新抽取的牌  
  7. for(int j = i - 1; j >= 0; j--)  
  8. {  
  9. if(array[j] > array[cur])  
  10. {  
  11. T temp = array[j];  
  12. array[j] = array[cur];  
  13. array[cur] = temp;  
  14. cur = j;  
  15. }  
  16. else  
  17. {  
  18. break;  
  19. }  
  20. }  
  21. //手中的牌是有序的,于是每抽一张就一次往前比较。新牌比前面的牌小便往前移动直到手牌整理完成  
  22. }  
  23. }  



3)折半插入算法:基本思想和简单插入算法相同,但是新牌与手牌比较的时候不必一一比较(因为手牌已经有序排列了),我们可以用二分查找的方法来将新牌左移

总而言之,时间复杂度有所提高,为O(nlogn),而空间复杂度为O(1)。

[cpp]  view plain  copy
  1. template <class T>  
  2. void binaryInsertSort(T array[], int num)  
  3. {  
  4. for(int i = 1; i < num; i++)//初始时有一张牌,每抽取一张是一次循环  
  5. {  
  6. int low = 0;  
  7. int high = i - 1;  
  8. //用两个位置坐标来表示手牌  
  9.   
  10.   
  11. while(low <= high)  
  12. {  
  13. int middle = low + (high - low) / 2;  
  14. if(array[middle] <= array[i])  
  15. {  
  16. low = middle + 1;  
  17. }  
  18. else  
  19. {  
  20. high = middle - 1;  
  21. }  
  22. };  
  23. //利用二分法找到插入的位置  
  24.   
  25.   
  26. if(low != i)  
  27. {  
  28. T temp = array[i];  
  29. for(int j = i; j > low; j--)  
  30. {  
  31. array[j] = array[j-1];  
  32. }  
  33. array[low] = temp;  
  34. }  
  35. //如果插入的位置与新牌的位置不同,则将新牌插入  
  36. }  
  37. }  



4)希尔排序:基本思想和简单插入算法类似。简单插入算法每一次从距离为一的位置处抽取新值。如果数据分布比较分散,简单插入算法效率较低。于是,我们可以采用距离(称之为步长)大于一的方式来抽取新值。例如,当步长为n时,数组中相当于有n个平行的子数组在做简单插入算法。运算之后,数组变得更加为有序,于是我们可以不断地缩小步长直到步长为1(简单插入算法),最终完成排序。当数组越有序时,简单插入算法的效率越高。

总而言之,时间复杂度不容易计算,但实验统计结果大约为n1.25到1.6n1.25,而空间复杂度为O(1)。希尔是一种不稳定的算法。

[cpp]  view plain  copy
  1. template <class T>  
  2. void shellSort(T array[], int num)  
  3. {  
  4. int multipe = 3;//循环次数  
  5. int step = 1;  
  6. for(; step < num; step = step * multipe + 1);  
  7. step = (step - 1) / 3;  
  8. //计算出该数组支持的最长步长  
  9. while(step >= 1)  
  10. {  
  11. for(int i = 0; i < step; i++)//给定步长时平行计算的子数组数目  
  12. {  
  13. for(int j = i + step; j < num; j += step)  
  14. {  
  15. int cur = j;  
  16. for(int k = j - step; k >= i; k--)  
  17. {  
  18. if(array[k] > array[cur])  
  19. {  
  20. T temp = array[cur];  
  21. array[cur] = array[k];  
  22. array[k] = temp;  
  23. cur = k;  
  24. }  
  25. }  
  26. }  
  27. //步长为step的简单插入算法  
  28. }  
  29. step = (step - 1) / 3;//缩短步长  
  30. }  
  31. }  



5)归并排序:初始时,我们假设我们得到了num个长度为1的子数组;每一次运算时将两个有序的子数组合并成一个父数组。

总而言之,时间复杂度为O(n2),而空间复杂度为O(n)。

[cpp]  view plain  copy
  1. template <class T>  
  2. T minValue(T a, T b)  
  3. {  
  4. return a<b?a:b;  
  5. }  
  6. //两个值中取较小值  
  7.   
  8.   
  9. template <class T>  
  10. void merge(T from[], T to[], int low, int high, int length)//从数组from中合并从low到high之间的子数组并保存到数组to中:第一个数组长度为length,第二个数组长度可能为length或小于length  
  11. {  
  12. int first = low;//第一个子数组的开始位置  
  13. int second = low + length;//第二个子数组的开始位置  
  14. int cur = low;//目标存储数组中的开始位置  
  15.   
  16.   
  17. while(first < low + length && second <= high)  
  18. {  
  19. to[cur++] = minValue<T>(from[first],from[second]);  
  20. from[first]<from[second]?first++:second++;  
  21. };//依次选择两个子数组中较小值添加到目标存储数组中  
  22.   
  23.   
  24. while(first < low + length)  
  25. {  
  26. to[cur++] = from[first++];  
  27. };//若第一个子数组还有未添加的值,添加到目标存储数组的尾部  
  28.   
  29.   
  30. while(second <= high)  
  31. {  
  32. to[cur++] = from[second++];  
  33. };//若第二个子数组还有未添加的值,添加到目标存储数组的尾部  
  34. }  
  35.   
  36.   
  37. //归并排序  
  38. template <class T>  
  39. void mergeSort(T array[], int num)  
  40. {  
  41. if(num <= 0)  
  42. return;  
  43.   
  44.   
  45. int len = 1;//子数组长度  
  46. int cur = 0;//初始化指针  
  47. T* tempArray = new T[num];//临时存储数组  
  48.   
  49.   
  50. while(len<num)//子数组长度小于num时循环,大于num时排序结束  
  51. {  
  52. while(cur<num)  
  53. {  
  54. int m = minValue<int>(cur + len * 2, num);  
  55. //两个子数组的总长度,考虑越界  
  56. for(int i = cur; i < m;  i++)  
  57. {  
  58. tempArray[i] = array[i];  
  59. }  
  60. //将数值临时存储在临时存储数组中  
  61.   
  62.   
  63. merge(tempArray, array, cur, m - 1, len);  
  64. //合并相邻子数组  
  65.   
  66.   
  67. cur += len * 2;  
  68. //指针指向下一组子数组  
  69. };  
  70. //依次选择相邻的子数组合并  
  71. len *= 2;  
  72. cur = 0;  
  73. //长度翻倍且重置指针  
  74. };  
  75.   
  76. delete tempArray;  
  77. }  


6)冒泡排序:通过依次交换两个相邻的两个数,遍历num个数后便将最大的数推送到了最后。推送第一个数时,我们需要遍历num个数才能推出最大值;推送第i个数时,由于我们已经成功的将i-1个数按序推出,我们只需要遍历num-i+1个数便能推出当前最大的数。

总而言之,时间复杂度为O(n2),而空间复杂度为O(1)。

[cpp]  view plain  copy
  1. template <class T>  
  2. void bubbleSort(T array[], int num)  
  3. {  
  4. for(int i = 0; i < num; i++)  
  5. {  
  6. for(int j = 0; j < num - i; j++)  
  7. {  
  8. if(array[j-1] > array[j])  
  9. {  
  10. T temp = array[j-1];  
  11. array[j-1] = array[j];  
  12. array[j] = temp;  
  13. }  
  14. }  
  15. //依次交换相邻的数将最大值推送到数组尾部已排序的数组最前端  
  16. }  
  17. }  



7)快速排序:对冒泡排序法的优化,基本思想也是将两个数交换,但是我们希望尽量将较小的数交换到数组左边,将交大的数交换到数组右边。计算时我们从数组中随机选择一个数作为参考数,定义一个指针从左向右遍历,另一个指针从右向左遍历。第一个指针寻找大于参考数的数,第二个指针寻找小于参考数的数,并将两个数交换。当两个指针相遇时一次循环结束,循环结束时相遇位置的左侧的数均小于参考数,右侧的数均大于参考数。我们可以递归地对左侧和右侧的数组运用快速排序算法。

总而言之,时间复杂度为O(nlogn),而空间复杂度为O(1)。

[cpp]  view plain  copy
  1. template <class T>  
  2. void quick(T array[], int low, int high)  
  3. {  
  4. if(high - low > 10)  
  5. {  
  6. simpleInsertSort(array + low, high - low + 1);  
  7. return;  
  8. }  
  9. //长度小于10时采用简单插入算法  
  10.   
  11.   
  12. T key = array[low];//利用第一个数作为参考数  
  13. int i = low;  
  14. int j = high;  
  15.   
  16.   
  17. while(true)  
  18. {  
  19. for(;array[i] <= key && i < j;i++);//从左向右选择大于参考数的数  
  20. for(;array[j] > key && j > i;j--);//从右向左选择小于等于参考数的数  
  21. if(i == j)  
  22. {  
  23. break;  
  24. }  
  25. //两个指针相遇时这次循环结束  
  26.   
  27.   
  28. T temp = array[i];  
  29. array[i] = array[j];  
  30. array[j] = temp;  
  31. //交换两个指针的数  
  32. };  
  33.   
  34.   
  35. if(i == j)  
  36. {  
  37. if(array[i] > key)  
  38. {  
  39. quick<T>(array, low, i-1);  
  40. quick<T>(array, i, high);  
  41. }  
  42. else  
  43. {  
  44. quick<T>(array, low, i);  
  45. quick<T>(array, i + 1, high);  
  46. }  
  47. }  
  48. //针对左侧右侧循环采用快速循环  
  49. }  
  50.   
  51. //快速排序  
  52. template <class T>  
  53. void quickSort(T array[], int num)  
  54. {  
  55. int low = 0;  
  56. int high = num - 1;  
  57.   
  58.   
  59. quick<T>(array, low, high);  
  60. }  


8)堆排序:堆就是一颗树,分为最小化堆与最大化堆,最小化堆的特点是父节点大于等于子节点。堆的存储可以利用数字完成,位置为0的节点表示更节点。对于父节点n,左右子节点为n*2+1与n*2+2。

对于一个父节点两个子节点的子堆,我们可以分成方便的转化使父节点大于等于子节点。整个堆的整理便是循环递归计算。

总而言之,建堆时间复杂度为O(n),删除、插入等操作时间复杂度为)(logn)。而空间复杂度为O(n)。

[cpp]  view plain  copy
  1. template <class T>  
  2. class Heap  
  3. {  
  4. public:  
  5. Heap(T array[], int num);  
  6. ~Heap();  
  7. T pop();//取出根节点  
  8. void insert(T value);//插入新节点  
  9.   
  10.   
  11. private:  
  12. void buildHeap();//建堆  
  13. void switchHeap(int n);//计算一个父节点两个子节点的子堆  
  14. T* data;//数据空间  
  15. int len;//已有数据长度  
  16. int maxLen;//最大可以容纳数据长度  
  17. };  
  18.   
  19. template <class T>  
  20. Heap<T>::Heap(T array[], int num)  
  21. {  
  22. maxLen = num * 2;  
  23. data = new T[maxLen];  
  24. len = num;  
  25. //定义数据,记录已有长度,并预留相同的空间用于堆增长  
  26. for(int i = 0; i < num; i++)  
  27. {  
  28. data[i] = array[i];  
  29. }  
  30. //初始化已有数据  
  31. for(int i = num; i < num * 2; i++)  
  32. {  
  33. data[i] = array[i];  
  34. }  
  35. //初始化预留数据  
  36. buildHeap();  
  37. //建堆  
  38. }  
  39.   
  40.   
  41. template <class T>  
  42. Heap<T>::~Heap()  
  43. {  
  44. delete data;  
  45. data = null;  
  46. len = 0;  
  47. }  
  48.   
  49.   
  50. template <class T>  
  51. void Heap<T>::buildHeap()  
  52. {  
  53. int level = floor(log(len)/log(2)) + 1;  
  54. //根据长度计算树的高度,高度为l的树共2的l次方-1个数据  
  55. for(int i = pow(2, level) - 2; i >= 0; i--)  
  56. {  
  57. switchHeap(i);  
  58. }  
  59. //从树倒数第二层,叶节点的父节点向根节点循环,置换每个三节点子堆  
  60. }  
  61.   
  62.   
  63. template <class T>  
  64. void Heap<T>::switchHeap(int n)  
  65. {  
  66. if(n * 2 + 2 < len)//包含左右节点时  
  67. {  
  68. int m = data[n * 2 + 1]<data[n * 2 + 2]?n * 2 + 1:n * 2 + 2;  
  69. //找到左右节点中较小值  
  70. if(data[n]>data[m])  
  71. {  
  72. T temp = data[n];  
  73. data[n] = data[m];  
  74. data[m] = temp;  
  75. }  
  76. //如果较小值比父节点小,交换  
  77. switchHeap(n * 2 + 1);  
  78. switchHeap(n * 2 + 2);  
  79. //置换子节点中的子堆  
  80. }  
  81. else if(n * 2 + 1 < len)//只包含左节点  
  82. {  
  83. if(data[n]>data[n * 2 + 1])  
  84. {  
  85. T temp = data[n];  
  86. data[n] = data[n * 2 + 1];  
  87. data[n * 2 + 1] = temp;  
  88. }  
  89. //如果子节点比父节点小,交换  
  90. switchHeap(n * 2 + 1);  
  91. //置换子节点中的子堆  
  92. }  
  93. //没有子节点不计算  
  94. }  
  95.   
  96.   
  97. template <class T>  
  98. T Heap<T>::pop()  
  99. {  
  100. T temp = data[0];  
  101. //取出根节点  
  102. data[0] = data[len - 1];  
  103. len--;  
  104. //将最后一个数放到根节点  
  105. switchHeap(0);  
  106. //整理根节点  
  107.   
  108.   
  109. return temp;  
  110. }  
  111.   
  112.   
  113. template <class T>  
  114. void Heap<T>::insert(T value)  
  115. {  
  116. if(len == maxLen)//如果长度超过最长可用长度,扩展空间  
  117. {  
  118. maxLen = maxLen * 2;  
  119. T* tmp = new T[maxLen];  
  120. //分配新空间  
  121. for(int i = 0;i<len;i++)  
  122. {  
  123. tmp[i] = data[i];  
  124. }  
  125. //复制数据  
  126. for(int i = len;i<maxLen;i++)  
  127. {  
  128. tmp[i] = data[i];  
  129. }  
  130. //初始化预留数据  
  131. delete data;  
  132. data = tmp;  
  133. tmp = NULL;  
  134. }  
  135. data[len++] = value;  
  136. //将插入值放入数组末尾  
  137.   
  138. for(int i = len - 1;i>=0;i--)  
  139. {  
  140. if(data[(i - 2) / 2]>data[i])  
  141. {  
  142. T temp = data[(i - 2) / 2];  
  143. data[(i - 2) / 2] = data[i];  
  144. data[i] = temp;  
  145. }  
  146. else  
  147. {  
  148. break;  
  149. }  
  150. }  
  151. //沿着树枝将最小值向根节点推送  
  152. }  
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值