二叉树
想要存储一颗二叉树,一种是基于指针或者引用的二叉链式存储法;一种是基于数组的顺序存储法
- 顺序存储法:把根节点存储在下标 i=1 的位置,那左子节点存储在下表
2*i=2
的位置,右子节点存储在 2*i+1=3
的位置,依次类推。
如果二叉树是完全二叉树,用数组存储无疑是最节省内存的一种方式
一、遍历
递归的写法每个节点最多被访问两次,所以时间复杂度是 O(n)
前序、中序、后序、层序 遍历
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118
| void PreOrder(Node* root) { if (root == nullptr) return; std::stack<Node*> st; st.push(root); while (!st.empty()) { Node* tmp = st.top(); std::cout << tmp->value << " "; st.pop(); if (tmp->right != nullptr) { st.push(tmp->right); } if (tmp->left != nullptr) { st.push(tmp->left); } } }
void InOrder(Node* root) { if (root == nullptr) return; std::stack<Node*> st; while (!st.empty() || root != nullptr) { while (root != nullptr) { st.push(root); root = root->left; } root = st.top(); std::cout << root->value << " "; st.pop(); root = root->right; } }
void PostOrder(Node* root) { if (root == nullptr) return; std::stack<Node*> st1; std::stack<int> res; st1.push(root); while (!st1.empty()) { Node* tmp = st1.top(); st1.pop(); res.push(tmp->value); if (tmp->left != nullptr) { st1.push(tmp->left); } if (tmp->right != nullptr) { st1.push(tmp->right); } } while (!res.empty()) { std::cout << res.top() << " "; res.pop(); } }
void PostOrder2(Node* root) { if (root == nullptr) return; std::stack<Node*> st; Node* prev = nullptr; while (root != nullptr || !st.empty()) { while (root != nullptr) { st.push(root); root = root->left; } root = st.top(); if (root->right == nullptr || root->right == prev) { std::cout << root->value << " "; st.pop(); prev = root; root = nullptr; } else { root = root->right; } } }
void LevelOrder(Node* root) { if (root == nullptr) return; std::queue<Node*> qu; qu.push(root); while (!qu.empty()) { int cur_level_size = qu.size(); for (int i = 0; i < cur_level_size; i++) { Node* tmp = qu.front(); std::cout << tmp->value << " "; qu.pop(); if (tmp->left != nullptr) { qu.push(tmp->left); } if (tmp->right != nullptr) { qu.push(tmp->right); } } std::cout << std::endl; } }
|
二、操作
二叉树的删除操作分三种情况
- 如果要删除的节点没有子节点,则直接将父节点中指向的节点指针置为 NULL 即可
- 如果要删除的节点只有一个子节点,则只需要更新父节点中,指向要删除节点的指针,让其指向要删除节点的子节点即可
- 如果要删除的节点有两个子节点,则需要找到这个节点的右子树的最小节点,把它替换到要删除的节点上即可
支持重复数据的二叉查找树
- 将二叉查找树中每一个节点存储成一个链表或者支持动态扩容的数组等数据结构,把值相同的数据都存储在同一节点上
- 在插入时,遇到相同的节点,则将其插入这个相同节点的右子树上。那么在查找的时候遇到值相同的继续在右子树上查找;删除的时候也要删除所有的
三、二叉查找树对比哈希表
哈希表插入、删除、查找的时间复杂度都为 O(1)。而二叉查找树在比较平衡时,插入、删除、查找的时间复杂度才是 O(logn)
- 哈希表中的数据是无序存储的,如果要输出有序的数据,需要先排序;二叉树只需要中序遍历就可以 O(n) 输出有序的数据序列
- 哈希表扩容耗时很多,而且当遇到哈希冲突时,性能不稳定,
- 尽管哈希表查找速度是常量级别,但哈希冲突的存在,这个常量不一定比 logn 小,所以实际的查找速度可能不一定比 O(logn) 快;加上哈希函数的耗时,也不一定比平衡二叉查找树的效率高
- 哈希表的构造比二叉查找树复杂,需要考虑哈希函数设计、冲突解决办法、扩缩容。平衡二叉树只需要考虑平衡性,而且这个问题的解决方案比较成熟
红黑树
平衡二叉树的定义:二叉树中任意一个节点的左右子树的高度相差不能大于 1
AVL 树是一个高度平衡的二叉树,查找效率非常高,但是为了维持这种高度的平衡,每次插入、删除都要做调整,就比较复杂、耗时。对于频率插入、删除的数据集合,使用 AVL 树的代价有点高
红黑树是一种不严格的平衡二叉树,需要满足如下几个要求
- 根节点是黑色的
- 每个叶节点都是黑色的空节点,也就是说,叶子节点不存储数据
- 任何相邻的节点都不能同时为红色,也就是说,红色节点是被黑色节点隔开的
- 每个节点,从该节点到达其可达叶子节点的所有路径,都包含相同数目的黑色节点