树的定义
树状图是一种数据结构,它是由 n ( n ≥ 1 ) n(n\ge1) n(n≥1) 个有限结点组成一个具有层次关系的集合。把它叫做“树”是因为它看起来像一棵倒挂的树,也就是说它是根朝上,而叶朝下的。它具有以下的特点:
- 每个结点有零个或多个子结点;
- 没有父结点的结点称为根结点;
- 每一个非根结点有且只有一个父结点;
- 除了根结点外,每个子结点可以分为多个不相交的子树。
引自百度百科。
二叉树
在计算机科学中,二叉树是每个结点最多有两个子树的树结构。通常子树被称作“左子树”
(
l
e
f
t
s
u
b
t
r
e
e
)
(left \;subtree)
(leftsubtree)和“右子树”
(
r
i
g
h
t
s
u
b
t
r
e
e
)
(right \;subtree)
(rightsubtree)。二叉树常被用于实现二叉查找树和二叉堆。
一棵深度为k,且有
2
k
−
1
2^k-1
2k−1个结点的二叉树,称为满二叉树。这种树的特点是每一层上的结点数都是最大结点数。而在一棵二叉树中,除最后一层外,若其余层都是满的,并且或者最后一层是满的,或者是在右边缺少连续若干结点,则此二叉树为完全二叉树。具有
n
n
n 个结点的完全二叉树的深度为
f
l
o
o
r
(
l
o
g
2
n
)
+
1
floor(log_2 n)+1
floor(log2n)+1。深度为
k
k
k 的完全二叉树,至少有
2
k
−
1
{2^{k-1}}
2k−1 个叶子结点,至多有
2
k
−
1
{2^k-1}
2k−1 个结点。
引自百度百科。
简单的二叉树定义
public class TreeNode {
int val;
TreeNode left;
TreeNode right;
TreeNode(int x) {
val = x;
}
}
二叉搜索树
二叉查找树 ( B i n a r y S e a r c h T r e e ) (Binary \;Search\; Tree) (BinarySearchTree), ( ( (又:二叉搜索树,二叉排序树 ) ) )它或者是一棵空树,或者是具有下列性质的二叉树:
- 若它的左子树不空,则左子树上所有结点的值均小于它的根结点的值;
- 若它的右子树不空,则右子树上所有结点的值均大于它的根结点的值;
- 它的左、右子树也分别为二叉排序树。
引自百度百科。
堆
堆 ( h e a p ) (heap) (heap)通常是一个可以被看做一棵树的数组对象。堆总是满足下列性质:
- 堆中某个节点的值总是不大于或不小于其父节点的值;
- 堆总是一棵完全二叉树。
将根节点最大的堆叫做最大堆或大根堆,根节点最小的堆叫做最小堆或小根堆。常见的堆有二叉堆、斐波那契堆等。
堆是非线性数据结构,相当于一维数组,有两个直接后继。
堆的定义如下:
n
n
n 个元素的序列
k
1
,
k
2
,
k
i
,
…
,
k
n
{k_1,k_2,k_i,…,k_n}
k1,k2,ki,…,kn当且仅当满足下关系时,称之为堆。
(
k
i
≤
k
2
i
,
k
i
≤
k
2
i
+
1
)
(k_i \le k_{2i},k_i \le k_{2i+1})
(ki≤k2i,ki≤k2i+1)或者
(
k
i
≥
k
2
i
,
k
i
≥
k
2
i
+
1
)
(k_i \ge k_{2i},k_i \ge k_{2i+1})
(ki≥k2i,ki≥k2i+1),
(
i
=
1
,
2
,
3
,
4...
n
2
)
(i = 1, \,2,\, 3, \,4...\,\frac{n}{2})
(i=1,2,3,4...2n)
若将和此次序列对应的一维数组
(
(
(即以一维数组作此序列的存储结构
)
)
)看成是一个完全二叉树,则堆的含义表明,完全二叉树中所有非终端结点的值均不大于
(
(
(或不小于
)
)
)其左、右孩子结点的值。由此,若序列
[
k
1
,
k
2
,
…
,
k
n
]
[k_1,\,k_2,\,…,\,k_n]
[k1,k2,…,kn]是堆,则堆顶元素
(
(
(或完全二叉树的根
)
)
)必为序列中n个元素的最小值
(
(
(或最大值
)
)
)。
向下重排
从堆中删除一个数
向上重排
在堆中加入一个数
二叉树的遍历
前序遍历
前序遍历首先访问根结点然后遍历左子树,最后遍历右子树。在遍历左、右子树时,仍然先访问根结点,然后遍历左子树,最后遍历右子树。
若二叉树为空则结束返回,否则:
- 访问根结点。
- 前序遍历左子树。
- 前序遍历右子树 。
中序遍历、后序遍历
中序遍历首先遍历左子树,再访问根节点,最后遍历右子树。在遍历左、右子树时,仍然先遍历左子树,再访问根节点,最后遍历右子树。
后序遍历首先遍历左子树,再遍历右子树,最后访问根节点。在遍历左、右子树时,仍然先遍历左子树,再遍历右子树,最后访问根节点。
遍历方法
递归
递归的方法很简单,下面是前序遍历的递归算法:
class Solution {
public List<Integer> preorderTraversal(TreeNode root) {
List<Integer> ans;
ans = new ArrayList<>();
if(root == null)
return ans;
ans.add(root.val);
ans.addAll(preorderTraversal(root.left));
ans.addAll(preorderTraversal(root.right));
return ans;
}
递归算法很简单,中序和后序遍历只需要改变 a n s . a d d ( r o o t . v a l ) ; ans.add(root.val); ans.add(root.val); 的位置即可。但是由于递归的时候比较耗资源,下面介绍下迭代算法:
迭代
前序遍历
class Solution {
public List<Integer> preorderTraversal(TreeNode root) {
List<Integer> ans;
Stack<TreeNode> stack;
TreeNode cursor;
ans = new ArrayList<>();
stack = new Stack<>();
if(root == null)
return ans;
stack.push(root);
while(!stack.isEmpty()){
cursor = stack.pop();
ans.add(cursor.val);
if(cursor.right != null){
stack.push(cursor.right);
}
if(cursor.left != null){
stack.push(cursor.left);
}
}
return ans;
}
}
用一个辅助栈来实现前序遍历。需要主要的是右子树和左子树的插入顺序。
中序遍历
class Solution {
public List<Integer> inorderTraversal(TreeNode root) {
Stack<TreeNode> stack;
List<Integer> ans;
TreeNode cursor;
stack = new Stack<>();
ans = new ArrayList<>();
cursor = root;
while(cursor != null || !stack.isEmpty()){
while(cursor != null){
stack.push(cursor);
cursor = cursor.left;
}
cursor = stack.pop();
ans.add(cursor.val);
cursor = cursor.right;
}
return ans;
}
}
同样是利用辅助栈实现。
后序遍历
class Solution {
public List<Integer> postorderTraversal(TreeNode root) {
List<Integer> ans;
Stack<TreeNode> stack;
TreeNode cursor;
ans = new LinkedList<>();
stack = new Stack<>();
if(root == null)
return ans;
stack.push(root);
while(!stack.isEmpty()){
cursor = stack.pop();
ans.add(0, cursor.val);
if(cursor.left != null)
stack.push(cursor.left);
if(cursor.right != null)
stack.push(cursor.right);
}
return ans;
}
}
后序遍历和前序遍历有点像,如果把前序遍历倒过来就是后序遍历吗?前序遍历是:根节点 → \rightarrow →左子树 → \rightarrow →右子树;而后序遍历是:左子树 → \rightarrow →右子树 → \rightarrow →根节点。有点差别,但是如果把前序遍历做个改变(就是上面前序遍历说到的右子树和左子树的插入顺序),那么就变成了:根节点 → \rightarrow →右子树 → \rightarrow →左子树,再反向可得后序遍历。 L i n k e d L i s t LinkedList LinkedList 保证了插入新元素的时间复杂度是 O ( 1 ) O(1) O(1)。
二叉树遍历的应用
二叉搜索数与双向链表
输入一棵二叉搜索树,将该二叉搜索树转换成一个排序的双向链表。要求不能创建任何新的结点,只能调整树中结点指针的指向。
这是牛客上的一道题目。
根据二叉搜索树和中序遍历的定义可以知道,一颗二叉搜索树的中序遍历就是一个递增的数列,因此用中序遍历的思路解决这道题。
递归解法:
public class Solution {
public TreeNode Convert(TreeNode root) {
return ConvertHelper(root)[0];
}
private TreeNode[] ConvertHelper(TreeNode root){
TreeNode[] ans, left, right;
ans = new TreeNode[2];
if(root == null){
return ans;
}
left = ConvertHelper(root.left);
right = ConvertHelper(root.right);
if(left[0] != null){
root.left = left[1];
left[1].right = root;
ans[0] = left[0];
}else{
ans[0] = root;
}
if(right[0] != null){
root.right = right[0];
right[0].left = root;
ans[1] = right[1];
}else{
ans[1] = root;
}
return ans;
}
}
a n s [ 0 ] 、 a n s [ 1 ] ans[0]、ans[1] ans[0]、ans[1]分别保存双向链表的头和尾。
迭代解法:
public class Solution {
public TreeNode Convert(TreeNode root) {
TreeNode cursor, head, tail;
Stack<TreeNode> stack;
if(root == null)
return null;
stack = new Stack<>();
head = new TreeNode(0);
tail = head;
cursor = root;
while(cursor != null || !stack.isEmpty()){
while(cursor != null){
stack.push(cursor);
cursor = cursor.left;
}
cursor = stack.pop();
tail.right = cursor;
cursor.left = tail;
tail = tail.right;
cursor = cursor.right;
}
head = head.right;
head.left = null;
return head;
}
}