一头扎进算法导论-堆排序

深碍√TFBOYSˉ_ 2022-07-15 18:46 261阅读 0赞

定义:堆排序(Heapsort)是指利用堆积树(堆)这种数据结构所设计的一种排序算法,它是选择排序的一种。可以利用数组的特点快速定位指定索引的元素。堆分为大根堆和小根堆,是完全二叉树。大根堆的要求是每个节点的值都不大于其父节点的值,即A[PARENT[i]] >= A[i]。在数组的非降序排序中,需要使用的就是大根堆,因为根据大根堆的要求可知,最大的值一定在堆顶。

过程:
这里写图片描述

用自己的话说:
分成两个步骤:
1.构造最大堆(最小堆),即把原来数组的值假想放到一个二叉堆里面去,每次放置值的时都要和自己结点的父亲进行比较,把最大(最小)的值往上排,最后构造成一个最大(最小堆)。特别要注意的一点是在二叉堆中,假设当前结点指针为i,那么父节点的指针计算公式为i/2,但是要注意一点是数组是从0开始计算的,那么就会导致当指针为2的时候计算不正确,所以一定要认识这一个点,下面是我根据概念实现的一段代码。在这段代码里面我为了分辨坐标为0的问题,使用了一个临时数组进行最大堆(最小堆的构建)

  1. public static int[] buildMaxHeap(int[] a) {
  2. int[] temp = new int[a.length + 1];// 新建一个临时数组
  3. for (int i = 0; i < a.length; i++) {
  4. // 对每一个数进行判断
  5. int tid = i + 1;// 当前数对应临时数组的id
  6. int value = a[i];// 当前数值
  7. int pid = tid / 2;// 父结点在临时数组中的值
  8. while (temp[pid] < value && pid > 0) {
  9. // 父结点小于子结点,父结点下滑,且父结点比较大于0
  10. temp[tid] = temp[pid];// 父结点下滑
  11. tid = pid;// 当前插入位置改为原来的父节点位置
  12. pid = pid / 2;// 查现在父结点的父结点
  13. }
  14. temp[tid] = value;
  15. }
  16. return temp;
  17. }

按照上述代码可以更快理解最大堆(小堆)的构建方法,不过这里面产生出了一个临时数组,会导致了性能问题,那么接下来我将对它进行优化

  1. public static int[] goodBuildMaxHeap(int[] a){
  2. for(int i=0;i<a.length;i++){
  3. int value = a[i];//当前值
  4. int pid = (i-1)/2;//求父节点指针,原公式:(i+1)/2-1
  5. while(a[pid]<value&&pid>-1){
  6. //注意这里的父id是可以等于0的
  7. a[i] = a[pid];//父节点下滑
  8. i=pid;//当前插入结点更改为父节点
  9. if(i==0)//插入值为0那么就不用判断了,要加这一句,否则-1/2java会提升为0,造成死循环
  10. break;
  11. pid=(i-1)/2;//再求上一个父结点
  12. }
  13. a[i]=value;//插入值
  14. }
  15. return a;
  16. }

2.进行对最大堆(小堆)排序,是这样的,因为构建最大堆(小堆)完了之后呢,最大的值会在堆顶,也就是数组的第一个元素,那么把他摘除了之后,与数组的尾部的元素进行交换位置,然后记录当前排序个数+1,最后让堆重新进行一次排序,如此循环,就能把排序好的数组整理出来。交换位置完了之后的排序是这样的,每一次把第一个元素与自己的最大子结点数交换位置,直到把最大的数冒出去。同样的要注意java的数组指针问题,计算公式是这样的,左结点l = i*2,右结点r=i*2+1,首先为了方便理解,我假设传进来的数组坐标是从1开始的,a[0]=0

  1. // 清除排序
  2. public static int[] percDown(int[] maxa) {
  3. int l = 0;// 当前排序个数
  4. while (true) {
  5. // 把最大的摘除
  6. int c = 1;
  7. int li = maxa.length - 1 - l;// 最后一个数坐标
  8. int ti = maxa[li];// 最后一个数
  9. if (ti < maxa[c]) {
  10. //如果最后一个数小于第一个数,那么就交换
  11. maxa[li] = maxa[c];// 最后一个数等于最大数
  12. maxa[c] = ti;// 最大数等于最小数
  13. }
  14. l++;// 成功排序一个
  15. li--;// 最后一个数左边右移
  16. int lid = 1 * 2;// 左结点id
  17. int rid = 1 * 2 + 1;// 右结点id
  18. System.out.println("swap before:" + Arrays.toString(maxa));
  19. System.out.println("sort:" + l);
  20. while (lid <= li || rid <= li) {
  21. // 存在子结点,子节点等于最后一个结点位置
  22. int rv = rid <= li ? maxa[rid] : 0;// 右结点的值
  23. int lv = lid <= li ? maxa[lid] : 0;// 左结点的值
  24. if (rv > lv) {
  25. // 右边大,往右边下移
  26. int temp = maxa[c];
  27. maxa[c] = maxa[rid];
  28. maxa[rid] = temp;
  29. c = rid;
  30. rid = rid * 2 + 1;
  31. lid = rid * 2;
  32. } else {
  33. // 左边大,往左边下移
  34. int temp = maxa[c];
  35. maxa[c] = maxa[lid];
  36. maxa[lid] = temp;
  37. c = lid;
  38. rid = lid * 2 + 1;
  39. lid = lid * 2;
  40. }
  41. }
  42. System.out.println("swap end:" + Arrays.toString(maxa));
  43. if (li == 1)
  44. break;
  45. }
  46. return maxa;
  47. }

通过上面代码是我按照对堆排序的思想写出来的,比较方便理解,但是传入的数组毕竟不是从1开始的,所以,同一个道理,对方法进行优化。

  1. // 清除排序
  2. public static int[] goodPercDown(int a[]) {
  3. int l = 0;// 当前排序个数
  4. for (int i = 0; i < a.length; i++) {
  5. // 对每个数进行判断
  6. int fid = 0;// 第一个数的指针
  7. int eid = a.length - 1 - l;// 最后一个数的指针
  8. int fv = a[0];// 第一个数的值
  9. int ev = a[eid];// 最后一个数的值
  10. // 如果第一个数比最后一个数大那么置换
  11. if (fv > ev) {
  12. int t = ev;
  13. a[eid] = a[fid];
  14. a[fid] = t;
  15. }
  16. l++;// 排序个数+1
  17. eid--;// 指针-1
  18. int rid = 2 * fid + 2;// 原公式 (fid+1)*2+1-1
  19. int lid = 2 * fid + 1;// 原公式 (fid+1)*2-1
  20. System.out.println("swap before:" + Arrays.toString(a));
  21. System.out.println("sort:" + l);
  22. while (rid <= eid || lid <= eid) {
  23. // 只要子节点指针不要越界就行
  24. int rv = rid<=eid?a[rid]:0;
  25. int lv = lid<=eid?a[lid]:0;
  26. if(rv>lv){
  27. int t = a[fid];
  28. a[fid] = a[rid];
  29. a[rid] = t;
  30. fid = rid;
  31. rid = 2 * fid +2;
  32. lid = 2 * fid +1;
  33. }else{
  34. int t = a[fid];
  35. a[fid] = a[lid];
  36. a[lid] = t;
  37. fid = lid;
  38. rid = 2 * fid +2;
  39. lid = 2 * fid +1;
  40. }
  41. }
  42. System.out.println("swap end:" + Arrays.toString(a));
  43. }
  44. return a;
  45. }

以上实现均是根据图和算法思想自己写的,能达到排序目的,看书里的代码实在太难懂了,还不如根据自己的理解撸一套出来,这样还更不错,有什么不对希望有大神看到可以指点迷津
复杂度同并归排序
这里写图片描述

发表评论

表情:
评论列表 (有 0 条评论,261人围观)

还没有评论,来说两句吧...

相关阅读

    相关 一头算法导论-冒泡排序

    定义:交换排序的基本思想是,通过比较两个记录键值的大小,如果这两个记录键值的大小出现逆序,则交换这两个记录,这样将键值较小的记录向序列前部移动,键值较大的记录向序列后部移动。假

    相关 一头算法导论-插入排序

    定义:直接插入排序是一种简单的排序方法,她的基本思想是依次将每个记录插入到一个已排好序的有序表中去,从而得到一个新的、记录数增加1的有序表,就好比斗地主抓牌排序的这么一个过程