数据结构与算法之堆

1. 堆排序

  1. 堆排序是选择排序的一种。【选择排序:每次在待排序元素中选择最大/最小的一个元素,并加入到有序子序列中】

1.1 什么是堆

若n个关键字序列L[1…n]满足下面某一条规则,则称为堆【Heap】

  1. 若满足:L[i] >= L[zi] && L[i] >= L[2i+1],则称为大根堆【大顶堆】
  2. 若满足:L[i] <= L[zi] && L[i] <= L[2i+1],则称为小根堆【小顶堆】

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Cqd7AOs1-1621502582476)(resource/1621344336289.png)]

1.2 二叉树的顺序存储

  1. i 的左孩子【2i】
  2. i 的右孩子【2i+1】
  3. i 的父节点【i/2 向下取整】

在这里插入图片描述

  1. 完全二叉树中共有n个节点:
    1. 判断 i 是否有左孩子【2i <= n】
    2. 判断 i 是否有右孩子【2i+1 <= n】
    3. 判断 i 是叶子节点/分支节点 <==>判断 i 是否有左孩子【i > n/2向下取整】

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-WRmIBFiE-1621502582494)(resource/1621344816773.png)]

1.3 如何建立大根堆

  1. 大根堆:根 >= 左、右
  2. 思路:把所有非叶子结点都检查一遍,看是否都满足大根堆的要求,不符合则进行相应调整。
  3. 在顺序存储二叉树中,非叶子结点编号 i <= n/2向下取整

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-chvF9kCZ-1621502582508)(resource/1621345503957.png)]

/**
 * @author: zipeng Li
 * 2021/5/20  10:17
 */
public class Heap {
    public static void main(String[] args) {
        int[] nums = new int[]{0,53,17,78,9,45,65,87,32};
        Heap heap = new Heap();
        heap.BuildMaxHeap(nums);
        for(int item: nums){
            System.out.print(item + " ");
        }
    }

    /**
     * 在数组 nums 中建立大根堆
     * @param nums
     */
    void BuildMaxHeap(int[] nums){
        for(int i=nums.length/2;i>0;i--){
            heapAdjust(nums, i);
        }
    }

    /**
     * 调整大根堆中 下标为 k 的节点
     * @param nums
     * @param k
     */
    void heapAdjust(int[] nums, int k){
        // 开始调整
        for(int i=2*k; i<nums.length; i*=2){
            // 找到待调整节点的作用子节点的最大值的下标
            if(i+1 < nums.length){
                i = nums[i] < nums[i+1] ? i+1 : i;
            }
            // 与待调整节点比较
            if(nums[i] > nums[k]){
                swap(nums, i, k);
            }
            // 更新待调整节点
            k = i;
        }
    }

    void swap(int[] nums, int i, int j){
        int tmp = nums[i];
        nums[i] = nums[j];
        nums[j] = tmp;
    }
}

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-HovYQAql-1621502582518)(resource/1621479313271.png)]

1.4 基于大根堆进行排序

  1. 每次将堆顶元素与数组中待排序部分最后一个元素交换,后进行堆调整【在上述代码基础上,添加如下方法】
/**
 * 基于大根堆的升序排序
 * @param nums 大根堆
 * @param index 待排序部分最大下标
 */
void maxHeapSort(int[] nums, int index){
  while(index > 1){
    // 交换
    swap(nums,1, index);
    // 下标前移
    index--;
    // 调整
    heapAdjust(nums, 1, index);
  }
}

【测试】

public static void main(String[] args) {
  int[] nums = new int[]{0,53,17,78,9,45,65,87,32};
  Heap heap = new Heap();

  heap.BuildMaxHeap(nums);

  System.out.println("构造大根堆:");
  for(int item: nums){
    System.out.print(item + " ");
  }

  heap.maxHeapSort(nums, nums.length-1);

  System.out.println("\n大根堆排序:");
  for(int item: nums){
    System.out.print(item + " ");
  }
}

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-WnYuADYu-1621502582522)(resource/1621480392159.png)]

1.5 算法效率分析

  1. 时间复杂度:O(nlogn)
  2. 是不稳定排序

1.6 小根堆排序

时间复杂:O(nlogn)

不稳定排序

/**
 * @author: zipeng Li
 * 2021/5/20  11:27
 */
public class MinHeap {
    public static void main(String[] args) {
        int[] nums = new int[]{0,53,17,78,9,45,65,87,32};
        MinHeap minHeap = new MinHeap();
        minHeap.minHeapSort(nums, nums.length-1);

        System.out.println("\n小根堆排序:");
        for (int i = 1; i < nums.length; i++) {
            System.out.print(nums[i] + " ");
        }
    }

    /**
     * 利用小根堆进行降序排序
     * @param nums
     * @param index
     */
    void minHeapSort(int[] nums, int index){
        buildMinHeap(nums);
        while(index > 1){
            swap(nums, 1, index);
            index--;
            heapAdjust(nums, 1, index);
        }
    }

    /**
     * 根据 nums 建立小根堆
     * @param nums
     */
    void buildMinHeap(int[] nums){
        for(int i = nums.length/2; i>0; i--){
            heapAdjust(nums, i, nums.length-1);
        }
    }

    /**
     * 在指定范围内 调整下标为 k 的小根堆
     * @param nums
     * @param k
     * @param maxIndex
     */
    void heapAdjust(int[] nums, int k, int maxIndex){
        for(int i=2*k; i<=maxIndex; i*=2){
            if(i+1 <= maxIndex){
                i = nums[i] > nums[i+1] ? i+1 : i;
            }
            if(nums[k] > nums[i]){
                swap(nums, i, k);
            }
            k = i;
        }
    }

    void swap(int[] nums,int i, int j){
        int tmp = nums[i];
        nums[i] = nums[j];
        nums[j] = tmp;
    }
}

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-M9HZHuBA-1621502582527)(resource/1621498477476.png)]

1.7 堆的插入

新元素先插入到尾部,再与父节点进行比较,假设当前为大根堆,则若父节点比插入节点小,则进行位置互换。如此反复,直到到达根节点或者父节点比插入节点大为止。

/**
 * 大根堆插入,用链表维护大根堆,且链表第一个元素留空
 * @param nums
 * @param item
 */
void insertMaxHeap(LinkedList<Integer> nums, int item){
  // 先插入到尾部
  nums.add(item);
  // 调整
  int index = nums.size()-1;
  for(int i=index/2; i>=1; i/=2){
    if(nums.get(i) < nums.get(index)){
      int tmp = nums.get(i);
      nums.set(i, nums.get(index));
      nums.set(index, tmp);
    }else{
      break;
    }
    index = i;
  }
}

1.8 堆的删除

以大根堆为例子,使用链表尾部元素代替待删除元素,移除表尾元素。并从代替的位置开始进行堆调整。

/**
 * 大根堆删除,用链表维护大根堆,且链表第一个元素留空
 * @param nums
 * @param item
 */
void deleteMaxHeap(LinkedList<Integer> nums, int item){
  // 获取待删除元素位置
  int index = nums.indexOf(item);
  // 替代
  nums.set(index, nums.getLast());
  // 移除
  nums.removeLast();
  // 调整
  for(int i=2*index; i<nums.size();i*=2){
    // 获取下标
    if(i+1< nums.size()){
      i = nums.get(i) < nums.get(i+1) ? i+1 : i;
    }
    // 交换
    int tmp = nums.get(index);
    nums.set(index, nums.get(i));
    nums.set(i, tmp);
    // 更新待调整位置
    index = i;
  }
}

后序

  • 我是一名大三本科生,专业是软件工程【一本】。目前,正在准备找实习以及秋招,意向岗位是Java后端开发工程师。为此,在码云托管了一个项目,以整理我所有所学知识。涉及内容:计算机网络、操作系统、Java基础、主流Java后端框架、设计模式、Web前端框架等内容。欢迎大家访问我的开源项目编程之路
  • 码云地址:https://gitee.com/alizipeng/the-way-of-programming
  • 以上内容均记载在我的开源项目中
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值