Line tree of data structure and algorithm

Keywords: Attribute Lambda

Catalog

(1) definition

The line tree is a balanced binary search tree. What is balance? The absolute value of the height difference between the left and right subtrees of the line tree does not exceed 1. Each node of the line tree stores two parts:

  • Interval or line segment: [left end of interval, right end of interval] (in the form of parameter or member attribute of node class)
  • Storage elements of interval nodes: determined according to business requirements

Based on the characteristics of balanced binary tree (the absolute value of height difference between left and right subtrees is no more than 1), we can use NULL to complete the nonexistent interval, and finally complete it as a full binary tree. At this time, we can stack the interval nodes one by one in order (sequence output), so we can use array to represent a complete binary tree, Array needs a maximum of 4n storage space)

It can be seen from the above figure:

  • The range of the left child interval of each node is [l, mid], and the right child is [mid+1, r]. Where l: left endpoint, r: right endpoint, mid = (L + r) / 2
  • For node i, the left child node is 2*i + 1, and the right child node is 2*i + 2

(2) Custom line segment tree

1. Create line segment tree according to array

public class SegmentTree<E> {

	/**
	 * Source data array
	 */
	private E[] data;

	/**
	 * Represent the line tree as an array
	 */
	private E[] tree;
	
	/**
	 * Fuser interface: the element stored in the interval node of the segment tree is determined by the merger() method of the fuser interface
	 */
	private Merger<E> merger;

	/**
	 * Constructor: build the passed array into a line tree array
	 * 
	 * @param arr
	 */
	@SuppressWarnings("unchecked")
	public SegmentTree(E[] arr, Merger<E> merger) {
		// Fuser interface initialization 
		this.merger = merger;
		
		// Source data array initialization
		data = (E[]) new Object[arr.length];
		for (int i = 0; i < arr.length; i++) {
			data[i] = arr[i];
		}

		// The initialization size of the segment tree array is 4 times the array size
		tree = (E[]) new Object[4 * arr.length];
		// Building line tree array
		buildSegmentTree(0, 0, arr.length - 1);
	}

	/**
	 * Create a line segment tree representing the interval [l...r] at the treeIndex
	 * 
	 * @param treeIndex	Index of root node of segment tree
	 * @param l	Left end of interval
	 * @param r	Right endpoint of interval
	 */
	private void buildSegmentTree(int treeIndex, int l, int r) {
		// The termination condition of recursion: the left end of interval is equal to the right end of interval
		if (l == r) {
			tree[treeIndex] = data[l];
			return;
		}
		
		// Left child node index of current node
		int leftTreeIndex = leftChild(treeIndex);
		// Right child node index of current node
		int rightTreeIndex = rightChild(treeIndex);
		
		// Intermediate value
		int mid = l + (r - l) / 2;
		
		// First, create a line segment tree with the interval [leftmost endpoint... Middle value] at the left child node index
		buildSegmentTree(leftTreeIndex, l, mid);
		// Then create a line segment tree with the interval [middle value + 1... Rightmost endpoint] at the right child node index
        buildSegmentTree(rightTreeIndex, mid + 1, r);
		
		// Finally, the data to be maintained in the interval is determined by the merge(e1, e2) method of the merge interface
		tree[treeIndex] = merger.merge(tree[leftTreeIndex], tree[rightTreeIndex]);
	}
	
	/**
	 * Returns the index of the left child node of the element represented by an index in the array representation of a complete binary tree
	 *  
	 * @param index
	 * @return
	 */
	private int leftChild(int index) {
		return index * 2 + 1;
	}
	
	/**
	 * Returns the index of the right child node of the element represented by an index in the array representation of a complete binary tree
	 * 
	 * @param index
	 * @return
	 */
	private int rightChild(int index) {
		return index * 2 + 2;
	}
	
	@Override
	public String toString() {
		StringBuilder res = new StringBuilder();
		res.append('[');
		for (int i = 0; i < tree.length; i++) {
			res.append(tree[i]);
			if (i != tree.length - 1) {
				res.append(", ");
			}
		}
		res.append("]");
		return res.toString();
	}
}

test

public static void main(String[] args) {
	Integer[] nums = {1, 2, 3, 4, 5, 6};
	// lambda expression (a, b) - > A + B represents the merge(a, b) of the merge interface, and the sum of the two numbers is obtained
	SegmentTree<Integer> segmentTree = new SegmentTree<>(nums, (a, b) -> a + b);
	System.out.println(segmentTree);
}

2. interval query operation of line segment tree

There are three situations:

  • The left child interval of the line tree contains the search interval (mid > = right endpoint of the query interval), recursively entering the left child interval
  • The right child interval of the line tree contains the search interval (mid < = left end of the query interval), recursively entering the right child interval
  • The search interval has left and right child intervals of the line tree at the same time. Obtain the values of [left endpoint of the query interval, mid] and [mid, right endpoint of the query interval] for operation
/**
 * Return the value of interval [queryl, queryre]
 * 
 * @param queryL
 * @param queryR
 * @return
 */
public E query(int queryL, int queryR) {
	 if (queryL < 0 || queryL >= data.length || queryR < 0 || queryR >= data.length || queryR < queryL) {
		 throw new IllegalArgumentException("Index is illegel.");
	 }
	 return query(0, 0, data.length - 1, queryL, queryR);
}

/**
 * In the range of [r...l] in the segment tree whose root is treeIndex, search the value of the interval [queryll... Queryre]
 * 
 * @param treeIndex
 * @param l
 * @param r
 * @param queryL
 * @param queryR
 * @return
 */
private E query(int treeIndex, int l, int r, int queryL, int queryR) {
	// Recursive termination condition: the left end of the search interval is equal to the left end of the line tree whose root is treeIndex, and the right end of the search interval is equal to the right end of the line tree whose root is treeIndex
	if (l == queryL && r == queryR) {
		return tree[treeIndex];
	}
	
	int leftTreeIndex = leftChild(treeIndex);
	int rightTreeIndex = rightChild(treeIndex);
	int mid = l + (r - l) / 2; // 3 
	
	if (queryR <= mid) {
		// The first case: the left subtree interval of the line segment tree with treeIndex as the root contains the search interval
		return query(leftTreeIndex, l, mid, queryL, queryR);
	} else if (queryL >= mid + 1) {
		// The second case: the right subtree interval of the line segment tree with treeIndex as the root contains the search interval
		return query(rightTreeIndex, mid + 1, r, queryL, queryR);
	} else {
		// The third case: the search interval is contained in the left and right subtree intervals of the line segment tree with treeIndex as the root
		E leftResult = query(leftTreeIndex, l, mid, queryL, mid);
		E rightResult = query(rightTreeIndex, mid + 1, r, mid + 1, queryR);
		return this.merger.merge(leftResult, rightResult);
	}
}

3. Single update operation of line tree

Update the values of index index index in the segment tree, and maintain the values of all its parent nodes

/**
 * Update the value of index position to e
 * 
 * @param index
 * @param e
 */
public void set(int index, E e) {
	if (index < 0 || index >= data.length) {
		throw new IllegalArgumentException("Index is Illegal.");
	}
	data[index] = e;
	set(0, 0, data.length - 1, index, e);
}

/**
 * Update the value of index to e in the segment tree with treeIndex as root
 * 
 * @param treeIndex
 * @param l
 * @param r
 * @param index
 * @param e
 */
private void set(int treeIndex, int l, int r, int index, E e) {
	// Conditions for recursive termination: the left end of the search interval is equal to the right end of the search interval. Find the position of the index index in the segment tree
	if (l == r) {
		tree[treeIndex] = e;
		return;
	}
	
	int leftTreeIndex = leftChild(treeIndex);
	int rightTreeIndex = rightChild(treeIndex);
	int mid = l + (r - l) / 2;
	
	if (index <= mid) {
		// The left subtree interval of a line segment tree with treeIndex as its root contains index
		set(leftTreeIndex, l, mid, index, e);
	} else { // index >= mid + 1
		// The right subtree interval of a line segment tree with treeIndex as its root contains index
		set(rightTreeIndex, mid + 1, r, index, e);
	}
	
	// Reassign merge to the parent node of the modified element node
	tree[treeIndex] = merger.merge(tree[leftTreeIndex], tree[rightTreeIndex]);
}

(3) Time complexity analysis

function Time complexity Analysis
query(l, r) O(h) => O(logn) The user-defined line tree is a full binary tree, and the query operation only needs recursion at the height of the tree
set(index, e) O(h) => O(logn) The custom line tree is a full binary tree, and the update operation only needs to recurse at the height of the tree
Published 9 original articles, won praise 0, visited 301
Private letter follow

Posted by phdatabase on Thu, 12 Mar 2020 01:58:38 -0700