2018年1月23号笔记
优先队列和堆排序都是下标从1开始的,如果从下标为0开始,需要作改动,具体看代码。
1.优先队列:
提出背景:处理有序元素,但并不一定要求他们全部有序,很多情况下,我们收集一些数据,处理
当前最大的数据。优先队列支持的操作:1)删除最大元素
2)插入元素
2.堆的定义:堆是具有下列性质的完全二叉树:每个结点的值都大于或者等于其左右孩子结点的值,
称为大顶堆;或者每个结点的值小于或者等于其左右孩子结点的值,称为小顶堆。
3.堆排序的原理:将待排序的序列构成一个大顶堆,此时整个序列的最大值就是堆顶的根结点,将根
结点与末尾结点交换,然后将n-1个序列重新构建一个大顶堆,这样就会得到n个元素的次大值。如果
反复,便能得到一个有序序列。
4.将大顶堆用层序遍历存入数组:
5.上浮——当一个结点太大的时候它需要浮到堆的更高层
下沉——当一个结点太小的时候它需要下沉到堆的更底层
说明:不管是上浮还是下沉,都是让小的在下面,大的在上面
下沉可以用来做堆排序(大话数据结构),上浮可以用来构建大顶堆(算法第四版)
上浮算法的实现:
public void swim(int k)
{
while(k>1&&less(k/2,k)) //如果k为1,就不需要比较了
{
exch(k,k/2);
k/=2;
}
}
说明:k为末尾元素,将k位置的元素上浮,这里k>1是因为,如果k=1,也就是k是第一个元素,这是一个元
素没有上浮的必要。
假设k=5,首先k=5和k=2 比较,然后k=2和k=1比较,当k=1时,已经不需要再判断了,因为已经比较过了。
下沉的代码实现:
public void sink(int i)
{
int j = 2*i;
while(j<=N) //这边可以有等于号
{
if(j<N&&less(j,j+1))
j++;
if(less(j,i))
break;
exch(i,j);
}
}
注意:这里第一个j<=N ,这里有等号,而第二个j<N,这里没有等号。因为如果只有左子树的话,此时j+1为空,
此时会出现空指针异常。
1.堆排序
堆结构的特点:1)是完全二叉树
2)根结点是是所有元素中最大的,每个结点都比它的左右孩子要大
堆结构用到的完全二叉树的性质:在一个堆中,位置k的结点的父结点的位置为k/2取整,而它的两个子结点的位
置分别为2k和2k+1.
大话数据结构的中堆排序分为两步:
1)第一步构建一个堆
2)输出堆顶元素后,调整剩下的元素使之成为一个新堆
下沉的代码实现:
//自上而下下沉的实现
public static void HeapAdjust(int[] a,int s,int m)
{
while(2*s<=m) //这边可以用等于号,表示s结点存在它的做孩子
{
int j = 2*s;
if(j<m&&a[j+1]>a[j]) //如果既有左孩子,又有孩子时,就将j赋值给大的一个孩子
j++;
if(a[s]>=a[j]) //如果父结点大,那就不用下沉了,跳出循环
break;
{ //将父结点和大的子结点交换
int temp = a[s];
a[s] = a[j];
a[j] = temp;
}
s = j; //继续向下下沉
}
构建一个堆操作:下沉的实现如上,想要让无序㤡构成一个堆,大话数据结构中的做法是将有孩子的结点都
执行一次下沉。其实就是从下往上从右到到左,将每个非终端结点当做根结点,一次下沉。下图下沉的次序
是4->3->2->1。通过这样的方式构建一个堆。
int i;
for(i=(a.length-1)/2;i>=0;i--) //这边的i需要0{HeapAdjust(a,i,a.length-1);}调整剩下元素成为一个新堆:
基本原理:将对堆顶结点和最后一个结点交换,然后将堆顶结点下沉,找到合适位置,重新调整其成为大顶堆。
for(i=a.length-1;i>0;i--) //这边i>0
{
int temp = a[i]; //第一个结点的值和最后一个结点的值交换
a[i] = a[0];
a[0] = temp;
HeapAdjust(a,0,i-1); //每次将新交换的第一个结点下沉,找到合适位置
} //注意每次下沉后,都会少一个结点
说明:如果在Java中,要添加a[i]=null,防止对象游离,这边不需要,是因为这边数组里面存储的不是对象的引用。3.二叉排序树的性质:
1)若它的左子树不空,则左子树上所有结点的值均小于它的根结点的值
2)若它的右子树不空,则右子树上所有结点的值均大于它的根结点的值
3)结点的值不可以重复
删除结点的三种情况
(1)要删除的是二叉排序树的叶子结点,这个很容易,因为删除它们对整个是树没有影响
(2)要删除的结点只有左子树或只有右子树,这时直接将左子树或右子树移动到删除结点的位置
(3)要删除的结点既有左子树又有右子树,这时通过将这个结点的前驱或后继来代替这个结点
第(3)种情况又细分为两种情况:以前驱替代结点时举例,一种是p=q,另一种是p!=q。详见大话数据结构课本325页。
4.二叉排序树难点:涉及较多二级指针的操作。
二叉排序数的查找,注意要是有两个三个指针,第一个指针T用用于指向某个结点,第二个指针f用于指向查找路径上访
问的最后一个结点,第三个指针p,若查找成功,指针p指向该结点,若查找失败,指针p指向f,即查找路径上访问的
最后一个结点。
说明:二叉排序树结点值不能重复,插入某个结点和删除某个结点前,都要先查找,查找的过程是类似的。具体有三步:
1)第一步都是判别树是不是为空
2)如果根结点不为空,那就判断根结点的值是不是已经等于要查找的值key
3)如果根结点的值不为查找的值key,那就从根结点开始,根据二叉排序树的性质,往下寻找
查找的代码实现如下:
Status SearchBST(BiTree T,int key,BiTree f,BiTree *p)
{
//从二叉排序树T查找等于key的结点,并用*p指向它,其中f是指向T的双亲,为什么要双亲,因为要插入结点啊
if(!T) //树为空
{
*p = f; //指向查找结点位置的指针设置为空
return FALSE;
}else if(T->data==key) //如果找到要查找的值
{
*p = T; //将这个结点赋给*p
return TRUE;
}else if(T->data<key)
{
return SearchBST(T->rchild,key,T,p); //形参*p是一个二级指针即一级指针的地址
}else if(T->data>key)
{
return SearchBST(T->lchild,key,T,p); //同样的形参是二级指针即要输入一个一级指针的地址
}
}
插入代码:
Status InsertBST(BiTree *T,int key)
{
BiTree p,s;
if(!SearchBST(*T,key,NULL,&p))
{
s = (BiTree)malloc(sizeof(BiTNode));
s->data = key;
s->lchild = s->rchild = NULL;
if(!p)
{
*T = s;
}
else if(p->data>key)
{
p->lchild = s;
}else if(p->data<key)
{
p->rchild = s;
}
}
else
{
return FALSE;
}
}
1)判断是否找到了key元素,如果找到就不再插入
2)判断p是否指向空,如果指向空,说明二叉排序树没有元素,直接将*T指向新结点
3)根据key与p->data的大小,插入新结点
删除查找的代码实现如下:
Status DeleteBST(BiTree *T,int key)
{
if(!(*T)) //如果树为空
{
return FALSE;
}
else if((*T)->data==key) //如果知道该结点
{
return Delete(T);
}
else if((*T)->data>key) //否则向下继续查找
{
return DeleteBST(&(*T)->lchild,key);
}
else if((*T)->data<key)
{
return DeleteBST(&(*T)->rchild,key);
}
}
删除代码:
Status Delete(BiTree *p)
{
BiTree q,s;
if((*p)->lchild==NULL)
{
q = *p;
*p = (*p)->rchild;
free(q);
}
else if((*p)->rchild==NULL)
{
q = *p;
*p = (*p)->lchild;
free(q);
}
else //左右子树均不空
{
q = *p;
s = (*p)->lchild;
while(s->rchild) //找到待删结点的左子树中最大的结点
{
q = s;
s = s->rchild;
}
(*p)->data = s->data; //将被删除的结点被赋值
if(q!=*p) //被删结点有两个孩子中的第一种情况
{
q->rchild = s->lchild; //这边肯定是左孩子,因为有右孩子,q的位置就不在这了
}
else
{
q->lchild = s->lchild; //s结点这里也只会有左孩子,
}
free(s); //释放结点
}
}
疑问:为什么插入查找的形参是指针,而删除查找的形参是二级指针呢?只是因为如果只是查找的话,直接传入
指向二叉排序树的根结点的指针就行了,但是如果想要插入和删除结点,那么就要传输指向二叉排序树的根结点
的地址。这里其实不管是插入查找还是删除查找都是用的是一级指针。 要深入理解这个问题,从线性表
的链式存储理解起:???这个总结一下。