在学习二叉树之前先要学【先导知识】树的概念和表示方法

目录

1、二叉树

2、二叉树的遍历

3、二叉排序树


前言

对于大量的数据而言,链表的线性访问时间太慢,不宜使用。本章节将会介绍一种简单的数据结构:树(tree),其大部分操作的运行时间平均为O(logN)。在数据结构中树是非常有用的抽象概念,在本篇中我们将讨论二叉树的存储结构、二叉树的遍历和二叉排序树的实现,为后续平衡树以及高阶搜索树打下基础。

1、二叉树

二叉树(binary tree)是一棵每一个节点最多只能有两个孩子的树(度为2),当一棵树只有左孩子或者右孩子的最坏情况称为斜树

当一棵树的度只为0或者2,我们把这种树称为满二叉树;当一棵树的节点严格按照从上到下、从左到右的的次序依次排列,我们把这种树称为完全二叉树,根据定义,满二叉树是完全二叉树的一种特殊情况;如果我们用NULL或者其他不可能出现的关键字来补满所有的空节点,那么这种树称为扩展二叉树。

同时二叉树还有些性质在考试中经常考到,这里也总结一下:

  1. 在第 i  层的二叉树上最多有 2^(i-1) 个节点;
  2. 深度为 k 的二叉树有 2^k-1 个节点;
  3. 假设度为0、1、2的节点分别是n0、n1、n2,由于一棵树中节点数是T,所以T = n0 + n2 + n2,而树枝(节点之间的连线)的数量是T - 1,所以T - 1 = 2 * n2 + n1,由这两个关系式可得:n0=n2+1;
  4. 具有 n 个节点的完全二叉树深度为( log2n)+1 向下取整;
  5. 由性质4可得:具有 n 个节点的完全二叉树节点按层次编号(从上到下、从左到右),如果 i = 1是根节点,那么对于任意一个节点它的双亲节点序号是 i / 2(i > 1)向下取整,如果当 2 * i > n 那么 节点 i 一定没有左孩子(也没有右孩子),2 * i + 1 > n 没有右孩子;

对于二叉树的实现,我们同样可以考虑使用顺序结构或者链式结构。使用顺序结构,因为查找迅速在比赛中可以用到,我们可以根据上述的性质5,计算出父节点和它孩子之间的下标关系;

而因为一棵二叉树最多有两个孩子,所以我们在使用链式结构的时候可以直接用指针指向它们。

/*二叉树节点声明*/
typedef struct TreeNode
{int data;struct TreeNode* left, *right;
}Node;

至于二叉树的实现十分简单,哪有空就往哪插;

Node* root = NULL;void init(int key)
{root = (Node*)malloc(sizeof(Node));root->data = key;root->left = NULL;root->right = NULL;
}Node* findNode(Node* node ,int parent)
{static Node* temp = NULL;if (node->data == parent){temp = node;}if(node->left){findNode(node->left, parent);}if (node->right){findNode(node->right, parent);}return temp;
}void createNode(int key, int parent)
{Node* temp = findNode(root, parent);if (temp == NULL){printf("NOT FIND\n");return;}else{if (temp->left == NULL){Node* newnode = (Node*)malloc(sizeof(Node));newnode->data = key;newnode->left = NULL;newnode->right = NULL;temp->left = newnode;}else if (temp->right == NULL){Node* newnode = (Node*)malloc(sizeof(Node));newnode->data = key;newnode->left = NULL;newnode->right = NULL;temp->right = newnode;}else{//左右子树都存在printf("FULL\n");return;}}
}

2、二叉树的遍历

接下来我们重点介绍二叉树的遍历。

二叉树的遍历根据使用场景的不同分为:前序遍历、中序遍历、后序遍历及层序遍历;

  • 前序遍历:先访问根节点再访问左子树再访问右子树;

如上所示的二叉树中,前序遍历的顺序是:1 ->2 -> 4 -> 5 -> 3 -> 6 ;

具体步骤如下图所示,访问顺序(由先到后)  绿 、 红 、 蓝 

  • 中序遍历:先访问左子树,再访问根节点,再访问右子树;

如上所示的二叉树中,中序遍历的顺序是:4 ->2 -> 5 -> 1 -> 6 -> 3

  • 后序遍历:先访问左子树,再访问右子树,再访问根节点;

如上所示的二叉树中,后序遍历的顺序是:4 ->5 -> 2 -> 6 -> 3 -> 1

通过上述案例,我们可以得到一个重要性质:前序遍历的第一个一定是根,后序遍历的最后一个一定是根,那么通过前序和中序或者后序和中序就能推出树的抽象模型;

而用代码实现前中后序遍历,用递归实现是最简单不过的了:

//前序
void prev_order(Node* node)
{if (node == NULL){return;}printf("%d", node->data);prev_order(node->left);prev_order(node->right);
}//中序
void in_order(Node* node)
{if (node == NULL){return;}in_order(node->left);printf("%d", node->data);in_order(node->right);
}//后序
void past_order(Node* node)
{if (node == NULL){return;}past_order(node->left);past_order(node->right);printf("%d", node->data);
}

至于层序遍历,它能直观地将树的每一层输出,当然想要这样做用简单的递归肯定是不行的,这里我们使用队列来实现:

第一步:我们先把根节点入队;第二步:判断队列是否为空,如果不为空,就把队头出队,将它的孩子入队,如此循环,直到队列为空跳出;

代码的话我这里偷个小懒,用C++中的STL实现的队列,不会的同学正常写队列就行了,思路是一样的;

//层序
void level_order(Node* node)
{//创建队列std::queue<int> q;if (node != NULL){q.push(node->data);}while(!q.empty()){int temp = q.front();printf("%d ", temp);Node* tempNode = findNode(root, temp);q.pop();if (tempNode->left){q.push(tempNode->left->data);}if (tempNode->right){q.push(tempNode->right->data);}}
}

3、二叉排序树

二叉排序树又叫做二叉搜索树:如果一棵二叉树它的左子树不空,那么左子树的所有值都小于根节点;若右子树不为空,那么所有右子树的值都大于根节点,这就是二叉排序树(binary search tree)。

如上所示就是一棵二叉排序树。二叉排序树创造出来就是为了使我们在查找中更加方便(二分查找),由于二叉排序树的特殊性,使得其左边的值总是小于根节点,右边的值总是大于根节点,可以发现当我们用中序遍历这棵树时,它的结果是一个有序的序列:1 3 4 6 7 8 10 13 14;

它的节点的结构体和一般二叉树一样;

typedef struct TreeNode
{int data;struct TreeNode* left, * right;
}Node;

而构建一棵二叉排序树也很简单,按照它的性质插入即可;

//默认没有重复的key的情况
void insert(Node** root, int key)
{Node* prev = NULL;//用来指向正确的插入位置的父节点Node* temp = *root;while (temp != NULL){//追随root的上一个位置prev = temp;//如果插入的值小于该节点 往左边找if (key < temp->data){temp = temp->left;}//如果插入的值大于该节点 往右边找else if (key > temp->data){temp = temp->right;}}//找到了要插入的位置Node* newnode = (Node*)malloc(sizeof(Node));if (newnode == NULL){printf("ERR\n");return;}else{newnode->data = key;newnode->left = NULL;newnode->right = NULL;if (*root == NULL)//如果根节点没有初始化{*root = newnode;}else if (key < prev->data){prev->left = newnode;}else if (key > prev->data){prev->right = newnode;}}
}

二叉排序树的重点在于删除操作。对于二叉排序树的删除,我们一般分为三种情况:

  • 第一种情况:删除的是叶子节点,这时候直接删除就行了,因为删除它不会对整棵树产生影响;
  • 第二种情况:删除的节点有一个孩子(无论左右),这时候需要把它的孩子移动到被删除节点的位置就行了;
  • 第三种情况:删除的节点有两个孩子,这时候需要找到该节点的直接前驱或者直接后继来替换删除节点;

前两种情况都好理解,我们详细说说第三种情况。以上文中的有序序列1 3 4 6 7 8 10 13 14为例,假如我们要删除值为8的节点,要想不对整个有序序列产生影响,删除后的结果应该是1 3 4 6 7 10 13 14,也就是我们要找到值是7或者值是10的节点替换8节点,然后按照情况一、二删除原来的7或者10,。这个7节点就是删除节点的直接前驱,同理节点10是删除节点的直接后继;

要找到这个直接前驱或者直接后继也很简单:从下图中我们可以看出,直接前驱节点一定是删除节点的左子树中的最大值,直接后继节点一定是删除节点的右子树中的最小值

以直接前驱节点7为例,如果待删除的节点左子树存在,我们只需要先访问这个左子树,然后一直访问它的右子树即可。

/*真正的删除操作*/
void del(Node* node, Node* prev)
{Node* temp = NULL;//只有左子树或者只有右子树的情况 把要删除的节点删除 并把孩子替换上去if (node->left == NULL && node->right != NULL){temp = node;temp = temp->right;node->data = temp->data;node->left = temp->left;node->right = temp->right;free(temp);}else if (node->right == NULL && node->left != NULL){temp = node;temp = temp->left;node->data = temp->data;node->left = temp->left;node->right = temp->right;free(temp);}//叶子节点的情况else if (node->right == NULL && node->left == NULL){//左叶子的情况if (node->data < prev->data){prev->left = NULL;}else{prev->right = NULL;}free(node);}//左右子树都不为空的情况else{temp = node;Node* s = node;//指向左子树的最大值的指针//找左子树的最大值s = s->left;while (s->right != NULL){temp = s;//这里的temp指向s的父节点s = s->right;}//替换数据node->data = s->data;//还要删除s节点 又分两种情况//但是不管哪种情况此时的s要么没有孩子要么只有左孩子if (temp!= node){//说明s往下走了很多步 这个时候s一定在temp的右边temp->right = s->left;}else{//这个时候s一定在temp的左边temp->left = s->left;}}
}/*封装删除操作*/
void deleteNode(Node* root, int key)
{if(root == NULL){return;}else{static Node* prev = NULL;//prev是删除节点的父节点//递归地去寻找要删除的点if (key < root->data){prev = root;deleteNode(root->left, key);}else if (key > root->data){prev = root;deleteNode(root->right, key);}else{//删除del(root, prev);}}
}

用中序遍历测试结果

后话

正常来说,一棵二叉排序树的复杂度取决于树的高度h,但如果我插入的值是12345...呢?它是不是就成了我们所说的复杂度为O(N)的最坏的情况——斜树,为保证查找效率,人类历史上的第一棵自平衡树——二叉平衡树就此诞生!我们下次将详细讲解二叉平衡树的增删查改。

二叉树、二叉树排序树的实现及遍历相关推荐

  1. 按照前序遍历创建二叉树及树的四种遍历方式

    一.二叉树的介绍 二叉树的特点是二叉树的每个结点的度都不大于2,可以视为每个结点都有左孩子和右孩子.故二叉树结点的数据结构为 二.二叉树的特点 1.设根结点所在的层数为第1层,则第i层最多有个结点. ...

  2. 二叉树排序树插入、创建、删除和查找

    二叉树排序树插入.创建.删除和查找 二叉排序树的概念 ​ 二叉排序树又称二叉查找树,它或者是一棵空树,或者是具有下列性质的二叉树 若它的左子树不为空,则左子树上所有结点的值均小于根结点的值 若它的右子 ...

  3. 【练习】树(Tree, UVa 548)给一棵点带权(权值各不相同)的二叉树的中序和后序遍历,找一个叶子使得它到根的路径上的权和最小。

    给一棵点带权(权值各不相同,都是小于10000的正整数)的二叉树的中序和后序遍历,找一个叶子使得它到根的路径上的权和最小.如果有多解,该叶子本身的权应尽量小.输入中每两行表示一棵树,其中第一行为中序遍 ...

  4. 二叉树总结(二)树的遍历

    该文我会用来总结二叉树相关的知识 二叉树如下图: 二叉树的结构 struct TreeNode {int val;TreeNode *left;TreeNode *right;TreeNode(int ...

  5. 树和森林与二叉树的转换、树和森林的遍历

    全部数据结构.算法及应用课内模板请点击:https://blog.csdn.net/weixin_44077863/article/details/101691360 树和森林转二叉树其实十分简单,我 ...

  6. 由标明空子树的先序遍历序列创建二叉树

    由标明空子树的先序遍历序列创建二叉树 i=0 def createBiTree2(preOrder): # i为常数0 global i c = preOrder[i] # 取字符 if c != ' ...

  7. C语言学习笔记——根据二叉树的后序和中序遍历序列,求这棵树的先序和层次遍历序列

    先根据二叉树的后序和中序遍历序列,用递归的方法创建出这棵树,然后用的自定义栈的先序和层次方法遍历. 输入:  7 2 3 1 5 7 6 4                      1 2 3 4 ...

  8. 数据结构 | 第十一章:二叉树和其他树 | 【前序遍历】【中序遍历】【后序遍历】【层次遍历】 | 并查集

    第5-10章:线性结构,元素之间存在线性次序(线性表.数组与矩阵.栈.队列.跳表和散列表 第11-15章:层次结构(二叉树和树.优先队列.竞赛树.搜索树) 文章目录 11.1 树 11.2 二叉树 1 ...

  9. 在链式存储结构建立二叉树排序树

    #include <stdlib.h> #include <stdio.h>//定义树 typedef struct node{ //树的结点int data;struct n ...

最新文章

  1. Python 正在从简明转向臃肿,从实用转向媚俗
  2. 心得丨一文告诉你想学数据分析该读什么书、从哪本读起
  3. linux虚拟单用户数,Linux单用户模式
  4. vs mfc数据与控件绑定错了_如何进行数据趋势分析?VS扩展工具——C1迷你图控件了解一下...
  5. 叙述无保密机制的rsa签名过程_安全系列之——RSA的公钥私钥有多少人能分的清楚?RSA的签名验签与加密解密如何使用公私钥?...
  6. hdu 1233 最小生成树
  7. java timestamp时间戳_求助!java中关于时间戳Timestamp的问题
  8. 大家觉得现在最赚钱的电商形式是什么?
  9. 接口加密了该怎么测?
  10. 使用Screaming Frog SEO Spider 如何查找断开的链接
  11. ios查看ipa是否函数特定字符_利用strings 检测iOS ipa包是否调用私有api
  12. 读卡器 linux 驱动,基于Linux的公交一卡通读卡器驱动设计
  13. meltdown linux 补丁,谈谈CentOS发布内核安全补丁:修复Meltdown和Spectre漏洞
  14. adb 查看手机的ip地址
  15. python word2vector 词 财务报告 指数_使用Python可视化Word2vec的结果
  16. lucene java 庖丁解牛_Lucene分词器之庖丁解牛
  17. 让员工都是决策者!受到丰田集团启发:让企业少花500万的诀窍
  18. Advanced IP Scanner教程 详细使用方法
  19. 对接百度api之银行卡识别
  20. Vue 中使用npm run serve报错 vue-cli-service serve

热门文章

  1. 南师大计算机组成期末考试,南师计算机操作系统期末考试题及答案
  2. python [Errno socket error] [Errno 104] Connection reset by peer
  3. 遇见error: RPC failed; curl 28 OpenSSL SSL_read: Connection was reset, errno 10054 fatal: the remote e
  4. ARP ICMP欺骗
  5. 【QGIS基础操作】QGIS插件功能记录
  6. 前台Service发展历史
  7. 2014暑假总结——美好的事情即将发生
  8. 线性代数_6、范德蒙德行列式及克莱姆法则
  9. Linux下/proc
  10. 软件外包接单经验谈-需求篇