当前位置:   article > 正文

堆排序、快速排序、归并排序算法详解_快速排序 归并排序 堆排序

快速排序 归并排序 堆排序

目录

堆排序

堆的概念

实现堆排序

堆排序的时间复杂度:

快速排序

概念

第一种:挖坑法

第二种:左右指针法

 第三种:快慢指针

分治递归实现整体有序

非递归算法实现整体有序(了解)

归并排序


堆排序

堆的概念

学会使用堆排序首先了解堆的概念,堆是一个由数组储存的完全二叉树,它的逻辑结构是一颗二叉树,物理结构是一个数组。且任意子树的root的值是其左孩子和右孩子的值的最大值或最小值。堆分为大堆小堆,大堆:堆顶数据最大,小堆:堆顶数据最小。 

小堆如下所例:

大堆如下:

 

 由于堆是由数组顺序储存所以我们可轻松索引到某个节点。

堆中 父亲和儿子的下标关系为:

leftchild == parent*2 + 1 ;           rightchild == parent*2 + 2 == leftchild + 1 ;

parent == ( child - 1 ) / 2 ;

实现堆排序

第一步是建堆:

自底向上的方式建一个大堆(排升序)或建一个小堆(排降序),从最后一个非叶子节点(最后一个父亲节点)开始调整。

以建大堆为例:

实现原理:向下调整算法,在一任意一颗二叉树中若parent的值比child小,则将两个child中大的数值与parent互换,直到该二叉树中所有子树都满足大堆即可

  1. //交换两个节点的值
  2. void swap(int *p1, int*p2)
  3. {
  4. int tmp = *p1;
  5. *p1 = *p2;
  6. *p2 = tmp;
  7. }
  8. //向下调整算法 实现大堆
  9. void ADjustDown(int *arr, int n, int root)
  10. {
  11. int parent = root;
  12. int child = root * 2 + 1;
  13. while (child < n)
  14. {
  15. if (child+1< n && (arr[child] < arr[child + 1])) //(child+1)注意越界、选出两个child中较大的值
  16. {
  17. child += 1;
  18. }
  19. if (arr[child] > arr[parent])
  20. {
  21. swap(&arr[child], &arr[parent]);
  22. parent = child;
  23. child = parent * 2 + 1;
  24. }
  25. else
  26. {
  27. break;
  28. }
  29. }
  30. }
  31. //实现堆排序
  32. void HeapSort(int *arr, int n)
  33. {
  34. for (int i = (n - 1 - 1) / 2; i >= 0;--i) //自底向上 从最后一个parent节点开始调整,依次往上
  35. {
  36. ADjustDown(arr, n, i);
  37. }//循环走完 建堆完成
  38. //实现排序
  39. int end = n - 1;
  40. while (end > 0)
  41. {
  42. swap(&arr[end], &arr[0]); //将堆顶的数(该数组中最大的数)放到数组的组后一个位置
  43. ADjustDown(arr, end, 0); //继续向下调整,实现大堆
  44. end--;
  45. }
  46. //循环走完,排序完成
  47. }

堆排序的时间复杂度:

建堆的时间复杂度O(n),排序选数的时间复杂度为O(logN),所有最终时间复杂度为N*logN。是一个非常高效的排序算法。

快速排序

概念

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

第一种:挖坑法

 

先从该数列中选出一个基准值(key),并设key值的位置为坑(pivot)

 end先向左走,若找到比key小的值就往(坑)pivot里放,该数挪走了又出现一个新的坑,begin又向右走找到比key大的值就向坑里放,又会出现一个新的坑。循环下去,最终begin<=end时,将key值放到最后一个坑中去,最后实现key左边的都比key小,右边的都比key大,完成单趟排序。

  1. //单趟排序 挖坑法
  2. int PartSort1(int *arr,int left,int right)
  3. {
  4. int begin = left;
  5. int end = right;
  6. int index = GetMidindex(arr, left, right);
  7. swap(&arr[begin], &arr[index]);
  8. int key = arr[begin];
  9. int pivot = begin;
  10. //循环内实现左边小 右边大
  11. while (begin < end)
  12. {
  13. while (begin < end && arr[end] >= key)
  14. {
  15. --end;
  16. }
  17. arr[pivot] = arr[end];
  18. pivot = end;
  19. while (begin < end && arr[begin] <= key)
  20. {
  21. ++begin;
  22. }
  23. arr[pivot] = arr[begin];
  24. pivot = begin;
  25. }
  26. pivot = begin;
  27. arr[pivot] = key;
  28. return pivot;
  29. }

第二种:左右指针法

 思想和上述方法类似,但是不需要设pivot挖坑,采用交换的方式实现左边小右边大。
先从该数列中选出一个基准值的下标(keyi),end先向左走,找比keyi位置的值小的,在begin向右走找到比keyi位置大的值,然后交换begin和end的值,循环下去,begin=end最后将keyi和begin互换,达到同样的效果。

 

  1. //左右指针法 细节缺陷较多
  2. int PartSort2(int *arr, int left ,int right)
  3. {
  4. int begin = left;
  5. int end = right;
  6. int index = GetMidindex(arr, left, right);
  7. swap(&arr[left], &arr[index]);
  8. int keyi = begin;
  9. while (begin < end)
  10. {
  11. //end找小 while (begin<end && arr[end]>=arr[keyi])
  12. {
  13. --end;
  14. }
  15. //begin找大
  16. while (begin<end && arr[begin] <= arr[keyi])
  17. {
  18. ++begin;
  19. }
  20. swap(&arr[begin], &arr[end]); //互换begin和end的值
  21. }
  22. swap(&arr[begin], &arr[keyi]);
  23. return begin;
  24. }

 第三种:快慢指针

 

 最后cur=n-1时,再将prev和keyi互换,完成单趟排序

 

  1. //单趟排序 快慢指针法方便好用!
  2. int PartSort3(int*arr, int left, int right)
  3. {
  4. int prev = left;
  5. int cur = left + 1;
  6. int index = GetMidindex(arr, left, right);
  7. swap(&arr[left], &arr[index]);
  8. int keyi = left;
  9. while (cur <= right)
  10. {
  11. //升序cur找比arr[keyi]小交换,降序cur找大交换
  12. if (arr[cur] < arr[keyi])
  13. {
  14. ++prev;
  15. swap(&arr[prev], &arr[cur]); //通过prev使大的值往后扔
  16. }
  17. ++cur;
  18. }
  19. swap(&arr[prev], &arr[keyi]);
  20. return prev;
  21. }

分治递归实现整体有序

 最后实现整体排序,使用分治递归的思想,当左子区间有序,右子区间有序则该数组有序。将左右子区间继续分割,最终当区间中只有一个值时则该子区间有序。

 

  1. //递归实现
  2. void QuickSort(int*arr, int left,int right)
  3. {
  4. if (left >= right) //区间只有一个值时
  5. return;
  6. int indexkey = PartSort3(arr, left, right);
  7. //以下递归 为分治算法 满足key左区间有序 右区间有序,则该数组有序
  8. QuickSort(arr, left, indexkey - 1);
  9. QuickSort(arr, indexkey + 1, right);
  10. }

非递归算法实现整体有序(了解)

通过数据结构中的栈模拟操作系统中栈帧

  1. //非递归 实现快速排序
  2. void QuickSortNonR(int*arr, int n)
  3. {
  4. Stack st;
  5. StackInit(&st);
  6. StackPush(&st,n-1); //将数组的左右区间 压入栈中
  7. StackPush(&st, 0);
  8. while (!StackEmpty(&st))
  9. {
  10. int left=StackTop(&st);
  11. StackPop(&st);
  12. int right=StackTop(&st);
  13. StackPop(&st);
  14. int indexkey = PartSort2(arr, left, right);
  15. //如果被分子区间元素个数大于2 则继续入栈 出栈 操作
  16. //[left,indexkey-1] [indexkey+1,right]
  17. if (indexkey + 1 < right)
  18. {
  19. StackPush(&st, right);
  20. StackPush(&st, indexkey+1);
  21. }
  22. if (left < indexkey - 1)
  23. {
  24. StackPush(&st,indexkey-1);
  25. StackPush(&st, left);
  26. }
  27. }
  28. StackDestory(&st);
  29. }

归并排序

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

 代码实现

  1. //归并排序
  2. void _MergeSort(int*arr, int left, int right, int*tmp)
  3. {
  4. if (left >= right)
  5. {
  6. return;//该子区间只有一个数时
  7. }
  8. //分治 对半分解数组
  9. int mid = (left + right) >> 1;
  10. _MergeSort(arr, left, mid, tmp);
  11. _MergeSort(arr, mid + 1, right, tmp);
  12. //按升序归并
  13. int index = left;
  14. int begin1 = left, end1 = mid;
  15. int begin2 = mid + 1, end2 = right;
  16. while (begin1 <= end1 && begin2 <= end2)
  17. {
  18. if (arr[begin1] < arr[begin2]) //选大的向临时数组中拷贝
  19. {
  20. tmp[index++] = arr[begin1++];
  21. }
  22. else
  23. {
  24. tmp[index++] = arr[begin2++];
  25. }
  26. }
  27. while (begin1 <= end1)//当右区间归完,左区间还没归完时
  28. {
  29. tmp[index++] = arr[begin1++];
  30. }
  31. while (begin2 <= end2)//当左区间归完,右区间还没归完时
  32. {
  33. tmp[index++] = arr[begin2++];
  34. }
  35. //将临时数组的数据拷贝到原数组
  36. for (int i = 0; i <= right; i++)
  37. {
  38. arr[i] = tmp[i];
  39. }
  40. }
  41. void MergeSort(int*arr, int n)
  42. {
  43. int*tmp = (int*)malloc(sizeof(int)*n); //创建一个临时数组用来存放排好序的数
  44. _MergeSort(arr,0, n-1, tmp);
  45. free(tmp);
  46. }

归并排序的特性总结:
1. 归并的缺点在于需要 O(N) 的空间复杂度,归并排序的思考更多的是解决在磁盘中的外排序问
题。
2. 时间复杂度: O(N*logN)
3. 空间复杂度: O(N)
4. 稳定性:稳定
声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/凡人多烦事01/article/detail/730010
推荐阅读
相关标签
  

闽ICP备14008679号