目录



代码实现:
- private class Node<Key,Value>{
- //存储键
- public Key key;
- //存储值
- private 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;
- }
- }


查询方法get实现思想:
代码:
- //二叉树代码
- public class BinaryTree<Key extends Comparable<Key>, Value> {
- //记录根结点
- private Node root;
- //记录树中元素的个数
- private int N;
- //获取树中元素的个数
- public int size() {
- return N;
- }
- //向树中添加元素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) {
- //个数+1
- 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;
- }
- //查询树中指定key对应的value
- public Value get(Key key) {
- return get(root, key);
- }
- //从指定的树x中,查找key对应的值
- public Value get(Node x, Key key) {
- if (x == null) {
- return null;
- }
- int cmp = key.compareTo(x.key);
- if (cmp > 0) {
- //如果要查询的key大于当前结点的key,则继续找当前结点的右子结点;
- return get(x.right, key);
- } else if (cmp < 0) {
- //如果要查询的key小于当前结点的key,则继续找当前结点的左子结点;
- return get(x.left, key);
- } else {
- //如果要查询的key等于当前结点的key,则树中返回当前结点的value。
- return x.value;
- }
- }
- //删除树中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);
- if (cmp > 0) {
- //新结点的key大于当前结点的key,继续找当前结点的右子结点
- x.right = delete(x.right, key);
- } else if (cmp < 0) {
- //新结点的key小于当前结点的key,继续找当前结点的左子结点
- x.left = delete(x.left, key);
- } else {
- //新结点的key等于当前结点的key,当前x就是要删除的结点
- //1.如果当前结点的右子树不存在,则直接返回当前结点的左子结点
- if (x.right == null) {
- return x.left;
- }
- //2.如果当前结点的左子树不存在,则直接返回当前结点的右子结点
- if (x.left == null) {
- return x.right;
- }
- //3.当前结点的左右子树都存在
- //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;
- //个数-1
- N--;
- }
- return x;
- }
- private class Node {
- //存储键
- public Key key;
- //存储值
- private 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 class Test {
- public static void main(String[] args) throws Exception {
- BinaryTree<Integer, String> bt = new BinaryTree<>();
- bt.put(4, "二哈");
- bt.put(1, "张三");
- bt.put(3, "李四");
- bt.put(5, "王五");
- System.out.println(bt.size());
- bt.put(1,"老三");
- System.out.println(bt.get(1));
- System.out.println(bt.size());
- bt.delete(1);
- System.out.println(bt.size());
- }
- }
-
- //找出整个树中最小的键
- public Key min(){
- return min(root).key;
- }
- //找出指定树x中最小的键所在的结点
- private Node min(Node x){
- if (x.left!=null){
- return min(x.left);
- }else{
- return x;
- }
- }
public Key max() 找出树中最大的键
- //找出整个树中最大的键
- public Key max(){
- return max(root).key;
- }
- //找出指定树x中最大键所在的结点
- public Node max(Node x){
- if (x.right!=null){
- return max(x.right);
- }else{
- return x;
- }
- }
1.5 二叉树的基础遍历
我们把树简单的画作上图中的样子,由一个根节点、一个左子树、一个右子树组成,那么按照根节点什么时候被访问我们可以把二叉树的遍历分为以下三种方式:
- //使用前序遍历,获取整个树中的所有键
- public Queue<Key> preErgodic(){
- Queue<Key> keys = new Queue<>();
- preErgodic(root,keys);
- return keys;
- }
- //使用前序遍历,把指定树x中的所有键放入到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);
- }
- }
- //测试代码
- public class Test {
- public static void main(String[] args) throws Exception {
- BinaryTree<String, String> bt = new BinaryTree<>();
- bt.put("E", "5");
- bt.put("B", "2");
- bt.put("G", "7");
- bt.put("A", "1");
- bt.put("D", "4");
- bt.put("F", "6");
- bt.put("H", "8");
- bt.put("C", "3");
- Queue<String> queue = bt.preErgodic();
- for (String key : queue) {
- System.out.println(key+"="+bt.get(key));
- }
- }
- }
- //使用中序遍历,获取整个树中的所有键
- 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);
- }
- }
- //测试代码
- public class Test {
- public static void main(String[] args) throws Exception {
- BinaryTree<String, String> bt = new BinaryTree<>();
- bt.put("E", "5");
- bt.put("B", "2");
- bt.put("G", "7");
- bt.put("A", "1");
- bt.put("D", "4");
- bt.put("F", "6");
- bt.put("H", "8");
- bt.put("C", "3");
- Queue<String> queue = bt.midErgodic();
- for (String key : queue) {
- System.out.println(key+"="+bt.get(key));
- }
- }
- }
1.5.3 后序遍历
我们在4.4中创建的树上,添加前序遍历的API:
public Queue
private void afterErgodic(Node x,Queue
实现步骤:
1.找到当前结点的左子树,如果不为空,递归遍历左子树
2.找到当前结点的右子树,如果不为空,递归遍历右子树
- //使用后序遍历,获取整个树中的所有键
- 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);
- }
- //测试代码
- public class Test {
- public static void main(String[] args) throws Exception {
- BinaryTree<String, String> bt = new BinaryTree<>();
- bt.put("E", "5");
- bt.put("B", "2");
- bt.put("G", "7");
- bt.put("A", "1");
- bt.put("D", "4");
- bt.put("F", "6");
- bt.put("H", "8");
- bt.put("C", "3");
- Queue<String> queue = bt.afterErgodic();
- for (String key : queue) {
- System.out.println(key+"="+bt.get(key));
- }
- }
- }
- //使用层序遍历得到树中所有的键
- public Queue<Key> layerErgodic(){
- Queue<Key> keys = new Queue<>();
- Queue<Node> nodes = new Queue<>();
- nodes.enqueue(root);
- while(!nodes.isEmpty()){
- Node x = nodes.dequeue();
- keys.enqueue(x.key);
- if (x.left!=null){
- nodes.enqueue(x.left);
- }
- if (x.right!=null){
- nodes.enqueue(x.right);
- }
- }
- return keys;
- }
- //测试代码
- public class Test {
- public static void main(String[] args) throws Exception {
- BinaryTree<String, String> bt = new BinaryTree<>();
- bt.put("E", "5");
- bt.put("B", "2");
- bt.put("G", "7");
- bt.put("A", "1");
- bt.put("D", "4");
- bt.put("F", "6");
- bt.put("H", "8");
- bt.put("C", "3");
- Queue<String> queue = bt.layerErgodic();
- for (String key : queue) {
- System.out.println(key+"="+bt.get(key));
- }
- }
- }
上面这棵树的最大深度为
4
。
- //计算整个树的最大深度
- public int maxDepth() {
- return maxDepth(root);
- }
- //计算指定树x的最大深度
- private int maxDepth(Node x) {
- //1.如果根结点为空,则最大深度为0;
- if (x == null) {
- return 0;
- }
- int max = 0;
- int maxL = 0;
- int maxR = 0;
- //2.计算左子树的最大深度;
- if (x.left != null) {
- maxL = maxDepth(x.left);
- }
- //3.计算右子树的最大深度;
- if (x.right != null) {
- maxR = maxDepth(x.right);
- }
- //4.当前树的最大深度=左子树的最大深度和右子树的最大深度中的较大者+1
- max = maxL > maxR ? maxL + 1 : maxR + 1;
- return max;
- }
- //测试代码
- public class Test {
- public static void main(String[] args) throws Exception {
- BinaryTree<String, String> bt = new BinaryTree<>();
- bt.put("E", "5");
- bt.put("B", "2");
- bt.put("G", "7");
- bt.put("A", "1");
- bt.put("D", "4");
- bt.put("F", "6");
- bt.put("H", "8");
- bt.put("C", "3");
- int i = bt.maxDepth();
- System.out.println(i);
- }
- }
- public class PaperFolding {
- public static void main(String[] args) {
- //构建折痕树
- Node tree = createTree(3);
- //遍历折痕树,并打印
- printTree(tree);
- }
- //3.使用中序遍历,打印出树中所有结点的内容;
- private static void printTree(Node tree) {
- if (tree==null){
- return;
- }
- printTree(tree.left);
- System.out.print(tree.item+",");
- printTree(tree.right);
- }
- //2.构建深度为N的折痕树;
- private static Node createTree(int N) {
- Node root = null;
- for (int i = 0; i <N ; i++) {
- if (i==0){
- //1.第一次对折,只有一条折痕,创建根结点;
- root = new Node("down",null,null);
- }else{
- //2.如果不是第一次对折,则使用队列保存根结点;
- Queue<Node> queue = new Queue<>();
- queue.enqueue(root);
- //3.循环遍历队列:
- while(!queue.isEmpty()){
- //3.1从队列中拿出一个结点;
- Node tmp = queue.dequeue();
- //3.2如果这个结点的左子结点不为空,则把这个左子结点添加到队列中;
- if (tmp.left!=null){
- queue.enqueue(tmp.left);
- }
- //3.3如果这个结点的右子结点不为空,则把这个右子结点添加到队列中;
- if (tmp.right!=null){
- queue.enqueue(tmp.right);
- }
- //3.4判断当前结点的左子结点和右子结点都不为空,如果是,则需要为当前结点创建一个
- 值为down的左子结点,一个值为up的右子结点。
- if (tmp.left==null && tmp.right==null){
- tmp.left = new Node("down",null,null);
- tmp.right = new Node("up",null,null);
- }
- }
- }
- }
- return root;
- }
- //1.定义结点类
- private static class Node{
- //存储结点元素
- String item;
- //左子结点
- Node left;
- //右子结点
- Node right;
- public Node(String item, Node left, Node right) {
- this.item = item;
- this.left = left;
- this.right = right;
- }
- }
- }
我们会发现,如果我们要查找1这个元素,查找的效率依旧会很低。效率低的原因在于这个树并不平衡,全部是向左边分支,如果我们有一种方法,能够不受插入数据的影响,让生成的树都像完全二叉树那样那么即使在最坏情况下查找的效率依旧会很好。
一棵2-3查找树要么为空,要么满足满足下面两个要求:
1.1.3 插入







代码:
- private class Node<Key,Value>{
- //存储键
- public Key key;
- //存储值
- private Value value;
- //记录左子结点
- public Node left;
- //记录右子结点
- public Node right;
- //由其父结点指向它的链接的颜色
- public boolean color;
- public Node(Key key, Value value, Node left,Node right,boolean color) {
- this.key = key;
- this.value = value;
- this.left = left;
- this.right = right;
- this.color = color;
- }
- }

1.2.3.2 右旋
当某个结点的左子结点是红色,且左子结点的左子结点也是红色,需要右旋

1.2.4 向单个2-结点中插入新键



1.2.7 向一棵双键树(即一个3-结点)中插入新键






- //红黑树代码
- public class RedBlackTree<Key extends Comparable<Key>, Value> {
- //根节点
- private Node root;
- //记录树中元素的个数
- private int N;
- //红色链接
- private static final boolean RED = true;
- //黑色链接
- private static final boolean BLACK = false;
- /**
- * 判断当前节点的父指向链接是否为红色
- *
- * @param x
- * @return
- */
- private boolean isRed(Node x) {
- //空结点默认是黑色链接
- if (x == null) {
- return false;
- }
- //非空结点需要判断结点color属性的值
- return x.color == RED;
- }
- /**
- * 左旋转
- *
- * @param h
- * @return
- */
- private Node rotateLeft(Node h) {
- //找出当前结点h的右子结点
- Node hRight = h.right;
- //找出右子结点的左子结点
- Node lhRight = hRight.left;
- //让当前结点h的右子结点的左子结点成为当前结点的右子结点
- h.right = lhRight;
- //让当前结点h称为右子结点的左子结点
- hRight.left = h;
- //让当前结点h的color编程右子结点的color
- hRight.color = h.color;
- //让当前结点h的color变为RED
- h.color = RED;
- //返回当前结点的右子结点
- return hRight;
- }
- /**
- * 右旋
- *
- * @param h
- * @return
- */
- private Node rotateRight(Node h) {
- //找出当前结点h的左子结点
- Node hLeft = h.left;
- //找出当前结点h的左子结点的右子结点
- Node rHleft = hLeft.right;
- //让当前结点h的左子结点的右子结点称为当前结点的左子结点
- h.left = rHleft;
- //让当前结点称为左子结点的右子结点
- hLeft.right = h;
- //让当前结点h的color值称为左子结点的color值
- hLeft.color = h.color;
- //让当前结点h的color变为RED
- h.color = RED;
- //返回当前结点的左子结点
- return hLeft;
- }
- /**
- * 颜色反转,相当于完成拆分4-节点
- *
- * @param h
- */
- private void flipColors(Node h) {
- //当前结点的color属性值变为RED;
- h.color = RED;
- //当前结点的左右子结点的color属性值都变为黑色
- h.left.color = BLACK;
- h.right.color = BLACK;
- }
- /**
- * 在整个树上完成插入操作
- *
- * @param key
- * @param val
- */
- public void put(Key key, Value val) {
- //在root整个树上插入key-val
- root = put(root, key, val);
- //让根结点的颜色变为BLACK
- root.color = BLACK;
- }
- /**
- * 在指定树中,完成插入操作,并返回添加元素后新的树
- *
- * @param h
- * @param key
- * @param val
- */
- private Node put(Node h, Key key, Value val) {
- if (h == null) {
- //标准的插入操作,和父结点用红链接相连
- N++;
- return new Node(key, val, null, null, RED);
- }
- //比较要插入的键和当前结点的键
- int cmp = key.compareTo(h.key);
- if (cmp < 0) {
- //继续寻找左子树插入
- h.left = put(h.left, key, val);
- } else if (cmp > 0) {
- //继续寻找右子树插入
- h.right = put(h.right, key, val);
- } else {
- //已经有相同的结点存在,修改节点的值;
- h.value = val;
- }
- //如果当前结点的右链接是红色,左链接是黑色,需要左旋
- if (isRed(h.right) && !isRed(h.left)) {
- h=rotateLeft(h);
- }
- //如果当前结点的左子结点和左子结点的左子结点都是红色链接,则需要右旋
- if (isRed(h.left) && isRed(h.left.left)) {
- h=rotateRight(h);
- }
- //如果当前结点的左链接和右链接都是红色,需要颜色变换
- if (isRed(h.left) && isRed(h.right)) {
- flipColors(h);
- }
- //返回当前结点
- return h;
- }
- //根据key,从树中找出对应的值
- public Value get(Key key) {
- return get(root, key);
- }
- //从指定的树x中,查找key对应的值
- public Value get(Node x, Key key) {
- //如果当前结点为空,则没有找到,返回null
- if (x == null) {
- return null;
- }
- //比较当前结点的键和key
- int cmp = key.compareTo(x.key);
- if (cmp < 0) {
- //如果要查询的key小于当前结点的key,则继续找当前结点的左子结点;
- return get(x.left, key);
- } else if (cmp > 0) {
- //如果要查询的key大于当前结点的key,则继续找当前结点的右子结点;
- return get(x.right, key);
- } else {
- //如果要查询的key等于当前结点的key,则树中返回当前结点的value。
- return x.value;
- }
- }
- //获取树中元素的个数
- public int size() {
- return N;
- }
- //结点类
- private class Node {
- //存储键
- public Key key;
- //存储值
- private Value value;
- //记录左子结点
- public Node left;
- //记录右子结点
- public Node right;
- //由其父结点指向它的链接的颜色
- public boolean color;
- public Node(Key key, Value value, Node left, Node right, boolean color) {
- this.key = key;
- this.value = value;
- this.left = left;
- this.right = right;
- this.color = color;
- }
- }
- }
- //测试代码
- public class Test {
- public static void main(String[] args) throws Exception {
- RedBlackTree<Integer, String> bt = new RedBlackTree<>();
- bt.put(4, "二哈");
- bt.put(1, "张三");
- bt.put(3, "李四");
- bt.put(5, "王五");
- System.out.println(bt.size());
- bt.put(1,"老三");
- System.out.println(bt.get(1));
- System.out.println(bt.size());
- }
- }


执行 select * from user where id=18 ,需要从第一条数据开始,一直查询到第6条,发现id=18,此时才能查询出目标结果,共需要比较6次;