当前位置:   article > 正文

数据结构——树_在树形数据结构中,一个节点可以有多个子节点,但每个子节点只能有一个父节点的结构

在树形数据结构中,一个节点可以有多个子节点,但每个子节点只能有一个父节点的结构

树是一种非线性的数据结构,它由一组节点组成,这些节点通过边连接。在树中,有一个被称为根节点的特殊节点,其他节点都是从根节点开始通过一条唯一的路径到达的。树中的每个节点可以有多个子节点,但每个节点只有一个父节点(除了根节点)。树的分支被称为子树。

树的特点包括:
1. 根节点:树的顶部节点,其他节点都是从根节点开始通过一条唯一的路径到达的。
2. 节点:树中的每个元素,通常包含一个值和指向其子节点的指针。
3. 子节点:一个节点的直接子节点,它们位于该节点的下一层。
4. 父节点:一个节点的直接上层节点。
5. 叶节点:没有子节点的节点。
6. 深度:从根节点到某个节点的路径上的边数。
7. 高度:树中任意节点到叶节点的最长路径的边数。

树的应用非常广泛,它可以用来表示层次关系、组织结构、文件系统等。常见的树的应用包括二叉树、AVL树、红黑树、B树等。

树的操作包括插入节点、删除节点、查找节点、遍历节点等。常见的树的遍历算法包括前序遍历、中序遍历和后序遍历。

二叉树:

二叉树是一种特殊的树形数据结构,每个节点最多有两个子节点,分别称为左子节点和右子节点

二叉树的定义如下:


1.  二叉树可以为空树,也就是不包含任何节点。
2.  如果不为空树,那么每个节点包含一个数据元素和左子树、右子树两个指针。
3.  左子树和右子树都是二叉树。
4.  二叉树的左子树和右子树的位置是固定的。

二叉树的特点:


1.  每个节点最多有两个子节点。
2.  左子树的值小于等于父节点的值,右子树的值大于等于父节点的值(这是一棵二叉查找树)。
3.  左子树和右子树也都是二叉树。

二叉树的遍历方式:


1.  前序遍历(根-左-右):先访问根节点,再前序遍历左子树,最后前序遍历右子树。
2.  中序遍历(左-根-右):先中序遍历左子树,再访问根节点,最后中序遍历右子树。
3.  后序遍历(左-右-根):先后序遍历左子树,再后序遍历右子树,最后访问根节点。
4.  层序遍历(逐层访问):按照从上到下、从左到右的顺序逐层访问节点。

二叉树的应用:


1.  二叉搜索树:基于二叉树的特性,可以快速地进行查找、插入和删除操作。
2.  表达式树:将数学表达式表示为二叉树,方便进行计算。
3.  堆和优先队列:二叉树可以用来实现堆和优先队列,用于快速获取最大或最小的元素。
4.  文件系统:文件系统中的目录和子目录可以通过二叉树进行组织和管理。

需要注意的是,二叉树的高度可能会非常大,导致操作的时间复杂度较高。为了提高二叉树的效率,可以使用平衡二叉树等其他数据结构。

代码基本实现

  1. package com.zlk.tree;
  2. import com.zlk.line.Queue;
  3. /**
  4. * @author zlk
  5. * @date 2023年09月03日 19:34
  6. * 二叉树的基本实现
  7. */
  8. public class BinaryTree3<Key extends Comparable<Key>, Value> {
  9. //记录根结点
  10. private Node root;
  11. //记录树中元素的个数
  12. private int N;
  13. private class Node {
  14. //存储键
  15. public Key key;
  16. //存储值
  17. private Value value;
  18. //记录左子结点
  19. public Node left;
  20. //记录右子结点
  21. public Node right;
  22. public Node(Key key, Value value, Node left, Node right) {
  23. this.key = key;
  24. this.value = value;
  25. this.left = left;
  26. this.right = right;
  27. }
  28. }
  29. //获取树中元素的个数
  30. public int size() {
  31. return N;
  32. }
  33. //向树中添加元素key-value
  34. public void put(Key key, Value value) {
  35. root = put(root, key, value);
  36. }
  37. //向指定的树x中添加key-value,并返回添加元素后新的树
  38. private Node put(Node x, Key key, Value value) {
  39. //如果x子树为空,
  40. if (x==null){
  41. N++;
  42. return new Node(key,value, null,null);
  43. }
  44. //如果x子树不为空
  45. //比较x结点的键和key的大小:
  46. int cmp = key.compareTo(x.key);
  47. if (cmp>0){
  48. //如果key大于x结点的键,则继续找x结点的右子树
  49. x.right = put(x.right,key,value);
  50. }else if(cmp<0){
  51. //如果key小于x结点的键,则继续找x结点的左子树
  52. x.left = put(x.left,key,value);
  53. }else{
  54. //如果key等于x结点的键,则替换x结点的值为value即可
  55. x.value = value;
  56. }
  57. return x;
  58. }
  59. //查询树中指定key对应的value
  60. public Value get(Key key) {
  61. return get(root,key);
  62. }
  63. //从指定的树x中,查找key对应的值
  64. private Value get(Node x, Key key) {
  65. //x树为null
  66. if (x==null){
  67. return null;
  68. }
  69. //x树不为null
  70. //比较key和x结点的键的大小
  71. int cmp = key.compareTo(x.key);
  72. if (cmp>0){
  73. //如果key大于x结点的键,则继续找x结点的右子树
  74. return get(x.right,key);
  75. }else if(cmp<0){
  76. //如果key小于x结点的键,则继续找x结点的左子树
  77. return get(x.left,key);
  78. }else{
  79. //如果key等于x结点的键,就找到了键为key的结点,只需要返回x结点的值即可
  80. return x.value;
  81. }
  82. }
  83. //删除树中key对应的value
  84. public void delete(Key key) {
  85. delete(root, key);
  86. }
  87. //删除指定树x中的key对应的value,并返回删除后的新树
  88. private Node delete(Node x, Key key) {
  89. //x树为null
  90. if (x==null){
  91. return null;
  92. }
  93. //x树不为null
  94. int cmp = key.compareTo(x.key);
  95. if (cmp>0){
  96. //如果key大于x结点的键,则继续找x结点的右子树
  97. x.right = delete(x.right,key);
  98. }else if(cmp<0){
  99. //如果key小于x结点的键,则继续找x结点的左子树
  100. x.left = delete(x.left,key);
  101. }else{
  102. //如果key等于x结点的键,完成真正的删除结点动作,要删除的结点就是x;
  103. //让元素个数-1
  104. N--;
  105. //得找到右子树中最小的结点
  106. if (x.right==null){
  107. return x.left;
  108. }
  109. if (x.left==null){
  110. return x.right;
  111. }
  112. Node minNode = x.right;
  113. while(minNode.left!=null){
  114. minNode = minNode.left;
  115. }
  116. //删除右子树中最小的结点
  117. Node n = x.right;
  118. while(n.left!=null){
  119. if (n.left.left==null){
  120. n.left=null;
  121. }else{
  122. //变换n结点即可
  123. n = n.left;
  124. }
  125. }
  126. //让x结点的左子树成为minNode的左子树
  127. minNode.left = x.left;
  128. //让x结点的右子树成为minNode的右子树
  129. minNode.right = x.right;
  130. //让x结点的父结点指向minNode
  131. x = minNode;
  132. }
  133. return x;
  134. }
  135. //查找整个树中最小的键
  136. public Key min(){
  137. return min(root).key;
  138. }
  139. //在指定树x中找出最小键所在的结点
  140. private Node min(Node x){
  141. //需要判断x还有没有左子结点,如果有,则继续向左找,如果没有,则x就是最小键所在的结点
  142. if (x.left!=null){
  143. return min(x.left);
  144. }else{
  145. return x;
  146. }
  147. }
  148. //在整个树中找到最大的键
  149. public Key max(){
  150. return max(root).key;
  151. }
  152. //在指定的树x中,找到最大的键所在的结点
  153. public Node max(Node x){
  154. //判断x还有没有右子结点,如果有,则继续向右查找,如果没有,则x就是最大键所在的结点
  155. if (x.right!=null){
  156. return max(x.right);
  157. }else{
  158. return x;
  159. }
  160. }
  161. //获取整个树中所有的键
  162. public Queue<Key> preErgodic(){
  163. Queue<Key> keys = new Queue<>();
  164. preErgodic(root, keys);
  165. return keys;
  166. }
  167. //获取指定树x的所有键,并放到keys队列中
  168. private void preErgodic(Node x,Queue<Key> keys){
  169. if (x==null){
  170. return;
  171. }
  172. //把x结点的key放入到keys中
  173. keys.enqueue(x.key);
  174. //递归遍历x结点的左子树
  175. if (x.left!=null){
  176. preErgodic(x.left,keys);
  177. }
  178. //递归遍历x结点的右子树
  179. if (x.right!=null){
  180. preErgodic(x.right,keys);
  181. }
  182. }
  183. //使用中序遍历获取树中所有的键
  184. public Queue<Key> midErgodic(){
  185. Queue<Key> keys = new Queue<>();
  186. midErgodic(root,keys);
  187. return keys;
  188. }
  189. //使用中序遍历,获取指定树x中所有的键,并存放到key中
  190. private void midErgodic(Node x,Queue<Key> keys){
  191. if (x==null){
  192. return;
  193. }
  194. //先递归,把左子树中的键放到keys中
  195. if (x.left!=null){
  196. midErgodic(x.left,keys);
  197. }
  198. //把当前结点x的键放到keys中
  199. keys.enqueue(x.key);
  200. //在递归,把右子树中的键放到keys中
  201. if(x.right!=null){
  202. midErgodic(x.right,keys);
  203. }
  204. }
  205. //使用后序遍历,把整个树中所有的键返回
  206. public Queue<Key> afterErgodic(){
  207. Queue<Key> keys = new Queue<>();
  208. afterErgodic(root,keys);
  209. return keys;
  210. }
  211. //使用后序遍历,把指定树x中所有的键放入到keys中
  212. private void afterErgodic(Node x,Queue<Key> keys){
  213. if (x==null){
  214. return ;
  215. }
  216. //通过递归把左子树中所有的键放入到keys中
  217. if (x.left!=null){
  218. afterErgodic(x.left,keys);
  219. }
  220. //通过递归把右子树中所有的键放入到keys中
  221. if (x.right!=null){
  222. afterErgodic(x.right,keys);
  223. }
  224. //把x结点的键放入到keys中
  225. keys.enqueue(x.key);
  226. }
  227. //使用层序遍历,获取整个树中所有的键
  228. public Queue<Key> layerErgodic() throws InterruptedException {
  229. //定义两个队列,分别存储树中的键和树中的结点
  230. Queue<Key> keys = new Queue<>();
  231. Queue<Node> nodes = new Queue<>();
  232. //默认,往队列中放入根结点
  233. nodes.enqueue(root);
  234. while(!nodes.isEmpty()){
  235. //从队列中弹出一个结点,把key放入到keys中
  236. Node n = nodes.dequeue();
  237. keys.enqueue(n.key);
  238. //判断当前结点还有没有左子结点,如果有,则放入到nodes中
  239. if (n.left!=null){
  240. nodes.enqueue(n.left);
  241. }
  242. //判断当前结点还有没有右子结点,如果有,则放入到nodes中
  243. if (n.right!=null){
  244. nodes.enqueue(n.right);
  245. }
  246. }
  247. return keys;
  248. }
  249. //获取整个树的最大深度
  250. public int maxDepth(){
  251. return maxDepth(root);
  252. }
  253. //获取指定树x的最大深度
  254. private int maxDepth(Node x){
  255. if (x==null){
  256. return 0;
  257. }
  258. //x的最大深度
  259. int max=0;
  260. //左子树的最大深度
  261. int maxL=0;
  262. //右子树的最大深度
  263. int maxR=0;
  264. //计算x结点左子树的最大深度
  265. if (x.left!=null){
  266. maxL = maxDepth(x.left);
  267. }
  268. //计算x结点右子树的最大深度
  269. if (x.right!=null){
  270. maxR = maxDepth(x.right);
  271. }
  272. //比较左子树最大深度和右子树最大深度,取较大值+1即可
  273. max = maxL>maxR?maxL+1:maxR+1;
  274. return max;
  275. }
  276. }

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/我家小花儿/article/detail/747768
推荐阅读
相关标签
  

闽ICP备14008679号