当前位置:   article > 正文

数据结构初阶 —— 常见排序_数据结构排序

数据结构排序

目录

一,直接插入排序

二,希尔排序(又称缩小增量法)

三,直接选择排序

四,堆排序

五,冒泡排序

六,快速排序

七,归并排序

八,计数排序(非比较排序)

九,基数排序(非比较排序)


排序按照其中的某个或某些关键字的大小,递增或递减的排列起来的操作;

稳定性:若存在多个具有相同关键字的对象,经过排序,其相对位置次序保持不变,则称此算法稳定;

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

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

一,直接插入排序

逻辑思路:

  • 把待排序的对象,按其值的大小逐个插入到一个有序序列中(已经排好序),直到所有对象插入完为止,得到一个新的有序序列;
  • 即当插入第i个元素时,其前面元素已为有序序列,在依次与前面元素比较,直到找到插入位置插入为止,原位置元素按顺序后移;

特性:

  • 元素越接近有序,此算法时间效率越高;
  • 时间复杂度,O(N2)
  • 空间复杂度,O(1)
  • 稳定性,稳定;

  1. //直接插入法
  2. void InsertSort(int* arr, int n)
  3. {
  4. int i = 0;
  5. for (i; i < n - 1; i++)
  6. {
  7. //单次插入
  8. int end = i;
  9. int tmp = arr[end + 1];
  10. while (end >= 0)
  11. {
  12. if (arr[end] > tmp)
  13. {
  14. arr[end + 1] = arr[end];
  15. --end;
  16. }
  17. else
  18. break;
  19. }
  20. arr[end + 1] = tmp;
  21. }
  22. }

二,希尔排序(又称缩小增量法)

逻辑思路:

  • 先选定一个整数gap,把待排序文件分成gap个组(距离为gap为一组),并对每个组进行直接插入排序,即预排序(已达到接近有序);
  • 重复上操作,但gap取到1时排序为止;

注:

  • 数据一次挪动gap步,而不是一步,更快;
  • gap越小,挪动后越接近有序,但也会越慢;
  • gap为1时,其实就是直接排序;

特性:

  • 希尔排序时对直接插入排序的优化;
  • 时间复杂度,O(N1.3)O(N1.25)~O(1.6N1.25)
  • 空间复杂度,O(1); 
  • 稳定性,不稳定;

  1. //希尔排序
  2. void ShellSort(int* arr, int n)
  3. {
  4. int gap = n;
  5. //gap>1为预排序,gap=1为直接排序
  6. while (gap > 1)
  7. {
  8. //gap每三倍的逐渐减小,+1保证最后一次排序gap为1
  9. gap = gap / 3 + 1;
  10. //预排序(多组并排)
  11. int i = 0;
  12. for (i; i < n - gap; i++)
  13. {
  14. int end = i;
  15. int tmp = arr[end + gap];
  16. while (end >= 0)
  17. {
  18. if (tmp < arr[end])
  19. {
  20. arr[end + gap] = arr[end];
  21. end -= gap;
  22. }
  23. else
  24. break;
  25. }
  26. arr[end + gap] = tmp;
  27. }
  28. }
  29. }

三,直接选择排序

逻辑思路:

  • 从待排序元素中,选出最大(或最小)的元素,交换到起始位置;
  • 然后在剩余的元素中,重复上述操作,直到剩余一个元素为止;

特性:

  • 此排序思路简单,但效率最差,实际中很少使用;
  • 时间复杂度,O(N2)
  • 空间复杂度,O(1)
  • 稳定性,不稳定;

  1. //直接选择排序
  2. void SelectSort(int* arr, int n)
  3. {
  4. int begin = 0;
  5. int end = n - 1;
  6. while (begin < end)
  7. {
  8. int mini = begin;
  9. int maxi = begin;
  10. for (int i = begin; i <= end; i++)
  11. {
  12. if (arr[i] < arr[mini])
  13. mini = i;
  14. if (arr[i] > arr[maxi])
  15. maxi = i;
  16. }
  17. Swap(arr + begin, arr + mini);
  18. //但首元素即为最大值时,此时值已被交换需校正
  19. if (begin == maxi)
  20. maxi = mini;
  21. Swap(arr + end, arr + maxi);
  22. begin++;
  23. end--;
  24. }
  25. }

四,堆排序

逻辑思路:

  • 利用堆的数据结构进行排序,通过堆进行选择数据,是选择排序的一种;
  • 升序建大堆,降序建小堆;

特性:

  • 时间复杂度,O(NlogN)
  • 空间复杂度,O(1)
  • 稳定性,不稳定;
  1. //建堆排序
  2. void HeapSort(int* arr, int n)
  3. {
  4. //建堆 - O(N)
  5. int i = (n - 1 - 1) / 2; //最后节点的父节点
  6. for (i; i >= 0; i--)
  7. {
  8. AdjustDown(arr, n, i);
  9. }
  10. //排序 - O(N*log(N))
  11. int end = n - 1;
  12. while (end)
  13. {
  14. Swap(arr, arr + end);
  15. AdjustDown(arr, end, 0);
  16. end--;
  17. }
  18. }

五,冒泡排序

逻辑思路:

  • 从首个元素开始,依次比较,将较大的值向后交换;
  • 然后在剩余的元素中,重复上述操作,直到剩余一个元素为止;

特性:

  • 首趟交换将最大值排到最后,次躺将次大值排到倒数第二个;
  • 时间复杂度,O(N2)
  • 空间复杂度,O(1)
  • 稳定性,稳定;

  1. //冒泡排序
  2. void BubbleSort(int* arr, int n)
  3. {
  4. for (int i = 0; i < n - 1; i++)
  5. {
  6. int exchange = 0;
  7. for (int j = 0; j < n - 1 - i; j++)
  8. {
  9. if (arr[j] > arr[j + 1])
  10. {
  11. Swap(arr + j, arr + j + 1);
  12. exchange = 1;
  13. }
  14. }
  15. if (!exchange)
  16. break;
  17. }
  18. }

六,快速排序

一种二叉树结构的交换排序;

逻辑思路:

  • 任取某个元素为基准值(一般是头或尾),将序列分割成两个子序列,左序列所有元素均小于基准值,右子序列中所有元素均大于基准值;
  • 然后左右子序列,重复上述操作,直到所有元素都排序到相应位置为止;

特性:

  • 时间复杂度,O(N2),三数取中法O(NlogN)
  • 空间复杂度,O(NlogN)
  • 稳定性,不稳定;

按基准值将序列划分尾左右子序列方法:

  • 左右指针法(Hoare):
    • 如以左边首个元素为key;
    • 随后最右边right先向左走,遇到比key小的停下;
    • 在最左边left向右走,遇到比key大的停下;
    • 此时左右元素交换;
    • 然后重复操作,直到left等于right为止;
  • 挖坑法
    • 如以左边首个元素为key,并保存此元素;
    • 随后最右边right先向左走,遇到比key小的停下并将此元素填入key位置,此位置在标记为key;
    • 在最左边left向右走,遇到比key大的停下并将此元素填入key位置,此位置在标记为key;
    • 然后重复操作,直到left等于right为止,最后把最初key元素填入最后的key位置即可;
  • 前后指针法
    • 如以左边首个元素为key,此元素标记为前指针prev,下个元素标记为后指针cur;
    • cur先向后走,遇到比key小的停下,prev++,此时交换prev与cur位置的元素;
    • 然后重复操作,直到序列尾为止,最后交换key与prev位置元素即可;
  1. //针对有序序列,时间复杂度为O(N^2),会很大影响效率; 
  2. //三数取中法
  3. int GetMidIndex(int* arr, int left, int right)
  4. {
  5. int mid = left + (right - left) / 2; //(left+right)/2,left+right可能会溢出
  6. if (arr[left] < arr[mid])
  7. {
  8. if (arr[mid] < arr[right])
  9. return mid;
  10. else if (arr[left] < arr[right])
  11. return right;
  12. else
  13. return left;
  14. }
  15. else
  16. {
  17. if (arr[mid] > arr[right])
  18. return mid;
  19. else if (arr[left] < arr[right])
  20. return left;
  21. else
  22. return right;
  23. }
  24. }
  1. //左右指针法
  2. int PartSort(int* arr, int left, int right)
  3. {
  4. int midi = GetMidIndex(arr, left, right);
  5. Swap(arr + left, arr + midi);
  6. int keyi = left;
  7. while (left < right)
  8. {
  9. while (left < right && arr[right] >= arr[keyi])
  10. right--;
  11. while (left < right && arr[left] <= arr[keyi])
  12. left++;
  13. Swap(arr + left, arr + right);
  14. }
  15. Swap(arr + keyi, arr + left);
  16. return left;
  17. }
  1. //挖坑法
  2. int PartSort(int* arr, int left, int right)
  3. {
  4. //三数取中
  5. int midi = GetMidIndex(arr, left, right);
  6. Swap(arr + left, arr + midi);
  7. int keyi = left;
  8. int key = arr[keyi];
  9. while (left < right)
  10. {
  11. while (left < right && arr[right] >= key)
  12. right--;
  13. arr[keyi] = arr[right];
  14. keyi = right;
  15. while (left < right && arr[left] <= key)
  16. left++;
  17. arr[keyi] = arr[left];
  18. keyi = left;
  19. }
  20. arr[keyi] = key;
  21. return keyi;
  22. }
  1. //前后指针法
  2. int PartSort(int* arr, int left, int right)
  3. {
  4. //三数取中
  5. int midi = GetMidIndex(arr, left, right);
  6. Swap(arr + left, arr + midi);
  7. int keyi = left;
  8. int prev = left;
  9. int cur = left + 1;
  10. while (cur <= right)
  11. {
  12. if (arr[cur] < arr[keyi] && ++prev != cur)
  13. Swap(arr + prev, arr + cur);
  14. cur++;
  15. }
  16. Swap(arr + prev, arr + keyi);
  17. return prev;
  18. }
  1. //快速排序
  2. void QuickSort(int* arr, int left, int right)
  3. {
  4. if (left < right)
  5. {
  6. int keyi = PartSort(arr, left, right);
  7. QuickSort(arr, left, keyi - 1);
  8. QuickSort(arr, keyi + 1, right);
  9. }
  10. }

快速排序非递归 

逻辑思路:

  • 递归方法为,每躺排序后,获得左右子序列区间,然后子序列在排;
  • 非递归方法,即通过栈记录每躺排序后的子区间(入栈),然后在取此子区间排序,在记录此排序后的子区间(入栈);

注:

  • 递归太深,会容易导致栈溢出;
  • 循环或栈+循环;
  1. //快速排序非递归 
  2. void QuickSortNonR(int* arr, int left, int right)
  3. {
  4. Stack st; //创建栈st
  5. StackInit(&st); //初始化栈
  6. StackPush(&st, left); //记录区间(入栈)
  7. StackPush(&st, right); //记录区间(入栈)
  8. while (!StackEmpty(&st))
  9. {
  10. right = StackTop(&st);
  11. StackPop(&st);
  12. left = StackTop(&st);
  13. StackPop(&st);
  14. int keyi = PartSort(arr, left, right);
  15. if (keyi + 1 < right)
  16. {
  17. StackPush(&st, right);
  18. StackPush(&st, keyi + 1);
  19. }
  20. if (left < keyi - 1)
  21. {
  22. StackPush(&st, keyi - 1);
  23. StackPush(&st, left);
  24. }
  25. }
  26. StackDestroy(&st);
  27. }

七,归并排序

逻辑思路:

  • 原理为如有两有序序列,可直接合并为一个有序序列;
  • 将待排序序列二分为左右两个子序列,如左右子序列不为有序,通过递归分解直到左右有序,在一一归并为有序即可;

特性: 

  • 时间复杂度,O(NlogN)
  • 空间复杂度,O(N)
  • 稳定性,稳定;

  1. //归并排序
  2. void _MergeSort(int* arr, int left, int right, int* tmp)
  3. {
  4. if (left >= right)
  5. return;
  6. int mid = (left + right) / 2;
  7. _MergeSort(arr, left, mid, tmp);
  8. _MergeSort(arr, mid + 1, right, tmp);
  9. //归并
  10. int begin1 = left, end1 = mid;
  11. int begin2 = mid + 1, end2 = right;
  12. //排序
  13. int index = left;
  14. while (begin1 <= end1 && begin2 <= end2)
  15. {
  16. if (arr[begin1] < arr[begin2])
  17. tmp[index++] = arr[begin1++];
  18. else
  19. tmp[index++] = arr[begin2++];
  20. }
  21. while(begin1 <= end1)
  22. tmp[index++] = arr[begin1++];
  23. while (begin2 <= end2)
  24. tmp[index++] = arr[begin2++];
  25. //最后还原到原数组
  26. for (int i = 0; i <= right; i++)
  27. arr[i] = tmp[i];
  28. }
  29. void MergeSort(int* arr, int n)
  30. {
  31. int* tmp = (int*)malloc(sizeof(int) * n);
  32. _MergeSort(arr, 0, n - 1, tmp);
  33. free(tmp);
  34. }

归并排序非递归 

  • 循环,可倒循环;
  1. //归并非递归排序
  2. void MergeSortNonR(int* arr, int n)
  3. {
  4. int* tmp = (int*)malloc(sizeof(int) * n);
  5. int grpNum = 1;
  6. while (grpNum < n)
  7. {
  8. for (int i = 0; i < n; i += grpNum * 2)
  9. {
  10. //归并
  11. int begin1 = i, end1 = i + grpNum - 1;
  12. int begin2 = i + grpNum, end2 = i + grpNum * 2 - 1;
  13. //修正越界,设置为一个无效区间
  14. if (begin2 >= n)
  15. {
  16. begin2 = n + 1;
  17. end2 = n;
  18. }
  19. if (end2 >= n)
  20. end2 = n - 1;
  21. //排序
  22. int index = begin1;
  23. while (begin1 <= end1 && begin2 <= end2)
  24. {
  25. if (arr[begin1] < arr[begin2])
  26. tmp[index++] = arr[begin1++];
  27. else
  28. tmp[index++] = arr[begin2++];
  29. }
  30. while (begin1 <= end1)
  31. tmp[index++] = arr[begin1++];
  32. while (begin2 <= end2)
  33. tmp[index++] = arr[begin2++];
  34. }
  35. //最后还原到原数组
  36. for (int i = 0; i < n; i++)
  37. arr[i] = tmp[i];
  38. grpNum *= 2;
  39. }
  40. }

八,计数排序(非比较排序)

逻辑思路:

  • 统计原数组中,每个元素出现的次数,并记录于count数组内;
  • 排序,遍历count数组,在原数组中对应位置填写该次数个元素;

特性: 

  • 适合数据大小比较集中,且只适合整数排序(浮点和字符串不适合);
  • 时间复杂度,O(max(N,Range))
  • 空间复杂度,O(Range)
  • 稳定性,不稳定;
  1. //计数排序
  2. void CountSort(int* arr, int n)
  3. {
  4. //获取最值
  5. int min = arr[0], max = arr[0];
  6. for (int i = 0; i < n; i++)
  7. {
  8. if (arr[i] < min)
  9. min = arr[i];
  10. if (arr[i] > max)
  11. max = arr[i];
  12. }
  13. //相对映射
  14. int range = max - min + 1;
  15. int* count = (int*)calloc(range, sizeof(int));
  16. //统计次数
  17. for (int i = 0; i < n; i++)
  18. {
  19. count[arr[i] - min]++;
  20. }
  21. //排序,还原数组
  22. int i = 0;
  23. for (int j = 0; j < range; j++)
  24. {
  25. while (count[j]--)
  26. arr[i++] = j + min;
  27. }
  28. }

九,基数排序(非比较排序)

省略!!!

有兴趣可自行搜索!!!

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

闽ICP备14008679号