• 数据结构之初阶二叉树


    要努力,但不要急。繁花锦簇,硕果累累都需要过程!

     

    目录

    前言:

    1.树

    1.树的概念:

    2.树的相关概念:

    3.树的表示:

    4.小结:

    2.二叉树

    1.概念:

    2.特殊的二叉树:

    3.二叉树的性质:

    4.二叉树的存储结构:

    5.二叉树链式结构的实现:

    6.二叉树的应用结构:堆

    1.堆的概念和结构:

    2.堆的实现:

    3.堆排序

    3.二叉树的oj面试题


    前言:

    树是一种在数据结构中经典的非线性结构,在实际应用中非常普遍,例如文件的层级就是一种树形结构,每一个根之下对应许多的子文件,下面我们一一来剖析树的概念:

    1.树

    1.树的概念:

    树是一种非线性的数据结构,它是由n(n>=0)个有限结点组成一个具有层次关系的集合。把它叫做树是因 为它看起来像一棵倒挂的树,也就是说它是根朝上,而叶朝下的。

    有一个特殊的结点,称为根结点,根节点没有前驱结点

    除根节点外,其余结点被分成M(M>0)个互不相交的集合T1、 T2、……、 Tm,其中每一个集合Ti(1 又是一棵结构与树类似的子树。每棵子树的根结点有且只有一个前驱,可以有0个或多个后继

    树是递归定义的

     注意:树形结构中,子树之间不能有交集,否则就不是树形结构

    2.树的相关概念:

    节点的度:一个节点含有的子树的个数称为该节点的度; 如上图: A的为6

    叶节点或终端节点:度为0的节点称为叶节点; 如上图: B、 C、 H、 I...等节点为叶节点

    非终端节点或分支节点:度不为0的节点; 如上图: D、 E、 F、 G...等节点为分支节点

    双亲节点或父节点:若一个节点含有子节点,则这个节点称为其子节点的父节点; 如上图: A是B的父节点

    孩子节点或子节点:一个节点含有的子树的根节点称为该节点的子节点; 如上图: B是A的孩子节点

    兄弟节点:具有相同父节点的节点互称为兄弟节点; 如上图: B、 C是兄弟节点 树的度:一棵树中,最大的节点的度称为树的度; 如上图:树的度为6

    节点的层次:从根开始定义起,根为第1层,根的子节点为第2层,以此类推;

    树的高度或深度:树中节点的最大层次; 如上图:树的高度为4

    堂兄弟节点:双亲在同一层的节点互为堂兄弟;如上图: H、 I互为兄弟节点

    节点的祖先:从根到该节点所经分支上的所有节点;如上图: A是所有节点的祖先

    子孙:以某节点为根的子树中任一节点都称为该节点的子孙。如上图:所有节点都是A的子孙

    森林:由m(m>0)棵互不相交的树的集合称为森林;

    3.树的表示:

    树结构相对线性表就比较复杂了,要存储表示起来就比较麻烦了,既然保存值域,也要保存结点和结点之间 的关系

    1.第一种表示方法:

     缺点:在实际的过程中并不能确定树的度是多少

    2.第二种表示方法:顺序表存储

     3.第三种表示方法:链表存储

    父亲指向第一个孩子,孩子之间用兄弟指针链接起来 

    4.小结:

    以上就是关于一些树的基本概念,现阶段不进行深入研究,下面我们来看树的一种特殊结构,二叉树。

    2.二叉树

    1.概念:

    一颗二叉树是结点的一个有限集合,该集合为空或者一个根结点由左子树和右子树组成

    1.二叉树不存在度大于2的结点

    2.二叉树的子树有左右之分,次序不能颠倒,因此二叉树是有序树

    现实中的二叉树:

     所有的二叉树都是一下几种情况组成的:

    2.特殊的二叉树:

    1 . 满二叉树一个二叉树,如果每一个层的结点数都达到最大值,则这个二叉树就是满二叉树。也就是 说,如果一个二叉树的层数为K,且结点总数是2^(K)-1 ,则它就是满二叉树。

    2. 完全二叉树:完全二叉树是效率很高的数据结构,完全二叉树是由满二叉树而引出来的。对于深度为K的,有n个结点的二叉树,当且仅当其每一个结点都与深度为K的满二叉树中编号从1至n的结点一一对 应时称之为完全二叉树。 要注意的是满二叉树是一种特殊的完全二叉树。

     结点数范围:[2^(k-1),2^(k)-1]

    3.二叉树的性质:

    1 . 若规定根节点的层数为1,则一棵非空二叉树的第i层上最多有2^(i-1) 个结点.

    2. 若规定根节点的层数为1,则深度为h的二叉树的最大结点数是 2^h-1

    3. 对任何一棵二叉树, 如果度为0其叶结点个数为n0 , 度为2的分支结点个数为n1 ,则有

    n0 =n2 + 1(度为0的结点个数总是比度为2的结点多一)

    4. 若规定根节点的层数为1,具有n个结点的满二叉树的深度, h= log(n+1). (ps:log(n+1)) 是log以2为底, n+1为对数)

    4.二叉树的存储结构:

    1.顺序存储:

    顺序存储的结构一般是采用数组来存储的,对于完全二叉树一般采用数组存储,但是对于普通二叉树采用数组存储会浪费许多的空间,所以普通的二叉树一般采用的存储结构进行存储

    图解:

    2.链式存储

    图解:

    5.二叉树链式结构的实现:

    1.二叉树结构的简单创建:

     2.二叉树的遍历:

     前序遍历:访问根结点的操作发生在遍历其左右子树之前(根,左子树,右子树

     递归详图分解:

     

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

     

    后序遍历:访问根结点的操作发生在遍历其左右子树之后。(左子树,根,右子树

      

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

    实现方式:构建一个队列,上一层结点出的时候带入下一层

     

    3.求二叉树结点个数:

     4.求叶子节点的个数:

     5.求树的高度:

    6.求第K层结点的个数:

     7.返回x所在的结点:

    8.销毁二叉树:

     9.判断一个树是否是完全二叉树:

    思路:采用层序遍历的方式,遇到空之后,如果后边还存在空,则不是完全二叉树:

     

    6.二叉树的应用结构:堆

    1.堆的概念和结构:

    如果有一个关键码的集合K = { , , ,…, },把它的所有元素按完全二叉树的顺序存储方式存储 在一个一维数组中,并满足: 且 = 且 >= ) i = 0, 1,2…,则称为小堆(或大堆)。将根节点最大的堆叫做最大堆或大根堆,根节点最小的堆叫做最小堆或小根堆

    堆的性质:

    1.堆中某个节点的值总是不大于或不小于其父节点的值;

    2.堆总是一颗完全二叉树;

    堆的图解:

    1.小堆:所有孩子结点的值大于等于父亲结点的值

    逻辑结构:

    物理结构:

     2.大堆:所有孩子的值小于等于父亲的值:

    逻辑结构:

    物理结构:

     数组下标计算父子之间的关系:

    leftChild = parent*2+1;

    rightChild = parent*2+2;

    parent = (child-1)/2;

    2.堆的实现:

    例:实现小堆:用数组实现

    1.创建结构体:

    2.堆的初始化:

    void HeapInit(HP* php); 

     3.插入数据并保持堆形态:
    void HeapPush(HP* php, HpDataType x);

    思路:将数据插入数组的最后一个位置,然后通过最后一个位置的下标计算出父亲的值进行比较,如果孩子的值比父亲的值小就交换,直到孩子的值比父亲的值大(小堆)

    4.删除堆顶的数据:
    void HeapPop(HP* php); 

    思路:将第一个数据和最后一个数据交换,然后size--,交换上去的数据如果比孩子的值大,为了保持堆形态,就向下调整直到父亲的值比孩子的值小。

    5.获取堆顶的元素:
    HpDataType HeapTop(HP* php); 

    6.判断堆是否为空:
    bool HeapEmpty(HP* php); 

    7.获取堆上元素的个数:
    int HeapSize(HP* php); 

    8.堆的打印:
    void HeapPrint(HP* php); 

    9.堆销毁:
    void HeapDestroy(HP* php); 

    3.堆排序

    1.建堆:

    第一种方案:向上调整建堆

     

    第二种方案:向下调整建堆

    方法:从倒数第一个非叶子结点(最后一个结点的父亲)开始向下调整建堆,直到调整到根

     

    在堆排序的时候既可以采用向上调整建堆,也可以采用向下调整建堆,但是相互比较下向下调整建堆时间复杂度比向上调整建堆低,所以采用向下调整建堆

    2.排序:

    思路:升序排序的时候采用建大堆的方式,降序排序的时候采用建小堆的方式。

    原因:当建好堆之后,采用向下调整的方式,将排序的复杂度优化到O(N*logN)

    例:降序:

    1. void Swap(int* p1, int* p2)
    2. {
    3. int tmp = *p1;
    4. *p1 = *p2;
    5. *p2 = tmp;
    6. }
    7. void AdjustDown(int* a, int n, int parent)
    8. {
    9. int minChild = parent * 2 + 1;
    10. while (minChild < n)
    11. {
    12. if (minChild + 1 < n && a[minChild + 1] < a[minChild])
    13. {
    14. minChild++;
    15. }
    16. if (a[minChild] < a[parent])
    17. {
    18. Swap(&a[minChild], &a[parent]);
    19. parent = minChild;
    20. minChild = parent * 2 + 1;
    21. }
    22. else
    23. {
    24. break;
    25. }
    26. }
    27. }
    28. void HeapSort(int* arr, int sz)
    29. {
    30. //向下调整建堆:
    31. int i = 0;
    32. for (i = (sz - 1 - 1) / 2; i >= 0; i--)
    33. {
    34. AdjustDown(arr, sz, i);
    35. }
    36. i = 1;
    37. while (i < sz)
    38. {
    39. Swap(&arr[0], &arr[sz - i]);
    40. AdjustDown(arr, sz - i, 0);
    41. i++;
    42. }
    43. }
    44. int main()
    45. {
    46. int arr[] = { 32,56,79,45,57,100,67,78 };
    47. int sz = sizeof(arr) / sizeof(arr[0]);
    48. HeapSort(arr, sz);
    49. int i = 0;
    50. for (i = 0; i < sz; i++)
    51. {
    52. printf("%d ", arr[i]);
    53. }
    54. return 0;
    55. }

    TOP-K问题:

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

    解决思路:

    1.用数据集合中前k个元素建堆:

    求K个最大的元素,建立小堆;

    求K个最小的元素,建立大堆;

    2.遍历剩余的N-K个元素,不满足就交换:最后在堆中的元素就是最大或者是最小的K个元素

    时间复杂度为O(N)  空间复杂度为O(K)

    例:找出文件中前10个最大的数据:

    3.二叉树的oj面试题:

    题目1:

    1.单值二叉树:oj链接

    题目描述:如果二叉树每个节点都具有相同的值,那么该二叉树就是单值二叉树。

    只有给定的树是单值二叉树时,才返回 true;否则返回 false

    1. bool isUnivalTree(struct TreeNode* root){
    2. if(root == NULL)
    3. return true;
    4. if(root->left != NULL && root->val != root->left->val)
    5. return false;
    6. if(root->right != NULL && root->val != root->right->val)
    7. return false;
    8. return isUnivalTree(root->left) && isUnivalTree(root->right);
    9. }

    题目二:

    2.检查两颗树是否相同oj链接

    题目描述:给你两棵二叉树的根节点 p 和 q ,编写一个函数来检验这两棵树是否相同。

    如果两个树在结构上相同,并且节点具有相同的值,则认为它们是相同的。

    1. bool isSameTree(struct TreeNode* p, struct TreeNode* q){
    2. if(p == NULL && q == NULL)
    3. return true;
    4. if(p == NULL || q == NULL)
    5. return false;
    6. if(p->val != q->val)
    7. return false;
    8. return isSameTree(p->left,q->left) && isSameTree(p->right,q->right);
    9. }

    题目三:

    3.对称二叉树:oj链接

    题目描述:给你一个二叉树的根节点 root , 检查它是否轴对称。

    1. bool isSameTree(struct TreeNode* left, struct TreeNode* right){
    2. if(left == NULL && right == NULL)
    3. return true;
    4. if(left == NULL || right == NULL || left->val != right->val)
    5. return false;
    6. return isSameTree(left->left,right->right) && isSameTree(left->right, right->left);
    7. }
    8. bool isSymmetric(struct TreeNode* root){
    9. if(root == NULL)
    10. return true;
    11. return isSameTree(root->left,root->right);
    12. }

    题目四:

    4.二叉树的前序遍历:oj链接

    题目描述:给你二叉树的根节点 root ,返回它节点值的 前序 遍历。

    1. int TreeSize(struct TreeNode* root){
    2. if(root == NULL)
    3. return 0;
    4. return TreeSize(root->left) + TreeSize(root->right) + 1;
    5. }
    6. void preorder(struct TreeNode* root,int*a,int* pos){
    7. if(root == NULL)
    8. return;
    9. a[*pos] = root->val;
    10. (*pos)++;
    11. preorder(root->left,a,pos);
    12. preorder(root->right,a,pos);
    13. }
    14. int* preorderTraversal(struct TreeNode* root, int* returnSize){
    15. //求结点的个数:
    16. int n = TreeSize(root);
    17. int* a = (int*)malloc(sizeof(int)*n);
    18. *returnSize = n;
    19. int pos = 0;
    20. //前序遍历:
    21. preorder(root,a,&pos);
    22. return a;
    23. }

    题目五:

    5.另一棵树的子树:oj链接

    题目描述:给你两棵二叉树 root 和 subRoot 。检验 root 中是否包含和 subRoot 具有相同结构和节点值的子树。如果存在,返回 true ;否则,返回 false 。

    1. bool isSameTree(struct TreeNode* p, struct TreeNode* q){
    2. if(p == NULL && q == NULL)
    3. return true;
    4. if(p == NULL || q == NULL)
    5. return false;
    6. if(p->val != q->val)
    7. return false;
    8. return isSameTree(p->left,q->left) && isSameTree(p->right,q->right);
    9. }
    10. bool isSubtree(struct TreeNode* root, struct TreeNode* subRoot){
    11. if(root == NULL)
    12. return false;
    13. if(isSameTree(root,subRoot))
    14. return true;
    15. return isSubtree(root->left,subRoot) || isSubtree(root->right,subRoot);
    16. }

    题目六:

    6.二叉树的构建和遍历:oj链接

    题目描述:编一个程序,读入用户输入的一串先序遍历字符串,根据此字符串建立一个二叉树(以指针方式存储)。 例如如下的先序遍历字符串: ABC##DE#G##F### 其中“#”表示的是空格,空格字符代表空树。建立起此二叉树以后,再对二叉树进行中序遍历,输出遍历结果。

    1. #include
    2. #include
    3. typedef char BTDataType;
    4. typedef struct BinaryTreeNode
    5. {
    6. BTDataType data;
    7. struct BinaryTreeNode* left;
    8. struct BinaryTreeNode* right;
    9. }BTNode;
    10. BTNode* BinaryTreeCreate(BTDataType* a,int* pi)
    11. {
    12. if(a[*pi] == '#')
    13. {
    14. (*pi)++;
    15. return NULL;
    16. }
    17. BTNode* root = (BTNode*)malloc(sizeof(BTNode));
    18. if(root == NULL)
    19. {
    20. perror("malloc fail");
    21. return NULL;
    22. }
    23. root->data = a[*pi];
    24. (*pi)++;
    25. root->left = BinaryTreeCreate(a,pi);
    26. root->right = BinaryTreeCreate(a,pi);
    27. return root;
    28. }
    29. void InOrder(BTNode* root)
    30. {
    31. if(root == NULL)
    32. return;
    33. InOrder(root->left);
    34. printf("%c ",root->data);
    35. InOrder(root->right);
    36. }
    37. int main()
    38. {
    39. char str[100];
    40. scanf("%s",str);
    41. //前序构建树:
    42. int i = 0;
    43. BTNode* root = BinaryTreeCreate(str,&i);
    44. //中序遍历打印:
    45. InOrder(root);
    46. return 0;
    47. }

  • 相关阅读:
    keycloak~jwt的rs256签名的验证方式
    CAD软件的二次开发
    【python debug】python常见编译问题解决方法_1
    Transformer/Bert
    【AWS】实操-保护 Amazon S3 VPC 终端节点通信
    022python - http请求(理论)
    使用NRM管理Node镜像源,提升包下载速度
    网工内推 | 雄岸区块链集团,网安工程师,HCIE-Security、CISP优先
    room数据库的使用
    77. 组合
  • 原文地址:https://blog.csdn.net/qq_65307907/article/details/126226967