1二叉堆基本性质
1.二叉堆用数组表示,对于任意位置i上面的元素,左儿子的位置是2i,右儿子的位置是2i+1。所以每个节点的父亲位置是在i/2s上面。如下图(来自数据结构与算法分析)
2.根节点永远小于叶子节点,或者说,在一个堆中,对于每一个节点X,X的父亲中的关键字小于X中的关键字,根节点除外(它没有父亲)。
2二叉堆基本操作
1添加(插入)
在下一个位置创建一个空穴,判断空穴的父亲是否大于插入的元素,如果是,则交换位置。如果不是,返回此处位置,将插入元素放入此位置之中。如图在树中插入元素14:
代码片段如下:
public void insert(int i) {
if (this.currentSize == arr.length-1 ) {
dilatation(arr.length * 2 + 1);//扩容
}
int hole = ++currentSize;
//上滤
for (arr[0] = i; i < arr[hole / 2]; hole /= 2) {
arr[hole] = arr[hole / 2];
}
arr[hole] = i;
}
时间复杂度为O(logN),但平均来说,一般在上滤到根处之前就截止,所以略低于O(logN)
2删除最小元(出队)
二叉堆中最小元素在根节点返回a[1]即可,但是删除需要一系列操作,首先在节点建立空穴,堆中元素数目需要减1,需要将最后一个元素X放入空穴中,然后和两个儿子的较小者比较,如果小于则插入,否则空穴和此儿子交换位置。如图
代码片段:
public int deleteMin() {
int hole = 1, min = arr[1];
arr[1] = arr[currentSize--];
percolateDown(1);
return min;
}
//下虑
private void percolateDown(int hole) {
int child;
int tmp = arr[hole];
for (; hole*2 <= currentSize ; hole = child) {
child = hole * 2;
if (child != currentSize && arr[hole * 2] > arr[hole * 2 + 1])
child++;
if (arr[child] < tmp)
arr[hole] = arr[child];
else
break;
}
arr[hole] = tmp;
}
运行时间也是O(logN),一般来说,基本都会下滤到最底层。平均运行时间也是O(logN)
3.构建堆
将N个元素放入堆中,这种操作的一种想法是连续N次的插入。
书中的描述insert是O(1)的平均时间,和总的平均运行时间O(n)这里我没太明白。我认为这种方式将花费O(n*logn↓)。
另一个想法是不保证堆序性质,先将所有元素放入堆中,再进行下滤。代码如下:
//构建堆
public Heap(int[] a) {
this.currentSize = a.length;
this.arr = new int[a.length * 2 + 1];
int i = 1;
for (int i1 : a) {
arr[i++] = i1;
}
buildheap();
}
private void buildheap() {
for (int i = currentSize / 2; i > 0; i--) {
percolateDown(i);
}
}
总的运行时间是O(n),书中有详细说明和证明。大概来说是从每个根节点开始下滤,由于树中下面的位置相对稳定,树上面位置下滤的位置不会过于深。
3完整代码
public class Heap {
private int[] arr;
private int currentSize;
public Heap() {
int DEAUFT_SIZE = 10;
this.arr = new int[DEAUFT_SIZE];
this.currentSize = 0;
}
public Heap(int size) {
this.arr = new int[size];
this.currentSize = 0;
}
//构建堆
public Heap(int[] a) {
this.currentSize = a.length;
this.arr = new int[a.length * 2 + 1];
int i = 1;
for (int i1 : a) {
arr[i++] = i1;
}
buildheap();
}
private void buildheap() {
for (int i = currentSize / 2; i > 0; i--) {
percolateDown(i);
}
}
private void dilatation(int size) {
int[] newarr = new int[size];
System.arraycopy(this.arr, 0, newarr, 0, this.arr.length);
this.arr = newarr;
}
/**
* 添加至堆中
*
* @param i
*/
public void insert(int i) {
if (this.currentSize == arr.length-1 ) {
dilatation(arr.length * 2 + 1);//扩容
}
int hole = ++currentSize;
//上滤
for (arr[0] = i; i < arr[hole / 2]; hole /= 2) {
arr[hole] = arr[hole / 2];
}
arr[hole] = i;
}
public int deleteMin() {
int hole = 1, min = arr[1];
arr[1] = arr[currentSize--];
percolateDown(1);
return min;
}
//下虑
private void percolateDown(int hole) {
int child;
int tmp = arr[hole];
for (; hole*2 <= currentSize ; hole = child) {
child = hole * 2;
if (child != currentSize && arr[hole * 2] > arr[hole * 2 + 1])
child++;
if (arr[child] < tmp)
arr[hole] = arr[child];
else
break;
}
arr[hole] = tmp;
}
private String addEmpty(int num, String s) {
StringBuilder stringBuilder = new StringBuilder();
for (int i = 0; i < num; i++) {
stringBuilder.append(s);
}
return stringBuilder.toString();
}
private String addEmpty(int num) {
return addEmpty(num, " ");
}
private String format(int i) {
String s = i + "";
s = addEmpty(3 - s.length(), " ") + s;
return s;
}
@Override
public String toString() {
int line = (int) (Math.log(currentSize) / Math.log(2)) + 1;
StringBuilder s = new StringBuilder();
int i = 0;
for (int l = 0; l < line; l++) {
for (int j = 0; j < Math.pow(2, l) && i <= currentSize; j++) {
s.append(addEmpty((int) Math.pow(2, line - l) - 1)); //打印spaceNum个空格
s.append(format(arr[i++]));//打印数据
s.append(addEmpty((int) Math.pow(2, line - l) - 1)); //打印spaceNum个空格
s.append("***");
}
s.delete(s.length() - 3, s.length());
s.append("\n");
}
return s.toString();
}
}
值得一说的是,代码中重新写了toString用于展示二叉堆,首先计算行数,行数为 log(N)+1,遍历每一行,计算打印空格数为2的总行数-当前行数-1次方。,打印过两次空格之后打印占位符,在删除行末尾的占位符,效果如下:
上面是原数组,下面是构建之后打印的二叉树。
用于方便测试的生成工具:
public class CreateBasicData {
static private final int defaultLength = 20;
static private final int defaultMax = 100;
static private final int defaultStringLength = 10;
/**
* 返回限定长度 限定大小 随机 int[]
* 不限定默认值为 length:20 max:100
* @return int[]
*/
public static int[] createRandomIntArray() {
return createRandomIntArray(defaultLength, defaultMax);
}
public static int[] createRandomIntArray(int length) {
return createRandomIntArray(length, defaultMax);
}
public static int[] createRandomIntArray(int length, int max) {
Random seed = new Random(System.currentTimeMillis());
return seed.ints(0, max).limit(length).toArray();
}
/**
* 返回限定长度 限定大小 随机的 List<Integer>
* 不限定默认值为 length:20 max:100
* @return List<Integer>
*/
public static List<Integer> createRandomIntList() {
return createRandomIntList(defaultLength,defaultMax);
}
public static List<Integer> createRandomIntList(int length) {
return createRandomIntList(length,defaultMax);
}
public static List<Integer> createRandomIntList(int length,int max) {
Random seed = new Random(System.currentTimeMillis());
Supplier<Integer> random = () -> seed.nextInt(max);
return Stream.generate(random).limit(length).collect(Collectors.toList());
}
/**
* 返回限定长度 限定大小 随机的 Set<Integer>
* 不限定默认值为 length:20 max:100
* @return Set<Integer>
*/
public static Set<Integer> createRandomIntSet() {
return createRandomIntSet(defaultLength, defaultMax);
}
public static Set<Integer> createRandomIntSet(int length) {
return createRandomIntSet(length, defaultMax);
}
public static Set<Integer> createRandomIntSet(int length,int max) {
Random seed = new Random(System.currentTimeMillis());
Supplier<Integer> random = () -> seed.nextInt(max);
return Stream.generate(random).limit(length).collect(Collectors.toSet());
}
/**
* 返回限定长度 限定大小 随机的 Set<String>
* 不限定默认值为 length:20 max:10
* @return Set<String>
*/
public static Set<String> createRandomStringSet() {
return createRandomStringSet(defaultLength,defaultStringLength);
}
public static Set<String> createRandomStringSet(int length) {
return createRandomStringSet(length,defaultStringLength);
}
public static Set<String> createRandomStringSet(int length,int max) {
return Stream.generate(new PersonSupplier(max)).limit(length).collect(Collectors.toSet());
}
/**
* 返回限定长度 限定大小 随机的 List<String>
* 不限定默认值为 length:20 max:10
* @return List<String>
*/
public static List<String> createRandomStringList() {
return createRandomStringList(defaultLength,defaultStringLength);
}
public static List<String> createRandomStringList(int length) {
return createRandomStringList(length,defaultStringLength);
}
public static List<String> createRandomStringList(int length,int max) {
return Stream.generate(new PersonSupplier(max)).limit(length).collect(Collectors.toList());
}
/**
* 返回限定长度 限定大小 随机 String[]
* 不限定默认值为 length:20 max:10
* @return String[]
*/
public static String[] createRandomStringArray() {
return createRandomStringArray(defaultLength,defaultStringLength);
}
public static String[] createRandomStringArray(int length) {
return createRandomStringArray(length,defaultStringLength);
}
public static String[] createRandomStringArray(int length,int max) {
return (String[]) Stream.generate(new PersonSupplier(max)).limit(length).toArray();
}
public static <K,V> Map<K, V> createRandomMap(Set<K> keys, Collection<V> value) throws Exception {
Map<K, V> map = new HashMap<>();
if (keys.size() != value.size()) {
throw new Exception("长度不符");
}
Iterator<K> itKey = keys.iterator();
Iterator<V> itValue = value.iterator();
while (itKey.hasNext()) {
map.put(itKey.next(), itValue.next());
}
return map;
}
/**
* 返回给定长度随机字符串
* @param length
* @return
*/
public static String getRandomString(int length){
//定义一个字符串(A-Z,a-z,0-9)即62位;
String str="zxcvbnmlkjhgfdsaqwertyuiopQWERTYUIOPASDFGHJKLZXCVBNM1234567890";
//由Random生成随机数
Random random=new Random();
StringBuffer sb=new StringBuffer();
//长度为几就循环几次
for(int i=0; i<length; ++i){
//产生0-61的数字
int number=random.nextInt(62);
//将产生的数字通过length次承载到sb中
sb.append(str.charAt(number));
}
//将承载的字符转换成字符串
return sb.toString();
}
}