每天get一个新知识,超开心~
前面讲的数据结构都是前后具有一对一的关系,无论是线性表还是队列,栈等都是一对一的关系,那么如果出现一对多的关系的时候该怎么办呢?这种一对多的关系,就是今天要学习的数据结构——树。
一、树的定义二、树的基本概念三、树的抽象数据类型四、树的双亲表示法(Java、C语言)五、树的孩子表示法(Java、C语言)六、树的孩子兄弟表示法(Java、C语言)
一、树的定义树(Tree)是n(n>=0)个结点的有限集。n=0时称为空树。在任意一颗非空树中:
- 有且仅有一个特定的称为根(root)的结点;当n>1时,其余结点可分为m(m>0)个互补交互的有限集T1、T2…Tm,其中每一个集合本身又是一棵树,并称为根的子树(SubTree)。
树需要注意两点
1.有且仅有一个根结点
2.子树没有限制,但是它们一定是不能相交的
1、结点的分类:
树的结点包含一个数据元素及若干个指向其子树的分支。结点拥有的子树数称为结点的度(Degree)。度为0的结点称为叶结点(Leaf)或终端结点;度不为0的结点称为非终端结点或分支支结点。除根结点之外,分支结点也称为内部结点。树的度是树内各结点的度的最大值。
2、结点间的关系
结点的子树的根称为该结点的孩子(Child),相应的,该结点称为孩子的双亲(Parent)(注意是双亲,不是单亲)。同一个双亲的孩子之间互称兄弟(Sibling)。结点的祖先是从根到该结点所经分支上的所有结点。以某结点为根的子树中的任一结点都称为该结点的子孙。
3、树的其他相关概念
结点的层次(Level)从根开始定义起,根为第一层,根的孩子为第二层。双亲在同一层的结点互为堂兄弟。树中结点的最大层次称为树的深度(Depth)或高度。
如果将树中结点的各子树看成从左至右是有次序的,不能互换,则称该树为有序树,否则为无序树。
森林(Forest)是m(m>=0)棵互不相交的树的集合。
ADT 树(tree) Date 树是由一个根结点和若干颗子树构成。树中结点具有相同数据类型及层次关系。 Operation InitTree(*T):构造空树T。 DestroyTree(*T):销毁树T。 CreateTree(*T,definition):按definition中的定义给出树。 ClearTree(*T):若树存在,则将树T清为空树。 TreeEmpty(T):若T为空树,则返回ture,否则返回false。 Tree depth(T):返回T的深度。 Root(T):返回T的根结点。 value(T,cur_e):cur_e是树中的一个结点,返回该结点的值。 Assign(T,cur_e,value):将结点cur_e的值赋值给value. Parent(T,cur_e):若cur_e是T的根结点,则返回空,否则返回其双亲。 LeftChild(T,cur_e):若cur_e是树T的非叶结点,则返回cur_e的最左孩子。 RightSibiling(T,cur_e):若cur_e有右兄弟,则返回它的右兄弟,否则返回空。 InsertChild(*T,*p,i,c):其中p指向树T的某个结点,i为p所指的结点度加上1, 非空树c与T不相交,将c插入p为所指结点的第i个子树 DeleteChild(*T,*p,i):其中p指向树T的某个结点,i为所指结点p的度, *** 作结果为删除T中p所指结点的第i颗子树。 endDate四、树的双亲表示法(Java、C语言)
双亲表示法定义
假设以一组连续空间存储数的结点,同时在每个结点中,附设一个指示器指示其双亲结点到链表中的位置。
双亲表示的结点结构
#define MAX_TREE_SIZE 100 typedef int TElemType; typedef struct PTNode { TElemType data; int parent; } PTNode; typedef struct { PTNode nodes[MAX_TREE_SIZE]; int r,n; } PTree;
下面是Java代码
package Trees; import java.util.Arrays; public class TreeImp1 { private int capacity;//树容量 private int nodeCount;//树结点数量 private Node[] elements;//存放Node类型的数组 //查询树的结点数量 public int getNodeCount() { return nodeCount; } public void setNodeCount(int nodeCount) { this.nodeCount = nodeCount; } public int getCapacity() { return capacity; } public void setCapacity(int capacity) { this.capacity = capacity; } public Node[] getElements() { return elements; } public void setElements(Node[] elements) { this.elements = elements; } //设置数组容量的构造方法 public TreeImp1(int capacity) { this.capacity = capacity; this.elements = new Node[capacity]; } @Override public String toString() { return "TreeImp1{" + "elements=" + Arrays.toString(elements) + '}'; } //是否空树 public boolean isEmpty(){ return nodeCount == 0; } //设置根结点 public Node setRootNode(int a){ this.elements[0] = new Node(a,-1); nodeCount++; return new Node(a, -1); } //查询根结点 public Node getRootNode(){ return this.elements[0]; } //为某个结点添加结点 public void addNode(int data, int parent, int newData){ if (nodeCount > capacity){ throw new RuntimeException("超出容量"); } else{ for (int i = 0; i < nodeCount; i++) { Node oldNode = new Node(data, parent); if (this.elements[i].equals(oldNode)){ this.elements[nodeCount] = new Node(newData,i); nodeCount++; break; } } } } //查找第一个存放数值为data的结点的索引位置,有的话返回该数据结点索引,否则返回-1 public int indexNode(int data){ for (int i = 0; i < nodeCount; i++) { if (this.elements[i].getData() == data){ return i; } } return -1; } //查询某个结点的所有子结点,并将他们放入一个新的数组中 public TreeImp1 arrayChildren(int data, int parent){ Node oldNode = new Node(data,parent); TreeImp1 t = new TreeImp1(this.capacity); for (int i = 0; i < nodeCount; i++) { if (this.elements[i].equals(oldNode)){ for (int j = 0; j < nodeCount; j++) { if (this.elements[j].getParent() == i){ t.elements[t.nodeCount] = elements[j]; t.nodeCount++; } } return t; } } return new TreeImp1(0); } //求树的度 public int degreeForTree(){ int max = 0; for (int i = 0; i < nodeCount; i++) { TreeImp1 x = arrayChildren(this.elements[i].getData(), this.elements[i].getParent()); int z = x.nodeCount; max = z > max ? z : max; } return max; } } //结点类 class Node{ private int data;//结点中存放的数据 private int parent;//结点的父结点的索引 //两个参数构造方法 public Node(int data, int parent) { this.setData(data); this.setParent(parent); } public int getData() { return data; } public void setData(int data) { this.data = data; } public int getParent() { return parent; } public void setParent(int parent) { this.parent = parent; } @Override public boolean equals(Object o) { if (this == o) return true; if (o == null || getClass() != o.getClass()) return false; Node node = (Node) o; return data == node.data && parent == node.parent; } @Override public String toString() { return "Node{" + "data=" + data + ", parent=" + parent + '}'; } }
树的存储描述是比较自由的,我们可以在指针域中加入一个指向孩子的指针域,那我们就可以知道这个结点的孩子是谁。
五、树的孩子表示法(Java、C语言)把每个结点的孩子结点排列起来,以单链表作为存储结构,则n个结点有n个孩子链表,如果是叶子结点则此单链表为空。然后n个头指针又组成一个线性表,采用顺序存储结构,存放进一个一维数组中。
孩子表示法的结点结构
孩子表示法有两种结点结构:孩子链表的孩子结点和表头数组的表头结点
孩子链表的孩子结点
表头数组的表头结点
C语言代码
#define MAX_TREE_SIZE 100 typedef int TElemType; typedef struct CTNode { int child; struct CTNode *next; } *ChildPtr; typedef struct { TElemType data; ChildPtr firstchild; } CTBox; typedef struct { CTBox nodes[MAX_TREE_SIZE]; int r,n; } CTree;
Java代码(转载的代码)
package com.datastruct.study.tree; public class SonShow { private final static Integer ARRAY_SIZE = 10; private InnerArray[] dataArray; private Integer position = null; class InnerArray{ private String data; private ChildNode firstChild; public InnerArray(String data, ChildNode firstChild) { this.data = data; this.firstChild = firstChild; } public String getData() { return data; } public ChildNode getFirstChild() { return firstChild; } } class ChildNode{ private Integer childPosition; private ChildNode next; public ChildNode(Integer childPosition, ChildNode next) { this.childPosition = childPosition; this.next = next; } public Integer getChildPosition() { return childPosition; } public void setChildPosition(Integer childPosition) { this.childPosition = childPosition; } public ChildNode getNext() { return next; } public void setNext(ChildNode next) { this.next = next; } } public SonShow(){ dataArray = new InnerArray[ARRAY_SIZE]; } public SonShow(int size){ dataArray = new InnerArray[size]; } public String add(String value,Integer parentPosition){ if(position == null){ position = 0; dataArray[position++] = new InnerArray(value,null); return value; } //先插入到数组的位置,再将该节点链接到对应的父节点的链表最后 dataArray[position++] = new InnerArray(value,null); //如果还没有孩子链表则生成 if(dataArray[parentPosition].firstChild==null){ //上面++了所以次数要-1,指定刚插入的孩子节点的位置 dataArray[parentPosition].firstChild = new ChildNode(position-1,null); return value; } //若是已经存在链表,则遍历链表,放在最后面 ChildNode firstNode = dataArray[parentPosition].getFirstChild(); while (firstNode.next!=null){ firstNode = firstNode.next; } firstNode.next = new ChildNode(position-1,null); return value; } public InnerArray getValueAndChild(Integer position){ return dataArray[position]; } public int size(){ return position; } } }六、树的孩子兄弟表示法(Java、C语言)
定义:任意一棵树,它的结点的第一个孩子如果存在就是唯一的,它的右兄弟存在也是唯一的。因此,设置两个指针,分别指向该结点的第一个孩子和此结点的右兄弟。
孩子兄弟表示法的结点结构
typedef struct CSNode { TElemType data; struct CSNode *firstchild,*rightsib; } CSNode,*CSTree;
这个孩子兄弟表示法,其实更好理解,就两个指针,一个指向孩子,一个指向兄弟,那么,这样一来就把复杂的树转化成为了二叉树。
这个Java代码也很简单,如下:
Java代码
class TreeNode { private TreeNode sonNode;//这里存储的是最左的儿子节点 private TreeNode brotherNode;//这里存储的是自己的兄弟节点 private String value; public TreeNode(TreeNode sonNode, TreeNode brotherNode, String value) { this.sonNode = sonNode; this.brotherNode = brotherNode; this.value = value; } public TreeNode getSonNode() { return this.sonNode; } public TreeNode getBrotherNode() { return brotherNode; } public String getValue() { return value; } public void setSonNode(TreeNode sonNode) { this.sonNode = sonNode; } public void setBrotherNode(TreeNode brotherNode) { this.brotherNode = brotherNode; } }
class SBTree { private TreeNode root;//这个是根节点 public SBTree(String rootValue) {//构建树的时候需要吧根节点的值初始化一下 root = new TreeNode(null, null, rootValue); } public TreeNode getRoot() { return this.root; } public TreeNode insertSonNode(TreeNode targetNode, String value) { TreeNode nowSonNode = new TreeNode(null, null, value); TreeNode preSonNode = targetNode.getSonNode(); if (preSonNode != null) { //已经有儿子,把前儿子的兄弟设置为自己. return insertBrotherNode(preSonNode, value); } else targetNode.setSonNode(nowSonNode); return nowSonNode; } public TreeNode insertBrotherNode(TreeNode targetNode, String value) { TreeNode nowBrotherNode = new TreeNode(null, null, value); while (targetNode.getBrotherNode() != null) { //有兄弟了 targetNode = targetNode.getBrotherNode(); } targetNode.setBrotherNode(nowBrotherNode); return nowBrotherNode; } static void printTree(TreeNode root, int depth) { if (root == null) return; for (int i = 0; i < depth; i++) { System.out.print("t|"); } System.out.println(root.getValue()); printTree(root.getSonNode(), depth + 1); printTree(root.getBrotherNode(), depth); } }
欢迎分享,转载请注明来源:内存溢出
评论列表(0条)