Estructura de datos de C ++ árbol de clasificación binaria / árbol de búsqueda binaria


Prefacio

Cuando estaba aprendiendo sobre árboles rojo-negro hoy, vi un ejemplo de un árbol de ordenamiento binario en la clase de introducción. Pensé que no había escrito antes el código de un árbol de ordenamiento binario, así que lo escribí esta vez. Aquí está la naturaleza del árbol de ordenación binaria y Comparta los problemas que ocurrieron al escribir el código.

concepto

Introducción

Antes de escribir, primero debemos entender qué es un árbol de ordenamiento binario

Árbol de búsqueda binaria, tiene un nodo raíz y cada nodo solo puede tener dos nodos secundarios como máximo, el valor del nodo secundario izquierdo es menor que su nodo principal y el valor del nodo secundario derecho es mayor que su nodo principal .

Insertamos los datos uno por uno: 43,98,2,4,0,5 La
siguiente figura muestra cómo se ve el árbol de búsqueda binaria una vez completada la inserción

Inserte la descripción de la imagen aquí
La manipulación de datos no es más que agregar, eliminar, modificar y verificar.
Para un árbol de ordenamiento binario,
agregar es un nodo de inserción,
eliminar es un nodo de eliminación y
verificar si el valor de consulta x existe en el árbol.

Reflexiones sobre la existencia y el significado del árbol de clasificación binaria

En este momento, alguien puede preguntar: "¿No es bueno usar matrices para buscar? Un bucle for puede hacerlo".
Aquí analizamos una ola de complejidad de tiempo
para que el bucle atraviese la matriz, el peor de los casos es atravesar el último elemento, la complejidad de tiempo es O (n)
y el método de búsqueda del árbol de búsqueda binaria es similar a la búsqueda binaria, siempre que como la altura del árbol de búsqueda, todos sabemos que la complejidad temporal de encontrar la altura del árbol es O (logn)

En este momento, alguien puede volver a preguntar: "Dado que la complejidad del tiempo es todo O (logn),
¿está bien que encuentre la búsqueda binaria directamente? Es muy problemático construir un árbol". Buena pregunta, pero pensemos sobre esto de nuevo, búsqueda binaria Antes, primero necesitamos ordenar la matriz. Si usamos la combinación de clasificación más rápida o la clasificación rápida , la complejidad de tiempo es O (nlogn) , más la complejidad de tiempo de la búsqueda binaria O (logn) (no hemos aún no hemos hablado de ello ) La complejidad temporal de insertar datos en la matriz y el problema que puede requerir expansión) , es decir, en el peor de los casos , el tiempo de la primera consulta es más del doble que el del árbol de ordenamiento binario .

En este momento hay lectores que pueden llamarme la salida fácil, "Dices que quieres ordenar el tiempo, ¿no haces logros del tiempo?"
Insulto bien, se necesita tiempo y espacio complejidad de los logros que dejamos solos, hablamos sobre el aumento de esta operación.
Para una matriz ordenada , inserte una parte de los datos en ella sin volver a ordenarla, simplemente recorra una vez, y la complejidad del tiempo es O (n);
pero para un árbol de ordenación binaria , inserte una nueva información , porque necesita ser recorrida. antes de la inserción La longitud más larga es la altura del árbol, por lo que la complejidad del tiempo es O (logn) El efecto de insertar un dato puede no ser obvio, pero ¿insertar 10, 10,000 o 100 millones? Obviamente, desde una perspectiva a largo plazo, el tiempo dedicado a un árbol de ordenación binaria es significativamente mejor que el uso de la búsqueda binaria.

Habiendo dicho tanto, todavía no he hablado de productos secos, ¡vayamos al tema!

Visualización de operación y código

Insertar elemento

De acuerdo con las características del árbol de ordenamiento binario, podemos pensar fácilmente en la idea de insertar datos. Si el nodo insertado es más pequeño que el nodo de comparación y la rama izquierda del nodo de comparación está vacía, se insertará en la rama izquierda del nodo de comparación. Si no está vacío, realice la misma operación de inserción (recursión) en la rama izquierda del nodo de comparación; si el nodo de inserción es más grande que el nodo de comparación, la operación es la misma, solo cambia la dirección.
Un pequeño análisis del proceso, sabemos que necesitamos usar la recursividad, el código es el siguiente

void BinSearchTree::insert(int x)
{
    
    
	/*
	这个重载是为了插入方便
	只要insert一个值就可以了
	不用自己再新建节点
	*/
	Node *root=new Node(x);
	if(this->Root==NULL)
	{
    
    
		this->Root=root;
		return;
	}
	Node *p=this->Root;
	insert(p,root);
}

void BinSearchTree::insert(Node *p,Node *root)
{
    
    
	/*
	可不可以有相同的元素要根据需求来
	如果不要相同元素,最好在用户插入的时候说明
	如果有相同,插左边还是右边都可以,但定下左右就不能改了
	这里我写的时候就简单一点,遇到相同的就不插了
	*/

	//学了点算法,感觉写起来没以前难了(滑稽)
	if(root->data==p->data) return;//遇到相同,直接不插入
	if(root->data < p->data)
	{
    
    
		if(p->lChild==NULL)
		{
    
    
			p->lChild=root;
			return;
		}
		else insert(p->lChild,root);
	}
	else//root->data > p->data
	{
    
    
		if(p->rChild==NULL)
		{
    
    
			p->rChild=root;
			return;
		}
		else insert(p->rChild,root);
	}
}

¿Puedes tener los mismos elementos según tus necesidades?
Si no quieres los mismos elementos, es mejor explicar
cuando el usuario los inserta. Si son iguales, puedes insertarlos a la izquierda oa la derecha, pero tú no puede cambiarlos si establece la izquierda y la derecha
., No conecte el mismo

Encontrar elemento

Aquí he escrito dos.
Uno es encontrar si el elemento está en el árbol de búsqueda y el
otro es devolver el nodo
que tiene el valor. Debido a que mi árbol está configurado para no tener elementos idénticos, no hay problema de encontrar múltiples elementos.

Encuentre si el elemento está en el árbol de búsqueda

bool BinSearchTree::search(int x)
{
    
    
	if(x==this->Root->data) return true;
	Node *p=this->Root;
	return search(p,x);
}

bool BinSearchTree::search(Node *p,int x)
{
    
    
	if(p==NULL) return false;//找到空的地方都没有,说明真没有
	if(p->data==x) return true;
	if(x<p->data) search(p->lChild,x);
	else search(p->rChild,x);
}

Devuelve el nodo que posee el valor.

Node * BinSearchTree::searchNode(int x)
{
    
    
	if(this->Root->data==x) return this->Root;
	Node *p=this->Root;
	return searchNode(p,x);
}

Node * BinSearchTree::searchNode(Node *p,int x)
{
    
    
	if(p->data==x) return p;
	if(x<p->data) return searchNode(p->lChild,x);
	else return searchNode(p->rChild,x);
}

Eliminar elemento

Hay tres casos de
eliminación: 1. El nodo
eliminado tiene elementos secundarios izquierdo y derecho 2. El nodo eliminado solo tiene un elemento secundario
3. El nodo eliminado no tiene elementos secundarios

Para el
caso 1. Reemplace el nodo más pequeño en el hijo derecho del nodo que se eliminará con el valor del nodo que se eliminará.
Esto convierte el problema en el Caso 2 o el Caso 3
Caso 2. El nodo principal del nodo eliminado apunta a el nodo secundario del nodo eliminado, y luego elimine el nodo
. 3. Elimine el nodo directamente

ps: ¿Por qué deberíamos tomar el valor mínimo del subárbol derecho en el primer caso? Esto es realmente muy inteligente.
Primero, el subárbol derecho del nodo que se eliminará debe ser más grande que el subárbol izquierdo del nodo que se eliminará. En
segundo lugar, como el número mínimo de subárboles del subárbol derecho del nodo que se eliminará, es mayor que todos los nodos del subárbol derecho del nodo que se va a eliminar. Pequeño,
por lo que es mejor reemplazar el nodo que se va a eliminar

void BinSearchTree::removeNode(int x)
{
    
    
	/*
	删除节点一共有三种情况
	1.删除的节点左右孩子都有
	2.删除的节点只有一个孩子
	3.删除的节点没有孩子

	对于
	1.将要删除节点的右孩子中的最小节点与要删除节点的值替换
	这样就将该问题变成了情况2 或者 情况3
	2.被删除节点的父节点指向被删除节点的子节点,然后删除该节点
	3.直接删除该节点

	ps:为什么第一种情况要取右子树的最小值?这其实十分巧妙
	首先,作为待删节点的右子树,肯定比待删节点的左子树大
	其次,作为待删节点右子树的最小子树数,比待删节点所有的右子树节点小
	所以其代替待删节点最好
	*/
	Node *p=searchNode(x);//找到待删除节点
	if(p->lChild==NULL && p->rChild==NULL) withNoChild(p);
	else if(p->lChild!=NULL && p->rChild!=NULL) withTwoChild(p);
	else withOneChild(p);
	return;
}
void BinSearchTree::withTwoChild(Node *p)//因为withOneChile有毛病,所以它也有毛病<---withOneChild好像没毛病
{
    
    //应该是它本身的毛病
	//情况1,将要删除节点的右孩子中的最小节点与要删除节点的值替换
	//这样就将该问题变成了情况2 或者 情况3
	Node *min=getMinNode(p->rChild);
	std::cout<<"右子树最小值为:";
	std::cout<<min->data<<std::endl;

	int minVal=min->data;

	if(min->lChild==NULL && min->rChild==NULL) withNoChild(min);
	else withOneChild(min);//<======我知道为什么会出问题了,当改了待删节点的数值后,就会出现两个min值
	//找父节点就会起冲突
	//所以应该先删在改

	p->data=minVal;//<-----要先删再改,不然会出现问题!!!
}

void BinSearchTree::withOneChild(Node *p)//有毛病<---貌似也没有毛病
{
    
    
	Node *father=getFather(p);
	Node *child;
	if(p->lChild==NULL) child=p->rChild;
	else child=p->lChild;

	//如果待删节点是其父节点的左孩子
	if(father->lChild->data==p->data) father->lChild=child;
	//如果待删节点是其父节点的右孩子
	else father->rChild=child;
	delete p;
}

void BinSearchTree::withNoChild(Node *p)//没毛病
{
    
    
	Node *father=getFather(p);
	std::cout<<father->data<<std::endl;
	if(father->lChild->data==p->data) father->lChild=NULL;
	else father->rChild=NULL;
	delete p;
}

resultado de la operación

Aquí ingresamos los números 43,98,2,4,0,5 en el ejemplo para construir un árbol de ordenamiento binario. El
Inserte la descripción de la imagen aquí
resultado primero en amplitud se ajusta a la estructura teórica.
Inserte la descripción de la imagen aquí
Aquí eliminamos el nodo 2 y el
Inserte la descripción de la imagen aquí
proceso de eliminación es como se
Inserte la descripción de la imagen aquí
Inserte la descripción de la imagen aquí
Inserte la descripción de la imagen aquí
muestra en la figura anterior. Después de eliminar el árbol, realice un recorrido jerárquico., El resultado es igual al resultado teórico, que puede confirmar aproximadamente que mi código debería ser correcto.

Las ventajas y desventajas de los árboles de ordenación binaria y sus soluciones

ventaja

Ventajas Estoy muy involucrado en la columna del significado del árbol de ordenamiento binario, así que no lo repetiré aquí.

Desventaja

Antes de hablar sobre las deficiencias, echemos un vistazo a la siguiente imagen
Inserte la descripción de la imagen aquí
¿Ha encontrado algún problema? Así es, esto también es un tipo binario , solo un lisiado. Cuando los datos insertados aumentan o disminuyen, el árbol de clasificación binaria se convierte en una lista vinculada y la complejidad del tiempo de la búsqueda se degrada a O (n), que no es el resultado que queremos.

solución

¿Cómo resolver este problema? Esto involucra estructuras de datos avanzadas árbol rojo-negro arriba

Alguien podría preguntar: “¿Por qué no hablaste de eso?” En realidad, todavía no he leído el árbol rojo-negro, así que compartámoslo cuando termine de estudiar, jeje.

-------------------------------------------------- ------ 6.17 Actualización ------------------------------------------ --------------------
He regresado de mis estudios, aquí está el compartir del árbol rojo-negro

Supongo que te gusta

Origin blog.csdn.net/weixin_44062380/article/details/106779951
Recomendado
Clasificación