二叉树的实现(下)

简介: 二叉树的实现(下)

二叉树的实现(上):https://developer.aliyun.com/article/1389394

🪐代码总结
🌠Heap.h头文件
//包含头文件
#include<stdio.h>
#include<stdlib.h>
#include<assert.h>
#include<string.h>
#include<time.h>
#include<stdbool.h>
//定义数据类型
typedef int HPDataType;
//二叉树堆的实现
typedef struct Heap
{
  //下一个节点
  HPDataType* a;
  //元素个数
  int size;
  //初始化个数
  int capacity;
}HP;
//初始化
void HeapInit(HP* php);
//建立二叉树
void HeapInitArray(HP* php, int* a, int n);
//销毁
void HeapDestroy(HP* php);
//实现交换元素的函数
void Swap(HPDataType* p1, HPDataType* p2);
//实现向上调整建小堆
void AdjustUp(HPDataType* a, int child);
//实现向下调整建大堆
void AdjustDown(HPDataType* a, int n, int parent);
//打印元素
void HeapPrint(HP* php);
//添加元素
void HeapPush(HP* php, HPDataType x);
//删除元素(这里可以实现排序 (从小到大) )
void HeapPop(HP* php);
//返回第一个元素
HPDataType HeapTop(HP* php);
//判断不能没有元素
bool HeapEmpty(HP* php);
🌠Heap.c源文件
//包含头文件
#include"Heap.h"
//初始化
void HeapInit(HP* php) 
{
  //断言
  assert(php);
  //初始化
  php->a = NULL;
  php->size = 0;
  php->capacity = 0;
}
//建立二叉树
void HeapInitArray(HP* php, int* a, int n)
{
  //断言
  assert(php);
  assert(a);
  //开辟空间
  php->a = (HPDataType*)malloc(sizeof(HPDataType) * n);
  //判断
  if (php->a == NULL)
  {
    perror("malloc fail");
    exit(-1);
  }
  //赋值
  php->size = n;
  php->capacity = n;
  //把a的元素拷贝的开辟的二叉树中
  memcpy(php->a, a, sizeof(HPDataType) * n);
  // 建堆
  for (int i = 1; i < n; i++)
  {
    //向上调整建小堆 实现取出最小的元素(后面会实现)
    AdjustUp(php->a, i);
  }
}
//销毁
void HeapDestroy(HP* php)
{
  //断言
  assert(php);
  //释放内存
  free(php->a);
  php->a = NULL;
  php->size = php->capacity = 0;
}
//实现交换元素的函数
void Swap(HPDataType* p1, HPDataType* p2)
{
  HPDataType tmp = *p1;
  *p1 = *p2;
  *p2 = tmp;
}
//实现向上调整建小堆
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 = (parent - 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 + 1] < a[child])
    {
      ++child;
    }
    //实现向下调整元素
    if (a[child] < a[parent])
    {
      Swap(&a[child], &a[parent]);
      // 继续往下调整
      parent = child;
      child = parent * 2 + 1;
    }
    else
    {
      break;
    }
  }
}
//打印元素
void HeapPrint(HP* php)
{
  //断言
  assert(php);
  for (size_t i = 0; i < php->size; i++)
  {
    printf("%d ", php->a[i]);
  }
  printf("\n");
}
//添加元素
void HeapPush(HP* php, HPDataType x)
{
  //断言
  assert(php);
  //扩容(这里实现元素一个添加在堆中)
  //(需要主函数整个循环)
  if (php->size == php->capacity)
  {
    //实现扩容
    int newCapacity = php->capacity == 0 ? 4 : php->capacity * 2;
    //开辟空间
    HPDataType* tmp = (HPDataType*)realloc(php->a, sizeof(HPDataType) * newCapacity);
    //判断
    if (tmp == NULL)
    {
      perror("realloc fail");
      exit(-1);
    }
    php->a = tmp;
    php->capacity = newCapacity;
  }
  php->a[php->size] = x;
  php->size++;
  //添加元素后实现向上调整 建成小堆
  AdjustUp(php->a, php->size - 1);
}
//删除元素(这里可以实现排序 (从小到大) )
void HeapPop(HP* php)
{
  //断言
  assert(php);
  assert(php->size > 0);
  //第一个元素和最后一个元素交换
  Swap(&php->a[0], &php->a[php->size - 1]);
  --php->size;
  //向下调整后 就可以剥离出最后一个元素实现排序(小到大)
  AdjustDown(php->a, php->size, 0);
}
//返回第一个元素
HPDataType HeapTop(HP* php)
{
  //断言
  assert(php);
  assert(php->size > 0);
  //返回第一个元素
  return php->a[0];
}
//判断不能没有元素
bool HeapEmpty(HP* php)
{
  //断言
  assert(php);
  //
  return php->size == 0;
}
🌠Test.c源文件
//包含头文件
#include"Heap.h"
//int main()
//{
//  //int a[] = { 65,100,70,32,50,60 };
//  int a[] = { 2,3,5,7,4,6,8,65,100,70,32,50,60 };
//  HP hp;
//  HeapInit(&hp);
//  for (int i = 0; i < sizeof(a)/sizeof(int); i++)
//  {
//    HeapPush(&hp, a[i]);
//  }
//  HeapPrint(&hp);
//
//  int k = 5;
//  while (!HeapEmpty(&hp) && k--)
//  {
//    printf("%d ", HeapTop(&hp));
//    HeapPop(&hp);
//  }
//
//  HeapDestroy(&hp);
//
//  return 0;
//}
// 这种写法的缺点:
// 1、先有一个堆的数据结构
// 2、空间复杂度复杂度的消耗
//排序
//void HeapSort(int* a, int n)
//{
//  //创建指针
//  HP hp;
//  //初始化
//  HeapInit(&hp);
//  for (int i = 0; i < n; i++)
//  {
//    //添加元素
//    HeapPush(&hp, a[i]);
//  }
//  int i = 0;
//  //不能没有元素
//  while (!HeapEmpty(&hp))
//  {
//    //返回第一个元素
//    printf("%d ", HeapTop(&hp));
//    a[i++] = HeapTop(&hp);
//    //删除元素(这里可以实现排序 (从小到大) )
//    HeapPop(&hp);
//  }
//  //销毁
//  HeapDestroy(&hp);
//}
//排序升序
//void HeapSort(int* a, int n)
//{
//  // 向上调整建堆 (大堆)or  (小堆)
//  // O(N*logN)
//  /*for (int i = 1; i < n; i++)
//  {
//    AdjustUp(a, i);
//  }*/
//
//  // 向下调整建堆
//  // O(N)
//  for (int i = (n - 1 - 1) / 2; i >= 0; i--)
//  {
//    AdjustDown(a, n, i);
//  }
//
//  // O(N*logN)
//  int end = n - 1;
//  while (end > 0)
//  {
//    Swap(&a[0], &a[end]);
//    AdjustDown(a, end, 0);
//    --end;
//  }
//}
//int main()
//{
//  //int a[] = { 65,100,70,32,50,60 };
//  //int a[] = { 2,3,5,7,4,6,8,65,100,70,32,50,60 };
//  //int a[] = { 70, 65, 100, 32, 50, 60 };
//  int a[] = { 2,3,5,7,4,6,8 };
//  HeapSort(a, sizeof(a) / sizeof(int));
//
//  return 0;
//}
💫拓展

        TOP-K问题:即求数据结合中前K个最大的元素或者最小的元素,一般情况下数据量都比较大。  比如:专业前10名、世界500强、富豪榜、游戏中前100的活跃玩家等。对于Top-K问题,能想到的最简单直接的方式就是排序,但是:如果数据量非常大,排序就不太可取了(可能数据都不能一下子全部加载到内存中)。最佳的方式就是用堆来解决,基本思路如下:

💦1. 用数据集合中前K个元素来建堆
      💧前k个最大的元素,则建小堆
      💧前k个最小的元素,则建大堆
💦2. 用剩余的N-K个元素依次与堆顶元素来比较,不满足则替换堆顶元素将剩余N-K个元素依次与堆顶元素比完之后,堆中剩余的K个元素就是所求的前K个最小或者最大的元素。


方法实现:

//大量数据排序
void PrintTopK(const char* filename, int k)
{
  // 1. 建堆--用a中前k个元素建堆
  //打开文件
  FILE* fout = fopen(filename, "r");
  //判断是否打开文件
  if (fout == NULL)
  {
    perror("fopen fail");
    return;
  }
  //开辟空间
  int* minheap = (int*)malloc(sizeof(int) * k);
  //判断
  if (minheap == NULL)
  {
    perror("malloc fail");
    return;
  }
  //存入数据
  for (int i = 0; i < k; i++)
  {
    fscanf(fout, "%d", &minheap[i]);
  }
  // 前k个数建小堆
  for (int i = (k - 2) / 2; i >= 0; --i)
  {
    AdjustDown(minheap, k, i);
  }
  // 2. 将剩余n-k个元素依次与堆顶元素交换,不满则则替换
  int x = 0;
  while (fscanf(fout, "%d", &x) != EOF)
  {
    if (x > minheap[0])
    {
      // 替换你进堆
      minheap[0] = x;
      AdjustDown(minheap, k, 0);
    }
  }
  //打印数据
  for (int i = 0; i < k; i++)
  {
    printf("%d ", minheap[i]);
  }
  printf("\n");
  //关闭文件
  fclose(fout);
}
int main()
{
  //创建数字
  CreateNDate();
  //实现寻找
  PrintTopK("data.txt", 5);
  return 0;
}

🌝二叉树的链表存储结构

       在学习二叉树的基本操作前,需先要创建一棵二叉树,然后才能学习其相关的基本操作。由于现在大家对二叉树结构掌握还不够深入,为了降低大家学习成本,此处手动快速创建一棵简单的二叉树,快速进入二叉树操作学习,等二叉树结构了解的差不多时,我们反过头再来研究二叉树真正的创建方式。

💫初始化二叉树的链表存储结构

这里就简单的续写咯

//包含头文件
#include<stdio.h>
#include<stdlib.h>
#include<assert.h>
#include<stdbool.h>
// 不是增删查改,学习二叉树结构
typedef struct BinaryTreeNode
{
  //左节点
  struct BinaryTreeNode* left;
  //右节点
  struct BinaryTreeNode* right;
  int val;
}BTNode;
//初始化
BTNode* BuyNode(int x)
{
  //开辟空间
  BTNode* node = (BTNode*)malloc(sizeof(BTNode));
  //判断
  if (node == NULL)
  {
    perror("malloc fail");
    exit(-1);
  }
  //初始化
  node->val = x;
  node->left = NULL;
  node->right = NULL;
  //返回值
  return node;
}
💫二叉树的遍历

按照规则,二叉树的遍历有:前序/中序/后序的递归结构遍历

💦1. 前序遍历(Preorder Traversal 亦称先序遍历)——访问根结点的操作发生在遍历其左右子树之前。

💦2. 中序遍历(Inorder Traversal)——访问根结点的操作发生在遍历其左右子树之中(间)。

💦3. 后序遍历(Postorder Traversal)——访问根结点的操作发生在遍历其左右子树之后。



// 二叉树前序遍历
void PrevOrder(BTNode* root) {
  if (root == NULL) 
  {
    printf("NULL ");
    return;
  }
  printf("%d ", root->val);
  PrevOrder(root->left);
  PrevOrder(root->right);
}
// 二叉树中序遍历
void InOrder(BTNode* root)
{
  if (root == NULL)
  {
    printf("NULL ");
    return;
  }
  InOrder(root->left);
  printf("%d ", root->val);
  InOrder(root->right);
}
// 二叉树后序遍历
void PostOrder(BTNode* root)
{
  if (root == NULL)
  {
    printf("NULL ");
    return;
  }
  PostOrder(root->left);
  PostOrder(root->right);
  printf("%d ", root->val);
}
💫二叉树层序遍历

       层序遍历:除了先序遍历、中序遍历、后序遍历外,还可以对二叉树进行层序遍历。设二叉树的根节点所在层数为1,层序遍历就是从所在二叉树的根节点出发,首先访问第一层的树根节点,然后从左到右访问第2层上的节点,接着是第三层的节点,以此类推,自上而下,自左至右逐层访问树的结点的过程就是层序遍历。

不知道大家还记得队列不,这里可是需要用到哟。

// 层序遍历
void LevelOrder(BTNode* root)
{
  Que q;
  //初始化
  QueueInit(&q);
  if (root)
    //添加元素
    QueuePush(&q, root);
  //判断
  while (!QueueEmpty(&q))
  {
    //找头
    BTNode* front = QueueFront(&q);
    printf("%d ", front->val);
    if (front->left)
      //添加元素
      QueuePush(&q, front->left);
    if (front->right)
      //添加元素
      QueuePush(&q, front->right);
    //删除元素
    QueuePop(&q);
  }
  printf("\n");
  //销毁
  QueueDestroy(&q);
}
💫二叉树的节点个数以及高度等
// 二叉树节点个数(总节点个数)
int TreeSize(BTNode* root)
{
  return root == NULL ? 0 : TreeSize(root->left) + TreeSize(root->right) + 1;
}
// 叶子节点个数
int TreeLeafSize(BTNode* root)
{
  if (root == NULL)
    return 0;
  if (root->left == NULL && root->right == NULL)
  {
    return 1;
  }
  return TreeLeafSize(root->left) + TreeLeafSize(root->right);
}
// 第k层的节点个数
int TreeKLevel(BTNode* root, int k)
{
  assert(k > 0);
  if (root == NULL)
    return 0;
  if (k == 1)
  {
    return 1;
  }
  return TreeKLevel(root->left, k - 1)
    + TreeKLevel(root->right, k - 1);
}
// 二叉树销毁
void TreeDestroy(BTNode* root)
{
  if (root == NULL)
  {
    return;
  }
  TreeDestroy(root->left);
  TreeDestroy(root->right);
  free(root);
  //root = NULL;
}
// 二叉树查找值为x的结点
BTNode* TreeFind(BTNode* root, int x)
{
  if (root == NULL)
    return NULL;
  if (root->val == x)
    return root;
  BTNode* ret = NULL;
  ret = TreeFind(root->left, x);
  if (ret)
    return ret;
  ret = TreeFind(root->right, x);
  if (ret)
    return ret;
  return NULL;
}

🌟结束语

      今天内容就到这里啦,时间过得很快,大家沉下心来好好学习,会有一定的收获的,大家多多坚持,嘻嘻,成功路上注定孤独,因为坚持的人不多。那请大家举起自己的小说手给博主一键三连,有你们的支持是我最大的动力💞💞💞,回见。

目录
相关文章
|
机器学习/深度学习 存储 算法
深入理解【二叉树】
深入理解【二叉树】
89 0
|
C语言
【二叉树】的实现
【二叉树】的实现
39 0
|
7月前
|
存储
|
7月前
|
存储 C++
二叉树
二叉树“【5月更文挑战第22天】”
36 3
|
6月前
|
Java
二叉树
二叉树
26 0
二叉树(详解)下
二叉树(详解)
62 0
|
7月前
|
算法 网络协议 NoSQL
认识二叉树(详细介绍)
认识二叉树(详细介绍)
|
7月前
|
存储 Java C++
二叉树的实现(上)
二叉树的实现
70 0
24 二叉树
24 二叉树
53 0
|
存储
二叉树讲解
二叉树讲解
79 0