1. 程式人生 > >資料結構07——線段樹

資料結構07——線段樹

 

一.線段樹的定義

首先,線段樹它是一棵二叉樹,但它又不是一個完全二叉樹,卻是一個平衡二叉樹。它和二叉樹一樣,有一個一個的節點,但是對於線段樹而言,它的每一個節點表示的都是一個區間類相應的資訊。以求和為例,線段樹每個節點,儲存的就是一段區間的數字和。根節點儲存的就是整個區間的相應的數字和,之後從根節點將這個區間平均分成兩段,例如A[0...7],那麼它的左孩子就是A[0...3],右孩子就是A[4...7]。對於滿二叉樹而言,如果有h層,那麼其節點數為2^h-1個節點,最後一層(h-1),有2^(h-1)個節點。線段樹其實主要用於解決連續區間的動態高效查詢的問題,由於二叉結構的這樣一個特性,使用線段樹可以快速的查詢某一個節點在若干條線段中出現的次數,而且時間複雜度為O(logN)。

 

二.線段樹具體的實現

 

線段樹

 

1.線段樹的基礎表示

package com.zfy.segmenttree;

public class SegmentTree<E> {

	private E[] tree;//宣告一個樹陣列
	private E[] data;//宣告一個數組
	
	public SegmentTree(E[] arr) {
		data = (E[])new Object[arr.length];
		for (int i = 0; i < arr.length; i++) {
			data[i] = arr[i];
		}
		tree = (E[])new Object[4 * arr.length];
	}
	
	//獲取個數
    public int getSize(){
        return data.length;
    }

    //按照index獲取元素
    public E get(int index){
        if(index < 0 || index >= data.length)
            throw new IllegalArgumentException("Index is illegal.");
        return data[index];
    }
    
    //返回完全二叉樹的陣列表示中,一個索引所表示的元素的左孩子節點的索引
    private int leftChild(int index){
        return 2*index + 1;//是以陣列以0為開始的計算
    }

    //返回完全二叉樹的陣列表示中,一個索引所表示的元素的右孩子節點的索引
    private int rightChild(int index){
        return 2*index + 2;
    }

    
}

2.線段樹的建立

package com.zfy.segmenttree;

/*
 * 計算treeIndex的介面
 * */
public interface Merger<E> {
	
	E merge(E a, E b);

}

 

        private Merger<E> merger;
	
	public SegmentTree(E[] arr, Merger<E> merger) {
		
		this.merger = merger;
		
		data = (E[])new Object[arr.length];
		for (int i = 0; i < arr.length; i++) {
			data[i] = arr[i];
		}
		tree = (E[])new Object[4 * arr.length];
		buildSegmentTree(0, 0, data.length - 1);
	}

	//在treeIndex的位置建立表示區間[l...r]的線段樹
	private void buildSegmentTree(int treeIndex, int l, int r) {
		
		//l==r,就是這個裡面只有一個節點
		if (l == r) {
			tree[treeIndex] = data[r];
			return;
		}
		
		int leftTreeIndex = leftChild(treeIndex);
		int rightTreeIndex = rightChild(treeIndex);

		int mid = l + (r - l) / 2;//區間邊界:左邊界加上左右邊界它們的距離除以2,得到的位置就是中間的位置
		buildSegmentTree(leftTreeIndex, l, mid);
		buildSegmentTree(rightTreeIndex, mid + 1, r);

		tree[treeIndex] = merger.merge(tree[leftTreeIndex], tree[rightTreeIndex]);//計算treeIndex
		
	}

3.線段樹的查詢

	//線段樹查詢方法,返回區間[queryL, queryR]的值
	public E query(int queryL, int queryR) {

		if (queryL < 0 || queryL >= data.length || queryR < 0 || queryR >= data.length || queryL > queryR)
			throw new IllegalArgumentException("Index is illegal.");

		return query(0, 0, data.length - 1, queryL, queryR);
	}

	//在以treeIndex為根的線段樹中[l...r]的範圍裡,搜尋區間[queryL...queryR]的值
	private E query(int treeIndex, int l, int r, int queryL, int queryR) {

		if (l == queryL && r == queryR)
			return tree[treeIndex];

		int mid = l + (r - l) / 2;

		int leftTreeIndex = leftChild(treeIndex);
		int rightTreeIndex = rightChild(treeIndex);

		//如果queryL > mid,則查詢其右孩子。反之則為左孩子
		if (queryL > mid + 1) {
			return query(rightTreeIndex, mid + 1, r, queryL, queryR);
		} else if (queryR <= mid) {
			return query(leftTreeIndex, l, mid, queryL, queryR);
		}

		E leftResult = query(leftTreeIndex, l, mid, queryL, mid);
		E rightResult = query(rightTreeIndex, mid + 1, r, mid + 1, queryR);
		return merger.merge(leftResult, rightResult);

	}

4.線段樹的更新操作

	//將index位置的值,更新為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);

	}

	//在以treeIndex為根的線段樹中更新index的值為e
	private void set(int treeIndex, int l, int r, int index, E e) {

		if (l == r) {
			tree[treeIndex] = e;
			return;
		}
		int mid = l + (r - l) / 2;
		//treeIndex的節點分為[l...mid]和[mid+1...r]兩部分

		int leftTreeIndex = leftChild(treeIndex);
		int rightTreeIndex = rightChild(treeIndex);
		if(index >= mid + 1)
            set(rightTreeIndex, mid + 1, r, index, e);
        else // index <= mid
            set(leftTreeIndex, l, mid, index, e);

        tree[treeIndex] = merger.merge(tree[leftTreeIndex], tree[rightTreeIndex]);
	}

 

5.完整程式碼

package com.zfy.segmenttree;

import com.zfy.segmenttree.Merger;

public class SegmentTree<E> {

	private E[] tree;//宣告一個樹陣列
	private E[] data;//宣告一個數組
	private Merger<E> merger;
	
	public SegmentTree(E[] arr, Merger<E> merger) {
		
		this.merger = merger;
		
		data = (E[])new Object[arr.length];
		for (int i = 0; i < arr.length; i++) {
			data[i] = arr[i];
		}
		tree = (E[])new Object[4 * arr.length];
		buildSegmentTree(0, 0, data.length - 1);
	}

	//在treeIndex的位置建立表示區間[l...r]的線段樹
	private void buildSegmentTree(int treeIndex, int l, int r) {
		
		//l==r,就是這個裡面只有一個節點
		if (l == r) {
			tree[treeIndex] = data[r];
			return;
		}
		
		int leftTreeIndex = leftChild(treeIndex);
		int rightTreeIndex = rightChild(treeIndex);

		int mid = l + (r - l) / 2;//區間邊界:左邊界加上左右邊界它們的距離除以2,得到的位置就是中間的位置
		buildSegmentTree(leftTreeIndex, l, mid);
		buildSegmentTree(rightTreeIndex, mid + 1, r);

		tree[treeIndex] = merger.merge(tree[leftTreeIndex], tree[rightTreeIndex]);//計算treeIndex
		
	}

	//獲取個數
    public int getSize(){
        return data.length;
    }

    //按照index獲取元素
    public E get(int index){
        if(index < 0 || index >= data.length)
            throw new IllegalArgumentException("Index is illegal.");
        return data[index];
    }
    
    //返回完全二叉樹的陣列表示中,一個索引所表示的元素的左孩子節點的索引
    private int leftChild(int index){
        return 2*index + 1;//是以陣列以0為開始的計算
    }

    //返回完全二叉樹的陣列表示中,一個索引所表示的元素的右孩子節點的索引
    private int rightChild(int index){
        return 2*index + 2;
    }

	//線段樹查詢方法,返回區間[queryL, queryR]的值
	public E query(int queryL, int queryR) {

		if (queryL < 0 || queryL >= data.length || queryR < 0 || queryR >= data.length || queryL > queryR)
			throw new IllegalArgumentException("Index is illegal.");

		return query(0, 0, data.length - 1, queryL, queryR);
	}

	//在以treeIndex為根的線段樹中[l...r]的範圍裡,搜尋區間[queryL...queryR]的值
	private E query(int treeIndex, int l, int r, int queryL, int queryR) {

		if (l == queryL && r == queryR)
			return tree[treeIndex];

		int mid = l + (r - l) / 2;

		int leftTreeIndex = leftChild(treeIndex);
		int rightTreeIndex = rightChild(treeIndex);

		//如果queryL > mid,則查詢其右孩子。反之則為左孩子
		if (queryL > mid + 1) {
			return query(rightTreeIndex, mid + 1, r, queryL, queryR);
		} else if (queryR <= mid) {
			return query(leftTreeIndex, l, mid, queryL, queryR);
		}

		E leftResult = query(leftTreeIndex, l, mid, queryL, mid);
		E rightResult = query(rightTreeIndex, mid + 1, r, mid + 1, queryR);
		return merger.merge(leftResult, rightResult);

	}
	
	//將index位置的值,更新為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);

	}

	//在以treeIndex為根的線段樹中更新index的值為e
	private void set(int treeIndex, int l, int r, int index, E e) {

		if (l == r) {
			tree[treeIndex] = e;
			return;
		}
		int mid = l + (r - l) / 2;
		//treeIndex的節點分為[l...mid]和[mid+1...r]兩部分

		int leftTreeIndex = leftChild(treeIndex);
		int rightTreeIndex = rightChild(treeIndex);
		if(index >= mid + 1)
            set(rightTreeIndex, mid + 1, r, index, e);
        else // index <= mid
            set(leftTreeIndex, l, mid, index, e);

        tree[treeIndex] = merger.merge(tree[leftTreeIndex], tree[rightTreeIndex]);
	}

	@Override
	public String toString() {
		StringBuilder res = new StringBuilder();
		res.append('[');
		for (int i = 0; i < tree.length; i++) {
			if (tree[i] != null)
				res.append(tree[i]);
			else
				res.append("null");

			if (i != tree.length - 1)
				res.append(", ");
		}
		res.append(']');
		return res.toString();
	}

    
}

 

結束語:工欲善其事,必先利其器。而基礎就是我們的利器,合抱之木,生於毫末;九層之臺,起於累土;千里之行,始於足下。對於知識的學習,我還是認為要學好基礎。

參考:bobobo老師的玩轉資料結構

版權宣告:尊重博主原創文章,轉載請註明出處 https://blog.csdn.net/zfy163520