堆排序2018年1月23号笔记

本文详细介绍了优先队列的概念及其支持的操作,探讨了堆的定义与性质,并深入讲解了堆排序的原理及实现过程。同时,文章还讨论了二叉排序树的性质与操作,包括插入、查找和删除等关键算法。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

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);            //释放结点
	}
}

疑问:为什么插入查找的形参是指针,而删除查找的形参是二级指针呢?只是因为如果只是查找的话,直接传入

指向二叉排序树的根结点的指针就行了,但是如果想要插入和删除结点,那么就要传输指向二叉排序树的根结点

的地址。这里其实不管是插入查找还是删除查找都是用的是一级指针。 要深入理解这个问题,从线性表

的链式存储理解起:???这个总结一下。










评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值