数据结构与算法(七):二叉树 | 您所在的位置:网站首页 › 平衡二叉树最大深度公式log2n › 数据结构与算法(七):二叉树 |
定义
树是一种非线性表结构,它是由n(n≥0)个有限节点组成一个具有层次关系的集合。把它叫做“树”是因为它看起来像一棵倒挂的树,也就是说它是根朝上,而叶朝下的。它具有以下的特点:每个节点有零个或多个子节点;没有父节点的节点称为根节点;每一个非根节点有且只有一个父节点;除了根节点外,每个子节点可以分为多个不相交的子树。
如下图,A 节点就是 B 节点的父节点,B 节点是 A 节点的子节点。B、C、D 这三个节点的父节点是同一个节点,所以它们之间互称为兄弟节点。我们把没有父节点的节点叫做根节点,也就是图中的节点 E。我们把没有子节点的节点叫做叶子节点或者叶节点,比如图中的 G、H、I、J、K、L 都是叶子节点。
二叉树,顾名思义,每个节点最多有两个“叉”,也就是两个子节点,分别是左子节点和右子节点。除了叶子节点之外,每个节点都有左右两个子节点,这种二叉树就叫做满二叉树。叶子节点都在最底下两层,最后一层的叶子节点都靠左排列,并且除了最后一层,其他层的节点个数都要达到最大,这种二叉树叫做完全二叉树。 如何存储一颗二叉树 链式存储法一种基于指针或者引用的二叉链式存储法,每个节点有三个字段,其中一个存储数据,另外两个是指向左右子节点的指针。我们只要拎住根节点,就可以通过左右子节点的指针,把整棵树都串起来。这种存储方式我们比较常用。大部分二叉树代码都是通过这种结构来实现的。结构如下图:
我们把根节点存储在下标 i = 1 的位置,那左子节点存储在下标 2 * i = 2 的位置,右子节点存储在 2 * i + 1 = 3 的位置。以此类推,B 节点的左子节点存储在 2 * i = 2 * 2 = 4 的位置,右子节点存储在 2 * i + 1 = 2 * 2 + 1 = 5 的位置。即如果节点 X 存储在数组中下标为 i 的位置,下标为 2 * i 的位置存储的就是左子节点,下标为 2 * i + 1 的位置存储的就是右子节点。
常用的二叉树的遍历有三种方法,前序遍历、中序遍历和后序遍历。
二叉树遍历的伪代码实现: void preOrder(Node* root) { if (root == null) return; print root // 此处为伪代码,表示打印root节点 preOrder(root->left); preOrder(root->right); } void inOrder(Node* root) { if (root == null) return; inOrder(root->left); print root // 此处为伪代码,表示打印root节点 inOrder(root->right); } void postOrder(Node* root) { if (root == null) return; postOrder(root->left); postOrder(root->right); print root // 此处为伪代码,表示打印root节点 } 二叉查找树二叉查找树是二叉树中最常用的一种类型,也叫二叉搜索树,它最大的特点就是,支持动态数据集合的快速插入、删除、查找操作。二叉查找树要求,在树中的任意一个节点,其左子树中的每个节点的值,都要小于这个节点的值,而右子树节点的值都大于这个节点的值。简易代码实现如下: public class LinkBinaryTree { private Node _root; public LinkBinaryTree() { } public void insert(int data) { if (_root == default(Node)) { _root = new Node(data); return; } var node = _root; while (node != null) { if (data < node.data) { if (node.left == null) { node.left = new Node(data); return; } node = node.left; } else { if (node.Right == null) { node.Right = new Node(data); return; } node = node.Right; } } } public bool Exist(int data) { if (_root == default(Node)) return false; var node = _root; while (node != null) { if (node.data == data) { return true; } else if (node.data < data) { node = node.Right; } else { node = node.left; } } return false; } public void Delete(int data) { if (_root == default(Node)) return; var node = _root; Node pNode = null; while (node != null && node.data != data) { pNode = node; node = node.data > data ? node.left : node.Right; } if (node == null) return; // 要删除的节点有两个子节点 if (node.left != null && node.Right != null) { Node minP = node.Right; Node minPP = node; while (minP.left != null) { minPP = minP; minP = minP.left; } node.data = minP.data; node = minP; pNode = minPP; } // 删除节点是叶子节点或者仅有一个子节点 Node child = null; if (node.left != null) { child = node.left; } else if (node.Right != null) { child = node.Right; } if (pNode == null) { _root = child; } else if (pNode.left == node) { pNode.left = child; } else { pNode.Right = child; } } public void Print() { if (_root == default(Node)) return; MiddlePrint(_root); } private void MiddlePrint(Node node) { if (node.left != null) { MiddlePrint(node.left); } Console.WriteLine($"{node.data}"); if (node.Right != null) { MiddlePrint(node.Right); } } class Node { public Node(int data) { this.data = data; } public Node(int data, Node left, Node right) { this.data = data; this.left = left; Right = right; } public int data { get; set; } public Node left { get; set; } public Node Right { get; set; } } } 二叉查找树的时间复杂度分析际上,二叉查找树的形态各式各样。比如这个图中,对于同一组数据,我们构造了三种二叉查找树。它们的查找、插入、删除操作的执行效率都是不一样的。图中第一种二叉查找树,根节点的左右子树极度不平衡,已经退化成了链表,所以查找的时间复杂度就变成了 O(n)。
|
CopyRight 2018-2019 实验室设备网 版权所有 |