树的C++实现


一、树存储的物理结构

1.1 树

因实际的内存结构多为线性结构,而树的逻辑结构并不是线性的,所以该部分主要说明一颗树是如何存储在内存中的。本文将以下图所示的树为例,进行说明:
树

1.2 树节点的存储

在存储树节点时,共需要使用到两种结构:TreeNodelistNode
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();
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值