标签:vat 是的 删除 ati 位置 快速排序 style span bsp
堆是一种特殊的树,只要满足以下两点,这个树就是一个堆。
①完全二叉树,完全二叉树要求除了最后一层,其他层的节点个数都是满的,最后一层的节点都靠左排列。
②树中每一个结点的值都必须大于等于(或小于等于)其子树中每个节点的值。大于等于的情况称为大顶堆,小于等于的情况称为小顶堆。
完全二叉树适合用数组来存储,因为数组中对于下标从1开始的情况,下标为i的节点的左子节点就是下标为i*2的节点,右子节点就是i下标为i*2+1的节点,其父节点时下标为i/2的节点
往堆中插入一个元素
把新插入的元素放到堆的最后就不符合第二个特性了,所以我们需要进行调整,让其重新满足堆的特性,这个过程我们起了一个名字,就叫作堆化(heapify)。
堆化就是顺着节点所在的路径,向上或者向下,对比,然后交换。我们先使用从下往上的堆化方法。
让新插入的节点与父节点对比大小。如果不满足子节点小于等于父节点的大小关系,我们就互换两个节点。一直重复这个过程,直到父子节点之间满足刚说的那种大小关系。
public class Heap{ private int[] data;//数组,从下标1开始存储 private int maxNum;//数组容量 private int count;//当前数组成员数量 //构造器初始化数组,大小和数量 public Heap(int size){ data = new int[size + 1]; maxNum = size; count = 0; } public void Insert(int item){ //堆满返回 if (count >= maxNum) return; //先将节点插入堆尾 data[count++] = item; int i = count; //再自下向上堆化,直到堆顶或者父节点比子节点大为止 while (i / 2 > 0 && data[i] > data[i / 2]){ //交换位置 int temp = data[i]; data[i] = data[i / 2]; data[i / 2] = temp; //更新下标 i = i / 2; } } }
删除堆顶元素
根据对的第二条定义,堆顶元素存储的就是堆中的最大值或最小值。
这里我们使用从上往下的堆化方法。将最后一个节点放到堆顶,然后利用同样的父子节点对比法,进行互换节点直到父子节点之间满足大小关系为止。
这样移除的就是数组中的最后一个元素,不会破环完全二叉树的定义。
public void RemoveMax(){ //堆空返回 if (count == 0) return; //将最后一个节点提到堆顶 data[1] = data[count--]; //进行堆化 Heapify(data,count,1); } public static void Heapify(int[] data,int n,int i){ while (true){ //记录更大节点的位置,初始化为当前节点的位置 int maxPos = i; //如果其左右子节点存在,且比当前节点大,就将左右节点下标设为更大的节点 if (i * 2 <= n && data[i] < data[i * 2]) maxPos = i * 2; if (i * 2 + 1 <= n && data[maxPos] < data[i * 2 + 1]) maxPos = i * 2 + 1; //否则就结束循环,堆化结束 if (maxPos == i) break; //节点交换位置 int temp = data[i]; data[i] = data[maxPos]; data[maxPos] = temp; //更新当前节点的下标,循环继续与下一个左右子节点比较 i = maxPos; } }
我们借助于堆这种数据结构实现的排序算法,就叫作堆排序。
我们可以把堆排序的过程大致分解成两个大的步骤,建堆和排序。
首先将数组原地建成一个堆。借助另一个数组,就在原数组上操作。我们要实现从后往前处理数组,并且每个数据都是从上往下堆化的建堆方法。
public static void BuildHeap(int[] data, int n){ //从下标n/2到1开始进行堆化,n/2就是最后一个叶子节点的父节点。 for (int i = n / 2; i >= 1; --i) Heapify(data,n,i); }
我们对下标从n/2开始到 111 的数据进行堆化,下标是n/2+1到n的节点是叶子节点,我们不需要堆化。
建堆操作的时间复杂度
排序的建堆过程的时间复杂度是 O(n)。
建堆结束之后,数组中的数据已经是按照大顶堆的特性来组织的。数组中的第一个元素就是堆顶,也就是最大的元素。我们把它跟最后一个元素交换,那最大元素就放到了下标为n的位置。
这个过程有点类似删除堆顶元素的操作,当堆顶元素移除之后,我们把下标为n的元素放到堆顶,然后再通过堆化的方法,将剩下的n-1个元素重新构建成堆。
堆化完成之后,我们再取堆顶的元素,放到下标是的位置,一直重复这个过程,直到最后堆中只剩下标为1的一个元素,排序工作就完成了。
public static void Sort(int[] data,int n){ //将数组建造为堆 BuildHeap(data, n); //获取堆尾的下标 int k = n; //循环直到k为1 while (k > 1){ //交换堆顶和堆尾的元素 int temp = data[k]; data[k] = data[1]; data[1] = temp; //将堆尾的下标递减并对1到k的下标的数组成员进行堆化 Heapify(data,--k,1); } }
堆排序的时间复杂度、空间复杂度以及稳定性
堆排序是原地排序算法。堆排序包括建堆和排序两个操作,建堆过程的时间复杂度是O(n),排序过程的时间复杂度是O(nlogn)所以,堆排序整体的时间复杂度是O(nlogn)。
堆排序不是稳定的排序算法,因为在排序的过程,存在将堆的最后一个节点跟堆顶节点互换的操作,所以就有可能改变值相同数据的原始相对顺序。
对于快速排序来说,数据是顺序访问的而对于堆排序来说,数据是跳着访问的。这样对 CPU 缓存是不友好的。
对于同样的数据,在排序过程中,堆排序算法的数据交换次数要多于快速排序。堆排序的第一步是建堆,建堆的过程会打乱数据原有的相对先后顺序,导致原数据的有序度降低。比如,对于一组已经有序的数据来说,经过建堆之后,数据反而变得更无序了。
标签:vat 是的 删除 ati 位置 快速排序 style span bsp
原文地址:https://www.cnblogs.com/errornull/p/10054391.html