实验要求
对红黑树算法进行修改,使其成为一颗区间数,并实现区间树上的重叠区间查找算法。
程序输入:
1、生成区间树
文件名: insert.txt
文件格式:第一行为待插入数据的个数,第二行之后每一行表示一个区间
注:1)初始时树应为空。
2)按顺序插入,例如,对于下图的数据,插入顺序应为 [50, 60],[20, 25], [70, 90]
2、待查询区间应由控制台输入
程序输出: 控制台直接打印查找结果(如果存在多个区间与query重叠, 需输出 所有与query重叠的区间)。
目录
1.实验内容
掌握区间树上重叠区间的查找算法。
2.实验目的
对实验三的红黑树算法进行修改,使其成为一棵区间树,并实现区间树上的重叠区间查找算法。
3.区间树的数据结构
public class IntervalTreeNode
{
private IntervalTreeNode left; // 左子
private IntervalTreeNode right; // 右子
private IntervalTreeNode parent; // 父结点
private int color; // 颜色,黑色为1,红色为0
private int max; // max=max(high, left.max, right.max])
private int low; // 区间左端点
private int high; // 区间右端点
}
上述代码所示为区间树的结点结构定义。每个结点具有指向父结点的指针parent ;指向左右孩子的指针left,right ;结点的颜色color ;区间的左右端点low,high ;以及以该结点为根的树中的右端点的最大值max 。
4.源码 + 注释
4.1区间树的构造
//插入 外部接口
public void insert(int[] interval)
//插入 内部接口
private void insert(IntervalTreeNode node)
//维护
private void insertFixup(IntervalTreeNode node)
上述代码为区间树构造的方法,其与红黑树的插入结点算法相同。
4.2 Max的维护
当我们对结点进行左旋或右旋操作时,由于改变了结点的相对位置,因此会导致max 发生改变,此时需要对max 做维护,使其仍然满足区间树的性质。
//左旋
private void leftRotate(IntervalTreeNode x)
{
//Step1:y指向x的右孩子
//Step2:y.left连接到x.right
//Step3:x父结点的left或right指向y
//Step4:x连接到y.left
//Step5:更新max信息
updateMax(x);
updateMax(y);
}
//右旋
private void rightRotate(IntervalTreeNode y)
{
//Step1:x指向y的左孩子
//Step2:x.right连接到y.left
//Step3:y父结点的left或right指向x
//Step4:y.left连接到x
//Step5:更新max信息
updateMax(x);
updateMax(y);
}
上述代码为左旋与右旋算法做的修改,在完成连接操作后,对旋转的结点及其左或右孩子的max 信息进行维护,具体更新max 的方法如下所示。若结点没有左右孩子,则max 值为结点的high 值;若只有一个孩子,则max 值为该结点的high 和孩子的max 中的较大值;若有两个孩子,则max 值为该结点的high ,左右孩子的max 中的最大值。
//更新max
private void updateMax(IntervalTreeNode node)
{
if (node.getLeft() == null && node.getRight() == null) node.setMax(node.getHigh());
else if (node.getLeft() == null) node.setMax(Math.max(node.getHigh(), node.getRight().getMax()));
else if (node.getRight() == null) node.setMax(Math.max(node.getHigh(), node.getLeft().getMax()));
else node.setMax(Math.max(node.getHigh(), Math.max(node.getLeft().getMax(), node.getRight().getMax())));
}
4.3 查找算法
//寻找
public List<IntervalTreeNode> intervalSearch(int[] interval)
{
//寻找重叠的第一个节点
IntervalTreeNode x = root;
while (x != null && !isOverlap(interval, new int[]{x.getLow(), x.getHigh()}))
{
if ((x.getLeft() != null) && x.getLeft().getMax() >= interval[0]) x = x.getLeft();
else x = x.getRight();
}
if (x == null)
{
System.out.println("No overlap intervals");
return null;
}
//一旦找到便遍历以该节点为根的子树,寻找所有结点
return levelOrder(interval, x);
}
上述代码为区间树上的重叠区间查找算法。x 从root 开始寻找重叠的第一个节点。如果x 不为空并且要查找的区间与x 区间不重合的话,如果x 的max 值比要查找区间的左端点大,则向左走;反之向右走。循环退出时若x 为null ,则说明在这个区间树中没有与待查找区间重叠的区间,若x 不为空,则遍历以x 为根的子树来寻找所有与待查找区间重叠的区间。
5.算法测试结果
输入查找区间[10,30] 返回结果如下
输入查找区间[100,110] 返回结果如下
6.实验过程中遇到的困难及收获
1、区间树是红黑树的扩张,大部分操作跟红黑树相同或相似,但在对附加信息max 进行维护时,左旋和右旋的过程中要对max 单独进行更新维护。
2、通过测试证明查找算法是正确有效的。
3、对于任意一个具有n 个结点的区间树,我们最多需要循环logn+2 次,(即从根节点查找到叶子结点),每次循环都是常数时间,故在最坏情况下时间复杂度为O(log n) 。在一个比较差的搜索中,假设每次我们寻找的区间都是叶子结点,在这个情况下的时间复杂度为Ω(log n) 。所以区间树上的重叠区间查找算法的时间复杂度为Ɵ(log n) 。
7.实验源代码
IntervalTreeNode.java
/*
* @Title:
* @Package
* @Description:区间树结点
* @author yangf257
* @date 2021/11/29 16:21
*/
public class IntervalTreeNode
{
private IntervalTreeNode left; // 左子
private IntervalTreeNode right; // 右子
private IntervalTreeNode parent; // 父结点
private int color; // 颜色,黑色为1,红色为0
private int max; // max=max(high, left.max, right.max])
private int low; // 区间左端点
private int high; // 区间右端点
public IntervalTreeNode(int color, int low, int high, int max, IntervalTreeNode left, IntervalTreeNode right, IntervalTreeNode parent)
{
this.color = color;
this.low = low;
this.high = high;
this.max = max;
this.left = left;
this.right = right;
this.parent = parent;
// TODO 自动生成构造函数存根
}
public IntervalTreeNode(int color, int low, int high)
{
this.color = color;
this.low = low;
this.high = high;
this.max = high;
this.left = null;
this.right = null;
this.parent = null;
}
public IntervalTreeNode getLeft()
{
return left;
}
public void setLeft(IntervalTreeNode left)
{
this.left = left;
}
public IntervalTreeNode getRight()
{
return right;
}
public void setRight(IntervalTreeNode right)
{
this.right = right;
}
public IntervalTreeNode getParent()
{
return parent;
}
public void setParent(IntervalTreeNode parent)
{
this.parent = parent;
}
public int getColor()
{
return color;
}
public void setColor(int color)
{
this.color = color;
}
public int getMax()
{
return max;
}
public void setMax(int max)
{
this.max = max;
}
public int getLow()
{
return low;
}
public int getHigh()
{
return high;
}
@Override
public String toString()
{
return "[" + this.low + ", " + this.high + "]";
}
}
IntervalTree.java
/*
* @Title:
* @Package
* @Description:区间树类
* @author yangf257
* @date 2021/11/29 16:37
*/
import java.util.ArrayList;
import java.util.LinkedList;
import java.util.List;
import java.util.Queue;
public class IntervalTree
{
private static final int BLACK = 1;
private static final int RED = 0;
private IntervalTreeNode root;
//寻找
public List<IntervalTreeNode> intervalSearch(int[] interval)
{
//寻找重叠的第一个节点
IntervalTreeNode x = root;
while (x != null && !isOverlap(interval, new int[]{x.getLow(), x.getHigh()}))
{
if ((x.getLeft() != null) && x.getLeft().getMax() >= interval[0]) x = x.getLeft();
else x = x.getRight();
}
if (x == null)
{
System.out.println("No overlap intervals");
return null;
}
//一旦找到便遍历以该节点为根的子树,寻找所有结点
return levelOrder(interval, x);
}
private boolean isOverlap(int[] interval1, int[] interval2)
{
return (interval1[1] >= interval2[0]) && (interval2[1] >= interval1[0]);
}
//插入 外部接口
public void insert(int[] interval)
{
IntervalTreeNode node = new IntervalTreeNode(RED, interval[0], interval[1]);
insert(node);
}
//插入 内部接口
private void insert(IntervalTreeNode node)
{
IntervalTreeNode parent = null;
IntervalTreeNode x = root;
while (x != null)
{
parent = x;
if (node.getLow() < x.getLow()) x = x.getLeft();
else x = x.getRight();
}
node.setParent(parent);
if (parent == null) root = node;
else
{
if (node.getLow() < parent.getLow()) parent.setLeft(node);
else parent.setRight(node);
}
insertFixup(node);
}
//维护
private void insertFixup(IntervalTreeNode node)
{
IntervalTreeNode parent, grandparent;
while ((parent = node.getParent()) != null && parent.getColor() == RED)
{
grandparent = parent.getParent();
//case 1,2,3
if (parent == grandparent.getLeft()) //case 1,2,3:父结点是祖父结点的left
{
IntervalTreeNode uncle = grandparent.getRight(); //叔叔结点
if (uncle != null && uncle.getColor() == RED) //case 1:叔叔为RED
{
uncle.setColor(BLACK);
parent.setColor(BLACK);
grandparent.setColor(RED);
node = grandparent;
}
else //case 2,3:叔叔为BLACK
{
if (node == parent.getRight()) //case 2:node是父结点的right
{
IntervalTreeNode tmp;
leftRotate(parent);
tmp = parent;
parent = node;
node = tmp;
}
//case 3:node是父结点的left
parent.setColor(BLACK);
grandparent.setColor(RED);
rightRotate(grandparent);
}
}
//case 4,5,6:父结点是祖父结点的right
else
{
IntervalTreeNode uncle = grandparent.getLeft();
if (uncle != null && uncle.getColor() == RED) //case 4:叔叔为RED
{
uncle.setColor(BLACK);
parent.setColor(BLACK);
grandparent.setColor(RED);
node = grandparent;
}
else //case 5,6:叔叔为BLACK
{
if (node == parent.getLeft()) //case 5:node是父结点的left
{
IntervalTreeNode temp;
rightRotate(parent);
temp = node;
node = parent;
parent = temp;
}
//case 6:node是父结点的right
parent.setColor(BLACK);
grandparent.setColor(RED);
leftRotate(grandparent);
}
}
}
root.setColor(BLACK);
}
//左旋
private void leftRotate(IntervalTreeNode x)
{
//Step1:y指向x的右孩子
IntervalTreeNode y = x.getRight();
//Step2:y.left连接到x.right
x.setRight(y.getLeft());
if (y.getLeft() != null) y.getLeft().setParent(x);
//Step3:x父结点的left或right指向y
y.setParent(x.getParent());
if (x.getParent() == null) root = y; // 如果 “x的父亲” 是空节点,则将y设为根节点
else if (x == x.getParent().getLeft()) x.getParent().setLeft(y); // 如果 x是它父节点的左孩子,则将y设为“x的父节点的左孩子”
else x.getParent().setRight(y); // 如果 x是它父节点的右孩子,则将y设为“x的父节点的右孩子”
//Step4:x连接到y.left
y.setLeft(x);
x.setParent(y);
//Step5:更新max信息
updateMax(x);
updateMax(y);
}
//右旋
private void rightRotate(IntervalTreeNode y)
{
//Step1:x指向y的左孩子
IntervalTreeNode x = y.getLeft();
//Step2:x.right连接到y.left
y.setLeft(x.getRight());
if (x.getRight() != null) x.getRight().setParent(y);
//Step3:y父结点的left或right指向x
x.setParent(y.getParent());
if (y.getParent() == null) root = x; // 如果 “y的父亲” 是空节点,则将x设为根节点
else if (y == y.getParent().getRight()) y.getParent().setRight(x); // 如果 y是它父节点的右孩子,则将x设为“y的父节点的右孩子”
else y.getParent().setLeft(x); // (y是它父节点的左孩子) 将x设为“x的父节点的左孩子”
//Step4:y.left连接到x
x.setRight(y);
y.setParent(x);
//Step5:更新max信息
updateMax(x);
updateMax(y);
}
//层序遍历
private List<IntervalTreeNode> levelOrder(int[] interval, IntervalTreeNode root)
{
List<IntervalTreeNode> overlapList = new ArrayList<>();
Queue<IntervalTreeNode> queue = new LinkedList<>();
queue.add(root);
IntervalTreeNode curr;
while (!queue.isEmpty())
{
curr = queue.poll();
if (isOverlap(interval, new int[]{curr.getLow(), curr.getHigh()})) overlapList.add(curr);
if (curr.getLeft() != null) queue.add(curr.getLeft());
if (curr.getRight() != null) queue.add(curr.getRight());
}
return overlapList;
}
//更新max
private void updateMax(IntervalTreeNode node)
{
if (node.getLeft() == null && node.getRight() == null) node.setMax(node.getHigh());
else if (node.getLeft() == null) node.setMax(Math.max(node.getHigh(), node.getRight().getMax()));
else if (node.getRight() == null) node.setMax(Math.max(node.getHigh(), node.getLeft().getMax()));
else node.setMax(Math.max(node.getHigh(), Math.max(node.getLeft().getMax(), node.getRight().getMax())));
}
}
IntervalSearchTest.java
/*
* @Title:
* @Package
* @Description:
* @author yangf257
* @date 2021/11/29 19:06
*/
import org.junit.Test;
import java.io.*;
import java.util.ArrayList;
import java.util.List;
import java.util.Scanner;
public class IntervalSearchTest
{
@Test
public void test1()
{
//课本案例
int[][] interval = {{0, 3}, {5, 8}, {6, 10}, {8, 9}, {15, 23}, {16, 21}, {17, 19}, {19, 20}, {25, 30}, {26, 26}};
IntervalTree intervalTree = new IntervalTree();
for (int i = 0; i < interval.length; i++)
{
intervalTree.insert(interval[i]);
}
List<IntervalTreeNode> list = intervalTree.intervalSearch(new int[]{3, 7});
for (int i = 0; i < list.size(); i++)
{
System.out.println(list.get(i).toString());
}
}
public static void main(String[] args)
{
int[][] interval = input();
IntervalTree intervalTree = new IntervalTree();
for (int i = 0; i < interval.length; i++)
{
intervalTree.insert(interval[i]);
}
Scanner scanner = new Scanner(System.in);
int[] target = new int[2];
System.out.println("Inter the interval: ");
for (int i = 0; i < 2; i++)
{
target[i] = scanner.nextInt();
}
List<IntervalTreeNode> list = intervalTree.intervalSearch(target);
if (list == null) return;
for (int i = 0; i < list.size(); i++)
{
System.out.println(list.get(i).toString());
}
}
private static int[][] input()
{
int length = 0;
String intervalstr = "";
String filePath = "C:\\Users\\HP\\Desktop\\算法\\Experiment\\ExperimentCode\\4.IntervalSearch\\src\\insert.txt";
try
{
File file = new File(filePath);
if (file.isFile() && file.exists())
{
InputStreamReader isr = new InputStreamReader(new FileInputStream(file), "utf-8");
BufferedReader br = new BufferedReader(isr);
String lineTxt = null;
int count = 0;
while ((lineTxt = br.readLine()) != null)
{
count++;
if (count == 1) length = Integer.parseInt(lineTxt);
else
{
intervalstr += lineTxt;
intervalstr += " ";
}
}
br.close();
}
else
{
System.out.println("文件不存在!");
}
} catch (Exception e)
{
System.out.println("文件读取错误!");
}
// System.out.println(intervalstr);
List<Integer> array = new ArrayList<>();
int num = 0;
for (int i = 0; i < intervalstr.length(); i++)
{
if (intervalstr.charAt(i) != ' ')
{
if (num != 0) num *= 10;
num += intervalstr.charAt(i) - '0';
}
else
{
array.add(num);
// System.out.println(num);
num = 0;
}
}
array.add(num);
int[][] interval = new int[length][2];
for (int i = 0; i < interval.length; i++)
{
for (int j = 0; j < 2; j++)
{
interval[i][j] = array.get(i * 2 + j);
}
}
// for (int i = 0; i < interval.length; i++)
// {
// for (int j = 0; j < 2; j++)
// {
// System.out.println(interval[i][j]);
// }
// }
return interval;
}
}