> 技术文档 > 堆和二叉树--数据结构初阶(3)(C/C++)

堆和二叉树--数据结构初阶(3)(C/C++)


文章目录

  • 前言
  • 理论部分
    • 堆的模拟实现:(这里举的大根堆)
    • 堆的创建
    • 二叉树遍历
    • 二叉树的一些其他功能实现
  • 作业部分

前言

这期的话讲解的是堆和二叉树的理论部分和习题部分

理论部分

二叉树的几个性质:1.对于任意一个二叉树,度为0的节点比度为2的节点多一个

 2.对于完全二叉树,度为1的节点要么是1,要么是0 3.表示二叉树的值在数组位置中父子下标关系:

parent = (child-1)/2 leftchild = parent*2+1 rightchild = parent* 2+2

前提:二叉树的根节点是下标为0,是第1个

此外,数组存储二叉树只适合完全二叉树(较满的),不然浪费的空间太多了

二叉树的第n层有2n-1个节点

节点的个数=边数+1 边数 = 节点的度相加之和

陌生的名词:

树度:也就是树的度,是树中节点度的最大值

堆是完全二叉树小根堆:树中所有的父亲的值都小于等于孩子(最小的在根节点)大根堆:树中所有的父亲的值都大于等于孩子(最大的在根节点)跟二叉搜索树要区分一样(那个对左右孩子放法也有要求)

堆的模拟实现:(这里举的大根堆)

#include#include#include#include// 大堆typedef int HPDataType;typedef struct Heap{HPDataType* a;int size;int capacity;}HP;void HeapInit(HP* php);void HeapPush(HP* php, HPDataType x);void HeapPop(HP* php);int HeapSize(HP* php);void AdjustUp(HPDataType* a, int child);void AdjustDown(HPDataType* a, int n, int parent);void Swap(HPDataType* p1, HPDataType* p2);void HeapInit(HP* php){assert(php);php->a = (HPDataType*)malloc(sizeof(HPDataType)*4);if (php->a == NULL){perror(\"malloc fail\");return;}php->size = 0;php->capacity = 4;}void Swap(HPDataType* p1, HPDataType* p2){HPDataType x = *p1;*p1 = *p2;*p2 = x;}// 除了child这个位置,前面数据构成堆void AdjustUp(HPDataType* a, int child){int parent = (child - 1) / 2;//while (parent >= 0)while(child > 0){if (a[child] > a[parent]){Swap(&a[child], &a[parent]);child = parent;parent = (child - 1) / 2;}else{break;}}}void HeapPush(HP* php, HPDataType x){assert(php);if (php->size == php->capacity){HPDataType* tmp = (HPDataType*)realloc(php->a, sizeof(HPDataType) * php->capacity*2);if (tmp == NULL){perror(\"realloc fail\");return;}php->a = tmp;php->capacity *= 2;}php->a[php->size] = x;php->size++;AdjustUp(php->a, php->size - 1);}

堆和二叉树--数据结构初阶(3)(C/C++)

注意:这种模拟实现操作的php->a[php->size]还没存数据如果要是小堆的话,就把判断条件的的a[child]>a[parent]改成a[child]<a[parent]即可
// 左右子树都是大堆/小堆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[0], &php->a[php->size - 1]);php->size--;AdjustDown(php->a, php->size, 0);}int HeapSize(HP* php){assert(php);return php->size;}
上面AdjustDown里面的n表示的是数组的长度size表示当前已存长度 size-1时那个最后的下标 size是还没存数据的那个的下标

堆的创建

想给数组排升序的话,要建大堆去搞代码实现:(这里的时间复杂度是N*logN)void HeapSort(int*a,int n){//向下调整建堆:for(int i = (n-1-1)/2;i>=0;--i)AdjustDown(php->a,php->size,i); int end = n-1;while(end>0) { Swap(&a[end],&a[0]); AdjustDown(a,end,0);--end; } }

建堆方式有两种:(N为节点个数)

1.向上调整建堆 时间复杂度为N*logN

2.向下调整建堆 时间复杂度为N(一般用这个)

这里的数组是从0开始存数的向上调整建堆代码实现:for(int i = 1;i=0;--i)AdjustDown(php->a,php->size,i);
TOPK问题:即求数据结合中前K个最大的元素或者最小的元素(一般情况下数据量都比较大)方法:1.用数据集合中的前k个来建堆 a.求前k个最大的元素,需要建小堆 b.求前k个最小的元素,需要建大堆2.用剩余的N-k个元素依次和堆顶元素来比较还要向下调整,满足则替换堆顶元素这里的满足是指比小堆堆顶大,比大堆堆顶小

二叉树的遍历

二叉树的遍历:1.前序遍历:根左右2.中序遍历:左根右3.后序遍历:左右根(前中后是针对的根,左右的关系全是左在右的左边)这里拿中序遍历来举例理解: 对于每一棵树,都先去遍历他的左子树,然后再写他的根,然后再遍历其右子树比如下面这个图:就是 1 2 4 5 6 7 8(用代码写的话,没有的位置要写NULL来代替,比如1的两个子树是NULL)4.层序遍历:就是每层从左到右,然后一层遍历完了就去下一层

堆和二叉树--数据结构初阶(3)(C/C++)

代码实现:struct BTNode{ TreeData data; struct BTNode *left; struct BTNode *right; };void PreOrder(BTNode* root) {if (root == NULL) {printf(\"NULL \");return;}printf(\"%d \", root->data);PreOrder(root->left);PreOrder(root->right);}//先序遍历void InOrder(BTNode* root) {if (root == NULL) {printf(\"NULL \");return;}InOrder(root->left);printf(\"%d \", root->data);InOrder(root->right);}//中序遍历void PostOrder(BTNode* root){if (root == NULL){printf(\"NULL \");return;}PostOrder(root->left);PostOrder(root->right);printf(\"%d \", root->data);}//后序遍历void LevelOrder(BTNode* root){Queue q;QueueInit(&q);if (root)QueuePush(&q, root);while (!QueueEmpty(&q)){BTNode* front = QueueFront(&q);QueuePop(&q);printf(\"%d \", front->data);if(front->left)QueuePush(&q, front->left);if (front->right)QueuePush(&q, front->right);}QueueDestroy(&q);}//堆的层序遍历:就是打印一个节点之后,把他的孩子节点加进队列中去注意:一般题目是不要那个printf(\"NULL\");的自己这个方法遍历是会出现NULL的,要注意

二叉树的一些其他功能实现

二叉树的销毁void TreeDestory(BTNode* root){if (root == NULL)return;TreeDestory(root->left);TreeDestory(root->right);free(root);}求二叉树节点的个数int TreeSize(BTNode* root){return root == NULL ? 0 : TreeSize(root->left) + TreeSize(root->right) + 1;//可以这样写,不用续行符}求二叉树的深度int TreeHeight(BTNode* root){if (root == NULL)return 0;int leftHeight = TreeHeight(root->left);int rightHeight = TreeHeight(root->right);return leftHeight > rightHeight ? leftHeight + 1 : rightHeight + 1;}//递归如果不好想的话要画图去理解特别注意,不要写成这样://int TreeHeight(BTNode* root)//{//if (root == NULL)//return 0;//return TreeHeight(root->left) > TreeHeight(root->right)//? TreeHeight(root->left) + 1 : TreeHeight(root->right) + 1;//}这样写的话会求很多次eg:TreeHeight(root->left)求二叉树第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);}二叉树查找首个值为x的结点(先序遍历的话)BTNode* TreeFind(BTNode* root, BTDataType x){if (root == NULL)return NULL;if (root->data == x)return root;BTNode* lret = TreeFind(root->left, x);if (lret)return lret;//这个办法好,解决了递归深层的想return出去的东西出不去的问题BTNode* rret = TreeFind(root->right, x);if (rret)return rret;return NULL;}判断二叉树是否是完全二叉树bool TreeComplete(BTNode* root){//把二叉树放入队列Queue q;QueueInit(&q);if (root)QueuePush(&q, root);while (!QueueEmpty(&q)){BTNode* front = QueueFront(&q);QueuePop(&q);if (front == NULL){break;//这里不是continue,遇到第一个NULL就可以停了}else{QueuePush(&q, front->left);QueuePush(&q, front->right);}}//判断是不是完全二叉树while (!QueueEmpty(&q)){BTNode* front = QueueFront(&q);QueuePop(&q);// 后面有非空,说明非空节点不是完全连续if (front){QueueDestroy(&q);return false;}}QueueDestroy(&q);//free空指针也没事return true;}

全局变量定义在函数声明后面,函数定义前面的话,可以直接在函数里面用

局部变量的话不行,必须传参过去(只用在函数调用之前生成就行了)

工程中很少用全局变量和静态变量(如果用了,要特别注意),一般用指针去代替

关于续行符:一般只有字符串需要使用续行符

作业部分

在用树表示的目录结构中,从根目录到任何数据文件,有(A)通道A.唯一一条B.二条C.三条D.不一定
下列关键字序列中,序列(D )是堆。A.{16,72,31,23,94,53}B.{94,23,31,72,16,53}C.{16,53,23,94,31,72}D.{16,23,53,31,94,72}这种题的话是把这些数据从左到右来按堆从上到下去排

力扣 单值二叉树

力扣 单值二叉树做法:遍历二叉树,然后让左右节点和自己的根节点比较(注意考虑节点为NULL的情况!)代码实现:bool isUnivalTree(struct TreeNode* root) { if(root == NULL) { return true; } if(root->left) { if(root->val!=root->left->val) return false; } if(root->right) { if(root->val!=root->right->val) return false; }return isUnivalTree(root->left)&& isUnivalTree(root->right);}

力扣 相同的树

力扣 相同的树(这个好)注意:不能对NULL进行解引用和->做法:要单独检验是否为空 然后递归return的是左子树相等&&右子树相等
代码实现:bool isSameTree(struct TreeNode*p,struct TreeNode*q){//两个都为空 if(p == NULL&&q == NULL) return true;//一个为空 if(p == NULL ||q == NULL) return false;//都不为空if(p->val!=q->val) return false; return isSameTree(p->left,q->left)&&isSameTree(p->right,q->right); }
力扣 对称二叉树做法;把除根外的二叉树分成两部分,然后把isSameTree的最后一句改成return isSameTree(p->left,q->right)&&isSameTree(p->right,q->left);即可

力扣 前序遍历

力扣 前序遍历注意:a[(*pi)++]的这个括号不能省略 那个pi要是指针才对下面这个只是代码的一部分要注意的是,题目要求的返回值要是int*类型的,那就要返回数组名了

堆和二叉树--数据结构初阶(3)(C/C++)

延伸出的一些问题:1.指针在定义和初始化一起搞的时候,eg:int*pi,要给他赋值地址才行,并且指针要是不初始化的话是不行的,要么就malloc一下,要么就给他一个量(不能是字面常量)的地址

 2.void类型的自定义函数的返回只能返回return ;不能return 0;
代码实现:int TreeSize(struct TreeNode* root){return root == NULL ? 0 : TreeSize(root->left) + TreeSize(root->right) + 1;//可以这样写,不用续行符}void preorder(struct TreeNode*root,int*a,int*pi){ if(root == NULL) return ; a[(*pi)++] = root->val; preorder(root->left,a,pi); preorder(root->right,a,pi); return ; }int* preorderTraversal(struct TreeNode* root, int* returnSize) { * returnSize = TreeSize(root); int *a = malloc(*returnSize * sizeof(int)); int*pi = malloc(sizeof(int*)); *pi = 0; preorder(root,a,pi);return a;}

力扣 另一棵树的子树

力扣 另一棵树的子树做法:把左边树的每一个结点对应的树跟目标树比较(这里的isSameTree是自己实现的比较两个树相等的)
代码实现:bool isSameTree(struct TreeNode*p,struct TreeNode*q){//两个都为空 if(p == NULL&&q == NULL) return true;//一个为空 if(p == NULL ||q == NULL) return false;//都不为空if(p->val!=q->val) return false; return isSameTree(p->left,q->left)&&isSameTree(p->right,q->right); }bool isSubtree(struct TreeNode* root, struct TreeNode* subRoot) { if(root == NULL) return false; if(isSameTree(root,subRoot)) return true;return isSubtree(root->left,subRoot)|| isSubtree(root->right,subRoot);}
错误写法:bool isSubtree(struct TreeNode* root, struct TreeNode* subRoot) { if(root == NULL) return false; isSubtree(root->left,subRoot); isSubtree(root->right,subRoot);//中间这两步让程序经过了正确的,但没有返回值产生反馈return isSameTree(root,subRoot);}

牛客网 KY11二叉树遍历

牛客网 KY11二叉树遍历注意:这题要求打印中序遍历,但是给的是前序遍历的数组,要注意易忘把树连接起来:root->left = PreOrder(); // 构建左子树root->right = PreOrder(); // 构建右子树//转换成二叉树的代码:BTNode* PreOrder() { if (i >= s1.size()) return NULL; char ch = s1[i++]; if (ch == \'#\') return NULL;//还是要转换成NULL BTNode* node = (BTNode*)malloc(sizeof(BTNode)); node->data = ch; node->left = PreOrder(); // 构建左子树 node->right = PreOrder(); // 构建右子树 return node;}
代码实现:#includeusing namespace std;string s1;int i;struct BTNode{ char data; struct BTNode *left; struct BTNode *right; };BTNode* PreOrder() { if (i >= s1.size()) return NULL; char ch = s1[i++]; if (ch == \'#\') return NULL; BTNode* node = (BTNode*)malloc(sizeof(BTNode)); node->data = ch; node->left = PreOrder(); // 构建左子树 node->right = PreOrder(); // 构建右子树 return node;}void InOrder(BTNode* root) {if (root == NULL) {return;}InOrder(root->left);printf(\"%c \", root->data);InOrder(root->right);}int main(){cin>>s1; BTNode*root = PreOrder(); InOrder(root); return 0;}

引伸:开辟的空间不会因为函数的结束而自动销毁,只有程序结束才会自动销毁

如果一颗二叉树的前序遍历的结果是ABCD,则满足条件的不同的二叉树有( B)种A.13B.14C.15D.16技巧:可以先判断出二叉树的层数假设有n个节点(不包含NULL),可以算出层数范围
一棵非空的二叉树的先序遍历序列与后序遍历序列正好相反,则该二叉树一定满足( C)A.所有的结点均无左孩子B.所有的结点均无右孩子//是错的,因为在子树只有一个孩子时,取左和取右是都是成立的C.只有一个叶子结点D.至多只有一个结点
已知某二叉树的中序遍历序列为JGDHKBAELIMCF,后序遍历序列为JGKHDBLMIEFCA,则其前序遍历序列为(B)A.ABDGHJKCEFILMB.ABDGJHKCEILMFC.ABDHKGJCEILMFD.ABDGJHKCEIMLF通用做法:像这种题的话,一般来说,都是中序遍历用来确定根的左右区间有啥 先或者后序遍历用来确定每个\"子树\"的根是啥eg:A的左子树:JGDHKB A的右子树:ELIMCFA的左子树的根:B …………B的左子树:JGDHK B的右子树:空 (因为JGDHKB 在B右边的为空) ……………………B的左子树的根为:D …………