分支任意的树构造的方法主要有两种,邻接矩阵和兄弟链表。邻接矩阵主要用于输入的范围很小的情况,因为矩阵反应的是对应情况,比如树的每一个节点都给与对应顺序的编号的话,如果有100个输入,就需要一个100*100的数组来存储联接关系。
链表的形式一般用于输入量很大的情况,可以很方便的适应。(需要仔细体会一下区别)
邻接矩阵的使用方法:
邻接矩阵的行数代表父节点ID,每一行用来存储其各子节点ID。比如 0 节点下面添加了一个子节点1的话,就需要在数组的第0行的第Tree(邻接数组名)[0]的位置上写入1.这里涉及到下面的小技巧中的每一行的第0位的使用。
邻接矩阵的使用小技巧:
邻接矩阵的每一行的第0位放当前父亲节点的子节点的数量,这样在遍历的时候可以很方便的控制循环。而且在插入新的节点的时候第0位里面的数字就是当前要放入数据的数组的下标(这里的方法很像之前学过的计数排序,把计数值从前一位累加到后一位,这样对应位置里面放的数值就是当前数据应该放在输出数组中的位置。计数排序一会可以再写一个随笔记录一下)。
用法举例:
Tree[fatherID][0]++;//这里是放的当前父亲节点的子节点的数量
Tree[fatherID][Tree[fatherID][0]] = childID;//把当前要插入的子节点放在它应该对应的数组位置上
如果单单是插入的话只需要一个邻接矩阵存储父节点对应的子节点的关系就可以了。但是如果要删除的话,我们往往只是给定当前要删除的节点的标号。这样的话我们没办法找到他的父节点(除非从root节点开始遍历我们刚才生成的邻接数组),这时候我们又需要一个数组来记录子对于父的连接关系(这里就有点像双向链表了,双向链表要实现的也是类似的功能,如果要删除当前节点,只要通过node->pre找到他的父亲节点,然后通过指针操作,把父亲节点的->next指向下一个node,把下一个node的->pre指向前面一个node即可)。于是我们创建一个Father[]数组来存储每一个节点的父亲节点标号。因为所有的节点肯定只有一个父亲(可以有多个孩子,但是只可能有一个父亲,这里仔细体会一下)。
int FatherID[MAX_ID];//存放每一个节点的父节点
我们在插入的时候要把fatherID放到FatherID数组的对应位置上,这样只要知道子节点和FatherID[]数组就可以知道当前的子节点的父亲节点是谁。
对应代码:
void insertNode(int fatherID,int childID){ Tree[fatherID][0]++; Tree[fatherID][Tree[fatherID][0]] = childID; FatherID[childID] = fatherID; }
所以在删除的时候我们要做的就是通过子节点和FatherID[childID] 找到他的父亲节点,然后维护自己创建的数据结构。这里面只有两个数据结构,1.FatherID[childID] 2.int Tree[MAX_ID][MAX_ID]; 因为我们只是通过FatherID数组来找父亲节点,所以嗯。。(纠结了一下)也可以把FatherID对应子节点位置的数组置零,主要是要更改Tree数组的值,
1.要更改Tree[fatherID][0]的值,子节点少了一个。需要把子节点的数目减1
2.要更改Tree[fatherID][Tree[fatherID][0]]的值,子节点已经删去了。需要把对应位置的对应关系删去,即把Tree[fatherID][Tree[fatherID][0]]清零
for(int i=1;i<MAX_ID;i++){ if(Tree[fatherId][i]==Id){ Tree[fatherId][i]=0; Tree[fatherId][0]--; return; } }
好了进行到最后一个步骤,需要前序遍历生成的树。
刚开始我总是想着要用FatherID这个数组,因为这个数组初始化的时候把对应位置初始化成了自己,这样在向上遍历的时候只要遍历到了自己的话就证明他是根节点。可以作为递归的出口(这里也不是想的很明白。。。汗-_-||等有时间再比较一下数组构建的二叉树的前序,然后更新一下)
但是用当前节点子节点的个数作为递归变量会更好一点(有点像那道给出前序和中序求后序遍历的题目一样,每一次递归的传入量就是当前的前序和中序,用长度来控制传入的前序和中序的字符串,根据每一次的前序提供的根再做下一次的划分,直到当前中序的长度为1,证明不能向下分割了,就是递归的出口)
所以我先把根打印出来,然后对于替换当前的父节点位置,和父节点下面的子节点的个数,如果子节点的个数为零的时候就return。
void preorder(int fatherID,int n){//父亲节点有几个根节点 if(n==0){return;} for(int i=1;i<n+1;i++){ printf("%d ",Tree[fatherID][i]);//root preorder(Tree[fatherID][i],count(Tree,Tree[fatherID][i]));//从前到后面遍历每一个节点 } }
哦,count函数就是为了计算当前的节点下面有几个子节点:利用了Tree[][]数组。(这里面特别骚气的循环是和金老师学的。这样可以少定义一个ret遍历,而且可以少一步传值的过程)
int count(int data[MAX_ID][MAX_ID],int fatherID){ int i=1; for(;data[fatherID][i]!=0;i++){ ; } return i-1; }
最终的代码:
#include <stdio.h> #define MAX_ID 20 int Tree[MAX_ID][MAX_ID];//存放对应关系 int FatherID[MAX_ID];//存放每一个节点的父节点 void insertNode(int fatherID,int childID){ Tree[fatherID][0]++; Tree[fatherID][Tree[fatherID][0]] = childID; FatherID[childID] = fatherID; } void delNode(int Id){ int fatherId = FatherID[Id]; FatherID[Id] = 0; for(int i=1;i<MAX_ID;i++){ if(Tree[fatherId][i]==Id){ Tree[fatherId][i]=0; Tree[fatherId][0]--; return; } } } //计算当前父亲节点有几个子节点 int count(int data[MAX_ID][MAX_ID],int fatherID){ int i=1; for(;data[fatherID][i]!=0;i++){ ; } return i-1; } void preorder(int fatherID,int n){//父亲节点有几个根节点 if(n==0){return;} for(int i=1;i<n+1;i++){ printf("%d ",Tree[fatherID][i]);//root preorder(Tree[fatherID][i],count(Tree,Tree[fatherID][i]));//从前到后面遍历每一个节点 } } void init(){ for(int i=0;i<MAX_ID;i++){ for(int j=0;j<MAX_ID;j++){ Tree[i][j] = 0; } } for(int i=0;i<MAX_ID;i++){ FatherID[i] = i;//指向它自己 } } int main(){ insertNode(0,1); insertNode(0,2); insertNode(0,3); insertNode(0,4); insertNode(1,5); insertNode(1,7); insertNode(2,8); insertNode(3,9); insertNode(4,10); insertNode(4,11); insertNode(11,12); insertNode(10,13); delNode(12); delNode(13); int num = count(Tree,0); printf("0 "); preorder(0,num); return 0; }
递归的过程还是不是那么的清晰,要再好好想想。