初阶数据结构——“堆”

目录

前言:

1.树

1.1树的概念

 1.2树的表示

2.二叉树

2.1二叉树的概念和结构

 2.2两种特殊的二叉树

    2.3二叉树的性质

2.4二叉树的存储结构

1.顺序存储

2.链式存储

2.5二叉树的顺序结构及其实现

1.二叉树的顺序结构

2.堆的概念及其结构

3.堆的创建

4.堆的插入  

5.堆的删除

6.后续接口及其实现方法


前言:

本文的关键点:

  • 介绍了树的概念和表示方式。
  • 讨论了二叉树及其概念、结构、特殊类型、性质。
  • 探讨了二叉树的存储结构,包括顺序存储和链式存储。
  • 讲解了二叉树的顺序结构以及堆的概念和结构。
  • 解释了堆的创建、插入和删除操作。

1.树

1.1树的概念

        树是一种数据结构,它是由n(n≥0)个有限节点组成一个具有层次关系的集合。把它叫做“树”是因为它看起来像一棵倒挂的树。树的结构大概是这样的

  • 下面是几个重要的概念
  • 节点的度:一个节点含有的子树的个数称为该节点的度;
  • 叶节点:度为0的节点称为叶节点;
  • 非终端节点或分支节点:度不为0的节点;
  • 双亲节点或父节点:若一个节点含有子节点,则这个节点称为其子节点的父节点;
  • 孩子节点或子节点:一个节点含有的子树的根节点称为该节点的子节点; 
  • 兄弟节点:具有相同父节点的节点互称为兄弟节点;
  • 树的度:一棵树中,最大的节点的度称为树的度;
  • 节点的层次:从根开始定义起,根为第1层,根的子节点为第2层,以此类推;
  • 树的高度或深度:树中节点的最大层次; 
  • 堂兄弟节点:双亲在同一层的节点互为堂兄弟;
  • 节点的祖先:从根到该节点所经分支上的所有节点;
  • 子孙:以某节点为根的子树中任一节点都称为该节点的子孙。
  • 森林:由m棵互不相交的树的集合称为森林;

值得注意的是,树的子节点之间是不相交的

 1.2树的表示

树的结构相对于前面所认识的几种线性表来说是较为复杂的,在树的存储中我们既要存储数据域,又要存储节点与节点之间的关系,那么我们应该如何表示一棵树呢?下面小编便带大家一起了解一下“树”最常用的一种表示方法,即:“左孩子右兄弟”如下:

我们按照这种结构来实现,无论一个父节点有多少个孩子,child指向左边的第一个孩子, 其余的孩子由第一个孩子的兄弟节点来遍历, 这样每个节点都可以遍历查询到

typedef int DataType;
struct Node
{
 struct Node* firstChild1; // 第一个孩子结点
 struct Node* pNextBrother; // 指向其下一个兄弟结点
 DataType data; // 结点中的数据域
};

下面我们来介绍一种特殊的树即为“二叉树”

2.二叉树

2.1二叉树的概念和结构

概念:

一棵二叉树是结点的一个有限集合,该集合:

  1.  或者为空
  2.  由一个根结点加上两棵别称为左子树和右子树的二叉树组成

结构: 

        如下图

任意二叉树都是由以下几种情况复合而成

 2.2两种特殊的二叉树

 1. 满二叉树:一个二叉树,如果每一个层的结点数都达到最大值,则这个二叉树就是满二叉树。也就是 说,如果一个二叉树的层数为K,且结点总数是,则它就是满二叉树。

2. 完全二叉树:完全二叉树是效率很高的数据结构,完全二叉树是由满二叉树而引出来的。对于深度为K 的,有n个结点的二叉树,当且仅当其每一个结点都与深度为K的满二叉树中编号从1至n的结点一一对 应时称之为完全二叉树。 要注意的是满二叉树是一种特殊的完全二叉树。

    2.3二叉树的性质

  1. 若规定根结点的层数为1,则一棵非空二叉树的第i层上最多有 2^(i-1) 个结点.
  2. 若规定根结点的层数为1,则深度为h的二叉树的最大结点数是2^h -1.
  3. 对任何一棵二叉树, 如果度为0其叶结点个数为n0 , 度为2的分支结点个数为n2 ,则有n0 =n2 +1
  4.  若规定根结点的层数为1,具有n个结点的满二叉树的深度,h= . (ps:是log以2 为底,n+1为对数)
  5. 对于具有n个结点的完全二叉树,如果按照从上至下从左至右的数组顺序对所有结点从0开始编号,则对 于序号为i的结点有:

                  1. 若i>0,i位置结点的双亲序号:(i-1)/2;i=0,i为根结点编号,无双亲结点

                  2. 若2i+1=n否则无左孩子

                  3. 若2i+2=n否则无右孩子

2.4二叉树的存储结构

二叉树一般可以使用两种结构存储,一种顺序结构,一种链式结构

1.顺序存储

顺序结构存储就是使用数组来存储,一般使用数组只适合表示完全二叉树,因为不是完全二叉树会有空 间的浪费。而现实中使用中只有堆才会使用数组来存储,二叉树顺 序存储在物理上是一个数组,在逻辑上是一颗二叉树。如下图:

2.链式存储

二叉树的链式存储结构是指,用链表来表示一棵二叉树,即用链来指示元素的逻辑关系。 通常的方法是 链表中每个结点由三个域组成,数据域和左右指针域,左右指针分别用来给出该结点左孩子和右孩子所 在的链结点的存储地址 。链式结构又分为二叉链和三叉链,当前我们学习中一般都是二叉链

typedef int BTDataType;
 // 二叉链
struct BinaryTreeNode
 {
    struct BinTreeNode* left;   // 指向当前结点左孩子
    struct BinTreeNode* right;  // 指向当前结点右孩子
    BTDataType data;            // 当前结点值域
}
 
// 三叉链
struct BinaryTreeNode
 {
    struct BinTreeNode* parent; // 指向当前结点的双亲
    struct BinTreeNode* left;   // 指向当前结点左孩子
    struct BinTreeNode* right;  // 指向当前结点右孩子
    BTDataType data;            // 当前结点值域
}

2.5二叉树的顺序结构及其实现

1.二叉树的顺序结构

普通的二叉树是不适合用数组来存储的,因为可能会存在大量的空间浪费。而完全二叉树更适合使用顺序结 构存储。现实中我们通常把堆(一种二叉树)使用顺序结构的数组来存储,需要注意的是这里的堆和操作系统 虚拟进程地址空间中的堆是两回事,一个是数据结构,一个是操作系统中管理内存的一块区域分段。例如下图左侧为完全二叉树的存储方式,右侧为普通二叉树的存储方式:

2.堆的概念及其结构

堆就是以二叉树的顺序存储方式来存储元素,同时又要满足父亲结点存储数据都要大于儿子结点存储数据(也可以是父亲结点数据都要小于儿子结点数据)的一种数据结构。堆只有两种即大堆和小堆,大堆就是父亲结点数据大于儿子结点数据,小堆则反之。

堆的性质:
堆中某个节点的值总是不大于或不小于其父节点的值;
堆总是一棵完全二叉树

3.堆的创建

下面我们给出一个数组,这个数组逻辑上可以看做一颗完全二叉树,但是还不是一个堆,现在我们通过算 法,把它构建成一个堆。根结点左右子树不是堆,我们怎么调整呢?这里我们从倒数的第一个非叶子结点的 子树开始调整,一直调整到根结点的树,就可以调整成堆。

int a[] = {1,5,3,8,7,6}; 

这里附上向上调整和向下调整两个代码:

void AdjustUp(HPDataType* a, int child) {
	int parent=(child-1)/2;
	while (child > 0) {
		if (a[child] > a[parent]) {
			Swap(&a[child], &a[parent]);
			child = parent;
			parent= (child - 1) / 2;
		}
		else {
			break;
		}
	}
}

void AdjustDown(HPDataType*a,int n,int parent) {
	int child = parent * 2 + 1;
	while (child < n) {
		if (child + 1 < n && a[child] > a[child + 1]) {
			++child;
		}
		if (a[parent] > a[child]) {
			Swap(&a[parent], &a[child]);
			parent = child;
			child= parent * 2 + 1;
		}
		else {
			break;
		}
	}
}

4.堆的插入  

我们直接将数据插入到堆的末尾,然后对该数据进行向上调整算法直至满足堆的概念

void HeapPush(Heap* hp, HPDataType x) {
	if (hp->_capacity == hp->_size) {
		HPDataType* ptr;
		//在这里用三目操作符来防止传过来的顺序表的内存空间为零
		int newcapacity = hp->_capacity == 0 ? 4 : 2 * hp->_capacity;
		ptr = (HPDataType*)realloc(hp->_a, newcapacity * sizeof(HPDataType));
		if (ptr == NULL) {
			perror(realloc);
			exit(1);
		}
		hp->_a = ptr;
		hp->_capacity = newcapacity;
	}
	hp->_a[hp->_size] = x;
	hp->_size++;
	AdjustUp(hp->_a, hp->_size - 1);
}

5.堆的删除

删除堆是删除堆顶的数据,将堆顶的数据根最后一个数据一换,然后删除数组最后一个数据,再进行向下调整算法。

void HeapPop(Heap* hp) {
	assert(hp);
	assert(hp->_size > 0);
	Swap(&hp->_a[0], &hp->_a[hp->_size - 1]);
	hp->_size--;
	AdjustDown(hp->_a, hp->_size, 0);
}

6.后续接口及其实现方法

//堆的初始化
void HeapInit(Heap* hp) {
	hp->_a = NULL;
	hp->_capacity = hp->_size = 0;
}
// 堆的销毁
void HeapDestory(Heap* hp) {
	free(hp->_a);
	hp->_capacity = hp->_size = 0;
}
// 取堆顶的数据
HPDataType HeapTop(Heap* hp) {
	assert(hp);
	return hp->_a[0];
}
// 堆的数据个数
int HeapSize(Heap* hp) {
	assert(hp);
	return hp->_size;
}
// 堆的判空
bool HeapEmpty(Heap* hp) {
	assert(hp);
	return hp->_size = 0;
}

完。

评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值