当前位置:   article > 正文

排序算法——上

排序算法——上

一、冒泡排序

1、冒泡排序算法的思想

我们从左边开始把相邻的两个数两两做比较,当一个元素大于右侧与它相邻的元素时,交换它们之间位置;反之,它们之间的位置不发生变化。冒泡排序是一种稳定的排序算法。

2、代码实现:

  1. void BubbleSort(int* a, int n)
  2. {
  3. for (int i = 1; i < n; i++)
  4. {
  5. for (int j = 0; j < n - i; j++)
  6. {
  7. if (a[j] > a[j + 1])
  8. {
  9. swap(&a[j], &a[j + 1]);
  10. }
  11. }
  12. }
  13. }

冒泡排序需要有两层循环,无论数组是否排好序,都会完成这两层循环,对于最差的情况,比如[9,8,7,6,5,4],对其进行升序排序,这两层循环必不可少;但是对于[9,1,2,3,4,5]这种情况,第一遍循环结束后,整个数组就已经是升序排列的了,但是普通的冒泡排序还会继续进行循环遍历比较,这就对做了不少无用功。所以需要设置一个排序完成的标志,如果排序已经完成,就没必要再继续循环遍历了,直接跳出循环。

优化版本:

  1. void BubbleSort(int* a, int n)
  2. {
  3. for (int i = 1; i < n; i++)
  4. {
  5. int flag = 0;
  6. for (int j = 0; j < n - i; j++)
  7. {
  8. if (a[j] > a[j + 1])
  9. {
  10. swap(&a[j], &a[j + 1]);
  11. flag = 1;
  12. }
  13. }
  14. if (flag == 0)
  15. {
  16. break;
  17. }
  18. }
  19. }

 二、插入排序

1、直接插入排序:

当插入第i(i>=1)个元素时,前面的array[0],array[1],…,array[i-1]已经排好序,此时用array[i]的排序码与array[i-1],array[i-2],…的排序码顺序进行比较,找到插入位置即将array[i]插入,原来位置上的元素顺序后移。

直接插入排序的特性总结:
1. 元素集合越接近有序,直接插入排序算法的时间效率越高
2. 时间复杂度:O(N^2)
3. 空间复杂度:O(1),它是一种稳定的排序算法
4. 稳定性:稳定

单趟:

  1. int end = i;
  2. int tmp = a[i + 1];
  3. while (end >= 0)
  4. {
  5. if (a[end] > tmp)
  6. {
  7. a[end+1] = a[end];
  8. end--;
  9. }
  10. else
  11. {
  12. break;
  13. }
  14. a[end+1] = tmp;
  15. }

这里有些同学可能会这样去写:

  1. int end = i;
  2. int tmp = a[i + 1];
  3. while (end >= 0)//如果写为>0则最后一个数据比较不了。
  4. {
  5. if (a[end] > tmp)
  6. {
  7. a[end+1] = a[end];
  8. end--;
  9. }
  10. }
  11. //最后出去时end为-1
  12. a[end+1] = tmp;

其实这样会造成不必要的比较,因为前面的数据已经排好序,所以当if条件不满足时,一定排好了序,只需将后一个数据放在end位置即可;

很多人会这么写:

  1. for (int i = 0; i < n ; i++)
  2. {
  3. int end = i;
  4. int tmp = a[i + 1];
  5. while (end >= 0)
  6. {
  7. if (a[end] > tmp)
  8. {
  9. a[end+1] = a[end];
  10. end--;
  11. }
  12. else
  13. {
  14. break;
  15. }
  16. a[end] = tmp;
  17. }
  18. }

但是实际上这样写会造成数组越界访问,[0, n-2]是最后一组[0,end]有序 end+1位置的值插入[0,end],保持有序

  1. void InsertSort(int* a, int n)
  2. {
  3. for (int i = 0; i < n - 1; i++)
  4. {
  5. // [0, n-2]是最后一组
  6. // [0,end]有序 end+1位置的值插入[0,end],保持有序
  7. int end = i;
  8. int tmp = a[i + 1];
  9. while (end >= 0)
  10. {
  11. if (a[end] > tmp)
  12. {
  13. a[end+1] = a[end];
  14. end--;
  15. }
  16. else
  17. {
  18. break;
  19. }
  20. a[end+1] = tmp;
  21. }
  22. }
  23. }

 2、希尔排序

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

 我们先尝试第一趟中红色组的排序:

  1. //对红色组
  2. int gap = 5;
  3. for (int i = 0; i < n - gap; i+=gap)
  4. {
  5. int end = i;
  6. int tmp = a[i + gap];
  7. while (end >= 0)
  8. {
  9. if (tmp < a[end])
  10. {
  11. a[end + gap] = a[end];
  12. end -= gap;
  13. }
  14. else
  15. {
  16. break;
  17. }
  18. a[end+gap] = tmp;
  19. }
  20. }

同理不可写为<n,因为可能造成数组越界访问。(我们不难发现其是插入排序的一种变形);

那么我们对第一趟排序就非常好写了:

1、分组排序

  1. int gap = 3;
  2. for (int j = 0; j < gap; j++)
  3. {
  4. for (size_t i = j; i < n - gap; i += gap)
  5. {
  6. int end = i;
  7. int tmp = a[end + gap];
  8. while (end >= 0)
  9. {
  10. if (tmp < a[end])
  11. {
  12. a[end + gap] = a[end];
  13. end -= gap;
  14. }
  15. else
  16. {
  17. break;
  18. }
  19. }
  20. a[end + gap] = tmp;
  21. }
  22. }

2、同时排序:

  1. int gap = 5;
  2. for (int i = j; i < n - gap; i ++)
  3. {
  4. int end = i;
  5. int tmp = a[end + gap];
  6. while (end >= 0)
  7. {
  8. if (tmp < a[end])
  9. {
  10. a[end + gap] = a[end];
  11. end -= gap;
  12. }
  13. else
  14. {
  15. break;
  16. }
  17. }
  18. a[end + gap] = tmp;
  19. }

虽然两个循环层数不一样但是效率是一模一样的(时间复杂度不能单看循环层数判断)。

我们知道:

gap > 1时是预排序

gap == 1时是插入排序

所以我们要想办法通过调整gap来实现插入排序:

所以每次调整为gap/3+1。

  1. void ShellSort(int* a, int n)
  2. {
  3. int gap = n;
  4. while (gap > 1)//这不可写为》=1,因为如果为1,则最后一次进去还是1,相当于多排了一次,浪费
  5. {
  6. gap = gap / 3 + 1;
  7. for (int i = j; i < n - gap; i++)
  8. {
  9. int end = i;
  10. int tmp = a[end + gap];
  11. while (end >= 0)
  12. {
  13. if (tmp < a[end])
  14. {
  15. a[end + gap] = a[end];
  16. end -= gap;
  17. }
  18. else
  19. {
  20. break;
  21. }
  22. }
  23. a[end + gap] = tmp;
  24. }
  25. }
  26. }

3、希尔排序时间复杂度的简单推理:

三、选择排序

选择排序( Selection sort)是一种简单直观的排序算法。它的工作原理是每一趟从待排序的数据元素中选出最小(或最大)的一个元素,顺序放在已排好序的数列的最后,直到全部待排序的数据元素排完。

   选择排序算法通过选择和交换来实现排序,其排序流程如下:

(1)首先从原始数组中选择最小的1个数据,将其和位于第1个位置的数据交换。
(2)接着从剩下的n-1个数据中选择次小的1个元素,将其和第2个位置的数据交换
(3)然后,这样不断重复,直到最后两个数据完成交换。最后,便完成了对原始数组的从小到大的排序。
假如给定初始数据:(118,101,105,127,112)

一次排序:101,118,105,127,112

二次排序:101,105,118,127,112

三次排序:101,105,112,127,118

四次排序:101,105,112,118,127

(绿色为交换的数据)

每一趟在n-i+1(i=1,2,...,n-1)个记录中选取关键字最小的记录作为有序序列中第i个记录。具体来说,假设长度为n的数组arr,要按照从小到大排序,那么先从n个数字中找到最小值min1,如果最小值min1的位置不在数组的最左端(也就是min1不等于arr[0]),则将最小值min1和arr[0]交换,接着在剩下的n-1个数字中找到最小值min2,如果最小值min2不等于arr[1],则交换这两个数字,依次类推,直到数组arr有序排列。算法的时间复杂度为O(n^2)。

  1. void SelectSort(int* a, int n)
  2. {
  3. int begin = 0;
  4. int end = n - 1;
  5. while (begin < end)
  6. {
  7. int min = begin;
  8. int max = begin;
  9. for (int i = 0; i < n; i++)
  10. {
  11. if (a[i] < a[min])
  12. {
  13. min = i;
  14. }
  15. if (a[i] > a[max])
  16. {
  17. max = i;
  18. }
  19. swap(&a[i], &a[min]);
  20. swap(&a[i], &a[max]);
  21. begin++;
  22. end--;
  23. }
  24. }
  25. }

这么写可能会导致对于些数据无法排序;具体原因如下:

即如果先交换最小值会导致,maxi可能指向的最大值移走,使得maxi指向的并不是最大值(先换最大值也可能有类似情况发生)。

所以出现入上情况时我们要经行判断:
如果maxi等于初始位置,那么真正的最大值为mini指向的位置(因为mini会将最小值放前面);

调整代码如下:

  1. void SelectSort(int* a, int n)
  2. {
  3. int begin = 0;
  4. int end = n - 1;
  5. while (begin < end)
  6. {
  7. int min = begin;
  8. int max = begin;
  9. for (int i = 0; i < n; i++)
  10. {
  11. if (a[i] < a[min])
  12. {
  13. min = i;
  14. }
  15. if (a[i] > a[max])
  16. {
  17. max = i;
  18. }
  19. swap(&a[i], &a[min]);
  20. if (max == begin)
  21. {
  22. max = min;
  23. }
  24. swap(&a[i], &a[max]);
  25. begin++;
  26. end--;
  27. }
  28. }
  29. }


 

四、堆排序

如果不了解可以去看我写过关于堆的博客,里面有详细介绍:
http://t.csdnimg.cn/VGbOO

  1. void AdjustDown(int* a, int n, int parent)
  2. {
  3. // 先假设左孩子小
  4. int child = parent * 2 + 1;
  5. while (child < n) // child >= n说明孩子不存在,调整到叶子了
  6. {
  7. // 找出小的那个孩子
  8. if (child + 1 < n && a[child + 1] > a[child])
  9. {
  10. ++child;
  11. }
  12. if (a[child] > a[parent])
  13. {
  14. Swap(&a[child], &a[parent]);
  15. parent = child;
  16. child = parent * 2 + 1;
  17. }
  18. else
  19. {
  20. break;
  21. }
  22. }
  23. }
  24. void HeapSort(int* a, int n)
  25. {
  26. // 向下调整建堆 O(N)
  27. for (int i = (n - 1 - 1) / 2; i >= 0; i--)
  28. {
  29. AdjustDown(a, n, i);
  30. }
  31. // O(N*logN)
  32. int end = n - 1;
  33. while (end > 0)
  34. {
  35. Swap(&a[0], &a[end]);
  36. AdjustDown(a, end, 0);
  37. --end;
  38. }
  39. }

五、计数排序

思想:计数排序又称为鸽巢原理,是对哈希直接定址法的变形应用。 操作步骤:
1. 统计相同元素出现次数
2. 根据统计的结果将序列回收到原来的序列中

代码实现;

  1. void countsort(int* a, int n)
  2. {
  3. int max = a[0];
  4. int min = a[0];
  5. for (int i = 0; i < n; i++)//找到最大最小值,确定开辟空间
  6. {
  7. if (a[i] > max)
  8. {
  9. max = a[i];
  10. }
  11. if (a[i] < min)
  12. {
  13. min = a[i];
  14. }
  15. }
  16. int* tmp = (int*)calloc(max - min + 1, sizeof(int));
  17. if (tmp == NULL)
  18. {
  19. perror("calloc fail");
  20. return;
  21. }
  22. for (int j = 0; j < n; j++)//计数
  23. {
  24. tmp[a[j] - min]++;
  25. }
  26. // 排序
  27. int j = 0;
  28. for (int i = 0; i < max-min+1; i++)
  29. {
  30. while (tmp[i]--)
  31. {
  32. a[j++] = i + min;
  33. }
  34. }
  35. free(tmp);
  36. }

 六、总结:

 

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

闽ICP备14008679号