线段树(区间树)及相关应用

一.线段树的定义

线段树的定义

二.线段树的构造及使用

问题1. 线段树是一棵二叉树,他的每个节点包含了两个额外的属性start和end用于表示该节点所代表的区间。start和end都是整数,并按照如下的方式赋值:

  • 根节点的 start 和 end 由 build 方法所给出。
  • 对于节点 A 的左儿子,有 start=A.left, end=(A.left + A.right) / 2。
  • 对于节点 A 的右儿子,有 start=(A.left + A.right) / 2 + 1, end=A.right。
  • 如果 start 等于 end, 那么该节点是叶子节点,不再有左右儿子。

实现一个 build 方法,接受 start 和 end 作为参数, 然后构造一个代表区间 [start, end] 的线段树,返回这棵线段树的根。

Example
给出[3,2,1,4],线段树将被这样构造:
               [0,  3]
              /       \
          [0,  1]     [2, 3]
          /    \      /    \
       [0, 0][1, 1][2, 2] [3, 3]

分析:
1. 当start > end时,应返回null;
2. 当start = end时,该线段树只有一个根节点;
3. 当start < end时,使用递归建立树。

public class SegmentTree {
/**
 * Definition of SegmentTreeNode:
 */
public static class SegmentTreeNode {
    public int start, end;
    public SegmentTreeNode left, right;

    public SegmentTreeNode(int start, int end) {
        this.start = start;
        this.end = end;
        this.left = this.right = null;
    }
}

/**
 * @param start: start value.
 * @param end: end value.
 * @return: The root of Segment Tree.
 */
public SegmentTreeNode build(int start, int end) {
    // write your code here
    if (start > end) {
        return null;
    }
    SegmentTreeNode rootNode = new SegmentTreeNode(start, end);
    if (start == end) {
        return rootNode;
    } else {
        int newStart = start;
        int newEnd = (start + end) >> 1;

        rootNode.left = build(newStart, newEnd);

        newStart = newEnd + 1;
        newEnd = end;
        rootNode.right = build(newStart, newEnd);
    }

    return rootNode;
}}

问题2. 对于一个有n个数的整数数组,在对应的线段树中, 根节点所代表的区间为0-n-1, 每个节点有一个额外的属性max,值为该节点所代表的数组区间start到end内的最大值。

为SegmentTree设计一个 query 的方法,接受3个参数root, start和end,线段树root所代表的数组中子区间[start, end]内的最大值。

Example
对于数组 [1, 4, 2, 3], 对应的线段树为:

                  [0, 3, max=4]
                 /             \
          [0,1,max=4]        [2,3,max=3]
          /         \        /         \
   [0,0,max=1] [1,1,max=4] [2,2,max=2], [3,3,max=3]

分析:

1. 首先构造线段树;
2. 然后使用递归,自低向上,找出每个区间的max值。如[0,1]的最大值,取其左子树最大值与右子树最大值进行比较,若子树最大值未知,则开始查找子树最大值。叶子节点的最大值是显见的。
   public static class SegmentTreeNode {
    public int start, end;
    public int max;
    public SegmentTreeNode left, right;

    public SegmentTreeNode(int start, int end) {
        this.start = start;
        this.end = end;
        this.left = this.right = null;
        this.max = Integer.MAX_VALUE;
    }
}

 public void findMax(SegmentTreeNode rootNode, int[] array) {
    if (rootNode.left == null) {
        rootNode.max = array[rootNode.end];
    } else {
        if (rootNode.left.max == Integer.MAX_VALUE) {
            findMax(rootNode.left, array);
        }
    }

    if (rootNode.right != null && rootNode.right.max == Integer.MAX_VALUE) {
        findMax(rootNode.right, array);
    }

    if (rootNode.left != null && rootNode.right != null) {
        rootNode.max = rootNode.left.max > rootNode.right.max ?
                rootNode.left.max : rootNode.right.max;
    }
}
3. 查找指定区间的最大值,先对特殊情况进行处理:
  • 要查找的区间不在线段树范围内

  • 线段树只有根节点

  • 要查找的区间等于根节点区间

4. 一般情况下,要查找的区间可能为左子树区间的子集,右子树的子集,或者跨左右子树三种情形。
 public int query(SegmentTreeNode root, int start, int end) {
    // write your code here
    if (end < root.start || start > root.end) {
        return Integer.MIN_VALUE;
    }

    if (root.start == root.end) {
        if (start == root.start || end == root.end) {
            return root.max;
        } else {
            return Integer.MIN_VALUE;
        }
    } else {
        if (start == root.start && end == root.end) {
            return root.max;
        }
    }


    if (end <= root.left.end) {
        /**
         * 区间位于左子树
         */
        SegmentTreeNode node = root.left;
        return query(node, start, end);
    } else if (start >= root.right.start) {
        /**
         * 区间位于右子树
         */
        SegmentTreeNode node = root.right;
        return query(node, start, end);
    } else {
        /**
         * 区间跨子树
         */
        int maxLeft = query(root.left, start, root.left.end);
        int maxRight = query(root.right, root.right.start, end);
        return maxLeft >= maxRight ? maxLeft : maxRight;
    }
}

问题3. 给定一个整数数组 (下标由 0 到 n-1,其中 n 表示数组的规模,数值范围由 0 到 10000),以及一个 查询列表。对于每一个查询,将会给你一个整数,请你返回该数组中小于给定整数的元素的数量。

Example
对于数组 [1,2,7,8,5] ,查询 [1,8,5],返回 [0,4,2]

分析:

1. 在SegmentTreeNode中再增加min字段:
 public static class SegmentTreeNode {
    public int start, end;
    public int max;
    public int min;
    public SegmentTreeNode left, right;

    public SegmentTreeNode(int start, int end) {
        this.start = start;
        this.end = end;
        this.left = this.right = null;
        this.max = Integer.MAX_VALUE;
        this.min = Integer.MIN_VALUE;
    }
}
2. 构造完区间树后,对min和max进行初始化:
    public void findMinAndMax(SegmentTreeNode node, int[] array) {
    if (node.left == null) {
        node.min = array[node.start];
        node.max = array[node.end];
    } else {
        if (node.left.min == Integer.MIN_VALUE || node.left.max == Integer.MAX_VALUE) {
            findMinAndMax(node.left, array);
        }
    }

    if (node.right != null) {
        if (node.right.min == Integer.MIN_VALUE || node.right.max == Integer.MAX_VALUE) {
            findMinAndMax(node.right, array);
        }
    }

    if (node.left != null && node.right != null) {
        node.min = node.left.min > node.right.min ? node.right.min : node.left.min;
        node.max = node.left.max > node.right.max ? node.left.max : node.right.max;
    }
}
3. 先处理特殊情况,即要查询的整数超出线段树区间,和线段树只有根节点的情形;
4. 一般情况下,分左右子树分别计算,然后将左右子树的结果作和,以左子树为例,若目标整数queryInt大于左子树max,则左子树区间内所有值都符合条件,否则,queryInt落在左子树区间内,以左子树为根节点,继续向下计算。右子树同理。
  public int query(SegmentTreeNode rootNode, int queryInt) {
    if (queryInt > rootNode.max || queryInt < rootNode.min) {
        return 0;
    }

    int count = 0;
    if (rootNode.left == null || rootNode.right == null) {
        if (queryInt > rootNode.max) {
            count++;
        }
    } else {
        SegmentTreeNode left = rootNode.left;
        if (queryInt > left.max) {
            count += left.end - left.start + 1;
        } else if (queryInt > left.min && queryInt <= left.max) {
            count += query(left, queryInt);
        }

        SegmentTreeNode right = rootNode.right;
        if (queryInt > right.max) {
            count += right.end - right.start + 1;
        } else if (queryInt > right.min && queryInt <= right.max) {
            count += query(right, queryInt);
        }
    }

    return count;
}

最后,问题答案:

public List<Integer> countOfSmallerNumber(int[] A, int[] queries) {
    // write your code here
    List<Integer> result = new LinkedList<>();

    if (A.length == 0) {
        while (result.size() != queries.length) {
            result.add(0);
        }
        return result;
    }

    SegmentTreeNode rootNode = build(A);
    HashMap<Integer, Integer> results = new HashMap<>();
    for (int i = 0; i < queries.length; i++) {
        int queryInt = queries[i];
        Integer integer = results.get(queryInt);
        if (integer != null) {
            result.add(integer);
        } else {
            integer = query(rootNode, queryInt);
            result.add(integer);
            results.put(queryInt, integer);
        }
    }

    return result;
}

这里对结果进行了缓存,若在查询过程中,遇到相同整数,则不必再查询线段树。

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值