首页 > 其他分享 >数据结构-二叉树(顺序结构)

数据结构-二叉树(顺序结构)

时间:2024-07-27 18:58:49浏览次数:19  
标签:arr php parent int 顺序 二叉树 child 数据结构 size

引言

顺序结构存储就是使⽤数组来存储,⼀般只适合表⽰完全⼆叉树,因为不是完全⼆叉树会有空间的浪费,完全⼆叉树更适合使⽤顺序结构存储。

一、堆的概念

将一个元素集合k里,所有数据按照完成二叉树的顺序存储方式存储。
并且数组中的元素,满足以下关系

i = 0、1、2... ,则称为⼩堆(或⼤堆)。将根结点最⼤的堆叫做最⼤堆或⼤根堆,根结点最⼩的堆
叫做最⼩堆或⼩根堆。

文字的描述比较抽象,以下为图例

二、堆的性质

堆是⼀种特殊的⼆叉树,具有⼆叉树的特性的同时,还具备其他的特性:

        • 堆中某个结点的值总是不⼤于或不⼩于其⽗结点的值;
        • 堆总是⼀棵完全⼆叉树

而二叉树有具有以下性质:

        1.若 i>0 , i 位置结点的双亲序号: (i-1)/2 ; i=0 , i 为根结点编号,⽆双亲结点
        2. 若 2i+1<n ,左孩⼦序号: 2i+1 , 2i+1>=n 否则⽆左孩⼦
        3. 若 2i+2<n ,右孩⼦序号: 2i+2 , 2i+2>=n 否则⽆右孩⼦

由上述所知,我们可以通过堆的数组中的下标来搜索到其子节点与双亲结点,由此我们便可以实现二叉树的顺序存储,即堆的结构

三、堆的实现

堆的实现分为以下三个部分(这里我们实现的小堆)

1.定义堆的结构

同上所述,堆的实现使用的是二叉树的顺序存储结构,其底层依然是数组,所以与顺序表的定义大致类似。因此不过多赘述。

typedef int HPDatatype;

typedef struct Heap
{
	HPDatatype* arr;
	int size;
	int capacity;
}HP;

2.堆的初始化与销毁

基于顺序结构实现的堆,将指向堆的变量的地址传递过来使用一级指针接收,实现形参的改变影响到实参。

初始化堆,只需对其指针置空、空间大小和栈顶置0即可。

void HPInit(HP* php)
{
	assert(php);
	php->arr = NULL;
	php->capacity = php->size = 0;
}

销毁堆:堆的空间是使用函数动态开辟的,那他得使用对应得free对空间进行释放,让后将堆的空间大小和,size置0即可。

void HPDestroy(HP* php)
{
	assert(php);
	if (php->arr)
		free(php->arr);
	php->arr = NULL;
	php->capacity = php->size = 0;
}

 3.向上调整算法

堆中的数据有着严格的要求,而我们堆的底层是一个数组,往堆中插入数据一般是往数组的末端进行插入,但是插入之后堆的顺序就乱了,因此我们还需在插入数据之后对堆中的元素进行调整,使其重新变为一个堆,这其中对堆中的元素进行调整的算法,我们成为向上调整算法。

 我们需要接收栈底层的数组,以及型加入的孩子节点的下标,将新插入的孩子节点与父亲节点进行比较大小,若孩子节点小于父亲节点就交换顺序,而新的父亲节点又是前一个节点的孩子节点,同样需要判断其大小,然后交换顺序,若父亲节点小于孩子节点的话就不需要继续循环下去使用break跳出。

因为后续还会频繁使用到交换结点的步骤,我们将其封装成一个函数Swap

void Swap(int* x, int* y)
{
	int temp = *x;
	*x = *y;
	*y = temp;
}
void Adjustup(HPDatatype* arr, int child)
{
	int parent = (child - 1) / 2;
	while (child>0)
	{
		if (arr[parent] >arr[child])
		{
			Swap(&arr[parent], &arr[child]);
			child = parent;
			parent = (child - 1) / 2;
		}
		else
		{
			break;
		}
	}
}

4.堆的插入

首先需要判断一下有效数据个数(php->size)和空间大小是否相等,所以使用if语句,若两者想的,那么则说明需要扩容,否则不需要

在扩容操作首先需要判断当前的栈是否为空栈,若是空栈我们则需要先给其一片固定的空间大小,若不是空栈则继续扩容,因此我们使用三木操作符进行操作。

在开辟内存时一般使用realloc函数开辟,增容到原空间的2倍可以减少扩容操作的频率。如果每次只增加少量空间,那么在元素数量增长时,需要频繁进行扩容操作,这会降低性能。

若开辟成功则将temp赋给php->arr,以及空间大小的更新,否则打印报错信息。

而后就要插入的数据加入堆,此时新加入的数据处于我们的堆底,因此我们还需要调用向上调整算法--函数Adjustup

最后不要忘记堆的有效数据php->size++

void HPPush(HP* php, HPDatatype x)
{
	assert(php);
	if (php->size == php->capacity)
	{
		int newcapacity = php->capacity == 0 ? 4 : 2 * php->capacity;
		HPDatatype* temp = (HPDatatype*)realloc(php->arr, sizeof(HPDatatype) * newcapacity);
		if(temp==NULL)
		{
			perror("realloc fail!");
			exit(1);
		}
		php->arr = temp;
		php->capacity = newcapacity;
	}
	php->arr[php->size] = x;
	Adjustup(php->arr, php->size);
	php->size++;
}

5.向下调整算法

在出堆操作时,我们也不能随便出堆,否则堆里的元素就乱套了,我们一般出的是堆顶的元素,因为对顶的元素为堆中的极值。

但是我们该如何将堆顶的元素出堆呢,如果直接将后面的元素前移一位,那么堆的元素顺序则会乱套,所以我们选择将堆顶的元素与堆底的元素互换,在使得size--;这样我们便将堆顶的元素从堆底出堆,同时也保证了堆中的元素除了堆顶都未发生改变。

然而这样还不是一个真正的堆,因为此时我们的堆顶元素为原先的堆底,所以我们要实现一个操作,将堆顶元素进行调整使其重新变为一个堆

向上调整算法是向上找父亲节点,而向下调整算法是向下找孩子节点,通过传递过来的参数 parent 向下找孩子结点。

我们无法确定双亲结点的两个子节点谁大谁小,因此在对孩子节点与父亲节点比较大小交换之前需要比较左孩子节点和右孩子节点arr[child] > arr[child + 1],若左孩子节点大于右孩子,那child就需要加1,同时为了防止child+1后不满足 child < n从而在后续的交换里导致数组越界访问,所以在if语句里还需要加上一条判断 child + 1 < n。

执行子节点与父节点交换的if语句里,在交换完两个节点后需要更新新的父节点,和子节点来比较,判断是否存在比新的父节点还小的值。最后若子节点大于父节点,那就说明不需要交换,使用break跳出循环即可。

void Adjustdown(HPDatatype* arr, int parent, int size)
{
	int child = 2 * parent + 1;
	while (child<size)
	{
		if (child+1<size && arr[child] > arr[child + 1])
		{
			child++;
		}
		if (arr[child] < arr[parent])
		{
			Swap(&arr[child], &arr[parent]);
			parent = child;
			child = parent * 2 + 1;
		}
		else
		{
			break;
		}
	}
}

6.堆的删除

在第五点中我们便提到,堆的删除一般的为删除堆顶元素

首先我们需要将其堆底元素和堆顶元素进行交换,之后将php->size--实现元素的出堆,而后我们再使用向下调整算法进行堆的调整使其重新变为一个堆即可

void HPPop(HP* php)
{
	assert(php);
	assert(!HPEmpty(php));
	Swap(&php->arr[0], &php->arr[php->size - 1]);
	php->size--;
	Adjustdown(php->arr, 0, php->size);
}

7.取堆顶数据

HPDatatype HPTop(HP* php)
{
	assert(php && php->size);
	return php->arr[0];
}

 8.堆的有效数据个数

int HPSize(HP* php)
{
	assert(php);
	return php->size;
}

四、源码

Heap.h

#pragma once
#include<stdio.h>
#include<assert.h>
#include<stdlib.h>
#include<stdbool.h>

typedef int HPDatatype;

typedef struct Heap
{
	HPDatatype* arr;
	int size;
	int capacity;
}HP;

//堆的初始化
void HPInit(HP* php);
// 堆的销毁
void HPDestroy(HP* php);
//向上调整
void Adjustup(HPDatatype* arr, int child);
// 堆的插入
void HPPush(HP* php, HPDatatype x);
//堆的判空
bool HPEmpty(HP* php);
//向下调整
void Adjustdown(HPDatatype* arr, int parent, int size);
// 堆的删除
void HPPop(HP* php);
// 取堆顶的数据
HPDatatype HPTop(HP* php);
// 堆的数据个数
int HPSize(HP* php);

 Heap.c

#define _CRT_SECURE_NO_WARNINGS 1
#include"Heap.h"
void HPInit(HP* php)
{
	assert(php);
	php->arr = NULL;
	php->capacity = php->size = 0;
}
void HPDestroy(HP* php)
{
	assert(php);
	if (php->arr)
		free(php->arr);
	php->arr = NULL;
	php->capacity = php->size = 0;
}
void Swap(int* x, int* y)
{
	int temp = *x;
	*x = *y;
	*y = temp;
}
void Adjustup(HPDatatype* arr, int child)
{
	int parent = (child - 1) / 2;
	while (child>0)
	{
		if (arr[parent] >arr[child])
		{
			Swap(&arr[parent], &arr[child]);
			child = parent;
			parent = (child - 1) / 2;
		}
		else
		{
			break;
		}
	}
}
void HPPush(HP* php, HPDatatype x)
{
	assert(php);
	if (php->size == php->capacity)
	{
		int newcapacity = php->capacity == 0 ? 4 : 2 * php->capacity;
		HPDatatype* temp = (HPDatatype*)realloc(php->arr, sizeof(HPDatatype) * newcapacity);
		if(temp==NULL)
		{
			perror("realloc fail!");
			exit(1);
		}
		php->arr = temp;
		php->capacity = newcapacity;
	}
	php->arr[php->size] = x;
	Adjustup(php->arr, php->size);
	php->size++;
}
bool HPEmpty(HP* php)
{
	assert(php);
	return php->size == 0;
}
void Adjustdown(HPDatatype* arr, int parent, int size)
{
	int child = 2 * parent + 1;
	while (child<size)
	{
		if (child+1<size && arr[child] > arr[child + 1])
		{
			child++;
		}
		if (arr[child] < arr[parent])
		{
			Swap(&arr[child], &arr[parent]);
			parent = child;
			child = parent * 2 + 1;
		}
		else
		{
			break;
		}
	}
}
void HPPop(HP* php)
{
	assert(php);
	assert(!HPEmpty(php));
	Swap(&php->arr[0], &php->arr[php->size - 1]);
	php->size--;
	Adjustdown(php->arr, 0, php->size);
}
HPDatatype HPTop(HP* php)
{
	assert(php && php->size);
	return php->arr[0];
}
int HPSize(HP* php)
{
	assert(php);
	return php->size;
}

test.c

#define _CRT_SECURE_NO_WARNINGS 1
#include"Heap.h"
void Testhp()
{
	HP hp;
	HPInit(&hp);
	int arr[]= { 17,20,10,13,19,15,18,16};
	for (int i = 0; i < 8; i++)
	{
		HPPush(&hp, arr[i]);
	}
	//HPPop(&hp);
	for (int i = 0; i < hp.size; i++)
	{
		printf("%d ", hp.arr[i]);
	}
	//while (hp.size != 0)
	//{
	//	printf("%d ", HPTop(&hp));
	//	HPPop(&hp);
	//}
	HPDestroy(&hp);
}
int main()
{
	Testhp();
	return 0;
}

标签:arr,php,parent,int,顺序,二叉树,child,数据结构,size
From: https://blog.csdn.net/2302_81813630/article/details/140698584

相关文章

  • 数据结构:顺序表
    顺序表的概述与实现顺序表(SequentialList)是计算机科学中一种常用的数据结构,其特点是用一段连续的存储单元依次存储数据元素。顺序表的底层实现通常采用数组,但与数组不同的是,顺序表封装了对数据的插入、删除、查找等操作,使其使用起来更加灵活和方便。本文将详细介绍顺序表的概......
  • 数据结构:算法复杂度
    目录前言数据结构和算法的基本概念数据结构和算法的重要性衡量算法的好坏时间复杂度空间复杂度例子分析例子1:冒泡排序例子2:对数时间复杂度总结前言在编程学习中,理解数据结构和算法是至关重要的。这不仅是计算机科学的基础知识,也是解决复杂问题和优化代码效率的关......
  • EEG数据结构
    基本数据集信息:EEG.setname-数据集的描述性名称/标题EEG.filename-磁盘上数据集文件的文件名EEG.filepath–数据集文件的文件路径(目录/文件夹EEG.trials-数据集中的历时(或试验)数。如果数据是连续的,则该数字为1。EEG.pnts-每次试验(历元)的时间点(或数据帧)数。如......
  • 数据结构篇——栈的操作实现(顺序栈、链栈)!
    一:前言对于栈的操作,虽不及其他数据结构一样多,但是栈的实际应用却是十分广泛。比如在我们进行代码编写的编译器中,对于函数调用、递归操作、表达式求值以及编译器的括号匹配等问题均是通过反复的入栈和出栈操作进行控制的。栈结构在计算机科学的历史上,地位是举重若轻的,值得我们......
  • 简单的数据结构:栈
    1.栈的基本概念1.1栈的定义栈是一种线性表,只能在一端进行数据的插入或删除,可以用数组或链表来实现,这里以数组为例进行说明栈顶 :数据出入的那一端,通常用Top表示栈底:相对于栈顶的另一端,也是固定的一端,不允许数据的插入和删除空栈:不含数据的栈1.2栈的基本操作栈的初始......
  • leetcode105. 从前序与中序遍历序列构造二叉树,步骤详解附代码
    leetcode105.从前序与中序遍历序列构造二叉树给定两个整数数组preorder和inorder,其中preorder是二叉树的先序遍历,inorder是同一棵树的中序遍历,请构造二叉树并返回其根节点。示例1:输入:preorder=[3,9,20,15,7],inorder=[9,3,15,20,7]输出:[3,9,20,nul......
  • 数据结构(顺序表)
     ......
  • 【数据结构】二叉树
    目录二叉树特殊二叉树二叉树的性质二叉树的模拟实现存储结构接口实现内部类遍历前序遍历中序遍历后序遍历遍历确定二叉树层序遍历获取节点个数获取叶子节点的个数获取第K层节点的个数获取二叉树的高度检测值为value的元素是否存在判断一棵树是不是完全二叉树练习链接......
  • 数据结构基础第7讲
    数据结构基础第7讲查找考点一:查找的基本概念1.概念静态查找动态查找分类2.查找性能计算平均查找长度:考点二:顺序查找1.顺序查找2.优劣考点三:折半查找(二分搜索)1.概念2.过程3.构建为判定树构建向上取整:左少右多向右偏向下取整:左多右少......
  • 数据结构基础第8讲
    数据结构基础第8讲排序考点一:排序的概念和性能分析1.排序的概念稳定性根据相对位置是否改变判断内排序2.排序的性能考点二:插入类排序1.直接插入排序\(复杂度O(n^2)\)3.折半插入排序改进了比较次数未改变移动次数,因此复杂度仍为\(O(n^2)\)3.希尔排序时......