完全二叉树
定义:一棵二叉树,除最后一层外都是满的。最后一层的节点集中在左边。
完全二叉树的节点可以按层序遍历的顺序放入数组。注意数组的第0位不存放信息,根节点放在第1位。
上图中的完全二叉树就可以以一个数组的形式存储:{#,1, 2, 3, 4, 5, 6, 7, 8, 9, 10}
为什么要这么存放?因为从下标为1的位置开始存放(也称为基1) ,当一个结点的下标为k时,下标为 [k/2] 的结点 就是它的根节点。下标为[2k] 和 [2k+1] 的结点就是它的左孩子结点和右孩子结点。
堆
堆是一个完全二叉树。
堆分为两种:
当堆的每个结点都大于其两个子结点时,这种堆叫大根堆。
当堆的每个结点都小于其两个子结点时,这种堆叫小根堆。
我们先来看大根堆。
1. 插入元素
当我们向一个堆中插入一个元素时,分为两步:
- 将新元素插入到堆的尾端。
- 不断比较插入结点和其父节点的值,若大于其父节点就交换位置。
不断向上交换的操作我们可以很容易用一个循环写出:
int N;//N表示当前堆内的节点数
int[] tree;//保存堆内元素
public void swap(int index1,int index2)//将两个下标所在的元素交换位置
{
int t = tree[index1];
tree[index1] = tree[index2];
tree[index2] = t;
}
public void swim(int index)//通过不断上浮,将指定下标位置的元素更换到到合适的位置上
{
if(index>N)
return;
while(index>1)
{
if(tree[index]>tree[index/2])
{
swap(index,index/2);
index = index/2;
}
else
break;
}
}
public void insert(int n)//插入操作
{
tree[++N] = n;//将新节点放入堆的尾部
swim(N);//不断将该节点上浮,直到放置到合适位置
}
删除堆顶最大元素
- 把最后一个元素与根节点交换。
- 把最后一个元素删除。
- 循环判断根节点是否小于其子结点,是则将其与子结点交换。
public void sink(int index)//将指定下标的元素不断下沉交换到合适位置
{
while(2*index<=N)
{
int swapIndex = 0;
if(2*index+1>N)
swapIndex = 2*index;
else
swapIndex = tree[2*index]>tree[2*index+1]?2*index:2*index+1;
if(tree[index]<tree[swapIndex])
{
swap(index,swapIndex);
index = swapIndex;
}
else
break;
}
}
public int delMax()//删除堆顶结点并返回
{
int Max = tree[N];
swap(1,N--);
sink(1);
return Max;
}
完整代码
class Heap
{
int[] tree;
int N = 0;
public newHeap(int capacity)
{
this.tree = new int[capacity+1];
tree[0] = 0;
}
public newHeap(int[] list)
{
this(list.length);
for(int i:list)
{
this.insert(i);
}
}
public void swap(int index1,int index2)
{
int t = tree[index1];
tree[index1] = tree[index2];
tree[index2] = t;
}
public void swim(int index)
{
if(index>N)
return;
while(index>1)
{
if(tree[index]>tree[index/2])
{
swap(index,index/2);
index = index/2;
}
else
break;
}
}
public void sink(int index)
{
while(2*index<=N)
{
int swapIndex = 0;
if(2*index+1>N)
swapIndex = 2*index;
else
swapIndex = tree[2*index]>tree[2*index+1]?2*index:2*index+1;
if(tree[index]<tree[swapIndex])
{
swap(index,swapIndex);
index = swapIndex;
}
else
break;
}
}
public void insert(int n)
{
tree[++N] = n;
swim(N);
}
public int delMax()
{
int Max = tree[N];
swap(1,N--);
sink(1);
return Max;
}
}
堆排序
将一个数组从大到小排序可以用到大根堆。逐一将数组元素插入堆,再删除堆顶元素直到堆空。每次删除的堆顶元素构成的序列就是这个从大到小排列的数组。
但是还有一个巧妙的方法可以节省时间复杂度:将待排序数组拷贝到堆内。然后从这个新数组的中间开始,从后往前遍历并将每个元素执行sink下沉交换操作。遍历完以后这个堆就是一个大根堆。然后不断删除堆顶结点,直到堆为空。