内容介绍
树结构简介
树结构是计算机中经常使用的一种数据结构。咱们先来看一下生活中的树: java
计算机中的树和生活中的树是相似的,只不过是倒着的,树根在上,树叶在下。树上的每一个组成元素都是一个节点,树根称为根节点
,树枝称为分支节点
,树叶称为叶子节点
,以下图所示: 编程
二叉树结构简介
二叉树是:每一个节点最多只能有两个子节点树。二叉树的子节点分为左节点和右节点,以下图:api
满二叉树:若是该二叉树的全部叶子节点都在最后一层,而且节点总数= 2^n -1 , n 为层数,则咱们称为满二叉树,以下图: 数组
彻底二叉树:若是该二叉树的全部叶子节点都在最后一层或者倒数第二层,并且最后一层的叶子节点在左边连续,倒数第二 层的叶子节点在右边连续,咱们称为彻底二叉树,以下图: 微信
咱们这里注意理解彻底二叉树,由于堆结构是一种特殊的彻底二叉树。关于树的结构,咱们先简单介绍,之后会专门讲解树结构,咱们这里主要是讲堆结构,因此先简单说起一下树结构。数据结构
堆结构简介
堆(Heap)是一种特殊的树形数据结构,每一个结点都有一个值。常见的堆有二叉堆、斐波那契堆等,一般咱们所说的堆的数据结构,是指二叉堆。以下图: 动画
堆知足下列两个性质:code
- 堆中某个节点的值老是不大于或不小于其父节点的值。
- 堆老是一棵彻底二叉树。 根节点最大的堆叫作
最大堆
或大根堆
,根节点最小的堆叫作最小堆
或小根堆
,以下图所示:
堆的存储
堆是非线性数据结构,能够使用一维数组来存储,将堆中序号对应的数据放到数组对应的索引中,以下图: blog
堆的一些概念和规律
概念:排序
- 某节点左边的子节点成为:左孩子。
- 某节点右边的子节点成为:右孩子。
- 某节点的上一个节点成为:父节点。
规律:假设当前节点的索引为i
- 父节点索引 = (i - 1) / 2 (Java中除以2取整数,好比7/2 = 3)
- 左孩子索引 = 2 * i + 1
- 右孩子索引 = 2 * i + 2
堆的定义性质:
- 最大堆节点的值大于左右孩子的值,也就是知足:
arr[i] > arr[2*i+1] && arr[i] > arr[2*i+2]
,以下图:
堆获取最大值
获取最大堆的最大值,其实就是获取堆中最前面一个元素。对于堆这种数据结构一般是将最前面的元素和最后面的元素换位置,最大值就到了最后一个位置,而后从堆中排除这个元素,当最后一个元素交换到最前面时,此时就不知足堆的性质了,咱们须要将最前面这个元素经过ShiftDown
(下沉)的手段让堆继续知足堆的规则。
堆获取最大值能够分红两个步骤:
- 将堆中最前面的最大值和最后一个元素交换位置。
- 使用
ShiftDown
让最前面的元素下沉到合适的位置,依然知足堆的性质。 动画演示效果以下:
这里面重点注意,ShiftDown
可让堆中的一个元素下沉到合适的位置,而且知足堆的规则。后面咱们构建堆就须要使用到ShiftDown
操做。
ShiftDown
详细图解:
最大堆的最后一个非叶子节点
- 咱们构建堆时须要从最后一个非叶子节点开始按照规则构建堆,因此咱们须要知道最后一个非叶子节点计算公式:(堆的最大索引-1) / 2。
构建一个堆结构
构建堆实际上是将无序的彻底二叉树调整为二叉堆。非叶子节点没有子节点不须要从新构建,而后自底向上对每个子树执行SiftDown
操做,直到完成二叉堆化。 假设咱们如今有一个数组,内容为:{6, 3, 7, 5, 8, 2, 1, 4, 9},它是不知足堆的规则,咱们如今将这个数组构建成一个二叉堆,步骤为:
- 找到最后一个非叶子节点,使用
ShiftDown
下沉,使这个颗树知足堆的规则。 - 找到倒数第二个非叶子节点,使用
ShiftDown
下沉,使这个颗树知足堆的规则。 - 以此类推,直到找到最前面的一个元素使用
ShiftDown
下沉,使这颗树知足堆的规则。 将无序的彻底二叉树调整为二叉堆的过程称为heapify
,动画以下:
堆添加数据
往堆中插入一个元素,是在数组的最末尾插入新的数据,此时可能不知足堆的特性,咱们须要进行自下而上调整子节点和父节点,不知足堆性质则交换父子元素,直到当前子树知足堆的性质。动画效果以下:
代码以下:
public class Heap { public static void main(String[] args) { int[] arr = {6, 3, 7, 5, 8, 2, 1, 4, 9}; heapify(arr); System.out.println("构建堆后:" + Arrays.toString(arr)); arr = insert(arr, 11); System.out.println("堆中插入数据后:" + Arrays.toString(arr)); } // 往数组中添加一个数据 public static int[] insert(int[] arr, int element) { arr = Arrays.copyOf(arr, arr.length + 1); // 复制以前的数组数据到新数组中 arr[arr.length-1] = element; shiftUp(arr, arr.length-1); return arr; } // 上浮操做,将i索引元素上浮到合适位置,保证知足堆的两个特性 private static void shiftUp(int[] arr, int i) { // (i-1) / 2: 是i的父节点 while ((i-1) / 2 >= 0 && arr[(i-1) / 2] < arr[i]) { swap(arr, (i-1) / 2, i); i = (i-1) / 2; } } // heapify将无序的彻底二叉树调整为二叉堆 private static void heapify(int[] arr) { // 从非叶子节点开始,Shift Down将每一个子树构建成最大堆 for (int i = (arr.length - 1 - 1) / 2; i >= 0; i--) { shiftDown(arr, i); } } // 下沉操做,将指定元素下沉到子树的合适位置,使这个颗树知足堆的规则。 private static void shiftDown(int[] arr, int i) { // 循环找子孩子交换位置。左孩子不能越界 while (2*i + 1 < arr.length) { // 假设要交换的是左孩子 int j = 2*i + 1; // 判断是否有有孩子,而且右孩子是否大于左孩子 if (j+1 < arr.length && arr[j+1] > arr[j]) { j++; // 若是是,和右孩子交换 } // 若是当前节点大于两个孩子,就不须要交换 if (arr[i] > arr[j]) break; // 当前节点小于子孩子,将当前节点和较大的子孩子交换 swap(arr, i, j); // 在判断下一层 i = j; } } public static void swap(int[] arr, int start, int end) { if (start == end) return; int temp = arr[start]; arr[start] = arr[end]; arr[end] = temp; } }
添加元素放在堆的最后面,使用ShiftUp
让元素上浮
,让添加的元素找到一个合适的位置,让堆中的数据依然知足堆的特性。
堆的做用
- 用做优先队列,咱们知道堆的最前一个元素就是堆的最大值。咱们能够依据优先级来构建堆,每次都取出堆中优先级最高的那个数据。
- 用做堆排序。
- 查找第N大(小)元素。
- 查找前N大(小)元素。
后续咱们会选择合适的时间来完成上面这些功能。
总结
- 堆是一种特殊的彻底二叉树,堆中某个节点的值老是不大于或不小于其父节点的值。堆是非线性数据结构,使用数组来存储,操做堆其实就是操做数组中的数据。
- 假设当前节点的索引为i,父节点索引 =
(i - 1) / 2
(Java中除以2取整数,好比7/2 = 3),左孩子索引 =2 * i + 1
,右孩子索引 =2 * i + 2
。 - 使用
ShiftDown
让堆中最前面的元素下沉到合适的位置,让堆依然知足堆的性质。 - 添加元素放在堆的最后面,使用
ShiftUp
让元素上浮
,让添加的元素找到一个合适的位置,让堆中的数据依然知足堆的特性。
原创文章和动画制做真心不易,您的点赞就是最大的支持! 想了解更多文章请关注微信公众号:表哥动画学编程