M.Sc. Sebastian David Ariza Coll
Siguiente tema
SUB preOrderT(Node node)
| if(node != null)
| | ESC(node.data)
| | EJECUTAR inOrderT(node.izq)
| | EJECUTAR inOrderT(node.der)
| end-if
end-function
SUB inOrderT(Node node)
| if(node != null)
| | EJECUTAR inOrderT(node.izq)+
| | ESC(node.data)
| | EJECUTAR inOrderT(node.der)
| end-if
end-function
SUB postOrderT(Node node)
| if(node != null)
| | EJECUTAR inOrderT(node.izq)
| | EJECUTAR inOrderT(node.der)
| | ESC(node.data)
| end-if
end-function
Dado el Pre-Order traversal y el In-Order traversal reconstruya el árbol binario.
5
Apuntador hijo izquierdo
Apuntador hijo derecho
function searchABBR(Node node, Ent: x)
| //Casos bases --> Llamados NO recursivos
| if(node = null) // Se ha recorrido todo el árbol y no se ha encontrado.
| | return null
| end-if
| if(node.data == x)//Se ha encontrado el elemento
| | return node
| end-if
|
| //LLamados recursivos
| if(node.data > x)// El dato a buscar es menor que el padre, avanzar hacia la izq
| | return searchABBR(node.izq, x)
| end-if
| if(node.data < x)// El dato a buscar es mayor que el padre, avanzar hacia la der
| | return searchABBR(node.der, x)
| end-if
end-function
x = 11
function addNode(Node node, Ent: data)
| //Casos bases --> Llamados NO recursivos
| if(node = null) // Se ha encontrado el padre del nuevo nodo
| | return getNode(x)
| end-if
| if(node.data == data) // Se ha encontrado el padre del nuevo nodo
| | ESC(x," ya existe dentro del árbol.")
| end-if
| //LLamados recursivos
| if(node.data > data)// El dato a añadir es hijo izq
| | node.izq <-- addNode(node.izq, data)
| end-if
| if(node.data < data)// El dato a añadir es hijo der
| | node.der <-- addNode(node.der, data)
| end-if
| return node
end-function
data = 11
data = 91
Dada la naturaleza de un ABB, para eliminar un nodo se deben contemplar 3 casos:
Como en la inserción, cada vez que se avanza se va van haciendo los llamados a izquierda o derecha sobre cada nodo. Esto con el fin de hacer las modificaciones en los enlaces de los nodos según el caso que aplique.
Caso 1. Nodo hoja.
function deleteR(Node node, Ent: data)
| //Casos bases --> Llamados NO recursivos
| if(node == null) // No se ha encontrado el elemento
| | return null
| end-if
| if(node.data == data)
| | if(node.izq = null)
| | | return node.der
| | end-if
| end-if
| //LLamados recursivos
| if(node.data > data) // El dato a añadir es hijo izq
| | node.izq <-- deleteR(node.izq, data)
| end-if
| if(node.data < data)// El dato a añadir es hijo der
| | node.der <-- deleteR(node.der, data)
| end-if
| return node
end-function
Caso 2. Tiene un único hijo.
function deleteR(Node node, Ent: data)
| //Casos bases --> Llamados NO recursivos
| if(node == null) // No se ha encontrado el elemento
| | return null
| end-if
| if(node.data == data)
| | if(node.izq = null)
| | | return node.der
| | end-if
| | if(node.der = null)
| | | return node.izq
| | end-if
| end-if
| //LLamados recursivos
| if(node.data > data) // El dato a añadir es hijo izq
| | node.izq <-- deleteR(node.izq, data)
| end-if
| if(node.data < data)// El dato a añadir es hijo der
| | node.der <-- deleteR(node.der, data)
| end-if
| return node
end-function
Caso 3: El nodo a eliminar tiene 2 hijos.
function deleteR(Node node, Ent: data)
| //Casos bases --> Llamados NO recursivos
| if(node == null) // No se ha encontrado el elemento
| | return null
| end-if
| if(node.data == data)
| | if(node.izq = null)
| | | return node.der
| | end-if
| | if(node.der = null)
| | | return node.izq
| | end-if
| | Node sucesor <-- minSubDer(node.der)
| | node.data <-- sucesor.data
| | node.der <-- deleteR(node.der, sucesor.data)
| end-if
| //LLamados recursivos
| if(node.data > data) // El dato a añadir es hijo izq
| | node.izq <-- deleteR(node.izq, x)
| end-if
| if(node.data < data)// El dato a añadir es hijo der
| | node.der <-- deleteR(node.der, x)
| end-if
| return node
end-function
function minSubDer(Node node)
| if(node.izq != null)
| | return minSubDer(node.izq)
| end-if
| return node
end-function
Longitud del camino más largo desde la raíz hasta una hoja.
Cada nodo o es una hoja o tine grado igual a 2
Todos los nodos excepto los del último nivel, tienen dos hijos
node.balance = height(node.left) - height(node.right)
Note que el factor de equilibrio de cada nodo en un AVL puede dar como resultado: -1, 0 o 1. Es decir
Solo en la inserción o eliminación de un nodo, los nodos ascendientes (padres) del nodo pueden sufrir un cambio en su factor de equilibrio.
Para reequilibrar el nodo, se deben utilizar ciertas operaciones llamadas rotaciones.
function rSI(Node node)
| Node aux <-- node.der
| node.der <-- aux.izq
| aux.izq <-- node
| node.balance <-- fb(node)
| aux.balance <-- fb(aux)
| return aux
end-function
Add 40
Add 3
function rSD(Node node)
| Node aux <-- node.izq
| node.izq <-- aux.der
| aux.der <-- node
| node.balance <-- fb(node)
| aux.balance <-- fb(aux)
| return aux
end-function
Add 1
¿?
node.izq <-- rSI(node.izq)
retorna rSD(node)
node.der <-- rSD(node.der)
retorna rSI(node)
¿?
¿?
function addNodeAVLR(Node node, Ent: x)
| //Casos bases --> Llamados NO recursivos
| if(node = null) // Se ha recorrido todo el árbol y no se ha encontrado se debe añadir.
| | return newNode(x)
| end-if
| if(node.data == x)//Se ha encontrado el elemento
| | ESC("El elemento ", x, " ya existe")
| | return node
| end-if
| //LLamados recursivos
| if(node.data > x)// El dato a añadir es menor que el padre, avanzar hacia la izq
| | node.izq <-- addNodeAVLR(node.izq, x)
| end-if
| if(node.data < x)// El dato a añadir es mayor que el padre, avanzar hacia la der
| | node.der <-- addNodeAVLR(node.der, x)
| end-if
| node.balance <-- fb(node)
| if(node.balance = -2)// rotación simple izquierda + rotación doble derecha - izquierda
| | if(node.der.balance = 1)
| | | node.der <-- rSD(node.der)
| | end-if
| | return rSI(node)
| end-if
| if(node.balance = 2)// rotación simple derecha + rotación doble izquierda - derecha
| | if(node.izq.balance = -1)
| | | node.izq <-- rSI(node.izq)
| | end-if
| | return rSD(node)
| end-if
| return node
end-function
Al eliminar un nodo, se puede dar el caso que el factor de equilibrio de un nodo de los sucesores del nodo desequilibrado sea cero. Por tanto, no se sabe qué rotación es posible aplicar.
Tener presente que: se elimina tal cual como un ABB, solo se le añade la validación sobre los factores de equilibrio.
Delete 10
Por simplicidad, tomaremos para eliminación cuando sus sucesores tengan fb igual a 0.
function deleteNodeAVLR(Node node, Ent: x)
| //Casos bases --> Llamados NO recursivos
| if(node = null) // Se ha recorrido todo el árbol y no se ha encontrado
| | ESC("No se ha encontrado el elemento ", x)
| | return null
| end-if
| //LLamados recursivos
| if(node.data > x)// El dato a eliminar es menor que el padre, avanzar hacia la izq
| | node.izq <-- deleteNodeAVLR(node.izq, x)
| end-if
| if(node.data < x)// El dato a eliminar es mayor es mayor que el padre, avanzar hacia la der
| | node.der <-- deleteNodeAVLR(node.der, x)
| end-if
| if(node.data == x)//Se ha encontrado el elemento
| | if(node.left = null)
| | | return node.right
| | end-if
| |
| | if(node.right = null)
| | | return node.left
| | end-if
| | Node sucesor <-- minSubDer(node.der)
| | node.data <-- sucesor.data
| | node.der <-- deleteR(node.der, sucesor.data)
| end-if
| node.balance <-- fb(node)
| if(node.balance = -2)// rotación simple izquierda + rotación doble derecha - izquierda
| | if(node.der.balance = 1)
| | | node.der <-- rSD(node.der)
| | end-if
| | return rSI(node)
| end-if
| if(node.balance = 2)// rotación simple derecha + rotación doble izquierda - derecha
| | if(node.izq.balance = -1)
| | | node.izq <-- rSI(node.izq)
| | end-if
| | return rSD(node)
| end-if
| return node
end-function
Grafique y especifique las rotaciones necesarias para crear un AVL con los siguientes nodos:
538, 624, 1607, 1481, 90, 1141, 668, 121, 456, 1095, 1917, 874, 1281, 664 643, 1345, 130, 927, 909, 253, 979, 1867, 1209, 816, 1784, 894, 1236, 936, 765, 1054
Grafique y especifique las rotaciones necesarias para eliminar del AVL por predecesor
los siguientes nodos: 130 927 909 253 979 1867 1209 456.
Rotación | Condición |
---|---|
Simple izquierda | fb(node) = -2 |
Simple derecha | fb(node) = 2 |
Doble izquierda - derecha | fb(node) = 2 y fb(node.izq) = -1 |
Doble derecha- izquierda | fb(node) = -2 y fb(node.der) = 1 |
Ejemplo: P = 5 ; d = 2
Los hijos de cualquier nodo deben cumplir con:
function buscarEnArbolB(Nodo nodo, Ent: clave)
| //Caso base: Si el nodo es null, la clave no está en el árbol
| if (nodo = null)
| | ESC("No se ha encontrado la clave ", clave)
| | return null
| end-if
|
| // Buscar la clave en el nodo actual
| Para i desde 1 hasta nodo.numClaves
| | if (clave = nodo.claves[i])
| | | ESC("Clave encontrada en el nodo")
| | | return nodo
| | end-if
| | if (clave < nodo.claves[i])
| | | // La clave puede estar en el subárbol izquierdo
| | | return buscarEnArbolB(nodo.hijos[i], clave)
| | end-if
| end-for
|
| // La clave puede estar en el subárbol derecho del último hijo
| return buscarEnArbolB(nodo.hijos[nodo.numClaves + 1], clave)
end-function
Si en el padre no se pueden insertar más claves, entonces se debe repetir recursivamente la partición de los nodos hasta poder insertar la nueva clave.
Add 27
Insertar en un árbol de orden P = 4 las siguientes claves:
37, 14, 60, 9, 22, 51, 10, 5, 55, 70, 1, 25
Ejercicio
Si el nodo es una hoja:
Si el nodo no es una hoja:
Si la eliminación causa que la altura del árbol disminuya.
Delete 67
Simplemente se elimina la clave
Delete 45
a. Si un nodo hermano tiene más de d claves, entonces se produce un “préstamo” de claves: Se elimina la clave, una clave del padre desciende al hijo y una clave del hermano asciende al padre.
Delete 39
b. Si ningún nodo hermano tiene más de d claves, entonces se une la hoja con uno de estos: Se elimina la clave, las claves del hermano se copian al nodo y la clave del padre entre ambos desciende al hijo.
Delete 14
a. Si uno de los hijos inmediatos de la clave a eliminar tiene más de d claves, entonces se debe reequilibrar el árbol: Se suprime la clave y una clave del hijo asciende al padre.
Delete 22
b. Si ninguno de los hijos inmediatos de la clave a eliminar tiene más de d claves, entonces se deben unir los hijos: Se elimina la clave y se unen los 2 hijos en un solo nodo.
Se suprime la clave, se unen los hijos del nodo en uno solo y su padre se une a la información de su hermano.
Delete 9
Más sobre grafos
Un grafo es una estructura de datos compuesta por un conjunto de vértices (también llamados nodos) y un conjunto de aristas (o arcos), que representan relaciones entre los vértices.
V de vértices o nodos del grafo, los cuales representan objetos.
y el conjunto E de aristas que representan relaciones entre objetos.
Una arista puede tener asociado un factor de peso.
Definimos a
como el grafo G conformado por el conjunto
Asismismo,
Son todos los vértices en G
Son todos las aristas en G las cuales representan las adyacencias del mismo
Los grafos permiten estudiar interrelaciones entre elementos que interactúan unos con otros
Son aplicables en:
Simple
Dirigido
No Dirigido
Multigrafo
Ponderado
Completo
En un grafo no dirigido:
En un grafo dirigido:
Camino
Secuencia finita de vértices donde cada par consecutivo está conectado por una arista (puede repetir vértices/aristas).
Longitud del camino:
El número de arcos que lo forman.
Camino simple:
Camino que NO REPITE vértices. Los extremos pueden ser iguales.
Ciclo / Circuito (camino cerrado): Es un camino que inicia y termina en el mismo nodo. Al menos tiene 3 vértices.
Ciclo simple:
Es un ciclo donde todos sus vértices y aristas son distintos.
Bucle:
Arista que va desde un vértice en
a sí mismo.
Es un camino simple de longitud 3
No es un camino simple de longitud 6
Es un camino cerrado de longitud 5
Grafo k-regular:
Grafo donde todos los vértices tienen grado k.
Bipartito: Es un grafo que se puede expresar en dos conjuntos donde cada arista los conecta pero no hay aristas entre vértices del mismo conjunto.
Grafo completo: Kn
Grafo (n-1) regular con n vértices, y cada vértice está conectado a todos los demás:
Matriz de adyacencia:
Sea
un grafo donde
, suponemos entonces que los
vértices
están ordenamos ascendentemente, y
podemos representarlos con sus ordinales:
De lo anterior, podemos estructurar a la matriz de adyacencia A como sigue:
con las entradas según el caso:
0 | 1 | 0 |
1 | 0 | 1 |
0 | 0 | 0 |
0 | 1 | 0 | 2 | 0 |
1 | 0 | 1 | 3 | 4 |
4 | 6 | 0 | 30 | 15 |
25 | 1 | 8 | 0 | 99 |
87 | 65 | 95 | 18 | 0 |
0 | 1 | 1 | 1 | 1 |
1 | 0 | 0 | 0 | 1 |
1 | 0 | 0 | 1 | 1 |
1 | 0 | 1 | 0 | 0 |
1 | 1 | 1 | 0 | 0 |
SUB DFS(nodo_actual)
| visited.add(nodo_actual)
| ESC("Visitando: " + nodo_actual.valor)
| for each vecino in grafo.adyacentes(nodo_actual)
| | if vecino not in visited
| | | DFS(vecino)
| | end-if
| end-for
END-SUB
SUB BFS(nodo_inicial)
| queue = new Queue()
| visited = new Set()
|
| queue.enqueue(nodo_inicial)
| visited.add(nodo_inicial)
|
| while queue no esté vacía
| | nodo_actual = queue.dequeue()
| | ESC("Visitando: " + nodo_actual.valor)
|
| | for each vecino in grafo.adyacentes(nodo_actual)
| | | if vecino not in visited
| | | | visited.add(vecino)
| | | | queue.enqueue(vecino)
| | | end-if
| | end-for
| end-while
END-SUB
Dado el grafo
con:
Se desea encontrar el camino
que minimice la suma
los pesos, es decir:
Donde:
Tener presente que:
* Adapto bajo heurísticas
SUB DIJKSTRA(grafo G, nodo_fuente s)
| distancia = new Dictionary()
| padre = new Dictionary()
| visto = new Set()
| cola_prioridad = new PriorityQueue()
| for each nodo u in G.nodos
| | distancia[u] = INFINITO
| | padre[u] = NULL
| end-for
| distancia[s] = 0
| cola_prioridad.enqueue((s, 0))
| while cola_prioridad no esté vacía
| | (u, dist_actual) = cola_prioridad.dequeue()
| | if u not in visto
| | | visto.add(u)
| | | for each vecino v in G.adyacentes(u)
| | | | peso_uv = G.peso(u, v)
| | | | nueva_dist = distancia[u] + peso_uv
| | | | if nueva_dist < distancia[v]
| | | | | distancia[v] = nueva_dist
| | | | | padre[v] = u
| | | | | cola_prioridad.enqueue((v, nueva_dist))
| | | | end-if
| | | end-for
| | end-if
| end-while
END-SUB
A | B | C | D | E | F | G | |
---|---|---|---|---|---|---|---|
distan | |||||||
padre | |||||||
visto |
// Inicialización:
Para cada nodo vi en V(G): // Para cada vértice de origen
Para cada nodo vj en V(G): // Para cada vértice de destino
Si vi == vj:
D[vi][vj] = 0 // Distancia a sí mismo es 0
P[vi][vj] = vi // Camino a sí mismo es él mismo
Si existe arista vi -> vj:
D[vi][vj] = peso(vi -> vj) // Distancia directa
P[vi][vj] = vj // Siguiente nodo es vj
Sino:
D[vi][vj] = ∞ // Infinito si no hay conexión
P[vi][vj] = - // No hay camino
// Floyd-Warshall: Explorar todos los caminos posibles
Para cada nodo vk en V(G): // Nodo intermedio
Para cada nodo vi en V(G): // Origen
Para cada nodo vj en V(G): // Destino
// Verificar que exista camino por vk y sea mejor
Si D[vi][vk] + D[vk][vj] < D[vi][vj]:
D[vi][vj] = D[vi][vk] + D[vk][vj] // Actualizar distancia
P[vi][vj] = P[vi][vk] // Actualizar camino
A | B | C | D | E | F | G | |
---|---|---|---|---|---|---|---|
A | 0 | 20 | 10 | 8 | ∞ | ∞ | ∞ |
B | 20 | 0 | ∞ | 30 | 10 | 10 | ∞ |
C | 10 | ∞ | 0 | 5 | ∞ | ∞ | 8 |
D | 8 | 30 | 5 | 0 | ∞ | 40 | 20 |
E | ∞ | 10 | ∞ | ∞ | 0 | 20 | ∞ |
F | ∞ | 10 | ∞ | 40 | 20 | 0 | ∞ |
G | ∞ | ∞ | 8 | 20 | ∞ | ∞ | 0 |
Matriz D
Matriz P
A | B | C | D | E | F | G | |
---|---|---|---|---|---|---|---|
A | A | B | C | D | - | - | - |
B | A | B | - | D | E | F | - |
C | A | - | C | D | - | - | G |
D | A | B | C | D | - | F | G |
E | - | B | - | - | E | F | - |
F | - | B | - | D | E | F | - |
G | - | - | C | D | - | - | G |
Si D[vi][vk] + D[vk][vj] < D[vi][vj]:
D[vi][vj] = D[vi][vk] + D[vk][vj] // Actualizar distancia
P[vi][vj] = P[vi][vk] // Actualizar camino
A | B | C | D | E | F | G | |
---|---|---|---|---|---|---|---|
A | 0 | 20 | 10 | 8 | 30 | 30 | 18 |
B | 20 | 0 | 30 | 28 | 10 | 10 | 38 |
C | 10 | 30 | 0 | 5 | 40 | 40 | 8 |
D | 8 | 28 | 5 | 0 | 38 | 38 | 13 |
E | 30 | 10 | 40 | 38 | 0 | 20 | 48 |
F | 30 | 10 | 40 | 38 | 20 | 0 | 48 |
G | 18 | 38 | 8 | 13 | 48 | 48 | 0 |
A | B | C | D | E | F | G | |
---|---|---|---|---|---|---|---|
A | A | B | C | D | B | B | C |
B | A | B | A | A | E | F | A |
C | A | A | C | D | B | B | G |
D | A | A | C | D | B | B | C |
E | B | B | B | B | E | F | B |
F | B | B | B | B | E | F | B |
G | C | C | C | C | C | C | G |
Si D[vi][vk] + D[vk][vj] < D[vi][vj]:
D[vi][vj] = D[vi][vk] + D[vk][vj]// Actualizar distancia
P[vi][vj] = P[vi][vk] //Actualizar camino
Si D[vi][vk] + D[vk][vj] < D[vi][vj]:
D[vi][vj] = D[vi][vk] + D[vk][vj]// Actualizar distancia
P[vi][vj] = P[vi][vk] //Actualizar camino
Si D[vi][vk] + D[vk][vj] < D[vi][vj]:
D[vi][vj] = D[vi][vk] + D[vk][vj]// Actualizar distancia
P[vi][vj] = P[vi][vk] //Actualizar camino
Si D[vi][vk] + D[vk][vj] < D[vi][vj]:
D[vi][vj] = D[vi][vk] + D[vk][vj]// Actualizar distancia
P[vi][vj] = P[vi][vk] //Actualizar camino
Los métodos de ordenamientos son algoritmos que permiten organizar los elementos de una lista o arreglo en un orden específico (ascendente o descendente).
Principales métodos de ordemiento:
for i: 0 to length(data) - 2
for j: i + 1 to length(data) - 1:
if data[i] > data[j]:
swap data[i] and data[j]
for i: 0 to length(data) - 2
min_i = i
for j: i + 1 to length(data) - 1:
if data[min_i] > data[j]:
min_i = j
swap data[i] and data[min_i]
for i: 1 to length(data) - 1
key = data[i]
j = i + 1
while j >= 0 and key < data[j]
data[j + 1] = data[j]
j = j - 1
data[j + 1] = key
procedure quicksort(data, low, high):
if low < high:
i = low
j = high
p = data[(low + high) // 2]
while i <= j:
while data[i] < p:
i += 1
while data[j] > p:
j -= 1
if i <= j:
swap(data[i], data[j])
i += 1
j -= 1
# Recursión
quicksort(data, low, j)
quicksort(data, i, high)
Paralelismo a nivel de instrucción: ejecutar varias instrucciones al mismo tiempo.
Paralelismo a nivel de datos: realizar la misma operación sobre múltiples datos a la vez.
Paralelismo a nivel de tareas: ejecutar diferentes tareas o funciones simultáneamente.
Rol | Función | Descripción |
---|---|---|
Ambos | socket() | Crear el socket |
Servidor | bind() | Asignar IP y puerto |
Servidor | listen() | Esperar conexiones |
Servidor | accept() | Aceptar conexión entrante |
Cliente | connect() | Conectarse al servidor |
Ambos | send() / recv() | Enviar y recibir datos |
Ambos | close() | Cerrar la conexión |