数据结构中的“树”是一种重要的非线性数据结构,它呈现出层次嵌套的特点,就像一棵倒挂的树,根朝上,叶子朝下。
一、树的基本结构和特点
- 结构:树由节点(或称为结点)和边组成,节点包含数据元素及若干指向其子树的分支(即边)。
- 特点:
- 树的每一个节点可以有零个或多个后继节点(称为子节点),但有且只有一个前驱节点(称为父节点,根节点除外)。
- 数据节点按分支关系组织起来,清晰地反映了数据元素之间的层次关系。
- 数据元素之间存在的关系是一对多的关系。
二、树的类型,结构和特点
定义:二叉树是每个结点最多有两个子树的结构
性质:
- 二叉树第 i 层上的节点数目最多为 2 的 i-1 次方 (i>=1)
- 深度为 k 的二叉树最多有 2 的 k 次方 -1 (k>=1)
- 包含 n 个节点的二叉树的高度至少为 log 2 (n+1)
- 在任意一棵二叉树中,若叶子结点的个数为 n0 ,度为 2 的节点数为 n2 ,则 n0=n2+1
1.满二叉树
- 结构:除叶子节点外,每一个节点都有左、右两个子节点,且叶子节点都处在最底层。
- 特点:满二叉树是一种特殊的二叉树,其节点个数达到最大值
2.完全二叉树
- 一棵二叉树中,只有最下面两层节点的度可以小于2,并且最下层的叶子结点集中在靠左的若干位置上,这样的二叉树称为完全二叉树
- 特点:完全二叉树常用于实现二叉堆等数据结构
3.平衡二叉树(AVL树)
- 结构:任意节点的左、右子树都是平衡二叉树,且左、右子树的深度之差的绝对值不超过1。
- 特点:平衡二叉树保证了树的高度相对较小,从而提高了搜索、插入和删除操作的效率。
- 举例:在构建符号表、数据库索引等应用中,平衡二叉树常被用于实现高效的查找和插入操作。
4.红黑树
- 结构:红黑树是一种特殊的二叉排序树,其节点被标记为红色或黑色,并满足一系列额外的性质(如红色节点的两个子节点一定都是黑色的,从任意节点到其子孙节点的所有路径上必须包含相同数目的黑色节点等)。
- 特点:红黑树通过自动调整结构来保持平衡,从而保证了搜索、插入和删除操作的效率。
- 举例:红黑树在Java中的HashMap/TreeMap等容器的实现中有所应用,同时在Linux内核的完全公平调度器以及epoll机制的实现中也发挥着重要作用。
5.B树和B+树
- 结构:B树和B+树是一种多路搜索树,每个节点可以包含多个关键字和多个子节点。
- 特点:B树和B+树常用于数据库和文件系统的索引结构中,以提高数据的检索速度。其中,B+树的叶子节点形成了一个有序的链表,更加方便范围查询。
6.哈夫曼树(Huffman树)
- 结构:哈夫曼树是一种带权路径长度最短的二叉树,常用于数据压缩中的霍夫曼编码。
- 特点:哈夫曼树通过构建具有不同权重的叶子节点,并使得从根节点到每个叶子节点的路径长度与该叶子节点的权重乘积之和最小,从而实现了数据的高效压缩。
7.Trie树(字典树)
- 结构:Trie树是一种用于高效存储和检索字符串集合的树形数据结构。每个节点代表一个字符串的前缀。
- 特点:Trie树支持快速的字符串查找、插入和删除操作,常用于自动补全、拼写检查等应用场景。
三、树的遍历
树的遍历是指按照某种规则访问树中的每个节点,使得每个节点被访问且仅被访问一次。
常见的树的遍历方式有先序遍历、中序遍历、后序遍历、层次遍历等。
1.先序遍历:
先访问根节点,然后依次递归地遍历左子树和右子树。
//先序遍历-----递归
void Tree::PreOrder(Node* root) {
if (root == nullptr) return;
cout << root->key << " ";
PreOrder(root->left);
PreOrder(root->right);
}
//先序遍历-----非递归
void Tree::_PreOrder(Node* root) {
if (root == nullptr) return;
stack<Node*> stk;
stk.push(root);
while (!stk.empty()) {
Node* cur = stk.top();
stk.pop();
cout << cur->key << " ";
if (cur->right) {
stk.push(cur->right);
}
if (cur->left) {
stk.push(cur->left);
}
}
cout << endl;
}
2.中序遍历:
先递归地遍历左子树,然后访问根节点,最后递归地遍历右子树。
//中序遍历-----递归
void Tree::InOrder(Node* root) {
if (root == nullptr) return;
InOrder(root->left);
cout << root->key << " ";
InOrder(root->right);
}
//中序遍历-----非递归
void Tree::_InOrder(Node* root) {
if (root == nullptr) return;
stack<Node*> stk;
Node* cur = root;
while (!stk.empty() || cur) {
while (cur) {
stk.push(cur);
cur = cur->left;
}
Node* node = stk.top();
stk.pop();
cur = node->right;
cout << node->key << " ";
}
cout << endl;
}
3.后序遍历:
先递归地遍历左子树和右子树,最后访问根节点。
//后序遍历-----递归
void Tree::PostOrder(Node* root) {
if (root == nullptr) return;
PostOrder(root->left);
PostOrder(root->right);
cout << root->key << " ";
}
//后序遍历-----非递归
void Tree::_PostOrder(Node* root) {
if (root == nullptr) return;
stack<Node*> s1;
stack<Node*> s2;
s1.push(root);
while (!s1.empty()) {
Node* cur = s1.top();
s1.pop();
s2.push(cur);
if (cur->left ) {
s1.push(cur->left);
}
if (cur->right) {
s1.push(cur->right);
}
}
while (!s2.empty()) {
cout << s2.top()->key << " ";
s2.pop();
}
cout << endl;
}
4.层次遍历:
按层次从上到下、从左到右依次访问树中的每个节点。
//层次遍历
void Tree::Level(Node* root) {
if (root == nullptr) return;
queue<Node*> que;
que.push(root);
while (!que.empty()) {
Node* cur = que.front();
que.pop();
cout << cur->key << " ";
if (cur->left) {
que.push(cur->left);
}
if (cur->right) {
que.push(cur->right);
}
}
cout << endl;
}
void Tree::_Level(Node* root) {
if (root == nullptr) return;
queue<Node*> que;
que.push(root);
while (!que.empty()) {
int n = que.size();
while(n--) {
Node* cur = que.front();
que.pop();
cout << cur->key << " ";
if (cur->left) que.push(cur->left);
if (cur->right) que.push(cur->right);
}
cout << endl;
}
}
四、树的应用场景
树形结构在计算机科学领域有着广泛的应用,包括但不限于以下几个方面:
- 文件系统:在操作系统中,树形结构常用于表示文件系统的目录结构。
- 数据库索引:树形结构(如B树、B+树)常用于实现数据库的索引,以提高数据的检索速度。
- 数据压缩:哈夫曼树等数据结构常用于数据压缩中的霍夫曼编码。
- 自动补全和拼写检查:Trie树等数据结构常用于实现自动补全和拼写检查功能。
- 软件项目结构:在大型软件项目中,代码的结构可以用树形结构来表示,不同的模块和功能可以看作是树的不同节点。
综上所述,数据结构中的树具有广泛的应用场景和重要的研究价值。通过了解各类树的结构和特点,可以更好地理解和应用这些数据结构来解决实际问题。