martes, 1 de octubre de 2019

Unidad 2

Notación polaca

La notación polaca, también conocida como notación de prefijo o notación prefija, es una forma de notación para la lógica, la aritmética, el álgebra y la computación. Su característica distintiva es que coloca los operadores a la izquierda de sus operandos. Si la aridad de los operadores es fija, el resultado es una sintaxis que carece de paréntesis u otros signos de agrupación, y todavía puede ser analizada sin ambigüedad. El lógico polaco Jan Łukasiewicz inventó esta notación alrededor de 1920 para simplificar la lógica proposicional.
La notaciones de prefijo (o polaca, en homenaje a Jan Łukasiewicz), de infijo y de postfijo (o polaca inversa) son formas de escritura de expresiones algebraicas que se diferencian por la posición relativa que toman los operadores y los operandos. En la notación de prefijo, el operador se escribe delante de los operandos (+ 3 4), entre los operandos en la notación de infijo (3 + 4) y tras los operandos en la de posfijo (3 4 +).
La notación de prefijo fue propuesta en 1924 por el matemático, lógico y filósofo polaco Jan Łukasiewicz (1878-1956), de allí el nombre alternativo por la que se conoce.
Al igual que la de postfijo, la notación polaca permite prescindir de los paréntesis en el caso de operadores de paridad fija conocida. Por ejemplo, la operación 5 * (12 + 4).puede escribirse en prefijo como: * 5 (+ 12 4); o sencillamente: * 5 + 12 4 (y como 5 12 4 + *en postfijo).
Łukasiewicz introdujo esta notación con la intención de simplificar la lógica proposicional. El matemático y lógico Alonzo Church la mencionaba en su libro clásico Introduction to Mathematical Logic (1956) como una notación digna de observación. Aunque dejó pronto de utilizarse en lógica, encontró su lugar en las ciencias de la computación. Por ejemplo, el lenguaje de programación LISP basa precisamente su sintaxis en la notación polaca.
Las calculadoras Hewlett-Packard usan la notación polaca inversa, económica en número de entradas, pero que requiere un esfuerzo adicional para la interpretación del resultado. Esta empresa utilizó este sistema por primera vez en 1968, en la calculadora de sobremesa HP-9100A. Y fue también ésta la notación de la primera calculadora científica de bolsillo, la HP-35, usada entre 1972 y 1975.

código p


El código P comenzó como un código ensamblador objetivo estándar producido por varios compiladores Pascal en la década de 1970 y principios de la de 1980. Fue diseñado para código real para una maquina de pila hipotética la idea era hacer que los compiladores de Pascal se transportaran fácilmente requiriendo solo que se volviera a escribir el interprete de la maquina P para una plataforma, el código P también a probado ser útil como código intermedio y sean utilizado varias extensiones y modificaciones del mismo en diverso compiladores de código nativo,
La mayor parte para lenguaje tipo Pascal.
*Como el código P fue diseñado para ser directamente ejecutable, contiene una descripción implícita de un ambiente de ejecución particular que incluye tamaños de datos, además de mucha información especifica para la maquina P, que debe conocer si se desea que un programa de código P se comprensible. La maquina P esta compuesta por una memoria de código, una memoria de datos no específica para variables nombre das y una pila para datos temporales, junto como cualquiera registro que sea necesario para mantener la pila y apoyar la ejecución.
        COMPARACIÓN
*El código P en muchos aspectos está más código de maquina real que al código de tres direcciones. Las instrucciones en código P también requiere menos de tres direcciones: tosas las instrucciones que hemos visto son instrucciones de "una dirección" o "cero direcciones". Por otra parte, el código P es menos compacto que el código de tres direcciones en términos de números de instrucciones, y el código P no esta "auto contenido" en el sentido que las instrucciones funciones implícitas en una pila (y las localidades de pila implícitas son de hecho las direcciones "perdidas"). La ventaja respecto a la pila es que contiene los valores temporales necesarios en cada punto del código, y el compilador no necesita asignar nombre a ninguno de ellos, como el código de tres direcciones.

Triplos


En la historia de los compiladores han sido utilizadas una amplia variedad de representaciones intermedias como lo es la siguiente clase de representación de código intermedio de un árbol de 3 direcciones,2 para los operandos y una para la ubicación del resultado. esta clase incluye un amplio numero de representaciones diferentes entre las cuales encontramos cuadruplos y triples. la principal diferencia entre estas notaciones y la notación postfija es que ellos incluyen referencias explicitas para los resultados de los cálculos intermedios, mientras que la notación posfija los resultados son implícitos al representarlos en una pila.
  • La diferencia entre triples y cuadruplos es que con los triples es referenciado el valor intermedio hacia el numero del triple que lo creo, pero en los cuádruplos requiere que ellos tengan nombre implícitos.
  • Los triples tienen una ventaja obvia de ser mas consistente, pero ellos dependen de su posición, y hacen que la optimización presente cambios de código mucho mas compleja.
Para evitar tener que introducir nombres temporales en la tabla de símbolos, se hace referencia a un valor temporal según la posición de la proposición que lo calcula. Las propias instrucciones representan el valor del nombre temporal. La implementación se hace mediante registros de solo tres campos (op, arg1, arg2).
  • En la notación de tripletes se necesita menor espacio y el compilador no necesita generar los nombres temporales. Sin embargo, en esta notación, trasladar una proposición que defina un valor temporal exige que se modifiquen todas las referencias a esa proposición. Lo cual supone un inconveniente a la hora de optimizar el código, pues a menudo es necesario cambiar proposiciones de lugar.
  • Una forma de solucionar esto consiste en listar las posiciones a las tripletas en lugar de listar las tripletas mismas. De esta manera, un optimizador podría mover una instrucción reordenando la lista, sin tener que mover las tripletas en si.

Cuadruplos

Es una estructura tipo registro con cuatros campos que se llaman:
Operador
Operando1
Operando2
Resultado
Donde operando1, operando2 y resultado pueden ser constantes, identificadores y variables temporales definidos por el compilador mientras que operador representa una operación arbitraria.
Operador
Operando1
Operando2
Resultado
*
C
D
T1
+
B
T1
T2
=
T2
A
EJEMPLO:
A := B + C * D
Esquemas de generación.
¿Que son?
 Los esquemas de generación son las estrategias o acciones que deberán realizarse y tomarse en cuenta en el momento de generar código intermedio.
Declaración de variables y constantes.
Las declaraciones de variables y constantes deben separarse de tal manera que queden las expresiones una por una de manera simple.

• Por ejemplo int a,b,c;
se descompone a int a;
int b; intc; respectivamente.
Las variables utilizadas en los programas se clasifican en dos tipos:
variables locales y variables globales.

Esquema de generación


Una forma de hacer esto es mediante el llamado código de tres direcciones. Una sentencia en código de tres direcciones es: A := B op C, donde A, B y C son operandos y op es un operador binario. También permite condiciones simples y saltos. Por ejemplo, para la siguiente sentencia:
WHILE (A > B) AND (A < = 2 * B – 5) DO A : = A + B
el código intermedio generado ( código en tres direcciones) será:

L1 : IF A > B GOTO L2
GOTO L3
L2 : T1 : = 2 * B (*nivel más alto que ensamblador*)
T2 : = T1 – 5 (*pero más sencillo que Pascal*)
IF A < T2 GOTO L4
GOTO L3
L4 : A : = A + B
GOTO L1
L3 : . . . . . . .


Variables y constantes




Declaración de variables y constantes.
Las declaraciones de variables y constantes deben separarse de tal manera que queden las expresiones una por una de manera simple.

• Por ejemplo int a,b,c;
se descompone a int a;
int b; intc; respectivamente.
Las variables utilizadas en los programas se clasifican en dos tipos:
variables locales y variables globales.
Variables locales:
Aquella que está declarada para el programa o algoritmo completo.
 Para definir variables locales, la definición debe hacerse inmediatamente después de una llave de inicio ({), y la variable deja de existir fuera de la llave de fin(}) que corresponde a la llave de inicio después del cuál fue definida la variable.
Ejemplo:
{
int a,b;
a=5;
b=a + 100;
}
Variables globales:
Aquella que está declarada y definida dentro de una función y sólo es válida dentro de la misma función y no podrá utilizarse en otra parte del programa.
 Una variable global se declara fuera de cualquier función y primero que cualquier función que requiera de ella. Una variable se declara de la siguiente forma:
 tipo identificador1, identificador2..ident n;
Ejemplos:
 Algunos ejemplos de cómo definir variables son los siguientes:
 int alumnos, contador;
 float A,B,C;
 char Letra;
Declaracion de Constantes.
Para declarar constantes en el lenguaje C, sólo basta anteponer el calificador const seguido del tipo de dato que manejará esa constante (int,char,etc), y por último el valor que tomará esa variable.
Ejemplo:
const int k = 12

Expresiones


Es un equivalente algebraico para un autómata. Utilizado en muchos lugares como un lenguaje para describir patrones en texto que son sencillos pero muy útiles. Pueden definir exactamente los mismos lenguajes que los autómatas pueden describir: Lenguajes regulares.
Ofrecen algo que los autómatas no: Manera declarativa de expresar las cadenas que queremos aceptar.
Dado un alfabeto Σ, una , expresión regular sobre expresión regular sobre Σ se define de forma recursiva:
ER primitivas: Φ, λ, {a | a ЄЄЄ Σ Є}
Si α y β son ER, entonces son también ER: α + β (unión), α β (concatenación), α* (cierre), (α).
No existen otras reglas para la construcción de ER sobre Σ.
Ejemplos de usos.
Comandos de búsqueda, e.g., grep de UNIX.
sistema de formato de texto: Usan notación de tipo expresión regular para describir patrones.
Convierte la expresión regular a un DFA o un NFA y simula el autómata en el archivo de búsqueda.
Generadores de analizadores - Léxicos. Como Lex o Flex.
Los analizadores léxicos son parte de un compilador. Dividen el programa fuente en unidades lógicas (tokens) divide el programa fuente en unidades.
Produce un DFA que reconoce el token.
Las expresiones regulares denotan lenguajes.
Por ejemplo, la expresión regular: 01* + 10* denota todas las cadenas que son o un 0 seguido de cualquier cantidad 1's o un 1 seguida de cualquier cantidad de 0's.
Operaciones de los lenguajes:
Unión: Si L y M  son dos lenguajes, su unión se denota por L U M.
Concatenación: La concatenación es: LM o L.M.
Cerradura (o cerradura de Kleene): Si L es un lenguaje su cerradura se denota por L *.
Si E es una expresión regular, entonces L(E) denota el lenguaje que define E. Las expresiones se construyen de la manera siguiente:
Las contantes  y  son expresiones regulares que representan a los lenguajes L (Q) = {Q} y L (Φ) L = Φ respectivamente.
Si a es un símbolo, entonces es una expresión regular que representan al lenguaje: L (a) = {a}.

Instrucciones de asignación


Ahora que tenemos claro lo que es una variable veremos cómo almacenar información en ellas, para esto utilizamos la instrucción de asignación.


La instrucción de asignación se encarga de guardar un valor en una variable, para esto es importante tener en cuenta que el valor que se guarde debe ser del mismo tipo que se ha definido a la variable, es decir, si defino una variable de tipo entero no podré asignarle un decimal.

La instrucción de asignación tiene la siguiente sintaxis:

nombre variable receptora = valor que se le asigna;

Siempre la variable que recibe el valor va del lado izquierdo al “=”.

Cuando nos referimos a valor que se le asigna puede ser representado de varias formas, que describiremos a continuación:

Asignación de un valor fijo
Se asigna directamente un valor, por ejemplo:
edad = 18;
nombre = “juan”;
precio = 3.5;

Asignación del valor de una variable
Se asigna el valor que contiene otra variable
int edad, edad2; //definimos 2 variables de tipo entero
edad = 18; //inicializamos la variable edad con un valor fijo
edad2 = edad; //inicializamos la variable edad2 con el valor de la variable edad, es decir con 18

Asignación del valor del resultado de una operación
Se asigna el valor del resultado de una operación

Ejemplo con una variable de tipo entero
int edad, edad2; //definimos 2 variables de tipo entero
edad = 18 + 2; //inicializamos la variable edad con el valor resultante de la suma, edad = 20
edad2 = edad - 2; //inicializamos la variable edad2 con el valor de la operación resultante de la resta, edad2 = 18

Ejemplo con una variable de tipo cadena de caracteres
String cadena, cadena2; //definimos 2 variables de tipo cadena de caracteres
cadena = “Juan”;//se incializa con el valor fijo “juan”
cadena2 = cadena + “ “ + “Pérez”; //se inicializa con e valor resultante de la concatencación con “ “ y “Pérez”, queda como resultante “Juan Pérez”

Asignación del valor del resultado de una función
Se asigna el valor del resultado de una función, para esto es necesario que el tipo de dato que retorna la función sea del mismo tipo que el de la variable.

float area, lado; //definimos 2 variable de tipo decimal
lado = 3.5; //inicializamos el la variable lado con la longitud del lado de un cuadrado
area = calcularAreaCuadrado(lado); //invocamos a la función calcularAreaCuadrado que retorna una variable de tipo decimal con el resultado del cálculo del aréa de un cuadrado cuyo lado tiene el valor almacenado en la variable lado


Instrucciones de Control 



Como ya se ha mencionado, C es un ejemplo de programación estructurada. En este tipo de programación, es necesario contar con ciertas estructuras que permitan controlar el flujo del programa, es decir, tomar decisiones y repetir acciones.

En la gran mayoría de los programas será necesario tomar decisiones sobre qué acciones realizar. Esas decisiones pueden depender de los datos que introduzca el usuario, de si se ha producido algún error o de cualquier otra cosa.
La estructura condicional if ... else es la que nos permite tomar ese tipo de decisiones. Traducida literalmente del inglés, se la podría llamar la estructura "si...si no", es decir, "si se cumple la condición, haz esto, y sino, haz esto otro".
Un ejemplo sencillo sería el siguiente (no se trata de un programa completo, sino tan sólo una porción de código):
 if (edad < 18) 
  printf("No puedes acceder.\n");
 else
  printf("Bienvenido.\n");
Este código de ejemplo dice que si el valor de la variable edad es menor que 18 se imprimirá "No puedes acceder.\n", mientras que en caso contrario se imprimirá "Bienvenido.\n".
Como se ve en el ejemplo, la estructura de un condicional es bastante simple:
 if (condición) {
  sentencias_si_verdadero;
 } else {
  sentencias_si_falso;
 }


miércoles, 28 de agosto de 2019

RECORRIDO DE ARBOLES BINARIOS

Recorridos de árboles

Ahora que hemos examinado la funcionalidad básica de nuestra estructura de datos árbol, es hora de mirar algunos patrones de uso adicionales para los árboles. Estos patrones de uso se pueden dividir en las tres maneras en que tenemos acceso a los nodos del árbol. Hay tres patrones de uso común para visitar todos los nodos de un árbol. La diferencia entre estos patrones es el orden en que es visitado cada nodo. Llamamos a estas visitas de los nodos un “recorrido”. Los tres recorridos que vamos a ver se llaman preordeninorden y postorden. Comencemos definiendo estos tres recorridos con más cuidado, para luego mirar algunos ejemplos donde estos patrones son útiles.
preorden
En un recorrido en preorden, visitamos primero el nodo raíz, luego recursivamente realizamos un recorrido en preorden del subárbol izquierdo, seguido de un recorrido recursivo en preorden del subárbol derecho.
inorden
En un recorrido en inorden, realizamos recursivamente un recorrido en inorden en el subárbol izquierdo, visitamos el nodo raíz, y finalmente hacemos un recorrido recursivo en inorden del subárbol derecho.
postorden
En un recorrido en postorden, realizamos recursivamente recorridos en postorden del subárbol izquierdo y del subárbol derecho seguidos de una visita al nodo raíz.
Veamos algunos ejemplos que ilustran cada uno de estos tres tipos de recorridos. Primero veamos el recorrido en preorden. Como ejemplo de un árbol a recorrer, representaremos este libro como un árbol. El libro es la raíz del árbol, y cada capítulo es un hijo de la raíz. Cada sección dentro de un capítulo es un hijo del capítulo, y cada subsección es un hijo de su sección, y así sucesivamente. La Figura 5 muestra una versión limitada de un libro con sólo dos capítulos. Tenga en cuenta que el algoritmo de recorrido funciona para árboles con cualquier número de hijos, pero nos limitaremos a los árboles binarios por ahora.
image







Hay tres manera de recorrer un árbol: en inorden, preorden y postorden. Cada una de ellas tiene una secuencia distinta para analizar el árbol como se puede ver a continuación:
  1. INORDEN
    • Recorrer el subarbol izquierdo en inorden.
    • Examinar la raíz.
    • Recorrer el subarbol derecho en inorden.
  2. PREORDEN
    • Examinar la raíz.
    • Recorrer el subarbol izquierdo en preorden.
    • recorrer el subarbol derecho en preorden.
  3. POSTORDEN
    • Recorrer el subarbol izquierdo en postorden.
    • Recorrer el subarbol derecho en postorden.
    • Examinar la raíz.
A continuación se muestra un ejemplo de los diferentes recorridos en un árbol binario.
Recorrido de un arbol binario
  • Inorden: GDBHEIACJKF
  • Preorden: ABDGEHICFJK
  • Postorden: GDHIEBKJFCA


                                                                 EJERCICIO
ORDENAR

PRE-ORDEN

ALGORITMO:

RAIZ, IZQUIEDO, DERECHO

22,15,3,1,8,7,4,13,9,12,10,20,40,30,23,34,45,48,53,51

IN-ORDEN

IZQUIERDO-RAIZ-DERECHO
1,3,4,7,8,9,10,12,13,15,23,30,34,40,45,48,51,53

POST-ORDEN

IZQUIERDO-DERECHO-RAIZ
1,4,7,10,12,9,13,8,3,20,15,23,34,30,31,53,48,45,40,22



Pila semántica en un analizador sintáctico



El diseño ascendente se refiere a la identificación de aquellos procesos que necesitan computarizarse con forme vayan apareciendo, su análisis como sistema y su codificación, o bien, la adquisición de paquetes de software para satisfacer el problema inmediato.

Las pilas y colas son estructuras de datos que se utilizan generalmente para simplificar ciertas operaciones de programación. Estas estructuras pueden implementarse mediante arrays o listas enlazadas. Pila: colección de datos a los cuales se les puede acceder mediante un extremo, que se conoce generalmente como tope. Las pilas tienen dos operaciones básicas:

Push (para introducir un elemento)ØPop (para extraer un elemento)Sus características fundamentales es que al extraer se obtiene siempre el último elemento que acabe de insertarse. Por esta razón también se conoce como estructuras de datos LIFO, una posible implementación mediante listas enlazadas seria insertando y extrayendo siempre por el principio de la lista. Las pilas se utilizan en muchas aplicaciones que utilizamos con frecuencia. Las pilas y colas son estructuras de datos que se utilizan generalmente para simplificar ciertas operaciones de programación. Estas estructuras pueden implementarse mediante arrays o listas enlazadas .Un analizador sintáctico es un autómata de pila que reconoce la estructura de una cadena de componentes léxicos. En general, el analizador sintáctico inicializa el compilador y para cada símbolo de entrada llama al analizador morfológico y proporciona el siguiente símbolo de entrada.  Al decir pila semántica no se refiere a que hay varios tipos de pila, hace referencia a que se debe programar única y exclusivamente en un solo lenguaje, es decir, no podemos mezclar código de C++ con Visual Basic. Ventajas Los problemas de integración entre los subsistemas son sumamente costosos y muchos de ellos no se solucionan hasta que la programación alcanza la fecha límite para la integración total del sistema. Se necesita una memoria auxiliar que nos permita guardar los datos para poder hacer la comparación. Objetivo teórico  Es construir un árbol de análisis sintáctico, este raramente se construye como tal, sino que las rutinas semánticas integradas van generando el árbol de Sintaxis abstracta. Se especifica mediante una gramática libre de contexto. El análisis semántico detecta la validez semántica de las sentencias aceptadas por el analizador sintáctico. El analizador semántico suele trabajar simultáneamente al analizador sintáctico y en estrecha cooperación. Se entiende por semántica como el conjunto de reglas que especifican el significado de cualquier sentencia sintácticamente correcta y escrita en un determinado lenguaje. Las rutinas semánticas deben realizar la evaluación de los atributos de las gramáticas siguiendo las reglas semánticas asociadas a cada producción de la gramática. El análisis sintáctico es la fase en la que se trata de determinar el tipo de los resultados intermedios, comprobar que los argumentos que tiene un operador pertenecen al conjunto de los operadores posibles, y si son compatibles entre sí, etc.

En definitiva, comprobará que el significado de la que se va leyendo es válido. La salida teórica de la fase de análisis semántico sería un árbol semántico. Consiste en un árbol sintáctico en el que cada una de sus ramas ha adquirido el significado que debe tener.

Análisis Semántico
¿Qué es un análisis semántico?
Bueno se refiere a los aspectos del significado, sentido o interpretación de el mismo de un determinado elemento, símbolo, palabra, expresión o representación formal

Determina el tipo de resultados intermedios y que si los argumentos que tiene un operador pertenecen al conjunto de los operadores posibles.
Revisa si el significado de lo que se va leyendo es valido.
El resultado de la fase de análisis semántico viene siendo lo que se conoce como "árbol semántico"

Árbol De Expresiones o Árbol Semántico
Es una estructura jerárquica en la cual se registran las operaciones que realiza el programa fuente, en cada una de las ramas del árbol se registra el valor o significado que este debe tener y el análisis analiza cuál de los valores registrado en las ramas es aplicable






ACCIONES SEMÁNTICAS

Dependiendo del tipo de sentencias, las acciones semánticas pueden agruparse en: Sentencias de Declaración: Completar la sección de tipos de la Tabla de
Símbolos.
Sentencias “ejecutables”: Realizar comprobaciones de tipos entre los operados  implicados.
Funciones y procedimientos: Comprobar el número, orden y tipo de los parámetros actuales en cada llamada a una función o procedimiento.
Identificación de variables: Comprobar si un identificador ha sido declarado antes de utilizarlo.
Etiquetas: Comprobar si hay etiquetas repetidas y validación.
Constantes: Comprobar que no se utilicen en la parte izquierda de una asignación.
Conversiones y equivalencias de tipo: Verificación.
Sobrecarga de operadores y funciones: Detectar y solventar.

Comprobación de tipo en expresiones

E à literal         {E.tipo = char}
E à num           {E.tipo = entero}
E à id              {E.tipo = Consultar_TS(id.entrada)}
E à id  [E1]      {id.tipo = Consultar_TS(id.entrada)}

Pila semántica en un analizador sintáctico 

Como podemos entender un analizador sintáctico ascendente utiliza durante el análisis una pila. En esta va guardando datos que le permiten ir haciendo las operaciones de reducción que necesita.
Para incorporar acciones semánticas como lo es construir el árbol sintáctico, es necesario incorporar a la pila del analizador sintáctico ascendente otra columna que guarde los atributos de los símbolos que se van analizando.

Generación de tablas de símbolos y de direcciones

También se la llama tabla de nombres o tabla de identificadores y tiene dos funciones
principales:
- Efectuar chequeos semánticos.
- Generación de código.
Permanece sólo en tiempo de compilación, no de ejecución, excepto en aquellos casos en
que se compila con opciones de depuración.
La tabla almacena la información que en cada momento se necesita sobre las variables del
programa, información tal como: nombre, tipo, dirección de localización, tamaño, etc.

Manejo de errores semánticos
 Los errores semánticos son más sutiles. Un error semántico se produce cuando la sintaxis del código es correcta, pero la semántica o significado no es el que se pretendía. La construcción obedece las reglas del lenguaje, y por ello el compilador o intérprete no detectan los errores semánticos. Los compiladores e intérpretes sólo se ocupan de la estructura del código que se escribe, y no de su significado. Un error semántico puede hacer que el programa termine de forma anormal, con o sin un mensaje de error.

VÍDEO



MAPA MENTAL 




Código Arboles binarios
In-orden, pos-orden,pre-orden

public class NodoArbol
{
    //miembros de acceso
    NodoArbol nodoizquierdo;
    int datos;
    NodoArbol nododerecho;
    
    //iniciar dato y hacer de este nodo un nodo hoja
    public NodoArbol(int datosNodo)
    {
        datos = datosNodo;
        nodoizquierdo = nododerecho = null; //el nodo no tiene hijos
    }
    
    //buscar punto de insercion e inserter nodo nuevo
    public synchronized void insertar(int valorInsertar)
    {
        //insertar en subarbol izquierdo
        if(valorInsertar < datos)
        {
            //insertar en subarbol izquierdo
            if(nodoizquierdo == null)
                nodoizquierdo = new NodoArbol(valorInsertar);
            else    //continua recorriendo subarbol izquierdo
                nodoizquierdo.insertar(valorInsertar);
        }
        
        //insertar nodo derecho
        else if(valorInsertar > datos)
        {
            //insertar nuevo nodoArbol
            if(nododerecho == null)
                nododerecho = new NodoArbol(valorInsertar);
            else
                nododerecho.insertar(valorInsertar);
        }
    } // fin del metodo insertar
}

class Arbol
{
    private NodoArbol raiz;
    
    //construir un arbol vacio
    public Arbol()
    {
        raiz = null;
    }
    
    //insertar un nuevo ndo en el arbol de busqueda binaria
    public synchronized void insertarNodo(int valorInsertar)
    {
        if(raiz == null)
            raiz = new NodoArbol(valorInsertar); //crea nodo raiz
        else
            raiz.insertar(valorInsertar); //llama al metodo insertar        
    }
    
    // EMPIEZA EL RECORRIDO EN PREORDEN
    public synchronized void recorridoPreorden()
    {
        ayudantePreorden(raiz);
    }
    //meoto recursivo para recorrido en preorden
    
    private void ayudantePreorden(NodoArbol nodo)
    {
        if(nodo == null)
            return;
        
        System.out.print(nodo.datos + " ");     //mostrar datos del nodo
        ayudantePreorden(nodo.nodoizquierdo);   //recorre subarbol izquierdo
        ayudantePreorden(nodo.nododerecho);     //recorre subarbol derecho
    }
    
    //EMPEZAR RECORRIDO INORDEN
    public synchronized void recorridoInorden()
    {
        ayudanteInorden(raiz);
    }
    
    //meoto recursivo para recorrido inorden
    private void ayudanteInorden( NodoArbol nodo)
    {
        if(nodo == null)
            return;
        
        ayudanteInorden(nodo.nodoizquierdo);
        System.out.print(nodo.datos + " ");
        ayudanteInorden(nodo.nododerecho);
    }
    
    //EMPEZAR RECORRIDO PORORDEN
    public synchronized void recorridoPosorden()
    {
        ayudantePosorden(raiz);        
    }
    
    //meotod recursivo para recorrido posorden
    private void ayudantePosorden(NodoArbol nodo)
    {
        if( nodo == null )
            return;
        
        ayudantePosorden(nodo.nodoizquierdo);
        ayudantePosorden(nodo.nododerecho);
        System.out.print(nodo.datos + " ");
    }
}

Clase controladora:
import javax.swing.JOptionPane;

public class PruebaArbol
{
    public static void main(String args [])
    {
        Arbol arbol = new Arbol();
        int valor;
        String Dato;
        
        System.out.println("Insertando los siguientes valores: ");
        
        Dato = JOptionPane.showInputDialog("Inserta el numero de nodos que desea ingresar");
        int n = Integer.parseInt(Dato);
        
        for(int i = 1; i <= n; i++ )
        {
            Dato = JOptionPane.showInputDialog("Dame el " + i + " valor para colocar en el Arbol");
            valor = Integer.parseInt(Dato);
            System.out.print(valor + " ");
            arbol.insertarNodo(valor);
        }
        
        System.out.println("\n\nRecorrido Preorden");
        arbol.recorridoPreorden();
        
        System.out.println("\n\nRecorrido Inorden");
        arbol.recorridoInorden();
        
        System.out.println("\n\nRecorrido Postorden");
        arbol.recorridoPosorden();
    }
}