一、树存储的物理结构
1.1 树
因实际的内存结构多为线性结构,而树的逻辑结构并不是线性的,所以该部分主要说明一颗树是如何存储在内存中的。本文将以下图所示的树为例,进行说明:
1.2 树节点的存储
在存储树节点时,共需要使用到两种结构:TreeNode和listNode。
1. TreeNode: TreeNode代表实际树的一个节点,TreeNode结构体实现如下,共有两个成员变量,data用于存储树节点中的数据(在本例中,就是a, b, c, …),childHead存储孩子链表的头节点。孩子链表就是一个节点的所有孩子组成的链表,一个链表节点(listNode)对应一个孩子节点。
/* TreeNode结构体实现 */
struct TreeNode {
char data; //树中存储的数据
listNode* childHead; //孩子链表的头节点
};
2. listNode: 我们采用链表存储一个节点的所有孩子节点,每一个链表节点为listNode,listNode的结构体实现如下所示,共有两个成员变量:data和next,data为孩子树节点的地址,next为Next指针,指向下一个listNode的地址(孩子链表的最后一个Next指针为NULL)。
struct listNode {
TreeNode* data; //存储孩子(树)节点的地址
listNode* next; //指向下一个链表节点
};
下面以节点b为例(如下图所示),说明如何存储树节点b:
(1)初始化阶段:
TreeNode* node = new TreeNode(); //假设node的值为0X2
node->data = 0;
node->childHead = NULL;
(2)构造孩子链表:
listNode* childNode1 = new listNode();
childNode1->data = 0X4; //假设节点d的地址为0X4(链表节点的数据域存储树节点的地址)
childNode->next = NULL; //因节点b的孩子节点只有d,所以无后续链表节点
(3)给node中的childHead赋值:
node->childHead = childNode1;
1.3 树的存储
下面将结合上述两种结构(TreeNode和ListNode)构建一棵树,构建后的树的内存结构如下图所示,下面进行详细说明:
1. 节点池: 构建一个数组,数组的大小为树的节点个数,数组的每个元素为TreeNode,一个TreeNode代表一个树的节点。
举例:以本文开头的树为例,树共有9个节点,创建一个数组TreeNode[9],假设数组第一个元素(代表节点a)的地址为0X1,第二个元素(代表节点b)的地址为0X2,以此类推。0X1中共存储两部分内容,第一部分为节点值(这里就是a),第二部分为孩子链表的头指针。
nodes = new TreeNode()[9]; //nodes代表节点池的首地址
2. 孩子链表: 孩子链表存储一个树节点的所有孩子节点,一个链表节点代表一个孩子节点,每一个节点(listNode)共分为两个部分,第一个部分为孩子节点在节点池中的地址,第二个部分为下一个listNode的地址。
举例: 以节点a为例,节点a的孩子节点为b和c,故需要创建一个链表,该链表有两个链表节点,假设该链表的头节点地址(链表头指针)为0XA,故0X1中的第二部分(节点池第一个元素的孩子链表头指针)存储0XA。该链表共有两个listNode,分别为listNode1和listNode2,listNode1的地址为0XA,listNode的地址为0XE,其中ListNode1->data = 0X3(节点c在节点池中的地址),ListNode1->next = 0XE(下一个链表节点的地址为0XE)。
/*构造节点a的孩子链表*/
listNode* childNode1 = new listNode();
childNode1->data = &nodes[1];
listNode* childNode2 = new listNode();
childNode2->data = &nodes[2];
childNode1->next = childNode2;
childNode2->next = NULL;
/*将链表的头指针放入节点a的childHead中*/
nodes[0].childHead = childNodes; //这里的链表头指针就是childNode
二、C++代码实现
将利用C++模板和C++面向对象,实现一棵树。
2.1 结构体定义
ListNode定义如下,其中包含一个初始化列表,用于在创建ListNode时进行初始化。
template<typename T>
struct listNode {
T data;
listNode* next;
listNode(T d = 0) :data(d), next(NULL) {}
};
TreeNode定义如下:
template<typename T>
struct TreeNode {
T data;
listNode<TreeNode<T>*>* childHead;
TreeNode(T d = 0) :data(d), childHead(NULL) {}
void addChild(TreeNode<T>* data) {
listNode<TreeNode<T>*>* newNode = new listNode<TreeNode<T>*>(data);
if (childHead == NULL) {
childHead = newNode;
}
else {
newNode->next = childHead;
childHead = newNode;
}
}
};
注意1: 对listNode<TreeNode*>* childHead 进行说明。childHead代表头结点, <>中的TreeNode*代表listNode中的data数据类型为TreeNode*指针(链表节点中的数据域存储节点在节点池中的地址)
注意2 :这里的方法addChild(TreeNode* data)方法用于添加孩子链表节点(采用头插法)
2.2 树类的定义
template<typename T>
class Tree {
private:
TreeNode<T>* nodes;
TreeNode<T>* root;
int maxTreeNodes; //节点池总最多的节点数
public:
// 构造方法
Tree();
Tree(int maxNodes);
//析构函数
~Tree();
//id代表节点池的下标,根据下标返回节点在节点池中的地址
TreeNode<T>* getTreeNode(int id);
//设置根节点
void setRoot(int rootId);
//设置各个节点的孩子节点
void AddChild(int parentId, int sonId);
//设置各个节点的数据域
void AssignData(int id, T data);
//遍历树
void print(TreeNode<T>* node = NULL);
};
2.2 树类方法的实现
首先是构造函数和析构函数,构造函数分为两种,默认构造和有参构造,默认构造函数中,默认生成的节点池长度为1001,有参构造函数传入的参数的节点池的长度。在析构函数中,首先需要销毁各个节点的孩子链表,最后销毁节点池。
template<typename T>
Tree<T>::Tree() {
root = NULL;
nodes = new TreeNode<T>[1001];
maxTreeNodes = 1001;
}
template<typename T>
Tree<T>::Tree(int maxNodes) {
root = NULL;
nodes = new TreeNode<T>[maxNodes];
maxTreeNodes = maxNodes;
}
template<typename T>
Tree<T>::~Tree() {
for (int i = 0; i < maxTreeNodes; i++) {
listNode<TreeNode<T>*>* temp = (&(nodes[i]))->childHead;
while (temp) {
listNode<TreeNode<T>*>* temp2 = temp->next;
delete temp;
temp = temp2;
}
temp = NULL;
}
delete[] nodes;
root = NULL;
}
其他各个方法的实现,对于需要注意的代码,均在注释中进行说明
template<typename T>
TreeNode<T>* Tree<T>::getTreeNode(int id) {
return &nodes[id];
}
template<typename T>
void Tree<T>::setRoot(int rootId) {
root = getTreeNode(rootId);
}
/*
为每一个节点指定孩子节点,即采用头插法,完善各个节点的孩子链表
这里通过调用addChild方法,向每一个链表添加节点
*/
template<typename T>
void Tree<T>::AddChild(int parentId, int sonId) {
TreeNode<T>* parentNode = getTreeNode(parentId);
TreeNode<T>* sonNode = getTreeNode(sonId);
parentNode->addChild(sonNode);
}
/*
因在创建节点池中,节点池各个节点的数据域为0(参见TreeNode结构体中的初始化列表,和构造函数),
故需要对每一个节点的数据域指定数据
*/
template<typename T>
void Tree<T>::AssignData(int id, T data) {
TreeNode<T>* temp = getTreeNode(id);
temp->data = data;
}
template<typename T>
void Tree<T>::print(TreeNode<T>* node) {
if (node == NULL) {
if (root == NULL) {
cout << "Tree is empty" << endl;
return;
}
node = root;
}
cout << node->data << " ";
listNode<TreeNode<T>*>* temp = node->childHead;
while (temp) {
print(temp->data);
temp = temp->next;
}
}
}
测试:
void test_Tree() {
cout << sizeof(TreeNode<char>) << endl;
Tree<char> T(9);
T.setRoot(0);
cout << T.getTreeNode(0) << endl;
cout << T.getTreeNode(1) << endl;
cout << T.getTreeNode(2) << endl;
cout << T.getTreeNode(3) << endl;
T.AssignData(0, 'a');
T.AssignData(1, 'b');
T.AssignData(2, 'c');
T.AssignData(3, 'd');
T.AssignData(4, 'e');
T.AssignData(5, 'f');
T.AssignData(6, 'g');
T.AssignData(7, 'h');
T.AssignData(8, 'i');
T.AddChild(0, 1);
T.AddChild(0, 2);
T.AddChild(1, 3);
T.AddChild(2, 4);
T.AddChild(2, 5);
T.AddChild(3, 6);
T.AddChild(3, 7);
T.AddChild(3, 8);
cout << "" << endl;
T.print();
}