优先队列基础知识(三)---左式堆

首先,我们回顾二叉堆的作用,它的目的是实现查找最小值和插入的对数时间复杂度。但是如果要合并两个二叉堆,那么我们必须要将一个数组中的元素复制到另一个数组中去,然后再进行二叉堆的重构,这种操作的时间复杂度是 O(N) ,其中 N 表示二叉堆的节点的数量。是否存在以对数时间实现合并两个堆的操作呢?同时它也保证了实现查找最小值和插入的对数时间复杂度。
本文主要就是介绍这样的堆数据结构—左式堆,实现对数时间的合并操作。

1. () 针对一颗二叉树中的节点 X ,我们定义它的零路径长为从 X 到一个没有两个子节点的节点的距离。显然具有0或者1个孩子节点的节点的零路径长为0,同时我们给定NULL的零路径长为-1.

2.任意节点的零路径长是它儿子节点的零路径长的最小值加1.给出NULL的零路径长的目的就是使得具有少于两个孩子节点的节点的零路径也符合这一性质。

3. () 堆中的每个节点X,其左儿子的零路径长大于等于右儿子的零路径长。这个性质说明要想寻找一个节点的零路径长对应的路径,显然要从它的右子树开始。

4.我们定义一棵树的右路径从这个根节点出发一直沿右子树直到到达一个节点,这个节点的子节点数少于2个。那么我们有在右路径上有R个节点的左式堆有必然至少有 2R1 个节点。这个性质保证了运用左式堆来实现合并操作是对数时间复杂度。原因就是N个节点的左式堆的右路径最多含有 log(N+1) 个节点。那么我们就从它的右路径来进行合并操作。

下面我们用递归的方法来实现两个左式堆的合并操作。
我们首先确定递归结束条件,就是如果其中一个左式堆为NULL,那么我们直接返回另一个左式堆便可。如果两个左式堆都不为空,那么比较两个左式堆的根节点的关键字的大小关系,把根节点关键字大的左式堆和根节点关键字小的左式堆的右子树进行合并(这就是递归),作为根节点关键字小的左式堆的右子树。这样得到的这棵树的根节点有可能不是左式堆,但是它的左子树和右子树都是左式堆,也就是说,我们只需将它的左右子树进行交换便可。同时要将根节点的零路径长更新为其右子树的零路径长加1。
而对于优先队列的插入和删除最小值操作,其实都可以通过合并操作完成。下面我们将给出代码
头文件定义

#ifndef leftheap_H_
#define leftheap_H_
struct left_heap;
typedef struct left_heap *Pri_queue;
Pri_queue Insert1(int X,Pri_queue q);
Pri_queue Deletemin1(Pri_queue q);
Pri_queue Merge(Pri_queue q1,Pri_queue q2);
Pri_queue Merge1(Pri_queue q1,Pri_queue q2);
#define Insert(X,q) (q=Insert1((X),q))
#define Deletemin(q) (q=Deletemin1(q))
#endif // leftheap_H_

相关操作函数定义

struct left_heap
{
    int Element;
    Pri_queue Left;
    Pri_queue Right;
    int Npl;
};

Pri_queue Insert1(int X,Pri_queue q)
{
    Pri_queue q_insert=malloc(sizeof(struct left_heap));
    q_insert->Left=NULL;
    q_insert->Right=NULL;
    q_insert->Element=X;
    q_insert->Npl=0;
    if(q==NULL)
        return q_insert;
    return Merge(q_insert,q);
}
Pri_queue Deletemin1(Pri_queue q)
{
    return Merge(q->Left,q->Right);
}
Pri_queue Merge(Pri_queue q1,Pri_queue q2)
{
    if(q1==NULL)
        return q2;
    if(q2==NULL)
        return q1;
    if(q1->Element<q2->Element)
        return Merge1(q1,q2);
    else
        return Merge1(q2,q1);
}
Pri_queue Merge1(Pri_queue q1,Pri_queue q2)
{
    if(q1->Left==NULL)//目的是避免在更新零路径长时出现NULL的路径长,当然可以用其他方式实现;
        q1->Left=q2;
    else
    {
        q1->Right=Merge(q1->Right,q2);
        if(q1->Left->Npl<q1->Right->Npl)
            Swap_children(q1);
        q1->Npl=q1->Right->Npl+1;
    }
    return q1;
}
void Swap_children(Pri_queue q)
{
    Pri_queue q_tem=q->Left;
    q->Left=q->Right;
    q->Right=q_tem;
}

主函数程序如下

int main()
{
    Pri_queue left_heap=NULL;
    int Examp[10]={3,10,6,21,14,12,7,23,18,24};
    int i;
    for(i=0;i<10;i++)
        Insert(Examp[i],left_heap);
    Deletemin(left_heap);
    return 0;
}

你可能感兴趣的:(数据结构,c语言)