当前位置:   article > 正文

数据结构八大排序算法,排序只会冒泡排序?进来我教你

只会冒泡排序

1.插入排序

基本思想:
把待排序的记录按其关键码值的大小逐个插入到一个已经排好序的有序序列中,直到所有的记录插入完为 止,得到一个新的有序序列

代码实现:

  1. //直接插入排序
  2. void insert_sort(int* a, int k)
  3. {
  4. for (int i = 0; i < k - 1; i++)
  5. {
  6. int end = i;
  7. int tmp = a[end + 1];
  8. while (end >= 0)
  9. {
  10. if (a[end] > tmp)
  11. {
  12. //将前一个数覆盖后一个数
  13. a[end + 1] = a[end];
  14. end--;
  15. }
  16. else
  17. {
  18. break;
  19. }
  20. //将tmp插入到大于或者小于的那个数的后面
  21. a[end + 1] = tmp;
  22. }
  23. }
  24. }
直接插入排序总结:
1. 元素集合越接近有序,直接插入排序算法的时间效率越高
2. 时间复杂度: O(N^2)
3. 空间复杂度: O(1) ,它是一种稳定的排序算法
4. 稳定性:稳定

2.希尔排序 

希尔排序法又称缩小增量法。希尔排序法的基本思想是:先选定一个整数,把待排序文件中所有记录分成个组,所有距离为的记录分在同一组内,并对每一组内的记录进行排序。然后,取,重复上述分组和排序的工作。当到达=1时,所有记录在统一组内排好序

从下图可以看出:

gap越大时,大的数更快的跳到后面,但是数据更不接近有序。

gap越小时,大的数跳到后面更慢,但是数据更接近有序。

 排序原理如下:

 代码实现:

  1. void shell_sort(int* a, int k)
  2. {
  3. int gap = k;
  4. while (gap > 1)
  5. {
  6. gap = gap / 3 + 1;
  7. //确定排序次数
  8. for (int i = 0; i < gap; i++)
  9. {
  10. for (int j = i; j < k - gap; j+=gap)
  11. {
  12. int end = j;
  13. int tmp = a[end + gap];
  14. while (end >= 0)
  15. {
  16. if (a[end] > tmp)
  17. {
  18. a[end + gap] = a[end];
  19. end -= gap;
  20. }
  21. else
  22. {
  23. break;
  24. }
  25. }
  26. a[end + gap] = tmp;
  27. }
  28. }
  29. }
  30. //优化方案
  31. int gap = k;
  32. while (gap > 1)
  33. {
  34. gap = gap / 3 + 1;
  35. for (int i = 0; i < k - gap; i++)
  36. {
  37. int end = i;
  38. int tmp = a[end + gap];
  39. while (end >= 0)
  40. {
  41. if (tmp < a[end])
  42. {
  43. a[end + gap] = a[end];
  44. end -= gap;
  45. }
  46. else
  47. {
  48. break;
  49. }
  50. }
  51. a[end + gap] = tmp;
  52. }
  53. }
  54. }

希尔排序特性归纳总结:

1. 希尔排序是对直接插入排序的优化。
2. gap > 1 时都是预排序,目的是让数组更接近于有序。当 gap == 1 时,数组已经接近有序的了,这样就会很快。这样整体而言,可以达到优化的效果。我们实现后可以进行性能测试的对比。
3. 希尔排序的时间复杂度不好计算,因为 gap 的取值方法很多,导致很难去计算,因此在好些树中给出的希尔排序的时间复杂度都不固定。
4. 稳定性:不稳定
3.选择排序
2.2.1 基本思想:
每一次从待排序的数据元素中选出最小(或最大)的一个元素,存放在序列的起始位置,直到全部待排序的数据元素排完 。

将上述选择排序进行优化,依次选出最大值和最小值依次和第一个位置和最后一个位置交换,具体思想如下图:

代码如下:

  1. void select_sort(int* a, int k)
  2. {
  3. int begin = 0, end = k - 1;
  4. while (begin < end)
  5. {
  6. //假设最小值下标和最大值下标为begin位置
  7. int mini = begin, maxi = begin;
  8. for (int i = begin+1; i <= end; i++)
  9. {
  10. if (a[i] < a[mini])
  11. mini = i;
  12. if (a[i] > a[maxi])
  13. maxi = i;
  14. }
  15. swap(a[mini], a[begin]);
  16. //最大值下标在begin位置,但是begin位置的值被最小换走了
  17. //所以最大值就在mini下标的位置
  18. if (a[maxi]==a[begin])
  19. maxi = mini;
  20. swap(a[maxi], a[end]);
  21. begin++;
  22. end--;
  23. }
  24. }

 4.堆排序

堆排序 (Heapsort) 是指利用堆积树(堆)这种数据结构所设计的一种排序算法,它是选择排序的一种。它是通过堆来进行选择数据。需要注意的是排升序要建大堆,排降序建小堆。(如果堆排序有疑问请看我的另外一篇博客,有很详细的堆思想和图解)
  1. void adjust_down(int* a, int parent,int n)
  2. {
  3. int child = parent * 2 + 1;
  4. while (child < n)
  5. {
  6. //找最大孩子或者最小孩子
  7. if (child + 1 < n && a[child + 1] > a[child])
  8. child++;
  9. if (a[child] > a[parent])
  10. {
  11. swap(a[child], a[parent]);
  12. parent = child;
  13. child = parent * 2 + 1;
  14. }
  15. else
  16. {
  17. break;
  18. }
  19. }
  20. }
  21. //堆排序
  22. void heap_sort(int* a, int k)
  23. {
  24. //向下调整法建堆 时间复杂度O(n)
  25. for (int i =(k-1-1)/2; i >=0; i--)
  26. {
  27. adjust_down(a, i, k);
  28. }
  29. //排序
  30. for (int i = k - 1; i >= 1; i--)
  31. {
  32. swap(a[0], a[i]);
  33. adjust_down(a, 0, i);
  34. }
  35. }

堆排序的特性总结:

1. 堆排序使用堆来选数,效率就高了很多。
2. 时间复杂度: O(N*logN)
3. 空间复杂度: O(1)
4. 稳定性:不稳定

5.冒泡排序

基本思想:所谓交换,就是根据序列中两个记录键值的比较结果来对换这两个记录在序列中的位置,冒泡排序的特点是:将键值较大的记录向序列的尾部移动,键值较小的记录向序列的前部移动。

代码如下:

  1. //冒泡排序
  2. void bubble_sort(int* a, int k)
  3. {
  4. for (int i = 0; i < k - 1; i++)
  5. {
  6. //如果下面没有交换证明已经有序,则中断循环
  7. int exchange = 0;
  8. for (int j = 1; j < k - i; j++)
  9. {
  10. if (a[j - 1] > a[j])
  11. {
  12. swap(a[j - 1], a[j]);
  13. exchange = 1;
  14. }
  15. }
  16. if (exchange == 0)
  17. {
  18. break;
  19. }
  20. }
  21. }
冒泡排序的特性总结:
1. 冒泡排序是一种非常容易理解的排序
2. 时间复杂度:O(N^2)
3. 空间复杂度:O(1)
4. 稳定性:稳定
6.快速排序
6.1递归实现
快速排序是 Hoare 1962 年提出的一种二叉树结构的交换排序方法,其基本思想为: 任取待排序元
素序列中 的某元素作为基准值,按照该排序码将待排序集合分割成两子序列,左子序列中所有元素均小于基准值,右 子序列中所有元素均大于基准值,然后最左右子序列重复该过程,直到所有元素都排列在相应位置上为止
  1. // 假设按照升序对array数组中[left, right)区间中的元素进行排序
  2. void QuickSort(int array[], int left, int right)
  3. {
  4. if(right - left <= 1)
  5. return;
  6. // 按照基准值对array数组的 [left, right)区间中的元素进行划分
  7. int div = partion(array, left, right);
  8. // 划分成功后以div为边界形成了左右两部分 [left, div) 和 [div+1, right)
  9. // 递归排[left, div)
  10. QuickSort(array, left, div);
  11. // 递归排[div+1, right)
  12. QuickSort(array, div+1, right);
  13. }
快速排序常见的三种方式:
a. hoare版本
思想如下图:

代码如下:
  1. //hoare单趟排序
  2. int part_sort1(int* a, int left, int right)
  3. {
  4. int keyi = left;
  5. while (left < right)
  6. {
  7. while (left < right && a[right] >= a[keyi])
  8. {
  9. right--;
  10. }
  11. while (left < right && a[left] <= a[keyi])
  12. {
  13. left++;
  14. }
  15. swap(a[left], a[right]);
  16. }
  17. swap(a[keyi], a[left]);
  18. //找到单趟排序的key位置
  19. return left;
  20. }

b. 挖坑法版本

思想如下:

 代码如下:

  1. //挖坑法,更易理解
  2. int part_sort2(int* a, int left, int right)
  3. {
  4. int pit = left;
  5. int key = a[left];
  6. while (left<right)
  7. {
  8. //找小,找到小于key就停下来,填坑
  9. while (left < right && a[right] >= key)
  10. //注意:a. 每次结束必须判断left和right关系,防止越界非法访问
  11. //b. 必须>=否则就会死循环,栗子:(5,5,5)
  12. {
  13. right--;
  14. }
  15. a[pit] = a[right];
  16. pit = right;
  17. //找大,找到大于key就停下来,填坑
  18. while (left < right && a[left] <= key)
  19. {
  20. left++;
  21. }
  22. a[pit] = a[left];
  23. pit = left;
  24. }
  25. //把第一个数key填到pit位置
  26. a[pit] = key;
  27. return pit;
  28. }

 c. 双指针法

思想如下:

 代码如下:

  1. //双指针法
  2. int part_sort3(int* a, int left, int right)
  3. {
  4. int prev = left;
  5. int cur = left+1;
  6. int key = a[left];
  7. while (cur <=right)
  8. {//找大,遇到小的就停下来交换,遇到大的就往前走
  9. if (a[cur] < key && (++prev) != cur)
  10. {
  11. swap(a[cur], a[prev]);
  12. }
  13. cur++;
  14. }
  15. swap(a[prev], a[left]);
  16. return prev;
  17. }

完整的快速排序实现:分治,排完keyi位置,keyi位置保持不变,再使用递归思想排keyi左右两边,[begin,keyi-1] keyi [keyi+1,end],左边有序右边有序,排序就完成有序。

整趟代码如下:

  1. //快速排序
  2. void quick_sort(int* a, int begin, int end)
  3. {
  4. if (begin >= end)
  5. {
  6. return;
  7. }
  8. //三数取中,保证二分,降低递归层数
  9. int mid = get_mid_index(a, begin, end);
  10. swap(a[mid], a[begin]);
  11. //小区间优化,防止数据太大,递归层次太深,栈溢出
  12. if ((end - begin) >= 10)
  13. {
  14. insert_sort(a+begin,end-begin+1);
  15. }
  16. else
  17. {
  18. //单躺排序
  19. int keyi = part_sort3(a, begin, end);
  20. //左边有序
  21. quick_sort(a, begin, keyi - 1);
  22. //右边有序
  23. quick_sort(a, keyi + 1, end);
  24. }
  25. }

优化快速排序:a.三数取中  b.小区间优化

三数取中代码如下:

  1. int get_mid_index(int* a,int begin, int end)
  2. {
  3. int mid = (begin + end) / 2;
  4. if (a[begin] < a[end])
  5. {
  6. if (a[begin] > a[mid])
  7. return begin;
  8. else if (a[mid] > a[end])
  9. return end;
  10. else
  11. return mid;
  12. }
  13. //a[begin]>a[end]
  14. else
  15. {
  16. if (a[end] > a[mid])
  17. return end;
  18. else if (a[begin] > a[mid])
  19. return mid;
  20. else
  21. return begin;
  22. }
  23. }

6.2 非递归实现

思想如下:

 代码如下:

  1. //用一个栈模拟快速排序非递归实现
  2. void quick_sort_nonR(int* a, int begin, int end)
  3. {
  4. stack<int> s;
  5. s.push(end);
  6. s.push(begin);
  7. while (!s.empty())
  8. {
  9. int begin = s.top();
  10. s.pop();
  11. int end = s.top();
  12. s.pop();
  13. int keyi = part_sort1(a, begin, end);
  14. if (keyi + 1 < end)
  15. {
  16. s.push(end);
  17. s.push(keyi + 1);
  18. }
  19. if (begin < keyi - 1)
  20. {
  21. s.push(keyi - 1);
  22. s.push(begin);
  23. }
  24. }
  25. }
快速排序的特性总结:
快速排序的特性总结:
1. 时间复杂度:O(N*logN)                                                                                                           
2. 空间复杂度:O(logN)                                                                                       
3. 稳定性:不稳定
7.归并排序
7.1递归实现

 

基本思想:

归并排序( MERGE-SORT )是建立在归并操作上的一种有效的排序算法 , 该算法是采用分治法 的一个非常典型的应用。将已有序的子序列合并,得到完全有序的序列;即先使每个子序列有序,再使子序列段间有序。若将两个有序表合并成一个有序表,称为二路归并。 归并排序核心步骤:
代码实现:
  1. //子区间
  2. void _merge_sort(int* a, int begin, int end, int* tmp)
  3. {
  4. //先递归分解,后序
  5. if (begin = end)
  6. {
  7. return;
  8. }
  9. int mid = (end + begin) / 2;
  10. _merge_sort(a, begin, mid, tmp);
  11. _merge_sort(a, mid + 1, end, tmp);
  12. //归并排序
  13. int begin1 = begin, end1 = mid;
  14. int begin2 = mid + 1,end2 = end;
  15. int i = begin1;
  16. //取俩组区间内小的值依次放在a数组和tmp数组下标对应的位置中
  17. //当一个区间内没有数了,则把另外一个区间内的数依次放入tmp数组中
  18. while (begin1 <= end1 && begin2 <= end2)
  19. {
  20. if (a[begin1] < a[begin2])
  21. {
  22. tmp[i++] = a[begin1++];
  23. }
  24. else
  25. {
  26. tmp[i++] = a[begin2++];
  27. }
  28. }
  29. while (begin1 <= end1)
  30. {
  31. tmp[i++]= a[begin1++];
  32. }
  33. while (begin2 <= end2)
  34. {
  35. tmp[i++] = a[begin2++];
  36. }
  37. //拷贝tmp数组内数据回原数组a中
  38. memcpy(a + begin, tmp + begin, sizeof(int)*(end-begin+1));
  39. }
  40. /归并排序
  41. oid merge_sort(int* a, int begin, int end)
  42. {
  43. int* tmp = new int[end - begin + 1];
  44. _merge_sort(a, begin, end,tmp);
  45. }

7.2非递归实现

思想如下:

 但是上述思想只适合于数据个数是2^n,不是2^n的数据会有一定的越界,根据下面代码打印可得:

  1. void merge_sort_nonR(int* a, int begin, int end)
  2. {
  3. int* tmp = new int[end - begin + 1];
  4. int gap = 1;
  5. int n = end - begin + 1;
  6. while (gap < n)
  7. {
  8. cout << "gap->" << gap << " ";
  9. for (int i = 0; i < n; i +=2*gap)
  10. {
  11. int begin1 = i, end1 = gap+i-1;
  12. int begin2 = gap + i, end2 = i+2*gap-1;
  13. printf("[%d,%d]-[%d,%d] ", begin1, end1, begin2, end2);
  14. int j = begin1;
  15. while (begin1 <= end1 && begin2 <= end2)
  16. {
  17. if (a[begin1] < a[begin2])
  18. {
  19. tmp[j++] = a[begin1++];
  20. }
  21. else
  22. {
  23. tmp[j++] = a[begin2++];
  24. }
  25. }
  26. while (begin1 <= end1)
  27. {
  28. tmp[j++] = a[begin1++];
  29. }
  30. while (begin2 <= end2)
  31. {
  32. tmp[j++] = a[begin2++];
  33. }
  34. }
  35. printf("\n");
  36. memcpy(a, tmp, sizeof(int) * (n));
  37. gap *= 2;
  38. }
  39. }

结果如图所示:

正确代码:

  1. void merge_sort_nonR(int* a, int begin, int end)
  2. {
  3. int n = end - begin + 1;
  4. int* tmp = new int[n];
  5. int gap = 1;
  6. while (gap < n)
  7. {
  8. cout << "gap->" << gap << " ";
  9. for (int i = 0; i < n; i +=2*gap)
  10. {
  11. int begin1 = i, end1 = gap+i-1;
  12. int begin2 = gap + i, end2 = i+2*gap-1;
  13. if (end1 >= n)
  14. {
  15. //将end1修回最后一个数的下标位置
  16. end1 = n - 1;
  17. //begin2和end2修成不存在的区间
  18. begin2 = n;
  19. end2 = n - 1;
  20. }
  21. if (begin2 >= n)
  22. {
  23. begin2 = n;
  24. end2 = n - 1;
  25. }
  26. if (end2 >= n)
  27. {
  28. end2 = n - 1;
  29. }
  30. printf("[%d,%d]-[%d,%d] ", begin1, end1, begin2, end2);
  31. int j = begin1;
  32. while (begin1 <= end1 && begin2 <= end2)
  33. {
  34. if (a[begin1] < a[begin2])
  35. {
  36. tmp[j++] = a[begin1++];
  37. }
  38. else
  39. {
  40. tmp[j++] = a[begin2++];
  41. }
  42. }
  43. while (begin1 <= end1)
  44. {
  45. tmp[j++] = a[begin1++];
  46. }
  47. while (begin2 <= end2)
  48. {
  49. tmp[j++] = a[begin2++];
  50. }
  51. //拷贝tmp数组内数据回原数组a中
  52. }
  53. printf("\n");
  54. memcpy(a, tmp, sizeof(int) * (n));
  55. gap *= 2;
  56. }
  57. }

 归并排序的特性总结:

1. 归并的缺点在于需要 O(N) 的空间复杂度,归并排序的思考更多的是解决在磁盘中的外排序问题。
2. 时间复杂度: O(N*logN)
3. 空间复杂度: O(N)
4. 稳定性:稳定


8.计数排序

思想:

1. 统计相同元素出现次数                                                                                                                  2. 根据统计的结果将序列回收到原来的序列中

看图更清晰:
​​​​​​​

 具体实现请看代码:

  1. //计数排序
  2. void count_sort(int* a, int k)
  3. {
  4. //遍历求数组中的最大和最小值
  5. int max = a[0], min = a[0];
  6. for (int i = 1; i < k; i++)
  7. {
  8. if (a[i] < min)
  9. min = a[i];
  10. if (a[i] > max)
  11. max = a[i];
  12. }
  13. //求新开辟计数的数组空间大小
  14. int rage = max - min + 1;
  15. int* tmp = new int[rage];
  16. //初始化tmp中的数据为0
  17. memset(tmp, 0, sizeof(int) * rage);
  18. //在tmp数组中计a数组中数据出现的个数
  19. for (int i = 0; i < k; i++)
  20. {
  21. //相对映射
  22. tmp[a[i] - min]++;
  23. }
  24. //排序,拷贝数据回原数组中
  25. int i = 0;
  26. for (int j = 0; j < rage; j++)
  27. {
  28. while (tmp[j]--)
  29. {
  30. //回写
  31. a[i++] = j + min;
  32. }
  33. }
  34. delete[] tmp;
  35. }

计数排序特性总结:​​​​​​​

1. 计数排序在数据范围集中时,效率很高,但是适用范围及场景有限。
2. 时间复杂度: O(MAX(N, 范围 ))
3. 空间复杂度: O( 范围 )
4.稳定性:稳定​​​​​​​
声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/Gausst松鼠会/article/detail/501267
推荐阅读
相关标签
  

闽ICP备14008679号