删除节点原理示意图
构造队列方法
public class Queue<T>implements Iterable<T> {
//记录首结点
private Node head;
//记录首结点
private Node last;
//当前栈的元素个数
private int N;
public Queue() {
// TODO 自动生成的构造函数存根
head=new Node(null,null);
last=null;
N=0;
}
//定义节点类,以便上面可以使用Node,类的定义方法是private/private class 类名{}
private class Node{
//定义数据域
public T item;
//指向下一个节点
public Node next;
//生成构造方法
public Node(T item, Node next) {
this.item = item;
this.next = next;
}
}
//获取栈中元素
public int size() {
return N;
}
//判断当前栈中元素是否为0
public boolean isEmpty() {
return N==0;
}
//要明确无论是插入,都是从尾结点开始的,因为每插入一个节点都是尾结点
//向队列中插入元素
public void enqueue(T t) {
if (last==null) {
//如果对列为空,则存入的结点就是尾结点
last=new Node(t, null);
//收结点的下一个节点指向尾结点,来建立对列之间的联系
head.next=last;
}else {
//插入前老尾结点为尾结点
Node oldLast=last;
//令新插入的节点为尾结点
last=new Node(t, null);
//老尾结点的下一个节点为新尾结点
oldLast.next=last;
}
//元素个数加1
N++;
}
//从队列中拿出一个元素
public T dequeue() {
if (isEmpty()) {
return null;
}
//首节点的下一个节点等变成首节点的下两个节点,说明首节点下一个节点被删除了
Node oldFirst=head.next;
head.next=oldFirst.next;
N--;
if (isEmpty()) {
last=null;
}
return oldFirst.item;
}
@Override
//Iterator<T>是接口,需要一个类去实现方法,实现返回,所以定义了public class SIterator implements Iterator<T>
//去实现hasnext和next方法
public Iterator<T> iterator() {
return new QIterator();
}
public class QIterator implements Iterator<T> {
//Iterator<T>接口实现循环遍历,是从头开始遍历,所以如此定义n为头结点。
private Node n=head;
@Override
public boolean hasNext() {
return n.next!=null;
}
@Override
public T next() {
//因为head并不存储数据,所以head.next,让指针后移一位开始找数据
n=n.next;
return n.item;
}
}
}
实现代码
public class BinaryTree<Key extends Comparable<Key>,Value> {
//记录根节点
private Node root;
//记录树中元素个数
private int N;
//节点类
private class Node {
//存储键
public Key key;
public Value value;
public Node left;
public Node right;
public Node(Key key, Value value, Node left, Node right) {
this.key = key;
this.value = value;
this.left = left;
this.right = right;
}
}
//获取树中元素个数
public int size() {
return N;
}
插入方法put实现思想:
1.如果当前树中没有任何一个结点,则直接把新结点当做根结点使用
2.如果当前树不为空,则从根结点开始:
2.1如果新结点的key小于当前结点的key,则继续找当前结点的左子结点;
2.2如果新结点的key大于当前结点的key,则继续找当前结点的右子结点;
2.3如果新结点的key等于当前结点的key,则树中已经存在这样的结点,替换该结点的value值即可。
//添加元素key-value
public void put(Key key, Value value) {
root = put(root, key, value);
}
//向指定树x中添加key-value,并返回添加元素后的新树
private Node put(Node x, Key key, Value value) {
if (x == null) {
N++;
return new Node(key, value, null, null);
}
int cmp = key.compareTo(x.key);
if (cmp > 0) {
//新结点的key大于当前节点的key,递归调用继续寻找当前节点的右子节点,知道找到相同
x.right = put(x.right, key, value);
} else if (cmp < 0) {
//新结点的key小于当前节点的key,递归调用继续寻找当前节点的左子节点,知道找到相同
x.left = put(x.left, key, value);
} else {
//新结点的key等于当前节点的key,把当前节点的value值替换
x.value = value;
}
//返回添加完元素后的新树
return x;
}
查询方法get实现思想:
从根节点开始:
1.如果要查询的key小于当前结点的key,则继续找当前结点的左子结点;
2.如果要查询的key大于当前结点的key,则继续找当前结点的右子结点;
3.如果要查询的key等于当前结点的key,则树中返回当前结点的value。
//查询树中指定key对应value
public Value get(Key key) {
return get(root, key);
}
private Value get(Node x, Key key) {
if (x == null) {
return null;
}
int cmp = key.compareTo(x.key);
if (cmp > 0) {
return get(x.right, key);
} else if (cmp < 0) {
return get(x.left, key);
} else {
return x.value;
}
}
//找出树中最小节点
public Key min() {
return min(root).key;
}
private Node min(Node x) {
if (x.left != null) {
return min(x.left);
} else {
return x;
}
}
//找出树中最大节点
public Key max() {
return max(root).key;
}
private Node max(Node x) {
if (x.right != null) {
return max(x.right);
} else {
return x;
}
}
删除方法delete实现思想:
1.找到被删除结点;
2.找到被删除结点右子树中的最小结点minNode
3.删除右子树中的最小结点
4.让被删除结点的左子树称为最小结点minNode的左子树,让被删除结点的右子树称为最小结点minNode的右子
树
5.让被删除结点的父节点指向最小结点minNode
//删除树中key对应的value
public void delete(Key key) {
root = delete(root, key);
}
//删除指定树x中key对应的value,并返回删除后的新树
public Node delete(Node x, Key key) {
if (x == null) {
return null;
}
int cmp = key.compareTo(x.key);
//新结点的key大于当前节点的key,则递归调用继续寻找当前节点的右子节点
if (cmp > 0) {
x.right = delete(x.right, key);
//新结点的key小于当前节点的key,则递归调用继续寻找当前节点的左子节点
} else if (cmp < 0) {
x.left = delete(x.left, key);
//新结点的key等于当前节点的key,当前x就是要删除的节点
} else {
//1.如果当前节点的右子节点不存在,则直接返回当前节点的左子节点
if (x.right == null) {
return x.left;
}
//2.如果当前节点的左子节点不存在,则直接返回当前节点的右子节点
if (x.left == null) {
return x.right;
}
//3.1找到右子树中最小的节点
Node minNode = x.right;
while (minNode.left != null) ;
minNode = minNode.left;
//3.2删除右字树中最小的节点
Node n = x.right;
while (n.left != null) {
if (n.left.left != null) {
n.left = null;
} else {
n = n.left;
}
}
//3.3让删除节点的左子树成为最小节点minNode的左子树,让删除节点的右子树成为最小节点minNode的右子树
minNode.left = x.left;
minNode.right = x.right;
//3.4让删除节点的父节点指向最小节点minNode
x = minNode;
N--;
}
return x;
}
1.前序遍历;
先访问根结点,然后再访问左子树,最后访问右子树
2.中序遍历;
先访问左子树,中间访问根节点,最后访问右子树
3.后序遍历;
先访问左子树,再访问右子树,最后访问根节点
前序遍历实现步骤:
1.把当前结点的key放入到队列中;
2.找到当前结点的左子树,如果不为空,递归遍历左子树
3.找到当前结点的右子树,如果不为空,递归遍历右子树
//使用前序遍历,获取整个树中的键
public Queue<Key> preErgodic() {
Queue<Key> keys = new Queue<>();
preErgodic(root, keys);
return keys;
}
private void preErgodic(Node x, Queue<Key> keys) {
if (x == null) {
return;
}
//1.把当前节点的key放入到队列中
keys.enqueue(x.key);
//2.找到当前结点的左子树,如果不为空,递归遍历左子树
if (x.left != null) {
preErgodic(x.left, keys);
}
// 3.找到当前结点的右子树,如果不为空,递归遍历右子树
if (x.right != null) {
preErgodic(x.right, keys);
}
}
中序遍历实现步骤:
1.找到当前结点的左子树,如果不为空,递归遍历左子树
2.把当前结点的key放入到队列中;
3.找到当前结点的右子树,如果不为空,递归遍历右子树
//使用中序遍历,获取整个树中的所有键
public Queue<Key> midErgodic() {
Queue<Key> keys = new Queue<>();
midErgodic(root, keys);
return keys;
}
//使用中序遍历,把指定树x中的所有键放入到keys队列中
private void midErgodic(Node x, Queue<Key> keys) {
if (x == null) {
return;
}
//1.找到当前结点的左子树,如果不为空,递归遍历左子树
if(x.left!=null) {
midErgodic(x.left, keys);
}
//2.把当前结点的key放入到队列中;
keys.enqueue(x.key);
//3.找到当前结点的右子树,如果不为空,递归遍历右子树
if(x.right!=null) {
midErgodic(x.right, keys);
}
}
后序遍历实现步骤:
1.找到当前结点的左子树,如果不为空,递归遍历左子树
2.找到当前结点的右子树,如果不为空,递归遍历右子树
3.把当前结点的key放入到队列中;
//使用后序遍历,获取整个树中的所有键
public Queue<Key> afterErgodic(){
Queue<Key> keys = new Queue<>();
afterErgodic(root,keys);
return keys;
}
//使用后序遍历,把指定树x中的所有键放入到keys队列中
private void afterErgodic(Node x,Queue<Key> keys){
if(x==null){
return;
}
//1.找到当前结点的左子树,如果不为空,递归遍历左子树
if (x.left!=null){
afterErgodic(x.left,keys);
}
//2.找到当前结点的右子树,如果不为空,递归遍历右子树
if (x.right!=null){
afterErgodic(x.right,keys);
}
//3.把当前结点的key放入到队列中;
keys.enqueue(x.key);
}
层序遍历实现步骤:
1.创建队列,存储每一层的结点;
2.使用循环从队列中弹出一个结点:
2.1获取当前结点的key;
2.2如果当前结点的左子结点不为空,则把左子结点放入到队列中
2.3如果当前结点的右子结点不为空,则把右子结点放入到队列中
//使用层序遍历,获取整个树中的键(获取每个节点的key就是为了遍历使用,所以才创建了其队列)
public Queue<Key> layerErgodic(){
//定义两个队列,分别存储树中的键和节点
Queue<Key> keys=new Queue<>();
Queue<Node> nodes=new Queue<>();
//默认往队列中放入根节点
nodes.enqueue(root);
while (!nodes.isEmpty()) {
//从队列中弹出一个节点,把key放入keys中
Node n = nodes.dequeue();
keys.enqueue(n.key);
//判断当前节点还有没有左子节点,如果有,放入到nodes中
if (n.left != null) {
nodes.enqueue(n.left);
}
if (n.right != null) {
nodes.enqueue(n.right);
}
}
return keys;
}
求最大深度实现步骤:
1.如果根结点为空,则最大深度为0;
2.计算左子树的最大深度;
3.计算右子树的最大深度;
4.当前树的最大深度=左子树的最大深度和右子树的最大深度中的较大者+1
//求最大深度
public int maxDepth(){
return maxDepth(root);
}
private int maxDepth(Node x){
if (x==null){
return 0;
}
int max=0;
int maxL=0;
int maxR=0;
if (x.left!=null){
maxL=maxDepth(x.left);
}
if (x.right!=null){
maxR=maxDepth(x.right);
}
max=maxL > maxR ? maxL+1:maxR+1;
return max;
}
}