《数据结构复习笔记》--堆

把堆的相关知识在复习一下。加深理解

堆排序快速排序归并排序一样都是时间复杂度为O(N*logN)的几种常见排序方法。学习堆排序前,
先来了解一下二叉堆。

二叉堆的定义

二叉堆是完全二叉树或者是近似完全二叉树。

二叉堆满足二个特性:

1.父结点的键值总是大于或等于(小于或等于)任何一个子节点的键值。

2.每个结点的左子树和右子树都是一个二叉堆(都是最大堆或最小堆)。

当父结点的键值总是大于或等于任何一个子节点的键值时为最大堆。当父结点的键值总是小于或等于任何一个子节点的键值时为最小堆。下图展示一个最小堆:

由于其它几种堆(二项式堆,斐波纳契堆等)用的较少,一般将二叉堆就简称为堆。

堆的存储

一般都用数组来表示堆,i结点的父结点下标就为(i – 1) / 2。它的左右子结点下标分别为2 * i + 1和2 * i + 2。如第0个结点左右子结点下标分别为1和2。

创建堆:

typedef struct HeapStruct *MaxHeap;//创建最大堆
struct HeapStruct
{
    ElementType *Elements; /* 存储堆元素的数组 */
    int Size; /* 堆的当前元素个数 */
    int Capacity; /* 堆的最大容量 */
};
MaxHeap Create( int MaxSize )
{
    /* 创建容量为MaxSize的空的最大堆 */
    MaxHeap H = malloc( sizeof( struct HeapStruct ) );
    H->Elements = malloc( (MaxSize+1) * sizeof(ElementType));
    H->Size = 0;
    H->Capacity = MaxSize;
    H->Elements[0] = MaxData;/*把MaxData换成小于堆中所有元素的MinData,同样适用于创建最小堆*/
    /* 定义“哨兵”为大于堆中所有可能元素的值,便于以后更快操作 */
    return H;
}

堆的操作——插入删除

<span style="font-size:12px;">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 插入 */
}

删除
ElementType DeleteMax( MaxHeap H )//最大堆的删除
{
    /* 从最大堆H中取出键值为最大的元素,并删除一个结点 */
    int Parent, Child;
    ElementType MaxItem, temp;
    if ( IsEmpty(H) )
    {
        printf("最大堆已为空");
        return;
    }
    MaxItem = H->Elements[1]; /* 取出根结点最大值 */
    /* 用最大堆中最后一个元素从根结点开始向上过滤下层结点 */
    temp = H->Elements[H->Size--];
    for( Parent=1; Parent*2<=H->Size; Parent=Child )
    {
        Child = Parent * 2;
        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;
}</span>

图解:

至此,可以得出最大堆的建立:

方法1:通过插入操作,将N个元素一个个相继插入到一个初始为空的堆中去,其时间代价最大为O(N logN)。

建立最大堆:将已经存在的N个元素按最大堆的要求存放在一个一维数组中。

方法2:在线性时间复杂度下建立最大堆。

(1)将N个元素按输入顺序存入,先满足完全二叉树的结构特性。

(2)调整各结点位置,以满足最大堆的有序特性。

堆排序

这里直接引用维基百科:比自己表达更全面。

堆排序算法的演示。首先,将元素进行重排,以符合堆的条件。图中排序过程之前简单的绘出了堆树的结构。

首先可以看到堆建好之后堆中第0个数据是堆中最小的数据。取出这个数据再执行下堆的删除操作。这样堆中第0个数据又是堆中最小的数据,重复上述步骤直至堆中只有一个数据时就直接取出这个数据。

由于堆也是用数组模拟的,故堆化数组后,第一次将A[0]与A[n - 1]交换,再对A[0…n-2]重新恢复堆。第二次将A[0]与A[n – 2]交换,再对A[0…n - 3]重新恢复堆,重复这样的操作直到A[0]与A[1]交换。由于每次都是将最小的数据并入到后面的有序区间,故操作完成后整个数组就有序了,有点类似选择排序。

#include <iostream>
using namespace std;
/*
	#堆排序#%
          #数组实现#%
*/
//#筛选算法#%
void sift(int d[], int ind, int len)
{
	//#置i为要筛选的节点#%
	int i = ind;

	//#c中保存i节点的左孩子#%
	int c = i * 2 + 1; //#+1的目的就是为了解决节点从0开始而他的左孩子一直为0的问题#%

	while(c < len)//#未筛选到叶子节点#%
	{
		//#如果要筛选的节点既有左孩子又有右孩子并且左孩子值小于右孩子#%
		//#从二者中选出较大的并记录#%
		if(c + 1 < len && d[c] < d[c + 1])
			c++;
		//#如果要筛选的节点中的值大于左右孩子的较大者则退出#%
		if(d[i] > d[c]) break;
		else
		{
			//#交换#%
			int t = d[c];
			d[c] = d[i];
			d[i] = t;
			//
			//#重置要筛选的节点和要筛选的左孩子#%
			i = c;
			c = 2 * i + 1;
		}
	}

	return;
}

void heap_sort(int d[], int n)
{
	//#初始化建堆, i从最后一个非叶子节点开始#%
	for(int i = (n - 2) / 2; i >= 0; i--)
		sift(d, i, n);

	for(int j = 0; j < n; j++)
	{
                //#交换#%
		int t = d[0];
		d[0] = d[n - j - 1];
		d[n - j - 1] = t;

		//#筛选编号为0 #%
		sift(d, 0, n - j - 1);

	}
}

int main()
{
	int a[] = {3, 5, 3, 6, 4, 7, 5, 7, 4}; //#QQ#%

	heap_sort(a, sizeof(a) / sizeof(*a));

	for(int i = 0; i < sizeof(a) / sizeof(*a); i++)
	{
		cout << a[i] << ' ';
	}
	cout << endl;
    return 0;
}

最坏情况下,时间复杂度:

,另外,注意,堆排序是一种不稳定的排序。

时间: 2024-10-07 08:03:42

《数据结构复习笔记》--堆的相关文章

数据结构复习笔记--数组

最后还是决定在未来的道路上走向软件开发者这条路,从现在重新复习数据结构和算法. 关于数组有几个比较有意思的特点. 1.对于数组 int List[3],编译器将List[i]解释为指向一个地址为List + i*sizeof(int)的整数的指针. 对于int * List, int *List2[5],两个都是指向int 类型的变量,但是编译器会为后者分配五个整数存储空间. List2实际是上指向List2[0],List2 + i 实际上是&List2[i].在C语言中是不需要加上偏移量的.

《数据结构复习笔记》--哈夫曼树,哈夫曼编码

先来了解一下哈夫曼树. 带权路径长度(WPL):设二叉树有n个叶子结点,每个叶子结点带有权值 wk,从根结点到每个叶子结点的长度为 lk,则每个叶子结点的带权路径长度之和就是: 最优二叉树或哈夫曼树: WPL最小的二叉树. [例]有五个叶子结点,它们的权值为{1,2,3,4,5},用此权值序列可以构造出形状不同的多个二叉树. 其中结果wpl最小值的是:33=(1+2)*3+(3)*2+(4+5)*2: 哈夫曼树的构造: 每次把权值最小的两棵二叉树合并, 代码: typedef struct Tr

《数据结构复习笔记》--二叉搜索树

二叉搜索树:维基百科:click here 二叉查找树(Binary Search Tree),也称二叉搜索树.有序二叉树(ordered binary tree),排序二叉树(sorted binary tree),是指一棵空树或者具有下列性质的二叉树: 若任意节点的左子树不空,则左子树上所有结点的值均小于或等于它的根结点的值: 任意节点的右子树不空,则右子树上所有结点的值均大于它的根结点的值: 任意节点的左.右子树也分别为二叉查找树. 没有键值相等的节点(no duplicate nodes

2014年软考程序员-常考知识点复习笔记【第五章】

51CTO学院,在软考备考季特别整理了"2014年软考程序员-常考知识点复习笔记[汇总篇]",帮助各位学院顺利过关!更多软件水平考试辅导及试题,请关注51CTO学院-软考分类吧! 查看汇总:2014年软考程序员-常考知识点复习笔记[汇总篇]  内部排序 考查你对书本上的各种排序算法及其思想以及其优缺点和性能指标(时间复杂度)能否了如指掌. 排序方法分类有:插入.选择.交换.归并.计数等五种排序方法. (1)插入排序中又可分为:直接插入.折半插入.2路插入(?).希尔排序.这几种插入排序

[Java基础] Java线程复习笔记

先说说线程和进程,现代操作系统几乎无一例外地采用进程的概念,进程之间基本上可以认为是相互独立的,共享的资源非常少.线程可以认为是轻量级的进 程,充分地利用线程可以使得同一个进程中执行多种任务.Java是第一个在语言层面就支持线程操作的主流编程语言.和进程类似,线程也是各自独立的,有自 己的栈,自己的局部变量,自己的程序执行并行路径,但线程的独立性又没有进程那么强,它们共享内存,文件资源,以及其他进程层面的状态等.同一个进程内的 多个线程共享同样的内存空间,这也就意味着这些线程可以访问同样的变量和

Java基础复习笔记系列 四

Java基础复习笔记系列之 数组 1.数组初步介绍? Java中的数组是引用类型,不可以直接分配在栈上.不同于C(在Java中,除了基础数据类型外,所有的类型都是引用类型.) Java中的数组在申明时,不能指定其长度.不同于C 数组的小标可以是整型常量或整型表达式. .length方法是显示数组的长度:数组.length;String.length();一个是属性,一个是方法.注意区分. 2.数组的内存分析? Array a[]; a = new Integer(15); /*这个a在栈中,在堆

Java基础复习笔记系列 五 常用类

Java基础复习笔记系列之 常用类 1.String类介绍. 首先看类所属的包:java.lang.String类. 再看它的构造方法: 2. String s1 = “hello”: String s2 = “hello”:结论:s1 == s2. 字符串常量放在data区. 3. String s3 = new String("hello"); String s4 = new String("hello");结论:s3 != s4.但s3.equals(s4).

2014年软考程序员-常考知识点复习笔记【第二章】

51CTO学院,在软考备考季特别整理了"2014年软考程序员-常考知识点复习笔记[汇总篇]",帮助各位学院顺利过关!更多软件水平考试辅导及试题,请关注51CTO学院-软考分类吧! 查看汇总:2014年软考程序员-常考知识点复习笔记[汇总篇]  二叉树三种遍历的非递归算法(背诵版) 1.先序遍历非递归算法 #define maxsize 100 typedef struct { Bitree Elem[maxsize]; int top; }SqStack; void PreOrderU

2014年软考程序员-常考知识点复习笔记【第三章】

51CTO学院,在软考备考季特别整理了"2014年软考程序员-常考知识点复习笔记[汇总篇]",帮助各位学院顺利过关!更多软件水平考试辅导及试题,请关注51CTO学院-软考分类吧! 查看汇总:2014年软考程序员-常考知识点复习笔记[汇总篇]  2.线性表 (1) 性表的链式存储方式及以下几种常用链表的特点和运算:单链表.循环链表,双向链表,双向循环链表. (2)单链表的归并算法.循环链表的归并算法.双向链表及双向循环链表的插入和删除算法等都是较为常见的考查方式. (3)单链表中设置头指