数据结构第五周笔记(1)——树(下)(慕课浙大版本--XiaoYu)

148 阅读7分钟

开启掘金成长之旅!这是我参与「掘金日新计划 · 12 月更文挑战」的第17天,点击查看活动详情

5.1 堆(heap)

5.1.1 什么是堆

  1. 优先队列(Priority Queue):特殊的"队列",取出元素的顺序是依照元素的优先权(关键字)大小,而不是元素进入队列的先后顺序
    
    1. image-20220707112759644
  2. 是否可以采用二叉树存储结构?

    1. 二叉搜索树?

    2. 如果采用二叉树结构,应更关注插入还是删除?

      1. 树结点顺序怎么安排?
      2. 树结构怎么样?
  3. 优先队列的完全二叉树表示

    1. image-20220707114110845
    2. 堆的两个特性
          结构性:用数组表示的完全二叉树;
          有序性:任一结点的关键字是其子树所有结点的最大值(或最小值)
              "最大堆(MaxHeap)",也称"大顶堆":最大值
              "最小堆(MinHeap)",也称"小顶堆":最小值
          通俗的来说就是最大堆的上面的那个要比连接下面两个都要大。最小堆则相反,最上面那个要比连接下面两个都要小。同时需要是完全二叉树
          注意:从根结点到任意结点路径上结点系列的有序性!
      
    3. image-20220707114551521

堆的抽象数据类型描述

类型名称:最大堆(MaxHeap)
数据对象集:完全二叉树,每个结点的元素值不小于其子结点的元素值
操作集:最大堆H属于MAaxHeap,元素item属于ElementType,主要操作有:
1.MaxHeap Create(int MaxSize):创建一个空的最大堆
2.Boolean IsFull(MaxHeap H):判断最大堆H是否已满
3.Insert(MaxHeap H,ElementType item):将元素item插入最大堆H
4.Boolean IsEmpty(MaxHeap H):判断最大堆H是否为空
5.ElementType DeleteMax(MaxHeap H):返回H中最大元素(高优先级)

5.1.2 堆的插入

最大堆的创建

typedef struct HeapStruct *MaxHeap;
struct HeapStruct{
    ElementTye *Elements;//存储堆元素的数组
    int Size;//堆的当前元素个数
    int Capacity;//堆的最大容量
};
MaxHeap Create(int MaxSize)
{
    //创建容量为MaxSize的空的最大堆
    MaxHeap H = malloc(sizeof(struct HeapStruct));
    H->Elements = malloc((Maxsize+1)* sizeof(ElementType));//Maxsize+1是希望申请的容量
    H->Size = 0;
    H->Capacity = MaxSize;
    H->Elements[0] = MaxSize;
    H->Elements[0] = MaxData;
    //定义"哨兵"为大于堆中所有可能元素的值,便于以后更快操作
    return H;
}

最大堆的插入

  1. image-20220707205756557

  2. image-20220707205844776

  3. image-20220707205940384

  4. 如果在底下插入的数值比上面大就沿着线路一路换位置,直到上面没有在比插入的大了为止

  5. 算法的实现如下:将新增结点插入到从其父结点到根结点的有序序列中

    1. void Insert(Maxheap H,ElementType item)
      {
          //将元素item插入最大堆H,其中H->Elements[0]已经定义为哨兵
          int i;
          if(IsFull(H)){
          printf("最大堆已满");
          return;
      }
      i = ++H->Size;//i指向插入后堆中的最后一个元素的位置
      for(;H->Elements[i/2]) < item;i/=2)
          H->Elements[i] = H->Elements[i/2];//向下过滤结点
      H->Elements[i] = item;//将item插入
      }
      复杂度:T(N) = O(log N)
      

      image-20220707212158564

      问题:“哨兵”是在创建堆(Create函数)时设置的:H->Elements[0]=MaxData;

      答案:这是对的

5.1.3 堆的删除

最大堆的删除

  1. 取出根结点(最大值)元素,同时删除堆的一个结点

  2. image-20220707213148580

  3. image-20220707213217119

    1. 时间复杂度就等于树的高度
  4. 删除MaxHeap程序实现:

    1. ElementType DeleteMax(MaxHeap H)
      {
          //从最大堆H中取出键值为最大的元素,并删除一个结点
          int Parent,Child;
          ElementType MaxItem,temp;
          if( IsEmpty(H)){
              printf("最大堆已为空");
              return;
          }
          MaxItem = H->Elements[1];//取出根结点最大值,保存起来,最后是要return出去的
          //temp = H->Elements[H->Size--];
          for(Parent = 1;Parent*2 <= H->Size;Parent=Child){//Parent指示我将来要换的位置,退出循环就说明了我们要的位置找到了
              Child = Parent * 2;//Child指向左儿子
              if((Child != H->Size) && (H->Elements[Child] < H->Elements[Child+1]))
                  Child++;//Child指向左右子结点的较大者
              if(temp > H->Elements[Child])break;
              else//移动temp元素到下一层
                  H->Elements[Parent] = H->Elements[Child];
          }
          H->Elements[Parent] = temp;
          return MaxItem;
      }
      ​
      在完全二叉树里面,i代表一个节点,它的左儿子在2i,右儿子在2i+1
          如果左儿子2i已经超出Size的范围,说明左儿子已经在堆栈外面了,那就说明它没左儿子
          Parent*2 <= H->Size是判别它有没有左儿子,如果没有左儿子那右儿子肯定也没有那就退出循环了
          
          Child != H->Size:Child指向左儿子意味左儿子是最后一个元素了,那就意味着没有右儿子了。实际上这个就相当于判别有没有右儿子
      

      问题:有个堆其元素在数组中的序列为:58,25,44,18,10,26,20,12。如果调用DeleteMax函数删除最大值元素,请猜猜看:程序中的for循环刚退出时变量parent的值是多少?

      是6

5.1.4 堆的建立

堆的一个应用:堆排序--需要先建堆

最大堆的建立

建立最大堆:将已经存在的N个元素按最大堆的要求存放在一个一维数组中
​
方法1:通过插入操作,将N个元素一个个相继插入到一个初始为空的堆中去,其时间代价最大为O(NlogN)
每次插入 它的时间复杂性是log2N
总共循环N遍,所以整个时间复杂性是Nlog2N
上方这个方法1的效率是不够的,我们可以有更好的方法
方法2:在线性时间复杂度下建立最大堆.
(1)将N个元素按输入顺序存入,先满足完全二叉树的结构特性
(2)调整各结点位置,以满足最大堆的有序特性

问:建堆时,最坏情况下需要挪动元素次数是等于树中各结点的高度和。问:对于元素个数为12的堆,其各结点的高度之和是多少?10

image-20220707221437125

小测验:堆

image-20220707222337161

image-20220707223127298

C语言代码:堆的定义和操作

typedef struct HNode *Heap; /* 堆的类型定义 */
struct HNode {
    ElementType *Data; /* 存储元素的数组 */
    int Size;          /* 堆中当前元素个数 */
    int Capacity;      /* 堆的最大容量 */
};
typedef Heap MaxHeap; /* 最大堆 */
typedef Heap MinHeap; /* 最小堆 */#define MAXDATA 1000  /* 该值应根据具体情况定义为大于堆中所有可能元素的值 */
​
MaxHeap CreateHeap( int MaxSize )
{ /* 创建容量为MaxSize的空的最大堆 */
​
    MaxHeap H = (MaxHeap)malloc(sizeof(struct HNode));
    H->Data = (ElementType *)malloc((MaxSize+1)*sizeof(ElementType));
    H->Size = 0;
    H->Capacity = MaxSize;
    H->Data[0] = MAXDATA; /* 定义"哨兵"为大于堆中所有可能元素的值*/
​
    return H;
}
​
bool IsFull( MaxHeap H )
{
    return (H->Size == H->Capacity);
}
​
bool Insert( MaxHeap H, ElementType X )
{ /* 将元素X插入最大堆H,其中H->Data[0]已经定义为哨兵 */
    int i;
 
    if ( IsFull(H) ) { 
        printf("最大堆已满");
        return false;
    }
    i = ++H->Size; /* i指向插入后堆中的最后一个元素的位置 */
    for ( ; H->Data[i/2] < X; i/=2 )
        H->Data[i] = H->Data[i/2]; /* 上滤X */
    H->Data[i] = X; /* 将X插入 */
    return true;
}
​
#define ERROR -1 /* 错误标识应根据具体情况定义为堆中不可能出现的元素值 */
​
bool IsEmpty( MaxHeap H )
{
    return (H->Size == 0);
}
​
ElementType DeleteMax( MaxHeap H )
{ /* 从最大堆H中取出键值为最大的元素,并删除一个结点 */
    int Parent, Child;
    ElementType MaxItem, X;
​
    if ( IsEmpty(H) ) {
        printf("最大堆已为空");
        return ERROR;
    }
​
    MaxItem = H->Data[1]; /* 取出根结点存放的最大值 */
    /* 用最大堆中最后一个元素从根结点开始向上过滤下层结点 */
    X = H->Data[H->Size--]; /* 注意当前堆的规模要减小 */
    for( Parent=1; Parent*2<=H->Size; Parent=Child ) {
        Child = Parent * 2;
        if( (Child!=H->Size) && (H->Data[Child]<H->Data[Child+1]) )
            Child++;  /* Child指向左右子结点的较大者 */
        if( X >= H->Data[Child] ) break; /* 找到了合适位置 */
        else  /* 下滤X */
            H->Data[Parent] = H->Data[Child];
    }
    H->Data[Parent] = X;
​
    return MaxItem;
} 
​
/*----------- 建造最大堆 -----------*/
void PercDown( MaxHeap H, int p )
{ /* 下滤:将H中以H->Data[p]为根的子堆调整为最大堆 */
    int Parent, Child;
    ElementType X;
​
    X = H->Data[p]; /* 取出根结点存放的值 */
    for( Parent=p; Parent*2<=H->Size; Parent=Child ) {
        Child = Parent * 2;
        if( (Child!=H->Size) && (H->Data[Child]<H->Data[Child+1]) )
            Child++;  /* Child指向左右子结点的较大者 */
        if( X >= H->Data[Child] ) break; /* 找到了合适位置 */
        else  /* 下滤X */
            H->Data[Parent] = H->Data[Child];
    }