Árbol de segmento de línea de estructura de datos

Árbol de línea

1. Concepto (construcción)

El árbol de segmento de línea es un árbol binario completo basado en la idea de dicotomía. Se utiliza para mantener un dato o una matriz y mantener la complejidad de tiempo de todas las operaciones tan bajo como O (nlogn).
Para los nodos sin datos organizados, el valor predeterminado es 0 y el valor almacenado en cada nodo es el resultado de algunas operaciones realizadas por los elementos secundarios izquierdo y derecho (excepto para los nodos hoja).
Inserte la descripción de la imagen aquí

1. Implementación del código

De hecho, se puede implementar directamente como una matriz, pero para almacenar los valores de los intervalos izquierdo y derecho de la matriz original, se utiliza una estructura para implementar un árbol de segmento de línea.

#include<iostream>
using namespace std;

const int N = 1e4 +5;
int arr[N];

struct segmentTree{
    
    
	int l,r;
	int val;
}tree[4*N]; 

void build(int p,int l,int r) {
    
    
	tree[p].l = l;
	tree[p].r = r;
	//终止条件
	if(l==r) {
    
    
		tree[p].val = arr[l];
	} 
	else {
    
    
		//本质上是递归的过程
		int mid = (l+r)/2;
		build(2*p+1,l,mid);
		build(2*p+2,mid+1,r);
		tree[p].val = tree[2*p+1].val + tree[2*p+2].val;
	}
}

int main( ) {
    
    
	for(int i=0;i<=5;i++) {
    
    
		cin>>arr[i];
	}
	build(0,0,5);
	for(int i=0;i<15;i++) {
    
    
		printf("tree[%d,%d]=%d\n",tree[i].l,tree[i].r,tree[i].val);
	}
	return 0;
}

2. Ejecución de resultados

El árbol es seguido por el valor del intervalo, algunos nodos no ingresados ​​se inicializan a 0
Inserte la descripción de la imagen aquí

2. Operación básica

1. Consulta (punto único o intervalo)

Consulte las diferentes situaciones en las que el intervalo y el intervalo de nodo del árbol del segmento de línea se cruzan:

  1. El intervalo de consulta contiene completamente el intervalo de nodo de árbol de segmento de línea ------------ devuelve directamente el árbol de nodo [p] .sum
  2. El intervalo de consulta y el elemento secundario izquierdo del nodo del árbol del segmento de línea tienen una intersección --------- consulta recursiva (2 * p + 1, L, R)
  3. El intervalo de consulta y el hijo derecho del nodo del árbol del segmento de línea tienen una intersección --------- consulta recursiva (2 * p + 2, L, R)

La consulta de un solo punto es un caso especial de consulta de intervalo, en este momento L = R, solo modifique ligeramente el siguiente código

int query(int p,int L,int R) {
    
    
	int sum = 0;
	//终止条件
	if(tree[p].l>R||tree[p].r<L)
		sum = 0;//不在范围内直接返回
	else if(tree[p].l>=L&&R>=tree[p].r)
		sum += tree[p].val;//包含其中直接返回这个节点的值
	else {
    
    	
	    //以上两种情况都不包含则是有交集的情况,对左右孩子都递归,不符合会直接返回
		int mid  = (L+R)/2;
		sum += query(2*p+1,L,R);
		sum += query(2*p+2,L,R);
	}
	return sum;
}

Caso de prueba
Inserte la descripción de la imagen aquí

2. Modificación de un solo punto

Modificar un solo punto es modificar una consulta de un solo punto Una vez que el valor del nodo modificado se encuentra en la consulta, se actualiza el valor de cada nodo ancestro.

//单点修改 
void update(int p,int ind,int val) {
    
    
	if(ind < tree[p].l||ind > tree[p].r ) 
		return;
	else if(tree[p].r == tree[p].l) {
    
    
		tree[p].val = val;
	}
	else {
    
    
		update(2*p+1,ind,val);
		update(2*p+2,ind,val);
		//这一步很重要,一定不能少
		tree[p].val = tree[2*p+1].val + tree[2*p+2].val;
	}
}

Inserte la descripción de la imagen aquí

3. Modificación de intervalo (con pushdown ())

La modificación del intervalo significa que se debe modificar el valor de un intervalo determinado. Puede ser la operación de sumar uno a todos los valores de un intervalo determinado. Si modificas cada nodo ascendente y descendiente participante, se agotará el tiempo. Cambie el ancestro y modifique los nodos secundarios cuando sea necesario. Utilice una marca perezosa (marca de retardo) y luego utilice la marca para operar cuando la necesite.

//区间修改,此处是将[L,R]所有元素全部加一 
void change(int p,int L,int R) {
    
    
	if(R < tree[p].l||L > tree[p].r ) 
		return;
	if(L <= tree[p].l && tree[p].r <= R) {
    
    
		//完全包含其中,直接改变标记并处理然后返回即可 
		tree[p].val += (tree[p].r - tree[p].l +1);
		tree[p].lazy += 1;
		return;
	}
	//处理延迟标记,因为要用到左右儿子节点了 
	pushdown(p);
	int mid = (tree[p].l + tree[p].r)/2;
	if(L <= mid)	change(2*p+1,L,R);
	if(R >  mid) 	change(2*p+2,L,R);
	tree[p].val = tree[2*p+1].val + tree[2*p+2].val;
}

A continuación, introduciremos la función pushdown (). La función de esta función es procesar la marca de retardo. Para el nodo marcado, es decir, lazy no es 0, la marca se pasa a los hijos izquierdo y derecho, y luego se procesa el contenido de los hijos izquierdo y derecho, y finalmente este La bandera se borra porque se ha procesado.

void pushdown(int p) {
    
    
	if(tree[p].lazy != 0){
    
    
		tree[2*p+1].lazy += tree[p].lazy;
		tree[2*p+2].lazy += tree[p].lazy;
		int mid = (tree[p].l + tree[p].r)/2;
		tree[2*p+1].val += mid - tree[p].l + 1;
		tree[2*p+2].val += tree[p].r - mid;
		tree[p].lazy = 0;//父节点的标记清零 
	}
}

Finalmente, debido a que la modificación del intervalo usa la marca diferida, la consulta en este momento ya no es la consulta anterior. También es la función de consulta que necesita la función pushdown (), pero la función no ha cambiado en general.

//查询 
int _query(int p,int L,int R) {
    
    
	//终止条件
	if(tree[p].l>R||tree[p].r<L)
		return 0;
		
	int sum = 0;
	pushdown(p);
	if(tree[p].l>=L&&R>=tree[p].r) {
    
    
		sum += tree[p].val;
	} 		
	else {
    
    	
		int mid  = (L+R)/2;
		sum += _query(2*p+1,L,R);
		sum += _query(2*p+2,L,R);
	}
	return sum;
}

3. Árbol de segmento de línea especial

También hay dos árboles de segmento de línea de multiplicación más especiales y árboles de segmento de línea de número raíz, que se agregarán más adelante. . .

Supongo que te gusta

Origin blog.csdn.net/weixin_45203704/article/details/107256918
Recomendado
Clasificación