概要

本章介绍左倾堆,它和二叉堆一样,都是堆结构中的一员。和以往一样,本文会先对左倾堆的理论知识进行简单介绍,然后给出C语言的实现。后续再分别给出C++和Java版本的实现;实现的语言虽不同,但是原理如出一辙,选择其中之一进行了解即可。若文章有错误或不足的地方,请不吝指出!

目录
1. 左倾堆的介绍
2. 左倾堆的图文解析
3. 左倾堆的C实现(完整源码)
4. 左倾堆的C测试程序

转载请注明出处:http://www.cnblogs.com/skywang12345/p/3638327.html


更多内容:数据结构与算法系列 目录

(01) 左倾堆(一)之 图文解析 和 C语言的实现
(02) 左倾堆(二)之 C++的实现
(03) 左倾堆(三)之 Java的实现

左倾堆的介绍

左倾堆(leftist tree 或 leftist heap),又被成为左偏树、左偏堆,最左堆等。
它和二叉堆一样,都是优先队列实现方式。当优先队列中涉及到"对两个优先队列进行合并"的问题时,二叉堆的效率就无法令人满意了,而本文介绍的左倾堆,则可以很好地解决这类问题。

左倾堆的定义

左倾堆是一棵二叉树,它的节点除了和二叉树的节点一样具有左右子树指针外,还有两个属性:键值零距离
(01) 键值的作用是来比较节点的大小,从而对节点进行排序。
(02) 零距离(英文名NPL,即Null Path Length)则是从一个节点到一个"最近的不满节点"的路径长度。不满节点是指该该节点的左右孩子至少有有一个为NULL。叶节点的NPL为0,NULL节点的NPL为-1。

上图是一颗左倾堆,它满足左倾堆的基本性质:
[性质1] 节点的键值小于或等于它的左右子节点的键值。
[性质2] 节点的左孩子的NPL >= 右孩子的NPL。
[性质3] 节点的NPL = 它的右孩子的NPL + 1。

左倾堆,顾名思义,是有点向左倾斜的意思了。它在统计问题、最值问题、模拟问题和贪心问题等问题中有着广泛的应用。此外,斜堆是比左倾堆更为一般的数据结构。当然,今天讨论的是左倾堆,关于斜堆,以后再撰文来表。
前面说过,它能和好的解决"两个优先队列合并"的问题。实际上,左倾堆的合并操作的平摊时间复杂度为O(lg n),而完全二叉堆为O(n)。合并就是左倾树的重点,插入和删除操作都是以合并操作为基础的。插入操作,可以看作两颗左倾树合并;删除操作(移除优先队列中队首元素),则是移除根节点之后再合并剩余的两个左倾树。闲话说到这里,下面开始介绍左倾树的基本方法。

左倾堆的图文解析

合并操作是左倾堆的重点。合并两个左倾堆的基本思想如下:
(01) 如果一个空左倾堆与一个非空左倾堆合并,返回非空左倾堆。
(02) 如果两个左倾堆都非空,那么比较两个根节点,取较小堆的根节点为新的根节点。将"较小堆的根节点的右孩子"和"较大堆"进行合并。
(03) 如果新堆的右孩子的NPL > 左孩子的NPL,则交换左右孩子。
(04) 设置新堆的根节点的NPL = 右子堆NPL + 1

下面通过图文演示合并以下两个堆的过程。


提示:这两个堆的合并过程和测试程序相对应!

第1步:将"较小堆(根为10)的右孩子"和"较大堆(根为11)"进行合并。
合并的结果,相当于将"较大堆"设置"较小堆"的右孩子,如下图所示:

第2步:将上一步得到的"根11的右子树"和"根为12的树"进行合并,得到的结果如下:

第3步:将上一步得到的"根12的右子树"和"根为13的树"进行合并,得到的结果如下:

第4步:将上一步得到的"根13的右子树"和"根为16的树"进行合并,得到的结果如下:

第5步:将上一步得到的"根16的右子树"和"根为23的树"进行合并,得到的结果如下:

至此,已经成功的将两棵树合并成为一棵树了。接下来,对新生成的树进行调节。

第6步:上一步得到的"树16的右孩子的NPL > 左孩子的NPL",因此交换左右孩子。得到的结果如下:

第7步:上一步得到的"树12的右孩子的NPL > 左孩子的NPL",因此交换左右孩子。得到的结果如下:

第8步:上一步得到的"树10的右孩子的NPL > 左孩子的NPL",因此交换左右孩子。得到的结果如下:

至此,合并完毕。上面就是合并得到的左倾堆!

下面看看左倾堆的基本操作的代码

1. 头文件

#ifndef _LEFTIST_TREE_H_
#define _LEFTIST_TREE_H_ typedef int Type; typedef struct _LeftistNode{
Type key; // 关键字(键值)
int npl; // 零路经长度(Null Path Length)
struct _LeftistNode *left; // 左孩子
struct _LeftistNode *right; // 右孩子
}LeftistNode, *LeftistHeap; // 前序遍历"左倾堆"
void preorder_leftist(LeftistHeap heap);
// 中序遍历"左倾堆"
void inorder_leftist(LeftistHeap heap);
// 后序遍历"左倾堆"
void postorder_leftist(LeftistHeap heap); // 获取最小值(保存到pval中),成功返回0,失败返回-1。
int leftist_minimum(LeftistHeap heap, int *pval);
// 合并"左倾堆x"和"左倾堆y",并返回合并后的新树
LeftistNode* merge_leftist(LeftistHeap x, LeftistHeap y);
// 将结点插入到左倾堆中,并返回根节点
LeftistNode* insert_leftist(LeftistHeap heap, Type key);
// 删除结点(key为节点的值),并返回根节点
LeftistNode* delete_leftist(LeftistHeap heap); // 销毁左倾堆
void destroy_leftist(LeftistHeap heap); // 打印左倾堆
void print_leftist(LeftistHeap heap); #endif

LeftistNode是左倾堆对应的节点类。

2. 合并

/*
* 合并"左倾堆x"和"左倾堆y"
*
* 返回值:
* 合并得到的树的根节点
*/
LeftistNode* merge_leftist(LeftistHeap x, LeftistHeap y)
{
if(x == NULL)
return y;
if(y == NULL)
return x; // 合并x和y时,将x作为合并后的树的根;
// 这里的操作是保证: x的key < y的key
if(x->key > y->key)
swap_leftist_node(x, y); // 将x的右孩子和y合并,"合并后的树的根"是x的右孩子。
x->right = merge_leftist(x->right, y); // 如果"x的左孩子为空" 或者 "x的左孩子的npl<右孩子的npl"
// 则,交换x和y
if(x->left == NULL || x->left->npl < x->right->npl)
{
LeftistNode *tmp = x->left;
x->left = x->right;
x->right = tmp;
}
// 设置合并后的新树(x)的npl
if (x->right == NULL || x->left == NULL)
x->npl = ;
else
x->npl = (x->left->npl > x->right->npl) ? (x->right->npl + ) : (x->left->npl + ); return x;
}

merge_leftist(x, y)的作用是合并x和y这两个左倾堆,并返回得到的新堆。merge_leftist(x, y)是递归实现的。

3. 添加

/*
* 新建结点(key),并将其插入到左倾堆中
*
* 参数说明:
* heap 左倾堆的根结点
* key 插入结点的键值
* 返回值:
* 根节点
*/
LeftistNode* insert_leftist(LeftistHeap heap, Type key)
{
LeftistNode *node; // 新建结点 // 如果新建结点失败,则返回。
if ((node = (LeftistNode *)malloc(sizeof(LeftistNode))) == NULL)
return heap;
node->key = key;
node->npl = ;
node->left = node->right = NULL; return merge_leftist(heap, node);
}

insert_leftist(heap, key)的作用是新建键值为key的结点,并将其插入到左倾堆中,并返回堆的根节点。

4. 删除

/*
* 取出根节点
*
* 返回值:
* 取出根节点后的新树的根节点
*/
LeftistNode* delete_leftist(LeftistHeap heap)
{
if (heap == NULL)
return NULL; LeftistNode *l = heap->left;
LeftistNode *r = heap->right; // 删除根节点
free(heap); return merge_leftist(l, r); // 返回左右子树合并后的新树
}

delete_leftist(heap)的作用是删除左倾堆的最小节点,并返回删除节点后的左倾堆根节点。

注意:关于左倾堆的"前序遍历"、"中序遍历"、"后序遍历"、"打印"、"销毁"等接口就不再单独介绍了。后文的源码中有给出它们的实现代码,Please RTFSC(Read The Fucking Source Code)!

左倾堆的C实现(完整源码)

左倾堆的头文件(leftist.h)

 #ifndef _LEFTIST_TREE_H_
#define _LEFTIST_TREE_H_ typedef int Type; typedef struct _LeftistNode{
Type key; // 关键字(键值)
int npl; // 零路经长度(Null Path Length)
struct _LeftistNode *left; // 左孩子
struct _LeftistNode *right; // 右孩子
}LeftistNode, *LeftistHeap; // 前序遍历"左倾堆"
void preorder_leftist(LeftistHeap heap);
// 中序遍历"左倾堆"
void inorder_leftist(LeftistHeap heap);
// 后序遍历"左倾堆"
void postorder_leftist(LeftistHeap heap); // 获取最小值(保存到pval中),成功返回0,失败返回-1。
int leftist_minimum(LeftistHeap heap, int *pval);
// 合并"左倾堆x"和"左倾堆y",并返回合并后的新树
LeftistNode* merge_leftist(LeftistHeap x, LeftistHeap y);
// 将结点插入到左倾堆中,并返回根节点
LeftistNode* insert_leftist(LeftistHeap heap, Type key);
// 删除结点(key为节点的值),并返回根节点
LeftistNode* delete_leftist(LeftistHeap heap); // 销毁左倾堆
void destroy_leftist(LeftistHeap heap); // 打印左倾堆
void print_leftist(LeftistHeap heap); #endif

左倾堆的实现文件(leftist.c)

 /**
* C语言实现的左倾堆
*
* @author skywang
* @date 2014/03/31
*/ #include <stdio.h>
#include <stdlib.h>
#include "leftist.h" /*
* 前序遍历"左倾堆"
*/
void preorder_leftist(LeftistHeap heap)
{
if(heap != NULL)
{
printf("%d ", heap->key);
preorder_leftist(heap->left);
preorder_leftist(heap->right);
}
} /*
* 中序遍历"左倾堆"
*/
void inorder_leftist(LeftistHeap heap)
{
if(heap != NULL)
{
inorder_leftist(heap->left);
printf("%d ", heap->key);
inorder_leftist(heap->right);
}
} /*
* 后序遍历"左倾堆"
*/
void postorder_leftist(LeftistHeap heap)
{
if(heap != NULL)
{
postorder_leftist(heap->left);
postorder_leftist(heap->right);
printf("%d ", heap->key);
}
} /*
* 交换两个节点的内容
*/
static void swap_leftist_node(LeftistNode *x, LeftistNode *y)
{
LeftistNode tmp = *x;
*x = *y;
*y = tmp;
} /*
* 获取最小值
*
* 返回值:
* 成功返回0,失败返回-1
*/
int leftist_minimum(LeftistHeap heap, int *pval)
{
if (heap == NULL)
return -; *pval = heap->key; return ;
} /*
* 合并"左倾堆x"和"左倾堆y"
*
* 返回值:
* 合并得到的树的根节点
*/
LeftistNode* merge_leftist(LeftistHeap x, LeftistHeap y)
{
if(x == NULL)
return y;
if(y == NULL)
return x; // 合并x和y时,将x作为合并后的树的根;
// 这里的操作是保证: x的key < y的key
if(x->key > y->key)
swap_leftist_node(x, y); // 将x的右孩子和y合并,"合并后的树的根"是x的右孩子。
x->right = merge_leftist(x->right, y); // 如果"x的左孩子为空" 或者 "x的左孩子的npl<右孩子的npl"
// 则,交换x和y
if(x->left == NULL || x->left->npl < x->right->npl)
{
LeftistNode *tmp = x->left;
x->left = x->right;
x->right = tmp;
}
// 设置合并后的新树(x)的npl
if (x->right == NULL || x->left == NULL)
x->npl = ;
else
x->npl = (x->left->npl > x->right->npl) ? (x->right->npl + ) : (x->left->npl + ); return x;
} /*
* 新建结点(key),并将其插入到左倾堆中
*
* 参数说明:
* heap 左倾堆的根结点
* key 插入结点的键值
* 返回值:
* 根节点
*/
LeftistNode* insert_leftist(LeftistHeap heap, Type key)
{
LeftistNode *node; // 新建结点 // 如果新建结点失败,则返回。
if ((node = (LeftistNode *)malloc(sizeof(LeftistNode))) == NULL)
return heap;
node->key = key;
node->npl = ;
node->left = node->right = NULL; return merge_leftist(heap, node);
} /*
* 取出根节点
*
* 返回值:
* 取出根节点后的新树的根节点
*/
LeftistNode* delete_leftist(LeftistHeap heap)
{
if (heap == NULL)
return NULL; LeftistNode *l = heap->left;
LeftistNode *r = heap->right; // 删除根节点
free(heap); return merge_leftist(l, r); // 返回左右子树合并后的新树
} /*
* 销毁左倾堆
*/
void destroy_leftist(LeftistHeap heap)
{
if (heap==NULL)
return ; if (heap->left != NULL)
destroy_leftist(heap->left);
if (heap->right != NULL)
destroy_leftist(heap->right); free(heap);
} /*
* 打印"左倾堆"
*
* heap -- 左倾堆的节点
* key -- 节点的键值
* direction -- 0,表示该节点是根节点;
* -1,表示该节点是它的父结点的左孩子;
* 1,表示该节点是它的父结点的右孩子。
*/
static void leftist_print(LeftistHeap heap, Type key, int direction)
{
if(heap != NULL)
{
if(direction==) // heap是根节点
printf("%2d(%d) is root\n", heap->key, heap->npl);
else // heap是分支节点
printf("%2d(%d) is %2d's %6s child\n", heap->key, heap->npl, key, direction==?"right" : "left"); leftist_print(heap->left, heap->key, -);
leftist_print(heap->right,heap->key, );
}
} void print_leftist(LeftistHeap heap)
{
if (heap != NULL)
leftist_print(heap, heap->key, );
}

左倾堆的测试程序(leftist_test.c)

 /**
* C语言实现的左倾堆
*
* @author skywang
* @date 2014/03/31
*/ #include <stdio.h>
#include "leftist.h" #define LENGTH(a) ( (sizeof(a)) / (sizeof(a[0])) ) void main()
{
int i;
int a[]= {,,,,,,,};
int b[]= {,,,,,,};
int alen=LENGTH(a);
int blen=LENGTH(b);
LeftistHeap ha,hb; ha=hb=NULL; printf("== 左倾堆(ha)中依次添加: ");
for(i=; i<alen; i++)
{
printf("%d ", a[i]);
ha = insert_leftist(ha, a[i]);
}
printf("\n== 左倾堆(ha)的详细信息: \n");
print_leftist(ha); printf("\n== 左倾堆(hb)中依次添加: ");
for(i=; i<blen; i++)
{
printf("%d ", b[i]);
hb = insert_leftist(hb, b[i]);
}
printf("\n== 左倾堆(hb)的详细信息: \n");
print_leftist(hb); // 将"左倾堆hb"合并到"左倾堆ha"中。
ha = merge_leftist(ha, hb);
printf("\n== 合并ha和hb后的详细信息: \n");
print_leftist(ha); // 销毁左倾堆
destroy_leftist(ha);
}

左倾堆的C测试程序

左倾堆的测试程序已经包含在它的实现文件(leftist_test.c)中了,这里仅给出它的运行结果:

== 左倾堆(ha)中依次添加:
== 左倾堆(ha)的详细信息:
() is root
() is 's left child
() is 's left child
() is 's right child
() is 's right child
() is 's left child
() is 's left child
() is 's right child == 左倾堆(hb)中依次添加:
== 左倾堆(hb)的详细信息:
() is root
() is 's left child
() is 's left child
() is 's right child
() is 's right child
() is 's left child
() is 's right child == 合并ha和hb后的详细信息:
() is root
() is 's left child
() is 's left child
() is 's left child
() is 's right child
() is 's right child
() is 's left child
() is 's left child
() is 's right child
() is 's left child
() is 's right child
() is 's left child
() is 's right child
() is 's left child
() is 's right child

左倾堆(一)之 图文解析 和 C语言的实现的更多相关文章

  1. 二叉堆(一)之 图文解析 和 C语言的实现

    概要 本章介绍二叉堆,二叉堆就是通常我们所说的数据结构中"堆"中的一种.和以往一样,本文会先对二叉堆的理论知识进行简单介绍,然后给出C语言的实现.后续再分别给出C++和Java版本 ...

  2. 二项堆(一)之 图文解析 和 C语言的实现

    概要 本章介绍二项堆,它和之前所讲的堆(二叉堆.左倾堆.斜堆)一样,也是用于实现优先队列的.和以往一样,本文会先对二项堆的理论知识进行简单介绍,然后给出C语言的实现.后续再分别给出C++和Java版本 ...

  3. 斐波那契堆(一)之 图文解析 和 C语言的实现

    概要 本章介绍斐波那契堆.和以往一样,本文会先对斐波那契堆的理论知识进行简单介绍,然后给出C语言的实现.后续再分别给出C++和Java版本的实现:实现的语言虽不同,但是原理如出一辙,选择其中之一进行了 ...

  4. 二叉查找树(一)之 图文解析 和 C语言的实现

    概要 本章先对二叉树的相关理论知识进行介绍,然后给出C语言的详细实现.关于二叉树的学习,需要说明的是:它并不难,不仅不难,而且它非常简单.初次接触树的时候,我也觉得它似乎很难:而之所产生这种感觉主要是 ...

  5. AVL树(一)之 图文解析 和 C语言的实现

    概要 本章介绍AVL树.和前面介绍"二叉查找树"的流程一样,本章先对AVL树的理论知识进行简单介绍,然后给出C语言的实现.本篇实现的二叉查找树是C语言版的,后面章节再分别给出C++ ...

  6. 伸展树(一)之 图文解析 和 C语言的实现

    概要 本章介绍伸展树.它和"二叉查找树"和"AVL树"一样,都是特殊的二叉树.在了解了"二叉查找树"和"AVL树"之后, ...

  7. 左倾堆(二)之 C++的实现

    概要 上一章介绍了左倾堆的基本概念,并通过C语言实现了左倾堆.本章是左倾堆的C++实现. 目录1. 左倾堆的介绍2. 左倾堆的图文解析3. 左倾堆的C++实现(完整源码)4. 左倾堆的C++测试程序 ...

  8. 左倾堆(三)之 Java的实现

    概要 前面分别通过C和C++实现了左倾堆,本章给出左倾堆的Java版本.还是那句老话,三种实现的原理一样,择其一了解即可. 目录1. 左倾堆的介绍2. 左倾堆的图文解析3. 左倾堆的Java实现(完整 ...

  9. 数据结构图文解析之:二叉堆详解及C++模板实现

    0. 数据结构图文解析系列 数据结构系列文章 数据结构图文解析之:数组.单链表.双链表介绍及C++模板实现 数据结构图文解析之:栈的简介及C++模板实现 数据结构图文解析之:队列详解与C++模板实现 ...

随机推荐

  1. sqlserver卡号段分组

    之前给上海一家电子商务公司做一个卖卡系统,遇到了卡号段分组的问题.刚开始没什么好的实现方法,遂在博客园求助但未果,没法自己研究sql,终于搞定. 问题描述: 有个卡库存表,有个卡号字段,假设数据:16 ...

  2. 爬虫神器xpath的用法(二)

    爬取网页内容的时候,往往网页标签比较复杂,对于这种情况,需要用xpath的starts-with和string(.)功能属性来处理,具体看事例 #encoding=utf-8 from lxml im ...

  3. Beego源码分析(转)

    摘要 beego 是 @astaxie 开发的重量级Go语言Web框架.它有标准的MVC模式,完善的功能模块,和优异的调试和开发模式等特点.并且beego在国内企业用户较多,社区发达和Q群,文档齐全, ...

  4. svchost占用内存达1-2G的问题

    win7 64位,前一段时间老是如此,很烦,重装,还是有这个问题. 服务中禁用superfect服务,关闭后继续出现1G以上的内存占用,下载svchost viewer检查,发现: 关闭windows ...

  5. Spring框架下的 “接口调用、MVC请求” 调用参数、返回值、耗时信息输出

    主要拦截前端或后天的请求,打印请求方法参数.返回值.耗时.异常的日志.方便开发调试,能很快定位到问题出现在哪个方法中. 前端请求拦截,mvc的拦截器 import java.util.Date; im ...

  6. IOS图像拉伸解决方案

    UIButton实现背景拉伸,即图片两端不拉伸中间拉伸的办法有如下两种: 第一种方法很简单而且使用性更广.做法就是直接拉伸想要setBackgroundImage的image,代码如下: UIImag ...

  7. jQuery之Deferred对象详解

    deferred对象是jQuery对Promises接口的实现.它是非同步操作的通用接口,可以被看作是一个等待完成的任务,开发者通过一些通过的接口对其进行设置.事实上,它扮演代理人(proxy)的角色 ...

  8. 基于Qt的遥感图像处理软件设计总结

     开发工具 VS2008+Qt4.8.0+GDAL1.9  要点 接口要独立,软件平台与算法模块独立,平台中各接口设计灵活,修改时容易. 设计软件时一步步来,每个功能逐一实现,某个功能当比较独立时可以 ...

  9. 十大免费教程资源帮助新手快速学习JavaScript

    “JavaScript”的名头相信大家肯定是耳熟能详,但只有一小部分人群了解它的使用与应用程序构建方式.这“一小部分”人指的当然是技术过硬的有为青年.网络程序员以及IT专业人员.但对于一位新手或者说外 ...

  10. 使用 jackson 解析 json 演示样例

    首先须要下载3个包,下载地址在Github FasterXML,这三个核心模块各自是: Streaming ("jackson-core") defines low-level s ...