• 数据结构--排序


    1. 排序的概念及运用

    1.1 排序的概念

            排序:排序就是使一串记录,按照其中某个或某些关键字的大小,递增或递减的排列起来的操作。

            稳定性:假定在待排序的记录序列中,存在多个具有相同的关键字的记录,若经过排序,这些记录的相对次 序保持不变,即在原序列中,r[i]=r[j],且r[i]在r[j]之前,而在排序后的序列中,r[i]仍在r[j]之前,则称这种排 序算法是稳定的;否则称为不稳定的。

            内部排序:数据元素全部放在内存中的排序。

            外部排序:数据元素太多不能同时放在内存中,根据排序过程的要求不能在内外存之间移动数据的排序。

    1.2 常见的排序算法

            常见的算法有四种:插入排序,选择排序,交换排序,归并排序。

            插入排序:直接插入排序和希尔排序。

            选择排序:选择排序和堆排序。

            交换排序:冒泡排序和快速排序。

            归并排序:归并排序。

    2.常见排序算法的实现

    2.1 插入排序

    2.1.1基本思想

           直接插入排序是一种简单的插入排序法,其基本思想是: 把待排序的记录按其关键码值的大小逐个插入到一个已经排好序的有序序列中,直到所有的记录插入完为 止,得到一个新的有序序列 。 实际中我们玩扑克牌时,就用了插入排序的思想

    2.1.2 直接插入排序

            

            当插入第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. void InsertSort(int *a,int n) {
    2. for (int i = 1; i < n; ++i) {
    3. int tmp = a[i];
    4. int end = i-1;
    5. while (end >= 0) {
    6. if (a[end] > tmp) {
    7. a[end + 1] = a[end];
    8. --end;
    9. }
    10. else {
    11. break;
    12. }
    13. a[end + 1] = tmp;
    14. }
    15. }
    16. }
    2.1.3 希尔排序

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

    希尔排序的特性总结:

            1. 希尔排序是对直接插入排序的优化。

            2. 当gap > 1时都是预排序,目的是让数组更接近于有序。当gap == 1时,数组已经接近有序的了,这样就 会很快。这样整体而言,可以达到优化的效果。我们实现后可以进行性能测试的对比。

            3. 希尔排序的时间复杂度不好计算,因为gap的取值方法很多,导致很难去计算,因此在好些树中给出的 希尔排序的时间复杂度都不固定:

            4. 稳定性:不稳定

    希尔排序代码实现

    1. void ShellSort(int *a,int n) {
    2. /*int gap = 3;
    3. for (int j = 0; j < gap; ++j)
    4. {
    5. for (int i = j; i < n - gap; i += gap)
    6. {
    7. int end = i;
    8. int tmp = a[end + gap];
    9. while (end >= 0)
    10. {
    11. if (tmp < a[end])
    12. {
    13. a[end + gap] = a[end];
    14. end -= gap;
    15. }
    16. else
    17. {
    18. break;
    19. }
    20. }
    21. a[end + gap] = tmp;
    22. }
    23. }*/
    24. // 1、gap > 1 预排序
    25. // 2、gap == 1 直接插入排序
    26. int gap = n;
    27. while (gap > 1) {
    28. gap = gap / 3 + 1;
    29. for (int i = 0; i < n - gap; i++) {
    30. int end = i;
    31. int tmp = a[end+gap];
    32. while (end >= 0) {
    33. if (a[end] > tmp) {
    34. a[end + gap] = a[end];
    35. end -= gap;
    36. }
    37. else {
    38. break;
    39. }
    40. }
    41. a[end + gap] = tmp;
    42. }
    43. }
    44. }

    2.2 选择排序

    2.2.1基本思想:

            每一次从待排序的数据元素中选出最小(或最大)的一个元素,存放在序列的起始位置,直到全部待排序的 数据元素排完 。

    2.2.2 直接选择排序

            在元素集合array[i]--array[n-1]中选择关键码最大(小)的数据元素 若它不是这组元素中的最后一个(第一个)元素,则将它与这组元素中的最后一个(第一个)元素交换 在剩余的array[i]--array[n-2](array[i+1]--array[n-1])集合中,重复上述步骤,直到集合剩余1个元素

    直接选择排序的特性总结:

            1. 直接选择排序思考非常好理解,但是效率不是很好。实际中很少使用

            2. 时间复杂度:O(N^2)

            3. 空间复杂度:O(1)

            4. 稳定性:不稳定

    直接选择排序代码实现

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

            堆排序(Heapsort)是指利用堆积树(堆)这种数据结构所设计的一种排序算法,它是选择排序的一种。它是 通过堆来进行选择数据。需要注意的是排升序要建大堆,排降序建小堆。

    堆排序的特性总结:

            1. 堆排序使用堆来选数,效率就高了很多。

            2. 时间复杂度:O(N*logN)

            3. 空间复杂度:O(1)

            4. 稳定性:不稳定

    堆排序代码实现

    1. void Swap(int* a, int* b) {
    2. int tmp = *a;
    3. *a = *b;
    4. *b = tmp;
    5. }
    6. void AdjustDown(int* a, int size, int root) {
    7. int parent = root;
    8. int child = parent * 2 + 1;
    9. while (child < size) {
    10. if (child + 1 < size && a[child + 1] > a[child]) {
    11. ++child;
    12. }
    13. if (a[child] > a[parent]) {
    14. Swap(&a[child], &a[parent]);
    15. parent = child;
    16. child = parent * 2 + 1;
    17. }
    18. else {
    19. break;
    20. }
    21. }
    22. }
    23. void HeapSort(int* a, int n) {
    24. for (int i = (n-1-1)/2; i >=0; i--) {
    25. AdjustDown(a, n, i);
    26. }
    27. int end = n - 1;
    28. while (end > 0) {
    29. Swap(&a[end], &a[0]);
    30. AdjustDown(a, end, 0);
    31. --end;
    32. }
    33. }

    2.3 交换排序

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

    2.3.1 冒泡排序

    冒泡排序的特性总结:

            1. 冒泡排序是一种非常容易理解的排序

            2. 时间复杂度:O(N^2)

            3. 空间复杂度:O(1)

            4. 稳定性:稳定

    冒泡排序代码实现

    1. void Swap(int* a, int* b) {
    2. int tmp = *a;
    3. *a = *b;
    4. *b = tmp;
    5. }
    6. void BubbleSort(int* a, int n) {
    7. for (int j = 0; j < n; j++) {
    8. for (int i = 1; i < n-j; i++) {
    9. if (a[i - 1] > a[i]) {
    10. Swap(&a[i - 1], &a[i]);
    11. }
    12. }
    13. }
    14. }
    15. void PrintArray(int* a, int n)
    16. {
    17. for (int i = 0; i < n; ++i)
    18. {
    19. printf("%d ", a[i]);
    20. }
    21. printf("\n");
    22. }
    2.3.2 快速排序

            快速排序是Hoare于1962年提出的一种二叉树结构的交换排序方法,其基本思想为:任取待排序元素序列中 的某元素作为基准值,按照该排序码将待排序集合分割成两子序列,左子序列中所有元素均小于基准值,右 子序列中所有元素均大于基准值,然后最左右子序列重复该过程,直到所有元素都排列在相应位置上为止。

            快速排序中按照基准值将区间划分为左右两半部分常见的方式有三种:

    1. hoare版本

    2. 挖坑法

    3. 前后指针法

    快速排序的特性总结:

            1. 快速排序整体的综合性能和使用场景都是比较好的,所以才敢叫快速排序

            2. 时间复杂度:O(N*logN)

            3. 空间复杂度:O(logN)

            4. 稳定性:不稳定

    快速排序代码实现

    1. void Swap(int* a, int* b) {
    2. int tmp = *a;
    3. *a = *b;
    4. *b = tmp;
    5. }
    6. //三数取中
    7. int GetMidIndex(int* a, int left, int right) {
    8. int mid = left + (left + right) / 2;
    9. if (a[left] < a[mid]) {
    10. if (a[mid] < a[right]) {
    11. return mid;
    12. }
    13. else if (a[right] < a[left]) {
    14. return left;
    15. }
    16. else {
    17. return right;
    18. }
    19. }
    20. else {
    21. if (a[mid] > a[right]) {
    22. return mid;
    23. }
    24. else if (a[right] > a[left]) {
    25. return left;
    26. }
    27. else {
    28. return right;
    29. }
    30. }
    31. }
    32. //hore法
    33. int PartSort1(int* a, int left, int right) {
    34. int key = left;
    35. while (left < right) {
    36. while (left < right && a[right] >= a[key]) {
    37. --right;
    38. }
    39. while (left < right && a[left] <= a[key] ) {
    40. ++left;
    41. }
    42. Swap(&a[right], &a[left]);
    43. }
    44. Swap(&a[right], &a[key]);
    45. return left;
    46. }
    47. //挖坑法
    48. int PartSort2(int* a, int left, int right) {
    49. int key = a[left];
    50. int pit = left;
    51. while (left < right) {
    52. // 右边先走,找小
    53. while (left < right && a[right] >= key)
    54. {
    55. --right;
    56. }
    57. a[pit] = a[right];
    58. pit = right;
    59. // 左边走,找大
    60. while (left < right && a[left] <= key)
    61. {
    62. ++left;
    63. }
    64. a[pit] = a[left];
    65. pit = left;
    66. }
    67. a[pit] = key;
    68. return pit;
    69. }
    70. //前后指针法
    71. int PartSort3(int* a, int left, int right) {
    72. //int midi = GetMidIndex(a, left, right);
    73. //Swap(&a[midi], &a[left]);
    74. int key = left;
    75. int cur = left + 1;
    76. int prev = left;
    77. while (cur <= right) {
    78. if (a[key] > a[cur]) {
    79. ++prev;
    80. Swap(&a[prev], &a[cur]);
    81. }
    82. ++cur;
    83. }
    84. Swap(&a[prev], &a[key]);
    85. key = prev;
    86. return key;
    87. }
    88. void QuickSort1(int* a, int begin, int end) {
    89. if (begin >= end) {
    90. return;
    91. }
    92. int key = PartSort3(a, begin, end);
    93. QuickSort1(a, begin, key - 1);
    94. QuickSort1(a, key+1, end);
    95. }
    96. //三路划分
    97. void QuickSort2(int* a, int begin, int end) {
    98. if (begin >= end) {
    99. return;
    100. }
    101. int left = begin;
    102. int right = end;
    103. int cur = left + 1;
    104. int midi = GetMidIndex(a, left, right);
    105. Swap(&a[left], &a[midi]);
    106. int key = a[left];
    107. while (cur <= right) {
    108. if (a[cur] < key) {
    109. Swap(&a[left], &a[cur]);
    110. ++left;
    111. ++cur;
    112. }
    113. else if (a[cur] > key) {
    114. Swap(&a[right], &a[cur]);
    115. --right;
    116. }
    117. else {
    118. ++cur;
    119. }
    120. }
    121. QuickSort2(a, begin, left- 1);
    122. QuickSort2(a, right+1, end);
    123. }
    124. //快速排序非递归方法
    125. void QuickSortNre(int* a, int begin, int end) {
    126. Stack st;
    127. StackInit(&st);
    128. StackPush(&st, begin);
    129. StackPush(&st, end);
    130. while (!StackEmpty(&st)){
    131. int right = StackTop(&st);
    132. StackPop(&st);
    133. int left = StackTop(&st);
    134. StackPop(&st);
    135. int keyi = PartSort3(a, left, right);
    136. if (keyi+1 < right) {
    137. StackPush(&st, keyi+1);
    138. StackPush(&st, right);
    139. }
    140. if (keyi -1 > left) {
    141. StackPush(&st, left);
    142. StackPush(&st, keyi -1);
    143. }
    144. }
    145. StackDestroy(&st);
    146. }

    2.4 归并排序

            基本思想: 归并排序(MERGE-SORT)是建立在归并操作上的一种有效的排序算法,该算法是采用分治法(Divide and Conquer)的一个非常典型的应用。将已有序的子序列合并,得到完全有序的序列;即先使每个子序列有 序,再使子序列段间有序。若将两个有序表合并成一个有序表,称为二路归并。 归并排序核心步骤:

    归并排序的特性总结:

            1. 归并的缺点在于需要O(N)的空间复杂度,归并排序的思考更多的是解决在磁盘中的外排序问题。

            2. 时间复杂度:O(N*logN)

            3. 空间复杂度:O(N)

            4. 稳定性:稳定

    归并排序代码实现

    1. void _MergeSort(int* a, int begin,int end,int *tmp) {
    2. if (begin >= end) {
    3. return;
    4. }
    5. int mid = (begin + end) / 2;
    6. _MergeSort(a,begin,mid,tmp);
    7. _MergeSort(a,mid+1,end,tmp);
    8. int begin1 = begin,end1=mid;
    9. int begin2 = mid + 1, end2 = end;
    10. int index = begin;
    11. while (begin1 <= end1 && begin2 <= end2) {
    12. if (a[begin1] < a[begin2]) {
    13. tmp[index++] = a[begin1++];
    14. }
    15. else {
    16. tmp[index++] = a[begin2++];
    17. }
    18. }
    19. while (begin1 <= end1) {
    20. tmp[index++] = a[begin1++];
    21. }
    22. while (begin2 <= end2) {
    23. tmp[index++] = a[begin2++];
    24. }
    25. memcpy(a + begin, tmp + begin, sizeof(int) * (end - begin + 1));
    26. }
    27. void MergeSort(int* a, int n) {
    28. int* tmp = (int*)malloc(sizeof(int) * n);
    29. if (tmp == NULL) {
    30. perror("malloc fail");
    31. }
    32. _MergeSort(a, 0, n-1, tmp);
    33. free(tmp);
    34. }
    35. 非递归
    36. void MergeSortNonr(int* a, int n) {
    37. int* tmp = (int*)malloc(sizeof(int) * n);
    38. if (tmp == NULL) {
    39. perror("malloc fail");
    40. }
    41. int gap = 1;
    42. while (gap < n) {
    43. for (int i = 0; i < n; i += 2 * gap) {
    44. int begin1 = i, end1 = i + gap - 1;
    45. int begin2 = i + gap, end2 = i + 2 * gap - 1;
    46. if (end1 >= n || begin2 >= n) {
    47. break;
    48. }
    49. if (begin2 < n && end2 >= n) {
    50. end2 = n - 1;
    51. }
    52. int index = i;
    53. while (begin1 <= end1 && begin2 <= end2) {
    54. if (a[begin1] < a[begin2]) {
    55. tmp[index++] = a[begin1++];
    56. }
    57. else {
    58. tmp[index++] = a[begin2++];
    59. }
    60. }
    61. while (begin1 <= end1) {
    62. tmp[index++] = a[begin1++];
    63. }
    64. while (begin2 <= end2) {
    65. tmp[index++] = a[begin2++];
    66. }
    67. memcpy(a + i, tmp + i, sizeof(int) * (end2 - i + 1));
    68. }
    69. gap *= 2;
    70. }
    71. }

    2.5 非比较排序

     计数排序

    思想:

            计数排序又称为鸽巢原理,是对哈希直接定址法的变形应用。 操作步骤:
                    1. 统计相同元素出现次数
                    2. 根据统计的结果将序列回收到原来的序列中
    计数排序的特性总结:
            1. 计数排序在数据范围集中时,效率很高,但是适用范围及场景有限。
            2. 时间复杂度:O(MAX(N,范围))
            3. 空间复杂度:O(范围)
            4. 稳定性:稳定

    代码实现

    1. void CountSort(int* a, int n) {
    2. int min = a[0];
    3. int max = a[0];
    4. for (int i = 1; i < n; i++) {
    5. if (a[i] > max) {
    6. max = a[i];
    7. }
    8. if(a[i]
    9. min = a[i];
    10. }
    11. }
    12. int range = max - min + 1;
    13. int* CountA = (int*)malloc(sizeof(int) * range);
    14. assert(CountA);
    15. memset(CountA, 0, sizeof(int) * range);
    16. for (int i = 0; i < n; i++) {
    17. CountA[a[i] - min]++;
    18. }
    19. int j = 0;
    20. for (int i = 0; i < range; i++) {
    21. while (CountA[i]--) {
    22. a[j++] = i+min;
    23. }
    24. }
    25. }

    3. 排序算法稳定性

    定义

         稳定性:假定在待排序的记录序列中,存在多个具有相同的关键字的记录,若经过排序,这些记录的相对次序保持不变,即在原序列中,r[0] = r[j],且 r[i] 在 r0] 之前,而在排序后的序列中,r[i] 仍在 r[j] 之前,则称这种排序算法是稳定的;否则称为不稳定的。

  • 相关阅读:
    LeetCode 513找树左下角的值 112路径总和113路径总和ii 106从中序与后序遍历序列构造二叉树
    从kafka如何保证数据一致性看通常数据一致性设计
    基于Spring Boot+MyBatis+MySQL的高校试卷管理系统
    云计算技术 — 混合云 — 技术架构
    现状分析:“一云多芯”如何推动信创项目快速部署
    Android 编译插桩操纵字节码
    广州蓝景分享—开发人员都知道的JavaScript技巧
    全国临床遗传学及遗传咨询培训在湘举行,为18省培训百名医师
    Python tkinter -- 第15章 Combobox
    苹果MAC电脑如何清理垃圾加速运行速度?
  • 原文地址:https://blog.csdn.net/weixin_63219391/article/details/136589979