当前位置:   article > 正文

冒泡/归并/插入/选择排序的稳定性与复杂度_冒泡排序空间复杂度和稳定性

冒泡排序空间复杂度和稳定性

 先看表格,下面慢慢解释。

Bubble Sort——O(n²)——稳定

  1. void bubbleSort(int * a, int n)
  2. {
  3. bool isSorted = false;
  4. for (int i = n - 1; !isSorted && i > 0; --i) // 待排序范围
  5. {
  6. isSorted = true;
  7. for (int j = 0; j < i; ++j) // 从开头到无序的前一个位置
  8. {
  9. if (a[j] > a[j + 1])
  10. {
  11. std::swap(a[j], a[j + 1]);
  12. isSorted = false;
  13. }
  14. }
  15. }
  16. return;
  17. }

冒泡排序,相邻两两交换,每次交换消去一个逆序对。一个序列最多有n²/2个逆序对,所以复杂度是O(n²)。两个相邻的data若一样,则不会被交换。所以冒泡排序是稳定的。

对于一个算法,补充如下思考。(来自参考资料1)

robustenss:应对退化(degeneracy)情况,比如n<=0,上述代码仍可正常返回。

重用性:对于float/char等类型,算法应能推广。见下代码。这是一个成员函数模板。

实际上,stl中的排序算法在algorithm中,独立于容器。是函数模板。

  1. template <typename T>
  2. void Vector<T>::bubbleSort(int lo, int hi)
  3. {
  4. bool isSorted = false;
  5. for (int i = size - 1; !isSorted && i > 0; --i)
  6. {
  7. isSorted = true;
  8. for (int j = 0; j < i; ++j)
  9. {
  10. if (elem[j] > elem[j + 1])
  11. {
  12. std::swap(elem[j], elem[j + 1]);
  13. isSorted = false;
  14. }
  15. }
  16. }
  17. return;
  18. }

此后我们的代码都写为模板的形式,Vector的定义见:

数据结构之vector的实现

Merge Sort——O(nlogn)——稳定

总结:

1. 出于对空间复杂度的考量,我们不是在每次merge时开辟临时数组,而是在归并排序的最外层函数(调整接口的那一层)开辟一个最大的临时数组,然后每次merge或递归时带着这个临时数组的地址作为参数。

2. 左闭右开区间大大的简化了表达式与思维量。

3. 分治法的复杂度分析(递推式)

4. 临时数组来回倒腾可以把already设置成lo

5. 所有排序都是,参数是xxxSort(int lo, int hi), 但我们将其映射从0开始排序,共hi-lo个元素

6.error:不允许使用默认参数 可能原因 ①函数重复②从右向左③声明时指定 非静态成员变量当默认参数虽然不会报这个错,但也不行

7. 提高效率:tmp数组和elem每趟归并从一个倒到另一个,下次再倒回来。

8. 非递归归并映射之后,不能再用merge了,应该再写一个,以newElem为待归并数据,或压根不用映射。

  1. template <typename T>
  2. void Vector<T>::mergeSort_rec(int lo, int hi) //统一接口,递归版归并排序
  3. {
  4. T * tmp = new T[hi];
  5. _mergeSort(lo, hi, tmp);
  6. delete[]tmp;
  7. return;
  8. }
  9. template <typename T>
  10. void Vector<T>::_mergeSort(int lo, int hi, T * tmp)
  11. {
  12. if (hi - lo <= 1) return;
  13. else
  14. {
  15. _mergeSort(lo, (lo + hi) / 2, tmp);
  16. _mergeSort((lo + hi) / 2, hi, tmp);
  17. merge(lo, (lo + hi) / 2, hi, tmp);
  18. }
  19. return;
  20. }
  21. template <typename T>
  22. void Vector<T>::merge(int lo, int mi, int hi, T * tmp)
  23. {
  24. int left = lo, right = mi;
  25. int already = 0;
  26. while (left != mi && right != hi)
  27. {
  28. if (elem[left] <= elem[right])
  29. {
  30. tmp[already] = elem[left];
  31. ++left;
  32. ++already;
  33. }
  34. else
  35. {
  36. tmp[already] = elem[right];
  37. ++right;
  38. ++already;
  39. }
  40. }
  41. while (left != mi) tmp[already++] = elem[left++];
  42. while (right != hi) tmp[already++] = elem[right++];
  43. for (int i = lo; i < hi; ++i)
  44. {
  45. elem[i] = tmp[i - lo];
  46. }
  47. return;
  48. }
  49. template <typename T>
  50. void Vector<T>::mergeSort_nonrec(int lo, int hi) //非递归版归并排序
  51. {
  52. for (int i = 0; i < size; ++i) std::cout << elem[i] << " ";
  53. std::cout << std::endl;
  54. // 共 n 个元素
  55. int n = hi - lo;
  56. // 归并用的临时数组
  57. T * tmp = new T[hi];
  58. //最长有序区间长度
  59. int length = 1;
  60. //归并后的有序区间长度(不算尾巴)
  61. int doublelength = 2;
  62. for (; length < n; length *= 2, doublelength *= 2) //每一躺归并
  63. {
  64. for (int i = 0; (i+1)*doublelength <= n; ++i)
  65. {
  66. merge(i*doublelength+lo, i*doublelength + length+lo, (i + 1)*doublelength+lo, tmp); //加lo是取消映射
  67. }
  68. // 尾巴
  69. if (n % doublelength > length) merge(n/doublelength*doublelength + lo, n / doublelength * doublelength+length + lo, n + lo, tmp);
  70. for (int i = 0; i < size; ++i) std::cout << elem[i] << " ";
  71. std::cout << std::endl;
  72. }
  73. delete[]tmp;
  74. return;
  75. }

选择排序——O(n²)——稳定/不稳定?

稳定与否,取决于怎么移动元素。

每次选出最值,只一次交换,那就不稳定。

eg.

A 80 B 80 C 70 升序排列

第一趟 选出A,swap(A, C) 变为 C 70 B 80 A 80 

第二趟 选出B,序列不变,还是 C 70 B 80 A 80 排序完成

所以不稳定

有人说把大于号变成大于等于号,这样虽然在前缀中选出的最大值是最靠右的那一个,但是swap将原本最末位置的元素提到了前面,破坏了原有顺序,请看下例

A 90 B 80 C 70 D 20 E 20

第一趟选出B(因为用大于等于号,所以选出的是B不是A)E20 B 80 C 70 D 20 A 90

注意这样E就跑到D左边去了

第二趟:E20 D 20 C 70 B 80 A 90

第三趟、第四趟都是E20 D 20 C 70 B 80 A 90

还是不稳定

(来自我的知乎回答

基于链表或将所有元素左移,则稳定。

复杂度是O(n²)还是O(nlogn)受限于每趟选出最大值的方法,若用堆,则成为堆排序,不稳定,O(nlogn)。

所以背表格是没有用滴。但是“简单选择排序”的算法就是基于数组、扫描选最值、swap 所以是O(n²)且不稳定。

以下代码以基于List的选择排序为例。 数据结构List的定义请看此处

  1. template <typename T>
  2. void List<T>::selectionSort(listNode<T>*p, int n)
  3. {
  4. // 找到有序后缀(注意,初始长度为0)
  5. listNode<T>* pos = p; //一会儿要在pos之前插入
  6. for (int i = 1; i <= n; ++i) pos = pos->succ;
  7. for (int rank = 0; rank < n; ++rank, pos = pos->pred) //有序后缀的长度
  8. {
  9. // 找前缀中最大值
  10. listNode<T> * tmp_max = Max(pos->pred, n - rank);
  11. // 插入后缀最前面
  12. insertBefore(tmp_max->data, pos);
  13. remove(tmp_max);
  14. }
  15. }
  16. // 从p开始往前找n个,这些data之中最大的,且最右的
  17. template <typename T>
  18. listNode<T>* List<T>::Max(listNode<T>* p, int n)
  19. {
  20. listNode<T> * tmp_max = p;
  21. while (n-- && p != head)
  22. {
  23. if (p->data > tmp_max->data) tmp_max = p;
  24. p = p->pred;
  25. }
  26. return tmp_max;
  27. }

插入排序——O(n²)——稳定

  1. template <typename T>
  2. void List<T>::insertionSort(listNode<T>*p, int n)
  3. {
  4. for (int rank = 1; rank <= n; ++rank) //有序前缀的长度
  5. {
  6. listNode<T>* pos = search(p->data, p->pred, rank);
  7. insertAfter(p->data, pos);
  8. //p = p->succ;
  9. p = remove(p->pred);
  10. }
  11. }
参考资料

邓俊辉 数据结构

陈越 数据结构

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/weixin_40725706/article/detail/582056
推荐阅读
相关标签
  

闽ICP备14008679号