堆
这里所讲的堆不是之前的堆栈,而是一种树。
他总是一棵完全二叉树,其根节点总是不小于或不大于他的子孙节点。
每一个根节点都可以视为堆顶,因为对于堆的每棵子树都可以视为完全二叉树。
正是因为堆拥有这样的性质我们将他用来实现优先队列
优先队列意思就是每次出队其中权重较大的元素
当然可以用数组或链表来实现,但他们的复杂度总是不尽人意(因为要排序)
堆就不同了,最大堆每次取出其根节点就是最大的了,每次插入新元素时也不用像数组和链表那样排序。
堆的操作
堆一般都通过"数组"来实现。数组实现的堆,父节点和子节点的位置存在一定的关系。有时候,我们将"堆的第一个元素"放在数组索引0的位置,有时候放在1的位置。当然,它们的本质一样(都是堆),只是实现上稍微有一丁点区别。
假设"第一个元素"在数组中的下标为 0 的话,则父节点和子节点的位置关系如下:
(01) 父节点为i的左孩子的下标是 (2*i+1);
(02) 父节点为i的右孩子的下标是 (2*i+2);
(03) 父节点为i的父结点的下标是 floor((i-1)/2);
floor()意思是向下取整,即取不大于要求值的最大的那个整数值;
假设"第一个元素"在数组中的下标为 1 的话,则父节点和子节点的位置关系如下:
(01) 父节点为i的左孩子的下标是 (2*i);
(02) 父节点为i的右孩子的下标是 (2*i+1);
(03) 父节点为i的父结点的下标是 floor(i/2);
插入
当我们要往堆中插入新的元素时,需要遵循待插入堆的规则
如果是大根堆则要将最大的放在根节点,先将待插入元素放在堆底
将其与其根节点比较,若根节点比他小则交换,直到堆顶为止。
int swap(int &x,int &y)
{
int t;
t=x;
x=y;
y=t;
}
int a[1000];//堆
int size;//堆的大小
void insert(int n,int a[])
{
a[size++]=n;
cnt=size;//插入元素的位置
while(cnt)
{
//假设这是一个小根堆
int next=cnt/2;
//找到父节点,因为此时的堆顶为a[1];若为a[0],则为cnt/2-1;上面提到过。
if(a[next]>a[cnt])//如果根节点大于插入元素则交换
swap(a[next],a[cnt]);//交换
else
break;
cnt=next;
}
}
删除
怎么删除?在删除的过程中还是
要维护小根堆的性质
如果你直接删掉了,那就没有堆顶了,这个堆就直接乱了,所以我们要保证删除后这一整个堆还是个完好的小根堆
首先在它的两个儿子里面,找一个比较小的,和它交换一下,但是还是没法删除,因为下方还有节点,那就继续交换,直到下面没有节点了,这时候直接把它扔掉就好了
tput()
{
swap(heap[size],heap[1]);
size--;//交换堆顶和堆底,然后直接弹掉堆底
int cnt=1;
while((cnt*2)<=siz) //对该节点进行向下交换的操作
{
int next=cnt*2;//找出当前节点的左儿子
if(next+1<=size&&heap[next+1]<heap[next])
next++;//看看是要左儿子还是右儿子跟它换
if(heap[next]<heap[cnt])
swap(heap[cnt],heap[next]);//如果不符合堆性质就换
else
break;//否则就完成了
cnt=next;//往下一层继续向下交换
}
}
哈夫曼树与哈夫曼编码
树节点间的边相关的数叫做权。
从树中的一个节点到另一个节点之间的分支构成两个点之间的路径,路径上的分支数目称作路径长度。
如果考虑带权的节点,节点的带权的路径长度就是从该节点到树根之间的路径长度乘该节点的权。
树的带权路径长度就是所有叶子节点的带权路径长度之和。当该值最小时,称这棵二叉树为最优二叉树或哈夫曼树。
构造哈夫曼树:每次在集合中选取最小的两个元素求和,将这个和放进原来的集合,继续此操作。
下面我们以【5、8、4、11、9、13】为例来画出哈夫曼树(数字大小代表权重大小,越大的权重越大)
哈夫曼编码
编码在计算机行业是非常重要的,我们要将我们编写的程序编码成计算机能看懂的,这样程序才能运行。
编码时不能出现某一字符编码是另一个的前缀否则会导致歧义,这时我们应用哈夫曼树的结构,将其左支权值赋为0,右支赋为1,那么每一个节点对应的编码就是由根节点到该节点的路径上的权值组成的(涉及求堆中的路径)。路径是唯一的,这样就避免了编码的二义。