【数据结构】二叉树顺序实现(大堆)-->赋源码

简介: 【数据结构】二叉树顺序实现(大堆)-->赋源码

前言

在前面的顺序表链表、都是线性表。今天的所介绍的二叉树是一种非线性数据结构。

树的概念以及介绍

定义

树是一种非线性的数据结构,它由n(n>0)个有限结点组成一个具有层次关系的集合。

image.png

树的相关概念(类比人类血缘关系)

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

二叉树的概念

二叉树是树的一种特殊形式,其中每个节点最多有两个子节点,分别称为左子节点和右子节点。

满二叉树

满二叉树的定义

1.满二叉树是一种特殊的二叉树,它的特点是每一层的节点数都达到最大值,即每一层的节点数都恰好为2的幂次(除了最后一层以外)。

2.如果满二叉树的深度为k,那么它的节点总数为2^k - 1。在满二叉树中,除了最后一层的节点之外,其余每一层的节点都是完全填满的,而且最后一层的节点也是从左到右连续排列的

满二叉树性质
  1. 满二叉树的深度为k,节点总数为2^k - 1。
  2. 满二叉树的每一层的节点数都达到最大值,即每一层的节点数为2^(i-1),其中i是层数。
  3. 满二叉树的叶子节点全部位于最后一层。
  4. 满二叉树的节点要么是叶子节点(度为0),要么是度为2的节点,不存在度为1的节点

如下图:

image.png

完全二叉树

完全二叉树的定义
  1. 除了最后一层外,其他每一层的节点数都达到最大数量,即该层的节点数等于2的幂减去1。
  2. 最后一层的节点都集中在最左边,并且右边的节点可以少,但不能有空位。
完全二叉树的性质

1.完全二叉树的节点编号从1开始,对于编号为i的节点,其左子节点的编号为2i,右子节点的编号为2i+1。

2.如果一棵完全二叉树有n个节点,那么它的深度大约为log2(n)+1。

3.完全二叉树的特点是叶子节点只能出现在最下层和次下层,且最下层的叶子节点集中在树的左部。

image.png

二叉树的顺序实现

堆的定义

堆是一种特殊的完全二叉树,它可以被视为一种特殊的顺序表。

堆的分类

堆的特点是父节点的值总是大于或小于其子节点的值,根据这个特点,堆可以分为大根堆和小根堆。在大根堆中,根节点的值是最大的,而在小根堆中,根节点的值是最小的。

二叉树的实现(本质:顺序表,形式:大根堆)

二叉树的功能

//初始化
void HeapInit(Heap* hp);
//扩容
void CheckCapacity(Heap* hp);
//插入数据
void HeapPush(Heap* hp, HPDataType x);
//删除数据
void HeapPop(Heap* hp);
//顶数据
HPDataType HeapTop(Heap* hp);
//大小
int HeapSize(Heap* hp);
//堆是否为空
bool HeapEmpty(Heap* hp);
//销毁
void HeapDestory(Heap* hp);

二叉树的定义以及初始化

定义
typedef int HPDataType;

typedef struct Heap
{
  HPDataType* a;
  int size;
  int capacity;
}Heap;
初始化

本质就是顺序表,没有太大的变化,开辟初始空间。

//初始化
void HeapInit(Heap* hp)
{
  assert(hp);
  hp->a = (HPDataType*)malloc(sizeof(HPDataType)*4);
  if (hp->a == NULL)
  {
    perror("malloc fail");
    return NULL;
  }
  hp->capacity = 4;
  hp->size = 0;
}

二叉树空间扩容

//扩容
void CheckCapacity(Heap* hp)
{
  assert(hp);

  if (hp->size == hp->capacity)
  {
    HPDataType* tmp = (HPDataType*)realloc(hp->a,sizeof(HPDataType) * hp->capacity * 2);
    if(tmp == NULL)
    {
      perror("malloc fail");
      return NULL;
    }
    hp->a = tmp;
    hp->capacity *= 2;
  }

}

二叉树的插入数据

数据从尾部插入,但是要保证大堆结构,我们调用函数 AdjustUp(向上调整)。

插入
//插入数据
void HeapPush(Heap* hp,HPDataType x)
{
  assert(hp);
  CheckCapacity(hp);

  hp->a[hp->size++] = x;

  AdjustUp(hp,hp->size-1);
}
AdjustUp(向上调整)

在这里,由于孩子结点和父亲结点的关系是:父亲 = (孩子-1)/ 2. (顺序表本质就是数组,首元素下标就是0

如图:结点为4 父亲节点为 (4-1)/ 2 = 1。

0

1

2

3

4

5

6

DLeft...

DRight...

ELeft...

ERight...

FLeft...

FRihgt...

HLeft...

HRight...

如果发现,孩子节点大于父亲节点,进行交换。

//向上调整
void AdjustUp(Heap* hp, int child)
{
  assert(hp);
  int parent = (child - 1) / 2;
  while (child >= 0)
  {
    if (hp->a[child] > hp->a[parent])
    {
      Swap(&(hp->a[child]), &(hp->a[parent]));
      child = parent;
      parent = (child - 1) / 2;
    }
    else
    {
      break;
    }
  }
}
Swap(交换)
//交换
void Swap(HPDataType* child, HPDataType* parent)
{
  HPDataType tmp = *child;
  *child = *parent;
  *parent = tmp;
}

二叉树数据的删除

在这展示的是首节点的删除,尾节点删除的意义不大

删除

删除操作是首节点,和尾节点进行互换,删除尾节点。

  • 保证了删除效率
  • 保证了堆的原始关系

由于尾结点互换到了头结点,需要进行AdjustDown(向下调整)

//删除数据
void HeapPop(Heap* hp)
{
  assert(hp);
  
  hp->a[0] = hp->a[hp->size - 1];
  hp->size--;

  AdjustDown(hp,hp->size);
}
AdjustDown(向下调整)

在这里,由于孩子结点和父亲结点的关系是:孩子 = 父亲*2 + 1.

如图:1. 孩子结点 5 = 2 * 2 +1

2. 孩子结点 6 = 2 * 2 + 1 +1

0

1

2

3

4

5

6

DLeft...

DRight...

ELeft...

ERight...

FLeft...

FRihgt...

HLeft...

HRight...

//向下调整
void AdjustDown(Heap* hp,int size)
{
  assert(hp);
  int parent = 0;
  int child = parent * 2 + 1;
  while (child < size)
  {
    if (hp->a[parent] < hp->a[child])
    {
      Swap(&hp->a[parent], &hp->a[child]);
      parent = child;
      child = parent * 2 + 1;

    }
    else
    {
      break;
    }
  }
}

二叉树顶数据

//顶数据
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;
}

二叉树销毁

//销毁
void HeapDestory(Heap* hp)
{
  free(hp->a);
  hp->a == NULL;

}

源码

Heap.h

#pragma once

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


typedef int HPDataType;

typedef struct Heap
{
  HPDataType* a;
  int size;
  int capacity;
}Heap;


//初始化
void HeapInit(Heap* hp);
//扩容
void CheckCapacity(Heap* hp);
//插入数据
void HeapPush(Heap* hp, HPDataType x);
//删除数据
void HeapPop(Heap* hp);
//顶数据
HPDataType HeapTop(Heap* hp);
//大小
int HeapSize(Heap* hp);
//堆是否为空
bool HeapEmpty(Heap* hp);
//销毁
void HeapDestory(Heap* hp);

Heap.c

#define _CRT_SECURE_NO_WARNINGS  1

#include "heap.h"


//初始化
void HeapInit(Heap* hp)
{
  assert(hp);
  hp->a = (HPDataType*)malloc(sizeof(HPDataType)*4);
  if (hp->a == NULL)
  {
    perror("malloc fail");
    return NULL;
  }
  hp->capacity = 4;
  hp->size = 0;
}

//扩容
void CheckCapacity(Heap* hp)
{
  assert(hp);

  if (hp->size == hp->capacity)
  {
    HPDataType* tmp = (HPDataType*)realloc(hp->a,sizeof(HPDataType) * hp->capacity * 2);
    if(tmp == NULL)
    {
      perror("malloc fail");
      return NULL;
    }
    hp->a = tmp;
    hp->capacity *= 2;
  }

}

//交换
void Swap(HPDataType* child, HPDataType* parent)
{
  HPDataType tmp = *child;
  *child = *parent;
  *parent = tmp;
}

//向上调整
void AdjustUp(Heap* hp, int child)
{
  assert(hp);
  int parent = (child - 1) / 2;
  while (child >= 0)
  {
    if (hp->a[child] > hp->a[parent])
    {
      Swap(&(hp->a[child]), &(hp->a[parent]));
      child = parent;
      parent = (child - 1) / 2;
    }
    else
    {
      break;
    }
  }

}

//插入数据
void HeapPush(Heap* hp,HPDataType x)
{
  assert(hp);
  CheckCapacity(hp);

  hp->a[hp->size++] = x;

  AdjustUp(hp,hp->size-1);
}

//向下调整
void AdjustDown(Heap* hp,int size)
{
  assert(hp);
  int parent = 0;
  int child = parent * 2 + 1;
  while (child < size)
  {
    if (hp->a[parent] < hp->a[child])
    {
      Swap(&hp->a[parent], &hp->a[child]);
      parent = child;
      child = parent * 2 + 1;

    }
    else
    {
      break;
    }
  }
}

//删除数据
void HeapPop(Heap* hp)
{
  assert(hp);
  
  hp->a[0] = hp->a[hp->size - 1];
  hp->size--;

  AdjustDown(hp,hp->size);
}
//顶数据
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;
}

//销毁
void HeapDestory(Heap* hp)
{
  free(hp->a);
  hp->a == NULL;

}

test.c

#define _CRT_SECURE_NO_WARNINGS  1

#include "heap.h"



void Heaptest()
{
  Heap HP;
  HeapInit(&HP);
  HeapPush(&HP, 3);
  HeapPush(&HP, 5);
  HeapPush(&HP, 40);
  HeapPush(&HP, 70);
  HeapPush(&HP, 18);
  HeapPush(&HP, 25);

  while (!HeapEmpty(&HP))
  {
    printf("%d ", HeapTop(&HP));
    HeapPop(&HP);
  }

  HeapDestory(&HP);

}




int main()
{
  Heaptest();

  return 0;
}





目录
相关文章
|
11天前
|
存储 机器学习/深度学习
【数据结构】二叉树全攻略,从实现到应用详解
本文介绍了树形结构及其重要类型——二叉树。树由若干节点组成,具有层次关系。二叉树每个节点最多有两个子树,分为左子树和右子树。文中详细描述了二叉树的不同类型,如完全二叉树、满二叉树、平衡二叉树及搜索二叉树,并阐述了二叉树的基本性质与存储方式。此外,还介绍了二叉树的实现方法,包括节点定义、遍历方式(前序、中序、后序、层序遍历),并提供了多个示例代码,帮助理解二叉树的基本操作。
37 13
【数据结构】二叉树全攻略,从实现到应用详解
|
8天前
|
存储 算法 C语言
数据结构基础详解(C语言): 二叉树的遍历_线索二叉树_树的存储结构_树与森林详解
本文从二叉树遍历入手,详细介绍了先序、中序和后序遍历方法,并探讨了如何构建二叉树及线索二叉树的概念。接着,文章讲解了树和森林的存储结构,特别是如何将树与森林转换为二叉树形式,以便利用二叉树的遍历方法。最后,讨论了树和森林的遍历算法,包括先根、后根和层次遍历。通过这些内容,读者可以全面了解二叉树及其相关概念。
|
8天前
|
存储 机器学习/深度学习 C语言
数据结构基础详解(C语言): 树与二叉树的基本类型与存储结构详解
本文介绍了树和二叉树的基本概念及性质。树是由节点组成的层次结构,其中节点的度为其分支数量,树的度为树中最大节点度数。二叉树是一种特殊的树,其节点最多有两个子节点,具有多种性质,如叶子节点数与度为2的节点数之间的关系。此外,还介绍了二叉树的不同形态,包括满二叉树、完全二叉树、二叉排序树和平衡二叉树,并探讨了二叉树的顺序存储和链式存储结构。
|
8天前
|
存储 C语言
数据结构基础详解(C语言): 树与二叉树的应用_哈夫曼树与哈夫曼曼编码_并查集_二叉排序树_平衡二叉树
本文详细介绍了树与二叉树的应用,涵盖哈夫曼树与哈夫曼编码、并查集以及二叉排序树等内容。首先讲解了哈夫曼树的构造方法及其在数据压缩中的应用;接着介绍了并查集的基本概念、存储结构及优化方法;随后探讨了二叉排序树的定义、查找、插入和删除操作;最后阐述了平衡二叉树的概念及其在保证树平衡状态下的插入和删除操作。通过本文,读者可以全面了解树与二叉树在实际问题中的应用技巧和优化策略。
|
16天前
|
存储 程序员 C语言
堆和栈之间有什么区别
【9月更文挑战第1天】堆和栈之间有什么区别
87 0
|
27天前
|
算法 机器人
【数据结构】什么是堆
【数据结构】什么是堆
31 0
|
29天前
|
算法
【初阶数据结构篇】二叉树算法题
二叉树是否对称,即左右子树是否对称.
|
29天前
|
存储
【初阶数据结构篇】实现链式结构二叉树(二叉链)下篇
要改变root指针的指向,将本来指向根节点的root指针改为空,所以传二级指针(一级指针也可以,只不过在调用完记得把root置为空)。
|
6天前
|
存储 人工智能 C语言
数据结构基础详解(C语言): 栈的括号匹配(实战)与栈的表达式求值&&特殊矩阵的压缩存储
本文首先介绍了栈的应用之一——括号匹配,利用栈的特性实现左右括号的匹配检测。接着详细描述了南京理工大学的一道编程题,要求判断输入字符串中的括号是否正确匹配,并给出了完整的代码示例。此外,还探讨了栈在表达式求值中的应用,包括中缀、后缀和前缀表达式的转换与计算方法。最后,文章介绍了矩阵的压缩存储技术,涵盖对称矩阵、三角矩阵及稀疏矩阵的不同压缩存储策略,提高存储效率。
|
8天前
|
存储 C语言
数据结构基础详解(C语言): 栈与队列的详解附完整代码
栈是一种仅允许在一端进行插入和删除操作的线性表,常用于解决括号匹配、函数调用等问题。栈分为顺序栈和链栈,顺序栈使用数组存储,链栈基于单链表实现。栈的主要操作包括初始化、销毁、入栈、出栈等。栈的应用广泛,如表达式求值、递归等场景。栈的顺序存储结构由数组和栈顶指针构成,链栈则基于单链表的头插法实现。