当前位置: 首页 > news >正文

数据结构:树及二叉树--堆(下)

上篇文章回顾:https://blog.csdn.net/2401_86123468/article/details/151366890?spm=1001.2014.3001.5501

本章代码见:https://gitee.com/jxxx404/c-language-learning/commit/d14d68230c1e529bf90230d6129bbf8686871d64

https://gitee.com/jxxx404/c-language-learning/commit/1fbbad517be3138aa5fbf249fc6a48a4976efa78

https://gitee.com/jxxx404/c-language-learning/commit/3ad4a24284a8fc91f65cec496b28e4a1712f5dd0

1.向上调整算法建堆时间复杂度

考虑到堆是完全二叉树,满二叉树也是完全二叉树,因此简化使用满二叉树来证明。

分析:
第1层,2^0个结点,需要向上移动0层
第2层,2^1个结点,需要向上移动1层
第3层,2^2个结点,需要向上移动2层
第4层,2^3个结点,需要向上移动3层

……
第h层,2^(h-1)个结点,需要向上移动h-1层

因此:向上调整算法建堆的时间复杂度为:O(n * logn),log以2为底。

2.向下调整算法建堆时间复杂度

分析:
第1层,2^0个结点,需要向下移动h-1层
第2层,2^1个结点,需要向下移动h-2层
第3层,2^2个结点,需要向下移动h-3层
第4层,2^3个结点,需要向下移动h-4层

……
第h-1层,2^(h-2)个结点,需要向下移动1层

因此:向下调整算法建堆的时间复杂度为:O(n)

3.TOP-K问题

TOP-K问题:即求数据结合中前K个最大的元素或者最小的元素,一般情况下数据量都比较大。

比如:专业前10名、世界500强、富豪榜、游戏中前100的活跃玩家等。

对于Top-K问题,能想到的最简单直接的方式就是排序,但是:如果数据量非常大,排序就不太可

取了(可能数据都不能一下子全部加载到内存中)。最佳的方式就是用堆来解决,基本思路如下:

前k个最大的元素,则建小堆,遍历剩下的数据和堆顶比,比堆顶大就和堆顶交换。
前k个最小的元素,则建大堆,遍历剩下的数据和堆顶比,比堆顶小就和堆顶交换。

具体代码实现如下:

void CreateNData()
{//造数据int n = 100000;srand(time(0));const char* file = "data.txt";FILE* fin = fopen(file, "w");if (fin == NULL){perror("fopen fail!");return;}for (int i = 0; i < n; ++i){int x = (rand() + i) % 100000;fprintf(fin, "%d\n", x);}fclose(fin);
}void TopK()
{int k = 0;printf("输入k: ");scanf("%d", &k);const char* file = "data.txt";FILE* fout = fopen(file, "r");if (fout == NULL){perror("fopen fail!");exit(1);}//申请空间大小为k的整型数组int* minHeap = (int*)malloc(sizeof(int) * k);if (minHeap == NULL){perror("malloc fail!");exit(2);}//读取文件中k个数据放到数组中for (int i = 0; i < k; i++){fscanf(fout, "%d", &minHeap[i]);}//数组调整建堆--向下调整建堆//找最大的前k个数,建小堆for (int i = (k - 1 - 1) / 2; i >= 0; i--){AdjustDown(minHeap, i, k);}//遍历剩下的n - k个数,跟堆顶比较,谁小谁入堆int data = 0;while (fscanf(fout, "%d", &data) != EOF){if (data > minHeap[0]){minHeap[0] = data;AdjustDown(minHeap, 0, k);}}//打印for (int i = 0; i < k; i++){printf("%d ", minHeap[i]);}printf("\n");fclose(fout);
}int main()
{//CreateNData();TopK();return 0;
}

时间复杂度为:O(n) = k + (n - k) * logk(logk以2为底)。

4.实现链式结构二叉树

用链表来表示一棵二叉树(用链来表示元素的逻辑关系)。通常方法为链表中每个结点由三个域组成,数据域和左右指针域,左右指针分别用来给出该结点左孩子和右孩子所在的链结点的存储地址。

结构如下:

//定义二叉树结点结构
typedef int BTDataType;
typedef struct BinaryTreeNode {BTDataType data;struct BinaryTreeNode* left;struct BinaryTreeNode* right;
}BTNode;

手动创建一棵链式二叉树:

BTNode* buyNode(char x)
{BTNode* newnode = (BTNode*)malloc(sizeof(BTNode));newnode->data = x;newnode->left = newnode->right = NULL;return newnode;
}void test01()
{BTNode* nodeA = buyNode('A');BTNode* nodeB = buyNode('B');BTNode* nodeC = buyNode('C');BTNode* nodeD = buyNode('D');BTNode* nodeE = buyNode('E');BTNode* nodeF = buyNode('F');nodeA->left = nodeB;nodeA->right = nodeC;nodeB->left = nodeD;nodeC->left = nodeE;nodeC->right = nodeF;return nodeA;
}int main()
{test01();return 0;
}

4.1前中后序遍历

以上述代码构造的二叉树为例:

4.1.1遍历规则

二叉树的遍历有:前序/中序/后序的递归结构遍历:

(1)前序遍历(PreorderTraversal亦称先序遍历):访问根结点的操作发生在遍历其左右子树之前,访问顺序为:根结点、左子树、右子树

(2)中序遍历(InorderTraversal):访问根结点的操作发生在遍历其左右子树之中(间),访问顺序为:左子树、根结点、右子树

(3)后序遍历(PostorderTraversal):访问根结点的操作发生在遍历其左右子树之后,访问顺序为:左子树、右子树、根结点

4.1.2代码实现

1.前序遍历
//前序遍历--根左右
void PreOrder(BTNode* root)
{if (root == NULL){printf("NULL ");return;}printf("%c ", root->data);PreOrder(root->left);PreOrder(root->right);
}

结果正确:

递归分析:

2.中序遍历
//中序遍历
void InOrder(BTNode* root)
{if (root == NULL){printf("NULL ");return;}InOrder(root->left);printf("%c ", root->data);InOrder(root->right);
}

结果正确:

3.后序遍历
//后序遍历
void PostOrder(BTNode* root)
{if (root == NULL){printf("NULL ");return;}PostOrder(root->left);PostOrder(root->right);printf("%c ", root->data);
}

结果正确:

相关的函数递归栈帧图:

前序遍历结果:123456
中序遍历结果:321546
后序遍历结果:315641

4.2结点个数及高度计算等

4.2.1二叉树结点个数

代码一:
//代码一
int size = 0;
int BinaryTreeSize(BTNode* root)
{if (root == NULL){return 0;}size++;BinaryTreeSize(root->left);BinaryTreeSize(root->right);return size;
}

但是测试结果并不满足我们的要求:

可见代码有创建全局变量,多次调用方法会出现累加情况的错误。

代码二:
//代码二:改造函数定义
void BinaryTreeSize(BTNode* root,int* psize)
{if (root == NULL){return 0;}(*psize)++;BinaryTreeSize(root->left, psize);BinaryTreeSize(root->right, psize);
}

结果正确:

代码三:
//代码三
int BinaryTreeSize(BTNode* root)
{if (root == NULL){return 0;}return 1 + BinaryTreeSize(root->left) + BinaryTreeSize(root->right);
}

符合要求,结果正确:

4.2.2求二叉树叶子结点个数

叶子结点个数 = 左子树叶子结点个数 + 右子树叶子结点个数

代码见下:

// ⼆叉树叶⼦结点个数
int BinaryTreeLeafSize(BTNode* root)
{if (root == NULL){return 0;}//判断是否为叶子结点if (root->left == NULL && root->right == NULL){return 1;}return BinaryTreeLeafSize(root->left) + BinaryTreeLeafSize(root->right);
}

4.2.3求二叉树第K层结点个数

// ⼆叉树第k层结点个数
int BinaryTreeLevelKSize(BTNode* root, int k)
{if (root == NULL){return 0;}if (k == 1){return 1;}return BinaryTreeLevelKSize(root->left, k-1)+ BinaryTreeLevelKSize(root->right, k-1);
}

4.2.4二叉树的深度

//⼆叉树的深度/⾼度
int BinaryTreeDepth(BTNode* root)
{if (root == NULL){return 0;}int leftDep = BinaryTreeDepth(root->left);int rightDep = BinaryTreeDepth(root->right);return 1 + (leftDep > rightDep ? leftDep : rightDep);
}

4.2.5二叉树查找值为x的结点

// ⼆叉树查找值为x的结点
BTNode* BinaryTreeFind(BTNode* root, BTDataType x)
{if (root == NULL){return NULL;}if (root->data == x){return root;}BTNode* leftfind = BinaryTreeFind(root->left, x);if (leftfind){return leftfind;}BTNode* rightfind = BinaryTreeFind(root->right, x);if (rightfind){return rightfind;}return NULL;
}

4.2.6二叉树的销毁

// ⼆叉树销毁
void BinaryTreeDestory(BTNode** root)
{if (*root == NULL){return;}BinaryTreeDestory(&((*root)->left));BinaryTreeDestory(&((*root)->right));free(*root);*root = NULL;
}

4.3层序遍历

前中后序遍历属于深度优先遍历,层序遍历属于广度优先遍历。

利用数据结构--队列,将Queue.h和Queue.c引入,来实现层序遍历。

将根节点保存在队列中,使队列不为空,循环判断队列是否为空,不为空取对头,将对头结点不为空的孩子结点入队列。

// 层序遍历
void LevelOrder(BTNode* root)
{Queue q;QueueInit(&q);QueuePush(&q, root);while (!QueueEmpty(&q)){//取对头,打印对头BTNode* top = QueueFront(&q);printf("%c ", top->data);QueuePop(&q);//队头结点不为空的孩子结点入队列if (top->left)QueuePush(&q, top->left);if (top->right)QueuePush(&q, top->right);}QueueDestroy(&q);
}

4.2.8判断二叉树是否为完全二叉树

判断基础:最后一层结点个数不一定达到最大 ,其他每次结点个数都达到最大,结点从左到右依次排序。

思路:判断每层结点个数,叶子结点是否从左到右依次排列。

根结点先入队列,保证队列不为空,循环判断队列是否为空,不为空取对头,出对头,将对头结点的左右孩子都入队列。

非完全二叉树:

取到空的队头,跳出循环,此时队列中剩下了空结点和非空结点。

完全二叉树:

取到空的队头,此时队列中剩下了NULL,跳出循环。

代码:


// 判断⼆叉树是否是完全⼆叉树
bool BinaryTreeComplete(BTNode* root)
{Queue q;QueueInit(&q);//头结点入队列QueuePush(&q, root);while (!QueueEmpty(&q)){//取队头,出队头BTNode* top = QueueFront(&q);QueuePop(&q);if (top == NULL){break;}//左右孩子入列QueuePush(&q, top->left);QueuePush(&q, top->right);}//队列不为空,继续取队列中的队头while (!QueueEmpty(&q)){BTNode* top = QueueFront(&q);QueuePop(&q);if (top != NULL){//不是完全二叉树QueueDestroy(&q);return false;}}QueueDestroy(&q);return true;}

测试结果:

本章完。

http://www.dtcms.com/a/389783.html

相关文章:

  • TDengine 聚合函数 STDDEV 用户手册
  • ARM--启动代码
  • openharmony1.1.3 通过i2c进行温湿度采集
  • 虚拟仿真技术赋能国土资源监测教育,破解生态与安全人才培养困局
  • Vim 详细使用方法与运维工作常用操作
  • python基础数据分析与可视化
  • DeepSort学习与实践-原理学习
  • 贪心算法应用:多重背包启发式问题详解
  • 使用C#开发的控笔视频生成小程序
  • [重学Rust]之ureq
  • 水下机器人专用绝缘监测装置
  • C++中std::map容器中元素删除方法汇总
  • JavaEE 初阶第二十三期:网络原理,底层框架的“通关密码”(三)
  • 打工人日报#20250918
  • Git本地\远程分支区分查找
  • 从零开始手写机器学习框架:我的深度学习之旅——开启深度学习的底层探索
  • QT的部件
  • Ubuntu20.04仿真 | iris四旋翼添加双目相机D435i
  • eSIM时代来临!iPhone Air引爆无卡化革命
  • openkylin、ubuntu22部署opencv4.8.0
  • 各省产业结构合理化-摩尔(Moore)指数 1999-2023年
  • 【win10、win11】永久关闭自动更新
  • Linux基础实践(基于Ubuntu系统)
  • 线性控制理论:线性系统状态空间
  • OpenAI最新研究:为什么语言模型会产生幻觉
  • 人工智能通识与实践 - 自然语言处理
  • Coze源码分析-资源库-创建工作流-后端源码-安全/错误/流程
  • OneTerm开源堡垒机实战(四):访问授权与安全管控
  • 【赵渝强老师】基于PostgreSQL的MPP集群:Greenplum
  • leetCode算法题记录:27.移除元素