Galería de mapas mentales estructura de datos
Este es un mapa mental sobre estructuras de datos, que incluye tablas lineales, pilas y colas, cadenas, árboles y árboles binarios, búsqueda, clasificación, etc. Es muy práctico y vale la pena recopilarlo.
Editado a las 2021-09-25 14:52:15,Este es un mapa mental sobre una breve historia del tiempo. "Una breve historia del tiempo" es una obra de divulgación científica con una influencia de gran alcance. No sólo presenta los conceptos básicos de cosmología y relatividad, sino que también analiza los agujeros negros y la expansión. del universo. temas científicos de vanguardia como la inflación y la teoría de cuerdas.
¿Cuáles son los métodos de fijación de precios para los subcontratos de proyectos bajo el modelo de contratación general EPC? EPC (Ingeniería, Adquisiciones, Construcción) significa que el contratista general es responsable de todo el proceso de diseño, adquisición, construcción e instalación del proyecto, y es responsable de los servicios de operación de prueba.
Los puntos de conocimiento que los ingenieros de Java deben dominar en cada etapa se presentan en detalle y el conocimiento es completo, espero que pueda ser útil para todos.
Este es un mapa mental sobre una breve historia del tiempo. "Una breve historia del tiempo" es una obra de divulgación científica con una influencia de gran alcance. No sólo presenta los conceptos básicos de cosmología y relatividad, sino que también analiza los agujeros negros y la expansión. del universo. temas científicos de vanguardia como la inflación y la teoría de cuerdas.
¿Cuáles son los métodos de fijación de precios para los subcontratos de proyectos bajo el modelo de contratación general EPC? EPC (Ingeniería, Adquisiciones, Construcción) significa que el contratista general es responsable de todo el proceso de diseño, adquisición, construcción e instalación del proyecto, y es responsable de los servicios de operación de prueba.
Los puntos de conocimiento que los ingenieros de Java deben dominar en cada etapa se presentan en detalle y el conocimiento es completo, espero que pueda ser útil para todos.
Estructuras de datos 8.26
1. Introducción
1.1 Conceptos básicos de estructura de datos.
1.1.1Conceptos y terminología básicos
1.Datos
Es un portador de información, una colección de símbolos que pueden ingresarse en una computadora y ser reconocidos y procesados por un programa informático.
2. Elementos de datos
Es la unidad básica de datos. Un elemento de datos puede estar compuesto por varios elementos de datos.
Elemento de datos: la unidad indivisible más pequeña que constituye un elemento de datos.
3. Objetos de datos
Una colección de elementos de datos con la misma naturaleza, que es un subconjunto de datos (por ejemplo, el objeto de información del estudiante es un subconjunto de la información general del estudiante y la información del curso)
4.Tipo de datos
Es el nombre general de un conjunto de valores y un conjunto de operaciones definidas en este conjunto (como una variable de tipo int, cuyo conjunto de valores es un número entero en un intervalo determinado (el tamaño del intervalo varía según la máquina). ), y las operaciones en él se definen como suma, resta, multiplicación, división y toma de módulo y otras operaciones)
1.tipo atómico
Un tipo de datos cuyo valor no se puede subdividir.
2. Tipo de estructura
Un tipo de datos cuyo valor se puede dividir en varios (componentes)
3. Estructura de datos abstracta ADT
Se refiere a un modelo matemático y un conjunto de operaciones definidas en el modelo (solo se enfatizan las características lógicas)
Ilustración 1-4
1.1.2 Estructura de datos y tres elementos
estructura de datos
Es una colección de elementos de datos que tienen una relación específica entre sí.
Tres elementos
1. Estructura lógica de los datos.
estructura lineal
tabla lineal general
mesa lineal restringida
pila, cola
cadena
Promoción de mesa lineal.
formación
estructura no lineal
recolectar
estructura de árbol
estructura grafica
2. Estructura de almacenamiento de datos (imagen, estructura física)
almacenamiento secuencial
Ventajas: 1. Se puede lograr acceso aleatorio 2. Cada elemento ocupa el menor espacio de almacenamiento Desventajas: 1. Solo se puede usar un bloque completo de unidades de almacenamiento adyacentes, por lo que es fácil generar más fragmentos externos;
almacenamiento en cadena
Ventajas: 1. No requiere que los elementos lógicamente adyacentes sean físicamente adyacentes, por lo que no se producirá fragmentación. Desventajas: 1. Los punteros consumen espacio adicional 2. No se puede lograr acceso aleatorio, solo acceso secuencial.
Almacenamiento de índice
Mientras se almacena información del elemento, se crea una tabla de índice adicional y cada elemento de la tabla de índice se denomina elemento de índice (clave, dirección) Ventajas: 1. Recuperación rápida. Desventajas: 1. La tabla de índice adicional ocupa espacio de almacenamiento adicional; 2. Al agregar o eliminar datos, también necesita modificar la tabla de índice, lo que lleva mucho tiempo.
almacenamiento de hash
Calcule directamente la dirección de almacenamiento del elemento (almacenamiento Hash) según la palabra clave del elemento Ventajas: 1. La operación de agregar, eliminar y verificar nodos es muy rápida. Desventajas: 1. Si la función hash no es buena, habrá. Puede haber un conflicto entre unidades de almacenamiento de elementos y resolver el conflicto aumentará la sobrecarga de tiempo y espacio.
3. Operaciones de datos
Definición de operaciones
En vista de la estructura lógica, señale la función de operación.
Implementación de operaciones
Señale los pasos de operación específicos para la estructura de almacenamiento.
1.2 Algoritmos y evaluación de algoritmos
Es una descripción de la resolución de un problema específico y es una secuencia finita de instrucciones.
complejidad del tiempo
Cuando el tamaño del problema es n, el tiempo de ejecución es proporcional a O(1<log2n<n<nlog2n<n^2<n3<2^n<n!<n^n)
complejidad espacial
El tamaño del problema es n, espacio adicional además de la entrada y el programa, el algoritmo funciona en su lugar: el espacio auxiliar requerido por el algoritmo es constante, es decir, O (1)
2Mesa lineal
2.1 Definición de tabla lineal
Una secuencia finita de n elementos de datos con el mismo tipo de datos.
2.2 Representación secuencial de tablas lineales.
tabla de secuencia
insertar
Bool ListInsert(SqList &L,int i,ElemType e){ Si (i < 1 || i > L.length 1) devuelve falso; Si (L.length >= MaxSize) devuelve falso; For(int j = L.length;j>=i;j- -) //L.length es la posición después del último elemento en el subíndice de la matriz L.datos[j] = L.datos[j-1]; L.datos[i-1] = e; L.longitud; devolver verdadero; }
Complejidad del tiempo Mejor Peor Promedio O(1) O(n) O(n)
borrar
Bool ListDelete(SqList &L,int i,ElemType &e){ Si (i<1 || i>L.length) devuelve falso; e = L.datos[i-1]; Para(int j = i;j<L.length;j) L.datos[j-1] = L.datos[j]; L.longitud - -; Devuelve verdadero; }
O(1) O(n) O(n)
Buscar por valor (buscar secuencialmente)
Int LocateElem(SqList L,ElemType e){ Para (int i=0;i<L.length;i) Si(L.data[i] == e) devuelve i 1; Devuelve 0; }
O(1) O(n) O(n)
2.3 Representación encadenada de tablas lineales.
lista única estructura typedef LNode{ datos de tipo elemento; estructura LNode *siguiente; }LNodo,*ListaEnlaces;
Crear una lista enlazada individualmente
Método de inserción de la cabeza
LinkLIst List_HeadInsert(Lista de enlaces &L){ LNodo *s;int x; L = (LNodo*)malloc(tamañode(LNodo)); L->siguiente = NULL; scanf(“%d”,&x); mientras(x!= 9999){ s = (LNodo*)malloc(tamañode(LNodo)); s->datos = x;s->siguiente = L->siguiente; L->siguiente = s; s->datos = x; scanf(“%d”,&x); } devolver L;}
método de inserción de la cola
Lista de enlaces List_TailInsert(Lista de enlaces &L){ intx; L = (LNodo*)malloc(tamañode(LNodo)); LNodo *s,*r = L; scanf(“%d”,&x); mientras(x!= 9999){ s = (LNodo*)malloc(tamañode(LNodo)); s->datos = x; r->siguiente = s; s = r; scanf(“%d”,&x); } r-siguiente = NULL; devolver l: }
Buscar nodos por número de serie
LNodo *GetElem(ListaEnlaces L,int i){ int j = 1; LNodo *p = L->siguiente; si (i == 0) devuelve L; si (i < 1) devuelve NULL; mientras(p && j<i){ p = p ->siguiente; j; } devolver p; }
Buscar nodo de tabla de nodos por valor
LNodo *LocateElem(LinkList L,ElemType e){ LNodo *p = L->siguiente; while(p != NULL && p->datos != e) p = p->siguiente; devolver p; }
Insertar nodo
p = ObtenerElem(L,i-1); s->siguiente = p->siguiente; p->siguiente = s; (i-1 es p, insertado después de p)
Tiempo en)
s->siguiente = p->siguiente; p->siguiente = s; temperatura = p->datos; p->datos = s->datos; s->datos = temperatura; (i es p, inserta e intercambia datos después de p e implementa la inserción directa de p)
Hora O(1)
Eliminar nodo
p = ObtenerElem(L,i-1); q = p->siguiente; p->siguiente = p->siguiente->siguiente; libre(q);
Tiempo en)
q = p->siguiente; p->datos = q->datos; p->siguiente = p->siguiente->siguiente; libre(q);
Hora O(1)
Preguntar por el largo de la mesa
La longitud de la mesa no incluye el nodo principal.
En)
lista doblemente enlazada estructura typedef DNodo{ datos de tipo elemento; estructura DNodo *anterior,*siguiente; }NodoD,*ListaEnlaceD;
Insertar nodo
s->siguiente = p->siguiente; s->siguiente->anterior = s; s->anterior = p; p->siguiente = s;
O(1)
Eliminar nodo
p->anterior->siguiente = p->siguiente; p->siguiente->anterior = p->anterior; libre(p);
O(1)
lista circular enlazada Características obvias: el puntero del último nodo no es NULL. La condición nula es si el nodo principal es igual al puntero principal, no NULL, es decir, si (L->next == L)
Lista circular individualmente enlazada
Lista circular doblemente enlazada
lista enlazada estática Utilice matrices para describir la estructura de almacenamiento vinculada de una lista lineal. Características: El puntero es la dirección relativa del nodo (subíndice de matriz), también conocido como cursor. Al igual que la lista de secuencia, la lista enlazada estática también necesita preasignar un espacio de memoria continuo. #definirTamaño máximo 50 estructura typedef { datos de tipo elemento; int siguiente; }ListaEnlaceS[TamañoMax];
3 pilas y colas
pila Número de Cattelan: 1/n 1(C2n n)
Almacenamiento secuencial de la pila (pila secuencial) #definirTamaño máximo 50 estructura typedef { Datos ElemType[MaxSize]; int top; //Puntero superior de la pila }Pila cuadrada;
inicialización anular InitStack(SqStack &S){ arriba = -1; }
La pila esta vacia bool PilaVacía(SqStack S){ if(S.top == -1) devuelve verdadero; de lo contrario devolverá falso; }
empujar hacia la pila bool Push(SqStack &S,ElemType x){ if(S.top == MaxSize-1) devuelve falso; S.data[ S.top] = x; //top se inicializa a -1 devolver verdadero; }
estallido bool Pop(SqStack &S,ElemType &x){ if(S.top == -1) devuelve falso; x = S.datos[S.arriba- -]; devolver verdadero; }
Leer el elemento superior de la pila. bool GetTop(SqStack S,ElemType &x){ if(S.top == -1) devuelve falso; x = S.datos[S.arriba]; devolver verdadero; }
Pila compartida: aprovechando la posición relativamente sin cambios de la parte inferior de la pila, deje que dos pilas secuenciales compartan un espacio de matriz unidimensional. La parte inferior de las dos pilas se establece en ambos extremos del espacio compartido y la parte superior del espacio compartido. dos pilas se extienden hacia el centro del espacio compartido; la pila compartida puede hacer un uso más eficiente del espacio de almacenamiento. Los dos espacios de la pila se ajustan entre sí y el desbordamiento solo ocurre cuando todo el espacio de almacenamiento está lleno.
Inicial: top0 = -1 top1 = MaxSize; pila llena: solo cuando dos punteros de pila son adyacentes top1 - top0 = 1
pila de cadena estructura typedef Linknode{ datos de tipo elemento; estructura Linknode *siguiente; }*Pila de enlaces;
cola
Almacenamiento de pedidos en cola #definirTamaño máximo 50 estructura typedef { Datos ElemType[MaxSize]; delante, detrás; }ColaCuad;
cola secuencial
Condición de cola vacía: Q.front == Q.rear ==0; tenga en cuenta que Q.rear == MaxSize no se puede utilizar como condición para juzgar que el equipo está lleno y puede ser un desbordamiento falso.
cola circular Las operaciones de retirada de cola, unión y completa de la cola circular deben tener % y la condición vacía es solo Q.rear == Q.front;
inicialización void InitQueue(SqQueue &Q){ Q.trasero = Q.frontal = 0; }
el equipo esta vacio bool está vacío(SqQueue Q){ if(Q.trasero == Q.front) devuelve verdadero; de lo contrario devolverá falso; }
Únete al equipo bool EnQueue(SqQueue &Q,ElemType x){ if((Q.trasero 1)%MaxSize == Q.front) devuelve falso; Q.datos[Q.trasero] = x; Q.trasero = (Q.trasero 1)%MaxSize; devolver verdadero; }
quitar la cola bool DeQueue(SqQueue &Q,ElemType &x){ if(Q.trasero == Q.front) devuelve falso; x = Q.datos[Q.frente]; Q.frente = (Q.frente 1)%MaxSize; devolver verdadero; }
Almacenamiento en cadena de colas (cola en cadena) estructura typedef LNode{ datos de tipo elemento; estructura LNode* siguiente; }LNodo; estructura typedef { LNodo *delantero,*trasero; }Cola de enlaces; La cola en cadena es adecuada para situaciones en las que los elementos de datos cambian relativamente grandemente y no existe ninguna situación en la que la cola esté llena y provoque un desbordamiento.
inicialización anular InitQueue(LinkQueue &Q){ Q.front = Q.rear = (LinkQueue*)malloc(sizeof(LinkQueue) //Crea el nodo principal Q.front ->siguiente = NULL; }
el equipo esta vacio bool Está vacío(LinkQueue&Q){ if(Q.front == Q.rear) return true;//Con nodo principal de lo contrario devolverá falso; }
Únete al equipo anular EnQueue(LinkQueue &Q,ElemType x){ LinkNode *s = (LinkNode*)malloc(sizeof(LinkNode)); s->data = x; s->next = NULL;//Crea un nuevo nodo e insértalo al final de la cadena. Q.trasero->siguiente = s; Q.trasero = s; }
quitar la cola bool DeQueue(LinkQueue &Q,ElemType &x){ if(Q.trasero == Q.front) devuelve falso; LinkNode *p = Q.front->siguiente; x = p->datos; Q.front->siguiente = p->siguiente; if(Q.rear == p) Q.rear = Q.front;//La cola original tiene un solo nodo libre(p); devolver verdadero; }
Cola de doble extremo: una cola que permite que ambos extremos realicen operaciones de puesta en cola y retirada de la cola. Su estructura lógica de elementos sigue siendo una estructura lineal. Los dos extremos de la cola se denominan front-end y back-end respectivamente.
Entrada restringida deque
Permitir inserciones y eliminaciones en un extremo, pero solo eliminaciones en el otro extremo.
Deque restringido de salida
Permitir inserciones y eliminaciones en un extremo, pero solo inserciones en el otro extremo.
Aplicaciones de pilas y colas.
Aplicación de pila en coincidencia de paréntesis
[]()([][]()) es el formato correcto (] [) es el formato incorrecto;
Establezca una pila vacía y lea los paréntesis en secuencia; si es un paréntesis derecho, saque el elemento superior de la pila para inspeccionarlo. Si coincide, sáquelo de la pila; de lo contrario, se informará un error; paréntesis izquierdo, empújelo en la pila; al final del algoritmo, verifique si la pila está vacía, coincidirá. Si no está vacía, la secuencia de corchetes no coincidirá.
Aplicación de pila en evaluación de expresiones.
Expresiones infijas: dependen de la precedencia de los operadores, pero también tratan con paréntesis Expresiones postfix: la precedencia de los operadores ya se tiene en cuenta, sin paréntesis (recorrido posterior al orden del árbol de expresiones) Convertir una expresión infija en una expresión postfija
Procesamiento de expresiones postfix: escanee cada elemento de la expresión secuencialmente y, si es un operando, empújelo en la pila, si es un operador, salga continuamente de los dos operandos Y y X de la pila para formar X <OP>Y; y poner Los resultados del cálculo se devuelven a la pila
Aplicación de pila en recursividad.
Aplicación de cola en recorrido jerárquico.
El nodo raíz ingresa a la cola; si la cola está vacía (todos los nodos han sido procesados), el recorrido finaliza; de lo contrario, se repite el siguiente paso; el niño izquierdo se une al equipo; si tiene un niño derecho, agregue el niño derecho al equipo y regrese al paso anterior.
Aplicación de colas en sistemas informáticos.
1. Resuelva el problema de la discrepancia de velocidad entre el host y los dispositivos externos; 2. Resolver problemas de competencia de recursos causados por múltiples usuarios.
1. Host e impresora: configure un búfer de datos de impresión cuando el búfer esté lleno, el host pausará la salida. La impresora sacará fcfs de la cola y luego enviará una solicitud al host después de imprimir.
2.Asignación de recursos de CPU: FCFS
Almacenamiento comprimido de matrices especiales. Resolución de problemas: de acuerdo con las condiciones correspondientes, dibuje un boceto y haga una deducción ahora. Lo principal es comprender la lógica de almacenamiento en lugar de memorizar fórmulas.
formación Una secuencia finita de n elementos de datos del mismo tipo. Límite de dimensión: el rango de valores del subíndice Las matrices son una generalización de las tablas lineales. Una matriz de un bit puede verse como una tabla lineal, y una matriz bidimensional puede verse como una tabla lineal cuyos elementos también son tablas lineales de longitud fija, y así sucesivamente; La matriz está definida, sus dimensiones y límites ya no cambian, por lo que además de la inicialización y destrucción, la matriz solo tendrá operaciones de acceso a elementos y modificación de elementos. La matriz bidimensional se almacena físicamente como asignada a un bit; ¡formación!
Estructura de almacenamiento de matriz fila principal; columna principal
Almacenamiento comprimido de matrices. Almacenamiento comprimido: solo se asigna un espacio de almacenamiento para varios elementos con el mismo valor y no se asigna ningún espacio de almacenamiento para cero elementos. Matrices especiales: tienen muchos elementos matriciales idénticos o elementos cero, con distribución regular
matriz simétrica
A[1 n][1 n] se almacena en B[n(n 1)/2] Desarrolla la fórmula en un lado e intercambia i y j en el otro lado.
matriz triangular
A[1 n][1 n] se almacena en B[n(n 1)/2 1] Después de deducir la fórmula de un lado, el otro lado solo almacena un número en n (n 1)/2. Analizando la situación específica, esto se debe a que el subíndice comienza desde cero.
matriz triangular superior
matriz triangular inferior
matriz tridiagonal
k=2i j-3 Cuando el subíndice k de una matriz B de un bit, i=(k 1)/3 1 está acotado, j=k-2i 3 ! ! ! ¡No memorices de memoria! ! ! Para entenderlo, dibuje las filas correspondientes de una matriz tridiagonal. ! !
matriz dispersa El número de elementos t distintos de cero en la matriz es muy pequeño en relación con el número de elementos de la matriz s, es decir, la matriz con s >> t se llama matriz dispersa. Por ejemplo, una matriz de 100x100 tiene menos de 100 elementos distintos de cero.
Utilice el método triplete (etiqueta de fila, etiqueta de columna, valor) o lista cruzada
4 brochetas
Definición e implementación de cadena.
Definición de cadena: La cadena se conoce como cadena. Los objetos de procesamiento no numérico en las computadoras son básicamente datos de cadena. Cadena (Cadena) S; una subsecuencia compuesta de varias cadenas de caracteres consecutivas en una cadena se denomina subcadena de la cadena, y la cadena correspondiente que contiene la subcadena se denomina cadena principal. La estructura lógica de una cadena es muy similar a la de una tabla lineal. La única diferencia es que su objeto de datos se limita a un conjunto de caracteres; el objeto operativo de una tabla lineal es un solo elemento, mientras que el objeto operativo de una cadena; es una subcadena!
estructura de almacenamiento de cuerdas
Almacenamiento secuencial de longitud fija #definir MaxLen 255 estructura typedef { char ch[MaxLen]; longitud interna; }SSadena; Tampoco puede registrar la longitud y utilizar '\0', que no está incluido en la longitud de la cadena, para dar a entender la longitud de la cadena.
Representación de almacenamiento asignado en montón (asignación dinámica) estructura typedef { carbón*ch; longitud interna; }HString; En lenguaje C, el espacio asignado dinámicamente se almacena en el montón y se administra mediante malloc() free(). Si la asignación falla, se devuelve NULL.
Representación de almacenamiento de la cadena de bloques: cada nodo se denomina bloque, y un bloque puede almacenar uno o varios caracteres y la lista vinculada completa se denomina estructura de cadena de bloques; Cuando el último nodo ocupa menos de un bloque, normalmente se rellena con "#"
Operaciones básicas con cadenas Conjunto mínimo de operaciones: asignación de cadenas, comparación de cadenas, búsqueda de longitud de cadena, concatenación de cadenas y búsqueda de subcadenas; Es decir, estas operaciones no se pueden implementar utilizando otras operaciones de cadena.
Asignación de cadenas StrAssign(&T,chars) asigna el valor de T a los caracteres
Comparación de cadenas StrCompare(S,T) si S>T,return>0;S=T,return 0;S<T,return<0
Encuentre la longitud de la cadena StrLength(S) devuelve el número de S elementos
Concatenación de series Concat(&T,S1,S2) usa T para devolver una nueva cadena formada al concatenar S1 y S2.
Encuentre la subcadena SubString(&Sub,S,pos,len) Use Sub para devolver la subcadena comenzando desde el carácter pos de la cadena S y continuando con la longitud de len
coincidencia de patrones de cuerdas La operación de posicionamiento de subcadenas generalmente se denomina coincidencia de patrones de cadenas. Lo que se encuentra es la posición de la subcadena (a menudo llamada cadena de patrón) en la cadena principal.
Algoritmo de coincidencia de patrones simple
int índice(SString S,SString T){ int i=1,j=1; while(i<=S.longitud && j<=T.longitud){ si(S.ch[i] == T.ch[j]){ yo ;j ; } demás{ i = i-j 1 1;j = 1; } if(j > T.longitud) devuelve i - T.longitud; de lo contrario, devuelve 0; } Peor complejidad del tiempo O(mn)
Algoritmo de coincidencia de patrones mejorado: algoritmo KMP Durante el proceso de coincidencia, la información de coincidencia ya obtenida se utiliza para que el puntero de la cadena principal i ya no retroceda, sino que continúe haciendo coincidir hacia atrás desde la posición actual. ¡El cálculo del número de dígitos para deslizar el modo hacia atrás solo está relacionado con la estructura del modo en sí! No tiene nada que ver con la cadena principal. Aunque el modo normal es O (m n), la complejidad temporal reconocida del algoritmo KMP es O (n m)
Conceptos relacionados: Prefijo: todas las subcadenas de encabezado de la cadena excepto el último carácter; Sufijo: todas las subcadenas finales de la cadena excepto el primer carácter; Coincidencia parcial (PM): la longitud igual de prefijo y sufijo más larga de la cadena. Ejemplo: 'ababa' ¡Atención! ! A partir del primer carácter de la cadena principal, divida todas las subcadenas y luego analice la longitud del prefijo y sufijo y el sufijo y sufijo iguales más largos, respectivamente. El prefijo y sufijo 'a' es un conjunto vacío, y la longitud igual de prefijo y sufijo más larga es 0 'ab' prefijo 'a' sufijo 'b' La longitud de coincidencia igual más larga es 0 'aba' prefijo 'a' 'ab' sufijo 'a' 'ba' {'a' 'ab'} ^ {'a' 'ba'} = 'a', la longitud del sufijo igual más larga es 1 'abab' prefijo 'a' 'ab' 'aba' sufijo 'b' 'ab' 'bab', la longitud igual de prefijo y sufijo más larga es 2 'ababa' prefijo 'a' 'ab' 'aba' 'abab' sufijo 'a' 'ba' 'aba' 'baba' ,{ } ^ { } = {'a' 'aba'}, el sufijo igual más largo 3 Entonces el valor de coincidencia parcial de la cadena final es 00123
Cuando utilice el algoritmo KMP, primero escriba la tabla PM de la cadena de patrón (subcadena); cuando la subcadena no coincida con la cadena principal, busque el valor PM del último elemento coincidente de la subcadena en la tabla PM y luego deje que subcadena La distancia recorrida hacia atrás (número de caracteres coincidentes - valor PM del último carácter coincidente cuando ocurre una discrepancia en un determinado viaje, si el valor de coincidencia parcial de la parte correspondiente es 0, significa que no hay sufijos iguales y); sufijos en la secuencia coincidente El número de dígitos movidos es el mayor (longitud coincidente - 0) y se mueve directamente a la posición i señalada por la cadena principal en este momento para la siguiente comparación. Mover = (j-1) - PM[j-1];
Generación y modificación del siguiente arreglo: Para facilitar la falta de coincidencia, en lugar de esperar un carácter, verifique directamente el valor PM de la posición actual del carácter no coincidente, de modo que todos los elementos en el PM se muevan 1 posición hacia la derecha y la primera posición se llene con -1. y se ignora la última posición; Llamada siguiente matriz, Move = (j-1)-next[j] En este momento, j vuelve a j = j-Move = next[j] 1; Luego agregue 1 a todo lo siguiente y actualice la siguiente matriz para que j = next[j] (En este momento, la primera posición de la siguiente matriz es 0) El significado de la siguiente matriz final: cuando el carácter j-ésimo de la subcadena no coincide, salte a la siguiente posición [j] de la subcadena para la siguiente ronda de coincidencia. ! !
Código usando la siguiente matriz final: int index_KMP(Cadena T,Cadena S,int siguiente[]){ int i=1,j=1; while(i<=S.longitud && j<=T.longitud){ si(S.ch[i] == T.ch[j]){i ;j ;} más j = siguiente[j]; } if(j > T.length) devuelve i-T.length; de lo contrario, devuelve 0; }
Mayor optimización del algoritmo KMP Ejemplo: cuando la cadena principal 'aaabaaaab' y la cadena de patrón 'aaaab' coinciden, hay una discrepancia en la cuarta posición. Si se utiliza la siguiente matriz [j] anterior, se moverá uno hacia la izquierda cada vez. luego se compara; pero aparecerá Pj = Pnext[j], lo que resultará en una comparación redundante; Solución: asegúrese de que Pj = Pnext[j] no aparezca, si aparece, realice la recursividad nuevamente, corrija next[j] por next[next[j]] y asígnele el nombre nextval[j];
5 árboles y árboles binarios
árboles y bosques
concepto
Un árbol es un conjunto finito de n (n>0) nodos. Cuando n = 0, se llama árbol vacío, tiene solo un nodo raíz y los nodos restantes se pueden dividir en conjuntos finitos disjuntos, llamados subárboles; 1. El nodo raíz del árbol no tiene predecesor, y los nodos distintos del nodo raíz tienen uno y solo un predecesor. 2. Todos los nodos del árbol pueden tener cero o más sucesores; el árbol es adecuado para representar datos con un; estructura jerarquica; 3. Terminología: 1. Ancestro-descendiente (puede abarcar varios niveles, siempre que exista una relación de arriba hacia abajo); 2. El número de hijos de un nodo en el árbol se denomina grado del nodo; el grado máximo de un nodo en el árbol es el grado del árbol; 3. Los nodos con grado> 0 se denominan nodos de rama (nodos no terminales); los nodos con grado 0 se denominan nodos hoja (nodos terminales); 4. El nivel de nodos se define a partir de la raíz del árbol, siendo el nodo raíz el primer nivel. Los nodos cuyos nodos padres están en el mismo nivel son primos entre sí; La profundidad de un nodo se acumula capa por capa comenzando desde el nodo raíz y de arriba a abajo (imagine que las raíces del árbol se vuelven más profundas hacia abajo). La altura del nodo se acumula capa por capa comenzando desde el nodo de la hoja y de abajo hacia arriba (se ve muy alto de abajo hacia arriba) La altura o profundidad de un árbol es el número máximo de niveles de nodos en el árbol; 4. Árboles ordenados y árboles desordenados, los árboles ordenados no son intercambiables 5. Ruta y longitud de la ruta: la ruta se compone de la secuencia de nodos que pasan entre los dos nodos, y la longitud de la ruta es el número de bordes pasados. 6. Bosque: una colección de m árboles separados. Siempre que se elimine el nodo raíz, el árbol se convierte en un bosque; por el contrario, siempre que se agregue un nodo raíz a m árboles, el bosque se convierte en un árbol.
naturaleza
naturaleza: 1. El número de nodos del árbol es igual a la suma de los grados de todos los nodos (número total de ramas) 1 (nodo raíz) 2. El i-ésimo nivel de un árbol con grado m tiene como máximo m^i-1 nodos (i>1) 3. Un árbol m-ario con altura h tiene como máximo (m^i - 1)/(m-1) nodos. 4. La altura mínima de un árbol m-ario con n nodos es
estructura de almacenamiento
1. Almacenamiento secuencial: notación principal: una estructura grande establece una estructura pequeña, y la estructura pequeña son los datos y los punteros principales; la estructura grande es una matriz compuesta por múltiples estructuras pequeñas y el número de nodos; #definir Max_Tree_SIze 100 estructura typedef { datos de tipo elemento; padre entero; }PTNodo; estructura typedef { Nodos PTNode[Max_Tree_Size]; int n; }PTárbol; Esta estructura aprovecha el hecho de que cada nodo (excepto el nodo raíz) tiene solo un padre y puede obtener rápidamente el nodo padre de cada nodo, pero requiere atravesar todo el árbol para encontrar los hijos del nodo.
2. En la representación secundaria: conecte los nodos secundarios de cada nodo con una única lista vinculada para formar una estructura lineal. Hay n listas vinculadas para n nodos (la lista vinculada secundaria de un nodo hoja es una lista vinculada vacía); Este método es muy sencillo para encontrar hijos, pero encontrar padres requiere atravesar las n listas vinculadas de niños a las que apuntan los punteros de listas vinculadas de niños en n nodos; Similar a la tabla crítica
3. Representación de hermano menor: también conocida como representación de árbol binario. estructura typedef CSNode{ datos de tipo elemento; estructura CSNode *primerhijo,*siguientehermano; }NodoCS,*ÁrbolCS; Implemente convenientemente la operación de convertir un árbol en un árbol binario El niño izquierdo tiene un hermano.
Operaciones: Conversión de árboles, bosques y árboles binarios
Conversión entre árboles y árboles binarios; tanto los árboles como los árboles binarios se pueden representar mediante listas enlazadas binarias. La estructura física es la misma, pero las reglas para convertir un árbol en un árbol binario son diferentes: hijo izquierdo y hermano derecho. Dado que el nodo raíz no tiene hermanos, el nodo raíz no tiene un subárbol derecho; pasos de conversión de escritura a mano: 1. Conecte una línea entre los nodos hermanos 2. Conserve solo la conexión entre cada nodo y su primer hijo, y use otro Borre todas las conexiones de los hijos. 3. Tome la raíz del árbol como eje y gírelo 45 grados en el sentido de las agujas del reloj.
Conversión de bosque a árbol binario 1. Primero convierta cada árbol del bosque en un árbol binario. 2. La raíz de cada árbol se considera una relación entre hermanos y los árboles posteriores se consideran secuencialmente como el nodo derecho del árbol anterior. 3. Gire la raíz del primer árbol 45 grados en el sentido de las agujas del reloj.
Convierta un árbol binario en un bosque: 1. Desconecte el subárbol derecho del nodo raíz uno por uno hasta que solo quede un nodo raíz sin un subárbol derecho. 2. Convierta todos los árboles binarios desconectados en árboles para obtener el bosque original. Convertir un árbol binario en un árbol o bosque es único
Convertir árbol binario en árbol 1. Elimine todos los nodos derechos, nodos derechos y nodos derechos del subárbol izquierdo del nodo raíz. . . todos como hijos de raíz 2. Ajustar en secuencia
atravesar
recorrido del árbol
Primero el recorrido de la raíz. Equivalente al recorrido de preorden del árbol binario correspondiente
recorrido de la raíz posterior ¡Equivalente al recorrido en orden del árbol binario correspondiente! ! ! ! !
recorrido de nivel
Viajando por el bosque
Preordenar recorrido del bosque. De adelante hacia atrás, cada árbol se atraviesa primero desde la raíz.
Atravesando el bosque en secuencia. Recorre la raíz de cada árbol de adelante hacia atrás (el bosque dice que el recorrido en orden es relativo al árbol binario correspondiente)
Aplicación de árboles - búsqueda sindical.
Union-find es una representación de conjunto simple que admite 3 operaciones La definición de estructura del conjunto de búsqueda de unión: #definir TAMAÑO 100 int UFSets[TAMAÑO]; Un conjunto en la búsqueda de unión es un árbol y varios conjuntos forman un bosque.
Operación de inicialización (S es un conjunto de búsqueda de unión), inicializa cada elemento del conjunto S en un subconjunto con un solo elemento, S es el conjunto completo, que es un bosque, almacenado en la matriz de representación principal (ambos son - 1 en este momento) void Inicial(int S[]){ para(int i=0;i<tamaño;i) S[i] = -1; }
Operación de búsqueda, encuentra la subcolección donde se encuentra el elemento único x en el conjunto S y devuelve el nombre de la subcolección. int Buscar(int S[],int x){ while(S[x]>=0)//Bucle para encontrar la raíz de x x = S[x];//Asigna x a la raíz de x devolver x; }
La operación de unión fusiona el subconjunto root2 del conjunto S en root1. Requiere que root1 y root2 no se crucen entre sí; de lo contrario, no se fusionarán. Unión vacía (int S [], int Raíz1, int Raíz2) { //Requerir que Root1 y Root2 sean diferentes S[Root2] = Root1; //Conectar Root2 a otro Root1 } El valor del nodo raíz de cada árbol en el bosque en la matriz de representación principal es negativo y el valor absoluto es el número total de nodos debajo de la raíz.
Árbol binario
Concepto: cada nodo tiene como máximo dos subárboles y el orden no se puede invertir. El orden de los nodos en un árbol binario no es relativo a otro nodo, sino que está determinado. (distinto de un árbol de grado 2); Varios árboles binarios especiales: 1. Árbol binario completo: cada nivel contiene la mayor cantidad de nodos 2. Árbol binario completo: los números de secuencia 1-n corresponden completamente a los nodos del árbol binario completo 3. Las claves de todos los nodos en el subárbol izquierdo del árbol de clasificación binaria son más pequeñas que las claves del nodo raíz y las claves de la derecha son mayores que la raíz. 4. Árbol binario equilibrado: la diferencia de profundidad entre el subárbol izquierdo y el subárbol derecho de cualquier nodo del árbol no excede 1 5. Árbol binario de pistas
Árbol binario de pistas (desconocido, por lo que se ha ampliado especialmente) estructura typedef TreadNode{ datos de tipo elemento; struct TreadNode *lchild,*rchild; int ltag,rtag; }Nodo de hilo,*Árbol de hilo; Una lista binaria enlazada formada con una estructura de nodos como estructura de almacenamiento del árbol binario se denomina lista enlazada de pistas; los punteros que apuntan al predecesor y al sucesor del nodo se denominan pistas. Un árbol binario con pistas se llama árbol binario de pistas.
Características y Definición: Utilice el puntero nulo del árbol binario para almacenar el puntero predecesor o sucesor, de modo que pueda recorrerse tan fácilmente como atravesar una lista vinculada. ¡El árbol binario de pistas acelera la búsqueda del predecesor y sucesor de un nodo! Regulación: si no hay un subárbol izquierdo, deje que lchild apunte a su nodo predecesor; si no hay un subárbol derecho, deje que rchild apunte a su nodo sucesor; (lchild,ltag,data,rtag,rchild) ltag = 0,lchild se refiere al hijo izquierdo, ltag = 1,lchild se refiere al nodo predecesor rtag = 0, rchild se refiere al hijo correcto, rtag = 1, rchild se refiere al sucesor del nodo
Construcción de un árbol binario de pistas en orden. La esencia de dar pistas a un árbol binario es atravesar el árbol binario una vez anular InTread(ThreadTree &p,ThreadTree &pre){ si(p!=NULL){ InThread(p->lchild,pre); si(p->lniño == NULL){ p->lniño = pre; p->letiqueta = 1; } if(pre!=NULL && pre->siguiente->rchild == NULL){ pre->rniño = p; pre->rtag = 1; } pre=p; InThread(p->rchild,pre); } } void CreateInThread (ThreadTree T) { ThreadTree pre = NULL; si(T!=NULL){ EnHilo(T,pre); pre->rchild = NULL; pre->rtag = 1; } } También puede agregar un nodo principal a la lista de pistas del árbol binario, dejar que lchild se refiera al nodo raíz del árbol binario y rchild se refiera al último nodo visitado del árbol binario; Además, el lchild del primer nodo visitado y el rchild del último nodo visitado en el árbol binario apuntan al nodo principal. Se convierte en una lista enlazada de pistas bidireccional, lo que facilita el recorrido del árbol binario de pistas de adelante hacia atrás y de atrás hacia adelante.
Recorrido de árboles binarios con pistas de orden. Los nodos del árbol binario de pistas en orden ocultan la información predecesora y sucesora del árbol binario de pistas. 1. Encuentre el primer nodo debajo de la secuencia en orden en el árbol binario de pistas en orden: ThreadNode *Primernodo(ThreadNode *p){ while(p->ltag == 0) p = p->lniño; devolver p; }//Encuentra el nodo inferior izquierdo (no necesariamente un nodo hoja) 2. Encuentre el sucesor del nodo p en la secuencia en orden en el árbol binario de pistas en orden. ThreadNode *Siguientenodo(ThreadNode *p){ if(p->rtag == 0) return Primernodo(p->rchild); de lo contrario devolver p->rchild; } 3. Función principal void Inorder(ThreadNode *T){ for(ThreadNode *p = Primernodo(T);p!=NULL;p-Siguientenodo(p)) visita(p); }
Árbol binario de pistas de preorden y árbol binario de pistas de postorden Solo necesita cambiar el segmento de código de la transformación de subprocesos y la posición donde se llaman las funciones recursivas de los subárboles izquierdo y derecho de subprocesos. La dirección que apunta a NULL puede ser diferente: un predecesor se refiere a NULL y un sucesor se refiere a NULL; El predecesor y el sucesor específicos deben analizarse en detalle según el orden transversal; Especial: para encontrar el sucesor en el árbol binario de pistas de post-orden, necesita conocer los padres del nodo, es decir, necesita usar una lista enlazada de tridente con un campo de bandera como estructura de almacenamiento (la lista enlazada de tridente tiene tres campos y se agrega un puntero al nodo principal)
naturaleza: 1. El número de nodos hoja de un árbol binario no vacío es igual al número de nodos con grado 2 1, es decir, n0 = n2 1 2. El k-ésimo nivel de un árbol binario no vacío tiene como máximo 2^(k-1) nodos. 3. Un árbol binario no vacío con altura h tiene como máximo 2^h-1 nodos. 4. Árbol binario completo: i>1 El nodo padre está delimitado por i/2. i es un número par, i/2; i es un número impar, (i-1)/2; 2i<=n El hijo izquierdo de i es 2i; de lo contrario, no queda ningún hijo izquierdo. 2i 1<=n el hijo correcto de i es 2i 1, de lo contrario no hay hijo correcto 5. Dos fórmulas: 2^(h-1)-1<n<=2^h-1;2^(h-1) <=n <2^h
Estructura de almacenamiento: 1. Estructura de almacenamiento secuencial: Los árboles binarios completos y los árboles binarios completos son adecuados para estructuras de almacenamiento secuencial; los árboles binarios generales necesitan agregar muchos nodos vacíos (se recomienda comenzar el almacenamiento con 0 en la matriz desde el subíndice 1, que está en línea con las propiedades de); un árbol binario completo i 2. Estructura de almacenamiento en cadena: La utilización del espacio es mayor que la estructura de almacenamiento secuencial (árbol binario general) estructura typedef BiTNode{ datos de tipo elemento; estructura BiTNode *lchild,*rchild; }BiTNodo,*BiTree;
funcionar
atravesar
DFS Cada nodo se visita una vez y solo una vez. La complejidad del tiempo y la complejidad del espacio son O (n). La profundidad de la pila de trabajo recursiva es la profundidad del árbol.
recorrido de preorden anular pedido anticipado (BiTree T) { si(T!=NULL){ visita(T); Reserva(T->lchild); PreOreder(T->rchild); } } No recursivo: void PreOrder2(BiTree T){ InitStack(S);BiTree p = T; mientras(p || !está vacío(S)){ si p){ visitar(p);Empujar(S,p); p = p->lniño; } demás{ Pop(S,p); p = p->rniño; } } }
recorrido en orden anular en orden (BiTree T) { si(T!=NULL){ En orden(T->lchild); visita(T); En orden(T->rchild); } } No recursivo: anular InOrder2(BiTree T){ InitStack(S);BiTree p = T; while(p || !isEmpty(S)){//p no está vacío o la pila no está vacía if(p){ //Todo el camino hacia la izquierda Empujar(S,p); p = p->lniño; } demás{ Pop(S,p);visita(p); p = p->rchild; //p va al subárbol derecho } } }
recorrido posterior al pedido void PostOrder(BiTree T){ si(T!=NULL){ PostOrden(T->lchild); PostOrden(T->rchild); visita(T); } }
BFS
recorrido de nivel Orden de nivel vacía (BiTree T) { ColaInit(Q); BiTree p = T; Poner en cola(Q,p); mientras (! está vacío (Q)) { Sacar de la cola(Q,p); visita(p); if(p->lchild != NULL) Enqueue(Q,p->lchild); if(p->rchild != NULL) Enqueue(Q,p->rchild); } }
solicitud
Árbol de clasificación binaria BST (árbol de búsqueda binaria)
búsqueda de BST BSTNode *BST_Search(BiTree T, clave ElemType){ mientras(T!=NULL&&key!=T->datos){ if(clave<T.datos) T = T->lniño; else T=T->rniño; } devolver T; }
Inserción de BST: el nodo insertado debe ser un nodo hoja recién agregado int BST_Insert(BiTree &T,Tipo de clave k){ si(T==NULL){ T = (BiTree)malloc(tamañode(BSTNode)); T->tecla = k; T->lniño = T->rniño = NULL; devolver 1; } de lo contrario, si (k==T->clave) devuelve 0; de lo contrario, si (k<T->clave) devuelve BST_Insert(T->lchild,k); de lo contrario, devuelve BST_Insert(T->rchild,k); }
Estructura de BST void Creat_BST(BiTree &T,KeyType str[],int n){ T = NULO; intyo =0; mientras(yo<n){ BST_Insert(T,cadena[i]); i; } }
Eliminación de BST
El nodo eliminado es un nodo hoja y se elimina directamente.
Si el nodo eliminado z tiene solo un subárbol izquierdo o un subárbol derecho, deje que su subárbol izquierdo o subárbol derecho reemplace al de z.
Si el nodo eliminado z tiene subárboles izquierdo y derecho, reemplace z con el sucesor directo (o predecesor directo) de z, y luego elimine este sucesor directo (o predecesor directo) de BST, cambiando así a las dos primeras situaciones.
Análisis de eficiencia de búsqueda de BST
La eficiencia de búsqueda de BST depende principalmente de la altura del árbol. Si la diferencia de altura entre los subárboles izquierdo y derecho de BST nunca excede 1, entonces dicho árbol de clasificación binaria se denomina árbol binario equilibrado y la longitud de búsqueda promedio es O (log2n); Si el BST es un árbol único, la longitud de búsqueda promedio es O(n)
La longitud de búsqueda promedio ASL=∑PiCi (i=1,2,3,…,n)Pi es la probabilidad del i-ésimo elemento de datos en la tabla de búsqueda, y Ci es el número de comparaciones que se han realizado al encontrar el i-ésimo elemento de datos.
Comparación entre BST y búsqueda binaria: El árbol de decisión de la búsqueda binaria es único, pero la búsqueda de BST no es única (el orden de inserción afecta la forma del árbol); Inserción y eliminación: el objeto de la búsqueda binaria es una lista de secuencia ordenada, que requiere que O (n) no necesite mover el nodo, solo necesita modificar el puntero para completar, que es O (log2n); Resumen: cuando la tabla ordenada es una tabla de búsqueda estática, es adecuada para el almacenamiento de tablas secuenciales y utiliza búsqueda binaria; cuando la tabla ordenada es una tabla de búsqueda dinámica, se debe seleccionar un árbol de clasificación binaria como estructura lógica.
árbol binario equilibrado
Diseñado para evitar reducir el rendimiento de los árboles de clasificación binaria. Definición: el valor absoluto de la diferencia de altura entre las palabras izquierda y derecha de cualquier nodo no excede 1, lo que se denomina árbol equilibrado; La diferencia de altura entre el subárbol izquierdo y el subárbol derecho de un nodo es el factor de equilibrio del nodo. El factor de equilibrio de un nodo de árbol binario equilibrado sólo puede ser -1 0 1, por lo tanto, un árbol binario equilibrado puede definirse como un; árbol vacío.
Inserción en un árbol binario equilibrado: El objeto de cada ajuste es el subárbol mínimo desequilibrado, es decir, el subárbol cuyo valor absoluto del factor de equilibrio más cercano al nodo insertado en la ruta de inserción es mayor que 1 como raíz. La primera mitad del proceso de inserción de un árbol binario equilibrado es la misma que la de un árbol binario ordenado, pero si se produce un desequilibrio después de la inserción, se dividirá en cuatro situaciones. (BST mejorada); Aquí LL RR LR RL se refiere a la posición del nodo insertado en relación con el nodo raíz del subárbol mínimo desequilibrado (la posición de un determinado subárbol de un hijo)
Rotación equilibrada LL (rotación única derecha) El nodo se inserta en la parte inferior izquierda (no necesariamente inmediatamente adyacente) del subárbol izquierdo de A (debe estar inmediatamente adyacente a él), y el nodo B en la primera L (el hijo izquierdo del nodo raíz del subárbol mínimo desequilibrado) es girado a la derecha para reemplazar A, y A es el hijo derecho de B, B originalmente tenía hijos como el subárbol izquierdo de A.
Giro equilibrado RR (giro único a la izquierda) El nodo se inserta en la parte inferior derecha del hijo derecho de A. El nodo B en la primera R se gira hacia la izquierda para reemplazar A. A se usa como el subárbol izquierdo de B, y el subárbol izquierdo original de B se usa como el subárbol derecho de A. ps: los modos LL y RR ayudan al hijo B izquierdo/derecho del nodo del subárbol desequilibrado A a "surgir"
Rotación equilibrada LR (doble rotación primero a la izquierda y luego a la derecha) Se inserta un nodo en el subárbol derecho del hijo izquierdo del nodo A. Primero, gire el nodo raíz C del subárbol derecho del hijo izquierdo B de A en la dirección superior izquierda hasta la posición de B, y luego gire el nodo C en la dirección superior derecha hasta la posición del nodo A.
Rotación equilibrada RL (doble rotación derecha y luego izquierda) Se inserta un nodo en el subárbol izquierdo del hijo derecho del nodo A. Primero, gire el nodo raíz del subárbol izquierdo C del hijo derecho B del nodo A a la posición de B, y luego gire C a la posición del nodo A. ps: cuando se rotan LR y RL, si el nuevo nodo se inserta en el subárbol izquierdo de C o en el subárbol derecho de C no afecta el proceso de rotación. Los modos LR y RL son para ayudar al nodo raíz C del subárbol derecho/izquierdo del hijo B izquierdo/derecho del subárbol desequilibrado a ser "superior".
Encuentra un árbol binario equilibrado Igual que la búsqueda de árbol ordenado binario; La profundidad máxima de un árbol binario equilibrado que contiene n nodos es O (log2n), por lo que la longitud máxima de búsqueda también es O (log2n); El número máximo de comparaciones necesarias para encontrar un árbol binario equilibrado con un número determinado de nodos.
Un árbol binario equilibrado de altura h. Hay como máximo 2^h - 1 nodos; Hay al menos h(n) nodos, h(n)=h(n-1) h(n-1) 1,h(0)=0,h(1)=1,h(2)=2c
árbol negro rojo
Concepto de árbol negro rojo
Operaciones básicas de árboles rojo-negros.
propiedades del árbol rojo negro
Aplicaciones de los árboles rojo-negros.
Análisis del rendimiento del árbol rojo-negro.
Árboles de Huffman y codificación de Huffman
Definición del árbol de Huffman A un nodo en el árbol se le asigna un valor que representa un cierto significado, que se denomina peso del nodo; El producto de la longitud de la ruta desde la raíz del árbol hasta cualquier nodo (número de aristas pasadas) y el peso del nodo se denomina longitud de ruta ponderada del nodo; La suma de las longitudes de ruta ponderadas de todos los nodos de hoja en el árbol se denomina longitud de ruta ponderada del árbol y se registra como WPL (longitud de ruta ponderada del árbol); En un árbol binario con n nodos de hoja ponderados, el árbol binario con la longitud de ruta ponderada más pequeña WPL se llama árbol de Huffman. También llamado árbol binario óptimo.
La estructura del árbol de Huffman. Dados n nodos con pesos w1,w2,..,wn respectivamente, Cree un nuevo nodo raíz y seleccione los dos nodos con el peso más pequeño cada vez para formar un árbol binario. Agregue los pesos de los dos nodos y trátelos como los nuevos pesos del nodo raíz. se elimina el nodo raíz y se agrega un nuevo nodo raíz con peso; Repita este proceso hasta que no queden más nodos independientes.
Propiedades de los árboles de Huffman 1. Cada nodo inicial eventualmente se convierte en un nodo hoja y cuanto menor sea el peso, mayor será la longitud del camino desde el nodo hasta el nodo raíz. 2. Durante el proceso de construcción, se crean un total de n-1 nuevos nodos (nodos de doble rama), por lo que el número total de nodos del árbol de Huffman es 2n-1. 3. Cada construcción selecciona 2 árboles como hijos del nuevo nodo, por lo que no hay ningún nodo con grado 1 en el árbol de Huffman.
Codificación Huffman
Varios conceptos relacionados: Codificación de longitud fija: representación binaria de igual longitud de cada carácter Codificación de longitud variable: diferentes caracteres se representan mediante bits binarios de diferentes longitudes. La codificación de longitud variable es mucho mejor que la codificación de longitud fija, porque a los caracteres con alta frecuencia de aparición se les pueden asignar códigos cortos y a los códigos de baja frecuencia se les pueden asignar códigos largos, lo que acorta la longitud promedio de codificación de los caracteres y comprime los datos. La codificación de Huffman es una codificación de compresión de datos muy eficaz y ampliamente utilizada. Ninguno de los códigos es el código del prefijo, es el código del prefijo: decodificarlo: identificarlo de adelante hacia atrás, y traducirlo al código original;
El árbol de Huffman desciende desde la raíz, asignando 0 a un lado y 1 al otro (ya sea 0 o 1 a la izquierda y a la derecha, siempre que estén siempre unificados); El WPL del árbol de Huffman puede considerarse como la longitud del código binario obtenida mediante la codificación final; Los árboles de Huffman no son únicos, WPL debe ser óptimo
6 fotos
Conceptos básicos de gráficos.
G = (V, E), conjunto de vértices V, conjunto de aristas E; G ; |V| representa el número de vértices, |E| representa el número de aristas Una tabla lineal puede ser una tabla vacía y un árbol puede ser un árbol vacío, pero un gráfico no puede ser un gráfico vacío; El conjunto de vértices V en el gráfico no debe estar vacío y E puede estar vacío.
gráfico dirigido, gráfico no dirigido Gráfico dirigido: E es un borde dirigido (arco) y un arco es un par ordenado de vértices, denotado como <v,w> Gráfico no dirigido: E es un borde no dirigido (arista) y un borde es un par de vértices desordenados, registrados como (v, w)
Gráfico simple, gráfico múltiple Diagrama sencillo: 1. No hay aristas duplicadas (las aristas que apuntan entre sí entre dos nodos adyacentes en un gráfico dirigido no se cuentan como aristas duplicadas) 2. No hay arista desde el vértice hacia sí mismo. Múltiples tramas: 1. El número de aristas entre dos vértices es mayor que 1. 2. Permitir que los vértices se relacionen entre sí a través de una arista. Las definiciones de gráficos múltiples y gráficos simples son relativas. En la estructura de datos solo se analizan los gráficos simples.
Gráfico completo (gráfico completo simple) Para gráficos no dirigidos, el rango de valores de |E| es 0-n(n-1)/2, Un gráfico no dirigido con n (n-1)/2 aristas se llama gráfico completo y hay una arista entre dos vértices cualesquiera; Para gráficos dirigidos, el rango de valores de |E| es 0-n(n-1), Un gráfico dirigido con n (n-1) arcos se llama gráfico completo dirigido y hay un arco opuesto entre dos vértices cualesquiera.
subtrama G=(V,E),G'=(V',E'); Si V' es un subconjunto de V y E' es un subconjunto de E, entonces G' se llama subgrafo de G. Si se satisface V(G')=V(G), entonces G' se denomina subgrafo generado de G; Ningún subconjunto de V y E puede formar un subgrafo G, porque los vértices asociados con algunas aristas en el subconjunto de E pueden no estar en este subconjunto de V.
Conectividad, gráficos conectados y componentes conectados Si hay un camino desde el vértice v al vértice w, entonces se dice que v y w están conectados. Si dos vértices cualesquiera en G están conectados, se convierte en un gráfico conexo; de lo contrario, es un gráfico desconectado. El subgrafo conectado máximo en un gráfico no dirigido se llama componente conectado. Un gráfico conectado tiene al menos n-1 aristas. Si el número de aristas es menor que n-1, debe ser un gráfico no conexo; ¿Cuántas aristas puede tener como máximo un gráfico desconectado? : El estado crítico de n-1 vértices que forman un gráfico completo (agregando cualquier borde para formar un gráfico conectado)
Subgrafo conectado máximo: un subgrafo conectado en el gráfico G que no está contenido en otros subgrafos conectados (el máximo aquí no es el máximo de una cantidad específica, sino una relación que no está incluida, que en realidad son todos los vértices en el subgrafo conectado y el borde existir) Subgrafo mínimamente conectado: el árbol de expansión del gráfico G es un subgrafo mínimamente conectado
Gráfico fuertemente conectado, componentes fuertemente conectados En un gráfico dirigido, si un par de vértices v a w y w a v tienen caminos, entonces se dice que los dos vértices están fuertemente conectados. Si cualquier par de vértices del gráfico está fuertemente conexo, el gráfico se denomina gráfico fuertemente conexo. El subgrafo máximo fuertemente conexo en un gráfico dirigido se denomina componente fuertemente conexo del gráfico dirigido; La situación con la menor cantidad de aristas cuando un gráfico dirigido está fuertemente conectado: se necesitan al menos n aristas para formar un ciclo
árbol que se extiende, bosque que se extiende El árbol de expansión de un gráfico conectado es un subgrafo conectado mínimo que contiene todos los vértices del gráfico. Si el número de vértices fijos es n, entonces el árbol de expansión tiene n-1 aristas. Si se corta un borde del árbol de expansión, se convertirá en un subgrafo no conectado; si se agrega un borde, se formará un ciclo. En un gráfico desconectado, el árbol de expansión de componentes conectados constituye el bosque de expansión del gráfico desconectado.
Grado de vértice, grado interno y grado externo Gráfico no dirigido: TD (v), la suma de los grados de todos los vértices de un gráfico no dirigido es igual a 2 veces el número de aristas Gráfico dirigido: el grado del vértice v se divide en ID de grado interno (v) y OD (v) de grado externo. El grado del vértice v es igual a la suma de grados interno y externo: TD (v). =ID(v) OD(v), el grado de entrada de todos los vértices de un gráfico dirigido es igual al grado de salida igual al número de aristas
Bian's Quanhe.com Cada borde se puede marcar con un valor numérico que tiene un cierto significado, que se llama peso del borde. El gráfico con bordes ponderados se llama gráfico ponderado, también conocido como red.
Gráfico denso, gráfico disperso Un gráfico con pocas aristas se llama gráfico disperso y lo contrario se llama gráfico denso. Generalmente, cuando G satisface |E|<|V|log|V|, G se considera un gráfico disperso.
Caminos, longitudes de caminos y bucles Ruta: una ruta desde el vértice vp a vq se refiere a la secuencia de vértices vp,vi1...vim,vq, El número de aristas en un camino se llama longitud del camino. Un camino cuyo primer y último vértice son iguales se llama ciclo o ciclo. Si un gráfico tiene n vértices y más de n-1 aristas, entonces el gráfico debe tener un ciclo.
Camino simple, bucle simple En una secuencia de caminos, un camino cuyos vértices no aparecen repetidamente se denomina camino simple; Excepto el primer vértice y el último vértice, el ciclo en el que los vértices restantes no se detienen en ese gráfico se llama ciclo simple.
distancia Si existe el camino más corto desde el vértice u al vértice v, entonces la longitud de este camino es la distancia de u a v; No hay ningún camino de u a v, entonces la distancia se registra como ∞
árbol dirigido Un gráfico dirigido en el que el grado de entrada de un vértice es 0 y el grado de salida de los vértices restantes es 1 se llama árbol dirigido.
Almacenamiento de gráficos
método de matriz de adyacencia
Se refiere al uso de una matriz unidimensional para almacenar la relación entre los vértices en el gráfico y al uso de una matriz bidimensional para almacenar la información de los bordes en el gráfico (la relación de adyacencia entre los vértices La matriz bidimensional que almacena la). La relación de adyacencia entre vértices se llama matriz de adyacencia. La estructura de almacenamiento de todo el gráfico G: #definir MaxVertexNum 100 typedef char VertexType; typedef int tipo de borde; estructura typedef { VertexType Vex[MaxVertexNum]; //Tabla de vértices EdgeType Edge[MaxVertexNum][MaxVertexNum];//matriz de adyacencia, tabla de borde int vexnum, arcnum; }MGráfico;
Aviso: 1. En aplicaciones simples, se puede utilizar directamente una matriz bidimensional como matriz de adyacencia del gráfico. 2. En un gráfico no dirigido, la matriz de adyacencia es una matriz simétrica (única) y puede comprimirse y almacenarse. 3. Cuando la matriz de adyacencia solo indica si el borde existe, ElemType puede adoptar el tipo de enumeración de 0 y 1. 4. La complejidad espacial de la representación de la matriz de adyacencia es O (n ^ 2), n es el número de vértices |V|
Características de la representación matricial de adyacencia: 1. El número de elementos distintos de cero (o distintos de ∞) en la fila i (o columna j) de un gráfico no dirigido representa el grado TD (v) del nodo. El número de elementos distintos de cero (o distintos de ∞) en la i-ésima fila y la j-ésima columna del gráfico dirigido representa el OD (v) de grado externo y el ID (v) de grado interno del nodo. 2. Al utilizar una matriz de adyacencia para almacenar un gráfico, es fácil determinar si existe una conexión de borde entre dos vértices cualesquiera del gráfico, pero para determinar cuántos bordes hay en el gráfico, es necesario detectar cada elemento por fila; y columna, lo que requiere mucho tiempo. 3. Los gráficos densos son adecuados para la representación de almacenamiento utilizando matrices de adyacencia. 4. Suponga que la matriz de adyacencia del gráfico G es A, y que el elemento A^n[i][j] de A^n es igual al número de caminos de longitud n desde el vértice i al vértice j.
método de lista de adyacencia
Cuando un gráfico es un gráfico disperso, el uso del método de lista de adyacencia ahorra mucho espacio; el método de lista de adyacencia del gráfico combina métodos de almacenamiento secuencial y almacenamiento en cadena; Cree una lista enlazada individualmente para cada vértice del gráfico. El nodo en la i-ésima lista enlazada individualmente se adjunta al borde vi del vértice. Esta lista enlazada individualmente es la lista de bordes del vértice vi (un gráfico dirigido se llama. lista de bordes salientes) (la lista de bordes es "Borde" es realmente sorprendente) Cada nodo en la tabla de bordes representa un borde, pero almacena un número de vértice y omite otro número de vértice (en la tabla de vértices) El puntero principal de la tabla de bordes y la información de datos de vértice del gráfico G se almacenan secuencialmente (llamada tabla de vértices); #definir MaxVertexNum 100 typedef struct ArcNode{//nodo de tabla de borde int adjvex;// La posición del vértice señalado por el arco estructura ArcNode *siguiente; //Info Typeinfo; //El peso del borde de la red }Nodo de arco; estructura typedef VNode{ Datos VertexType;//Información de vértice struct VNode *first;//Puntero al primer arco adjunto al vértice }VNodo, AdjList[MaxVertexNum]; estructura typedef { AdjList vértices;//lista de adyacencia int vexnum, arcnum;// El número de vértices y arcos del gráfico }ALGráfico
Características: 1. Para un gráfico no dirigido, el espacio de almacenamiento requerido es O(|V| 2|E| (cada borde aparece dos veces en la lista de adyacencia) Para un gráfico dirigido, el espacio de almacenamiento requerido es O(|V| |E|); 2. Para gráficos dispersos, el uso de listas de adyacencia puede ahorrar mucho espacio de almacenamiento. 3. En la lista de adyacencia, dado un vértice, es fácil encontrar todos sus bordes en la matriz de adyacencia, es necesario escanear una fila, O (n); Si desea determinar si hay un borde entre dos vértices dados, puede encontrarlo rápidamente en la matriz de adyacencia (un poco de acceso aleatorio, mientras que la matriz de adyacencia necesita encontrar otro nodo correspondiente al nodo correspondiente en la tabla de borde); , que es más eficiente. 4. En un gráfico dirigido, encontrar el grado exterior de un vértice solo requiere calcular el número de nodos en su lista de adyacencia adyacente, pero encontrar el grado exterior requiere recorrer todas las listas de adyacencia, por lo tanto, se puede usar la lista de adyacencia inversa; acelerar el grado de entrada de una solución de vértice. 5. La lista de adyacencia del gráfico no es única y el orden de conexión de los nodos de borde es arbitrario.
método de lista cruzada
Una lista entrecruzada es una estructura de almacenamiento vinculada de un gráfico dirigido. Cada arco del gráfico tiene un nodo. El nodo de arco tiene 5 campos: (tailvex, headvex, hlink, tlink, info) tailvex y headvex son respectivamente los vértices de origen e inserción del borde; hlink y tlink conectan respectivamente los nodos de borde de los mismos vértices de origen e inserción. El nodo de vértice tiene 3 campos: (datos, primero en entrar, primero en salir) La lista entrecruzada no es única, pero una lista entrecruzada representa un gráfico determinado. En la lista cruzada, es fácil encontrar el grado de entrada de V y el grado de salida de V.
método de lista múltiple de adyacencia
La lista múltiple de adyacencia es una estructura de almacenamiento en cadena de gráficos no dirigidos Nodos de borde (mark,ivex,ilink,jvex,jlink,info) mark es un campo de bandera, que puede marcar si este borde ha sido buscado; ivex y jvex son las posiciones de los dos vértices adjuntos al borde en el gráfico; ilink apunta al siguiente borde adjunto a ivex; adjunto al vértice jvex info hay un campo de puntero que apunta a diversa información relacionada con el borde; vértice (datos, primer borde)
Operaciones básicas en gráficos.
Adyacente(G,x,y);Vecinos(G,x);InsertVertex(G,x);DeleteVertex(G,x); AgregarBorde(G,x);QuitarBorde(G,x,y);PrimerVecino(G,x);SiguienteVecino(G,x); Get_edge_value(G,x,y);Establecer_edge_value(G,x,y,v);
Recorrido de gráficos A partir de un determinado vértice, visitar todos los vértices una vez y solo una vez es la base para resolver problemas de conectividad, clasificación topológica y algoritmos de ruta crítica; A diferencia del árbol, para evitar que se visite el mismo vértice varias veces, es necesario registrarlo después de atravesar un vértice y visitar la matriz auxiliar []; El recorrido del gráfico incluye BFS y DFS Usando la matriz de adyacencia, la complejidad del tiempo es O(n^2); usando la lista de adyacencia, la complejidad del tiempo es O(|V| |E|); BFS\DFS requiere espacio O(n) (La complejidad del tiempo y el espacio no tiene nada que ver con la selección del método BFS\DFS, depende principalmente del método de almacenamiento)
amplitud primera búsqueda Recorrido jerárquico en forma de árbol Acceda capa por capa, por lo que se necesita una cola auxiliar bool visitado[MAV_VERTEX_NUM]; void BFSTraverse(Gráfico G){ para(int i=0;i<G.vexnum;i) visitado[i] = FALSO; ColaInit(Q); para(int i=0;i<G.vexnum;i) if(!visitado[i]) BFS(G,i); } void BFS(Gráfico G,int v){ visita(v); visitado[v] = VERDADERO; Poner en cola(Q,v); mientras (! está vacío (Q)) { for(w=PrimerVecino(G,v);w>=0;w=SiguienteVecino(G,v,w)) si(!visita[w]){ visita(w); visitado[w] = VERDADERO; Poner en cola(Q,w); } } }
BFS resuelve el problema de la ruta más corta de fuente única void BFS_MIN_Distance(Gráfico G,int u){ para(i=0;i<G.vexnum;i) //d[i] representa el camino más corto de u a i d[i]=∞;//Inicializar longitud de ruta visitado[u]=VERDADERO; d[u]=0; En cola(Q,u); mientras (! está vacío (Q)) { DeQueue(Q,u); for(w=PrimerVecino(G,u);w>=0;w=SiguienteN vecino(G,u,w)) if(visitado[w]){ visitado[w]=TRUE; d[w]=d[u] 1;//longitud de la ruta 1 EnQueue(Q,w); } } }
árbol de expansión en anchura La matriz de adyacencia se almacena de forma única: su árbol de expansión en amplitud es único El almacenamiento de la lista de adyacencia no es único: su árbol de expansión de amplitud no es único
primera búsqueda en profundidad
Similar al recorrido de preorden de un árbol bool visitado[MAX_VERTEX_NUM]; void DFSTraverse(Gráfico G){ para(v=0;v<G.vexnum;v) visitado[v] = FALSO; para(v=0,v<G.vexnum;v) si(!visitado[v]) DFS(G,v); } void DFS(Gráfico G,int v){ visita(v); visitado[v] = VERDADERO; for(w=PrimerVecino(G,v);w>=0;w=SiguienteVecino(G,v,w)) si(!visitado[w]){ DFS(G,w); } }
Árboles y bosques que se extienden primero en profundidad Los gráficos conectados pueden generar árboles de expansión profundos y los gráficos desconectados pueden generar bosques.
conectividad grafica
Se pueden utilizar algoritmos de recorrido de gráficos para determinar la conectividad del gráfico. Si un gráfico no dirigido está conectado, comenzando desde cualquier vértice, se puede acceder a todos los vértices del gráfico mediante un solo recorrido; Si un gráfico dirigido está conectado y hay una ruta desde el punto inicial hasta cada vértice del gráfico, entonces se puede acceder a todos los vértices del gráfico.
Aplicación de diagramas
árbol de expansión mínimo Si se corta un borde, el árbol de expansión se convertirá en un gráfico desconectado; Si se agrega un borde, se formará un bucle a lo largo del camino; El árbol de expansión mínimo es especial. Se requiere que sea el árbol de expansión con la suma más pequeña de pesos de borde en el conjunto de árboles de expansión; Cuando los pesos de cada borde son diferentes, el árbol de expansión mínimo es único; en general, el árbol de expansión mínimo no es necesariamente único; La suma de los pesos de los bordes del árbol de expansión mínimo es siempre única y mínima. GENÉRICO_MST(G){ T=NULO; mientras que T no forma un árbol de expansión; Haga encontrar una ventaja de costo mínimo (u, v) y no se generará ningún bucle después de agregar T; T=T∪(u,v); }
Algoritmo prim (BFS) De manera similar a Dijkstra, inicialmente seleccione un vértice del gráfico y agréguelo al árbol T, luego seleccione un vértice más cercano al conjunto actual de vértices en T y agregue el vértice y el borde al árbol T el número de vértices en T; después de cada operación, el número de aristas aumenta en 1 hasta que todos los vértices del gráfico se agreguen al árbol T, T es el árbol de expansión mínimo; Debe haber n-1 aristas en T vacíoPrim(G,T){ T=conjunto vacío; U={W}; while((V-U)!=conjunto vacío){//Si el árbol no contiene todos los vértices Sea (u, v) la arista que hace u∈U y v∈(V-U) y tiene el peso más pequeño; T=T∪{(u,v)};//Los bordes se clasifican en árboles U=U∪{v};//Los vértices se clasifican en árboles } } La complejidad temporal del algoritmo de Prim es O(|V^2|) y no depende de |E|, por lo que es adecuado para resolver el árbol de expansión mínimo de gráficos con aristas densas.
algoritmo de Kruskal A diferencia del algoritmo de Prim, que se expande desde los vértices para generar un árbol de expansión mínimo, el algoritmo de Kruskal selecciona los bordes apropiados en orden creciente de pesos para construir un árbol de expansión mínimo. Inicialmente, cada vértice forma un componente conectado. Luego, en orden de pesos de borde de pequeño a grande, el borde que aún no se ha seleccionado y tiene el peso más pequeño se selecciona continuamente si el vértice adjunto al borde cae en un conectado diferente. componente del componente T, agregue este borde a T; de lo contrario, descarte este borde hasta que todos los vértices en T estén en un componente conectado; vacío Kruskal(V,T){ T=V; //Inicializa T, solo vértices numS=n;//Número de componentes conectados while(numS>1){//Si el número de componentes conectados es mayor que 1 Tome el borde (v, u) con el peso más pequeño de E si (v y u pertenecen a diferentes componentes conectados en T) T=T∪{(v,u)}; números--; } } } El algoritmo de Kruskal utiliza un montón para almacenar el conjunto de aristas, por lo que solo toma O (log | E |) tiempo seleccionar la arista con el peso más pequeño cada vez que agregar una nueva arista es similar al proceso de resolver una equivalencia; clase y se puede utilizar el método de búsqueda de unión Estructura de datos para describir T, la complejidad temporal es O (|E|log|E|), por lo que Kruskal es adecuado para gráficos con aristas dispersas y muchos vértices.
camino más corto El recorrido BFS en la figura anterior para resolver la ruta más corta es para gráficos no ponderados. Aquí analizamos la ruta más corta con la longitud de ruta ponderada más corta; Todos los algoritmos para resolver el camino más corto se basan en una propiedad: el camino más corto entre dos puntos también incluye los caminos más cortos entre otros vértices del camino.
Algoritmo de Dijkstra (BFS) Ruta más corta de fuente única; El algoritmo de Dijkstra establece un conjunto S para registrar los vértices del camino más corto que se ha obtenido. Inicialmente, el punto fuente v0 se coloca en S. Cada vez que se incorpora un nuevo vértice vi al conjunto S, se debe modificar el punto fuente v0. al vértice más corto actual en el valor de longitud de Lujin establecido no se permiten valores de peso negativos en los bordes. Configure dos matrices auxiliares: dist []: registra la longitud de la ruta más corta actual desde el punto de origen v0 a otros vértices. Estado inicial: si hay un arco de v0 a vi, entonces dist [i] es el peso del arco; ] a ∞. (dist[] solo retrocede un paso a la vez - idea BFS) ruta []: ruta [i] representa el nodo predecesor de la ruta más corta desde el punto de origen hasta el vértice i. Al final del algoritmo, se puede trazar el camino más corto desde el punto fuente v0 hasta el vértice vi. Al resolver manualmente, dibuje una matriz dict. Lo más importante es agregar el vértice al conjunto S cada vez que lo seleccione, actualizar la distancia de la ruta de cada punto y luego dar el siguiente paso comenzando desde el último vértice del conjunto. S. Complejidad del tiempo O(|V|^2)
algoritmo de floyd El camino más corto entre cada par de vértices. La recursión genera una secuencia de matriz cuadrada de orden n: A^-1,A^0,..A^N; donde A^k[i][j] representa la longitud desde el vértice i al j, y se utiliza el k-ésimo vértice; como Relajar los vértices medios La complejidad del tiempo es O(|V|^3) (equivalente a ejecutar el algoritmo de Dijkstra una vez para cada vértice O(|V^2|)*|V|), Los bordes con pesos negativos no están permitidos y pueden actuar sobre gráficos dirigidos\gráficos no dirigidos (convertidos en gráficos dirigidos con los mismos lados bilaterales)
Expresión de descripción de gráfico acíclico dirigido DAG (Gráfico de ciclo dirigido): No hay ciclo en un gráfico dirigido. Es una herramienta eficaz para describir expresiones que contienen subexpresiones comunes. Por ejemplo, una cadena de expresiones infijas se puede representar mediante un árbol binario (el nodo raíz almacena operadores y el subárbol almacena operandos). Al utilizar un gráfico acíclico dirigido, se pueden compartir las mismas subexpresiones.
secuencia topológica Una secuencia compuesta por vértices de un gráfico acíclico dirigido, cada vértice aparece solo una vez, y si el vértice A está clasificado delante del vértice B en la secuencia, entonces no hay una ruta de B a A en el gráfico. También puede entenderse como: una secuencia topológica es un ordenamiento de los vértices de un grafo acíclico dirigido. Cada red AOV tiene una o más secuencias ordenadas topológicamente.
Red AOV (Actividad en la red de vértices) Si se utiliza un DAG para representar un proyecto, sus vértices representan actividades y el borde dirigido <Vi, Vj> representa una relación en la que Vi debe preceder a Vj, entonces este tipo de gráfico se denomina red con vértices que representan actividades. Existen muchos algoritmos para la clasificación topológica de redes AOV, uno de los algoritmos más utilizados es: 1. Seleccione un vértice sin predecesor de la red AOV y envíelo 2. Elimine el vértice y todos los bordes dirigidos a partir de él de la red. 3. Repita el paso 1.2 hasta que la red AOV esté vacía o no haya vértices sin predecesores en la red actual (lo que indica que debe haber un ciclo en el gráfico) Dado que generar cada vértice requiere eliminar su borde inicial, la complejidad temporal de la clasificación topológica es O(|V| |E|) Además, la clasificación topológica también se puede implementar usando DFS (comenzando desde los vértices con un grado de entrada de 0 y un grado de salida de no 0 uno por uno, y fallará siempre que se retroceda hasta que se complete un trazo). ) clasificación topológica inversa Comience desde el vértice con grado 0 La red AOV utiliza almacenamiento de matriz de adyacencia. Si es una matriz triangular, debe haber una secuencia topológica y viceversa.
bool TopologicalSort(Gráfico G){ pila inicial(S); para(int i=0;i<G.vexnum;i) si(grado[i]==0) Push(S,i);//Empuja todos los vértices con grado 0 en la pila recuento int = 0; mientras (! está vacío (S)) { Pop(S,i); print[count]=i;//Vértices de salida for(p=G.vertices[i].firstarc;p;p=p->nextarc){ // Disminuye el grado de entrada de todos los vértices apuntados por i en 1 y empuja los vértices cuyo grado de entrada se reduce a 0 en la pila v = p->adjvex; if(!(--grado[v])) Empujar(S,v); } si(cuenta<G.vexnum) falso retorno; demás devolver verdadero; }
Camino critico
Red AOE (Actividad en la red perimetral) Los eventos se representan mediante vértices, las actividades se representan mediante aristas dirigidas y el costo de completar la actividad se representa mediante el peso en la arista (como el tiempo necesario para completar la actividad). Dos propiedades de la red AOE: 1. Solo después de que ocurre el evento representado por un vértice, pueden comenzar las actividades representadas por los bordes dirigidos a partir del vértice. 2. El evento en un vértice solo puede ocurrir después de que se completen las actividades representadas por los bordes entrantes de un vértice. Solo hay un vértice con un grado de entrada de 0 en la red AOE, llamado vértice de inicio (punto de origen), que representa el comienzo de todo el proyecto. La red AOE también tiene solo un vértice con un grado de salida de 0; , llamado vértice final (sumidero). Representa el final de todo el proyecto. Algunas actividades en la red AOE se pueden llevar a cabo en paralelo, pero todas las actividades en todas las rutas no pueden finalizar hasta que se completen. Por lo tanto, entre todas las rutas desde el origen hasta el sumidero, la ruta con la mayor longitud se denomina ruta crítica. Las actividades en el camino se denominan actividades críticas (encontrar las actividades críticas equivale a encontrar el camino crítico). El tiempo más corto para completar todo el proyecto es la longitud de la ruta crítica.
Encuentra actividades clave 1. La hora de aparición más temprana del evento vk ve(k) Se refiere a la longitud de ruta más larga desde el punto de origen v1 hasta el vértice vk. El momento más temprano en que ocurre el evento vk es el momento más temprano en que pueden comenzar todas las actividades que comienzan desde vk. Sea vk cualquier sucesor de vj if(ve[j] Peso(vj,vk) > ve(k)) entonces ve[k]=ve[j] Peso(vj,vk) "Operación de tensión" Al calcular el valor ve (), proceda de adelante hacia atrás y se puede calcular en función de la clasificación topológica. 2. La última hora de aparición vl(k) del evento vk Se refiere al último momento vl(k) en el que k puede ocurrir sin retrasar la finalización de todo el proyecto. Al calcular el valor de vl(), proceda de atrás hacia adelante y se puede calcular en función de la secuencia topológica inversa. 3. La hora de inicio más temprana e(i) de la actividad ai 4. La última hora de inicio de la actividad ai es l(i) 5. La diferencia entre la hora de inicio más tardía l(i) de una actividad ai y su hora de inicio más temprana e(i) d(i)=l(i)-e(i) Se refiere al margen de tiempo para completar la actividad. Para decirlo sin rodeos, significa cuánto tiempo puede retrasarse la IA. Si d(i)=0, entonces i es la actividad clave La ruta crítica en la red no es única, y para redes con varias rutas críticas, aumentar la velocidad de las actividades clave en una ruta crítica no puede acortar la duración de todo el proyecto. Esto solo se puede lograr acelerando las interacciones clave incluidas en todas. caminos críticos. El propósito de acortar el período de construcción.
Comprensión de la solución vl (k): ve (k) es el movimiento más diligente desde el punto de origen al punto de sumidero; vl (k) es el movimiento desde el punto de sumidero al punto de origen, y el movimiento más cercano al punto de origen; Se selecciona el punto de hundimiento (también simplemente camine hacia adelante, que es la forma más cómoda, simplemente arrástrelo si puede)
7Encontrar
Conceptos básicos de búsqueda.
Búsqueda: el proceso de encontrar elementos de datos que cumplan ciertas condiciones en un conjunto de datos. Tabla de búsqueda (estructura de búsqueda): una colección de datos utilizados para la búsqueda. Hay cuatro operaciones de uso común en las tablas de búsqueda; 1. Consultar si un elemento de datos específico está en la tabla de búsqueda. 2. Recuperar un elemento de datos específico que cumpla con las condiciones. 3. Inserte un elemento de datos en la tabla de búsqueda. 4. Eliminar un elemento de datos de la tabla de búsqueda. Tabla de búsqueda estática: una tabla de búsqueda que solo involucra uno o dos pasos anteriores, sin necesidad de modificar dinámicamente la tabla de búsqueda (búsqueda secuencial, búsqueda media, tabla de búsqueda hash, etc.); , etc.) Palabra clave: el valor de un elemento de datos en un elemento de datos que identifica de forma única el elemento. Longitud promedio de búsqueda: durante el proceso de búsqueda, la duración de una búsqueda se refiere a la cantidad de palabras clave que deben compararse. La longitud promedio de búsqueda es la cantidad promedio de comparaciones de palabras clave en todos los procesos de búsqueda.
Practica ASL cuando busques el éxito y el fracaso.
Búsqueda secuencial y búsqueda binaria.
búsqueda secuencial También conocida como búsqueda lineal, es adecuada tanto para listas secuenciales como para listas enlazadas. Ventajas: no existen requisitos para el almacenamiento y pedido de elementos de datos. Desventajas: cuando n es grande, la eficiencia es baja;
Búsqueda secuencial de tablas lineales generales. typedef struct{//estructura de datos de la tabla de búsqueda ElemType *elem; //Dirección base del espacio de almacenamiento del elemento int TableLen;//La longitud de la tabla }SSTable; int Search_Seq(SSTable ST, clave ElemType){ ST.elem[0] = clave;//Centinela for(i=ST.TableLen;ST.elem[i]!=key;--i);//Mira de atrás hacia adelante devolver yo; } En el algoritmo anterior, ST.elem [0] se llama centinela, por lo que el bucle interno de Search_Seq no tiene que juzgar si la matriz se saldrá de los límites, porque el bucle definitivamente saltará cuando i == 0 sea satisfecho. La introducción de centinelas puede evitar muchos juicios innecesarios, mejorando así la eficiencia del programa.
Búsqueda secuencial en lista ordenada Si se sabe antes de la búsqueda que las palabras clave en la tabla están en orden, luego de que la comparación falle, no es necesario comparar con el otro extremo de la tabla para devolver el error de búsqueda, lo que reduce la longitud promedio de búsqueda de la fracaso de la búsqueda. Se puede utilizar un árbol de decisión para describir el proceso de búsqueda de una lista lineal ordenada. Los nodos circulares representan los elementos que existen en la lista lineal ordenada; los nodos rectangulares son nodos de falla (sus propios nodos vacíos ficticios si hay n nodos); , entonces, en consecuencia, hay n 1 nodos donde la búsqueda falló. La longitud de búsqueda promedio (ASL) de la búsqueda fallida es (1 2 .. n n)/(n 1); la probabilidad de encontrar cada 'nodo fallido' es 1/n 1;
La diferencia entre búsqueda secuencial desordenada y ordenada es solo la diferencia cuando la búsqueda falla.
Éxito en ASL=(n 1)/2
Media búsqueda (búsqueda binaria) Solo es adecuado para estructuras de almacenamiento secuencial, no para estructuras de almacenamiento en cadena y requiere que los elementos estén ordenados por palabras clave. El tiempo es O(log2n)
int Binary_Search(SeqList L, clave ElemType){ int bajo=0,alto=L.TableLen-1,medio; mientras(bajo<=alto){ medio = (bajo alto)/2; if(L.elem[mid]==key) return mid; else if(L.elem[mid]<clave){ bajo = medio 1; // medio = (bajo alto)/2; } demás{ alto = medio 1; // medio = (bajo alto)/2; } } devolver -1; }
Búsqueda de bloques (búsqueda por orden de índice) Absorbe las ventajas respectivas de la búsqueda secuencial y la búsqueda binaria. Tiene una estructura dinámica y es adecuada para búsquedas rápidas. La idea básica de la búsqueda de bloques es dividir la tabla de búsqueda en varios subbloques. Los elementos dentro de un bloque pueden estar desordenados, pero los bloques están ordenados (la palabra clave más grande en el primer grupo es más pequeña que todas las palabras clave en el segundo bloque, y así sucesivamente). Contiene la clave máxima de cada bloque y la dirección del primer elemento de cada bloque; la tabla de índice está ordenada por clave. El proceso de búsqueda de bloques: 1. Determinar el bloque donde se encuentra el registro a buscar en la tabla de índice (búsqueda secuencial o media búsqueda) 2. Búsqueda secuencial dentro del bloque Longitud promedio de búsqueda de búsqueda bloqueada: suma de la longitud promedio de la búsqueda de índice y la búsqueda dentro del bloque
Árbol B y árbol B
B-tree (árbol de búsqueda equilibrado multidireccional) (versión mejorada de BST) El número máximo de hijos de todos los nodos en el árbol B se denomina orden del árbol B, generalmente representado por m. Un árbol B de orden m es un árbol vacío o satisface las siguientes características: 1. Cada nodo del árbol tiene como máximo m subárboles, es decir, contiene como máximo m-1 palabras clave. 2. Si el nodo raíz no es un nodo terminal, hay al menos dos subárboles 3. Todos los nodos que no son hoja, excepto el nodo raíz, tienen al menos m/2 subárboles de límite superior, es decir, contienen al menos m/2 palabras clave de límite superior-1. 4. La estructura de todos los nodos que no son hojas es la siguiente (n, P0, K1, P1, K2, P2,..., Kn, Pn) Ki es una palabra clave y satisface K1<K2<..<Kn; Pi es un puntero al nodo raíz del subárbol. Todos los nodos en el subárbol señalado por el puntero Pi son mayores que Ki y menores que Ki n; la palabra clave en el número de nodo. 5. Todos los nodos hoja aparecen en el mismo nivel y no contienen información. En realidad, no existen. Los punteros a estos nodos están vacíos.
B altura del árbol El número de accesos al disco necesarios para la mayoría de las operaciones en un árbol B es proporcional a la altura del árbol B. Cada nodo del árbol tiene como máximo m subárboles y m-1 palabras clave, y el número de palabras clave debe satisfacer n≤m^h-1; La capa h1 tiene al menos 2 (m/2 límite superior) ^ (h-1) nodos, y la capa h1 es un nodo hoja que no contiene ninguna información; Para un árbol B con n palabras clave, el nodo donde falla la búsqueda del nodo hoja es n 1, por lo que n 1>=2 (m/2 toma el límite superior)^(h-1), es decir, h≤log ( m/2 toma el límite superior)((n 1)/2 1)
Búsqueda de árbol B 1. Encuentra nodos en el árbol B 2. Encuentra palabras clave dentro del nodo. La operación de búsqueda de 1. se realiza en el disco y la operación de búsqueda de 2. se realiza en la memoria; Después de encontrar el nodo de destino, léalo en la memoria y luego use el método de búsqueda secuencial o el método de búsqueda binaria dentro del nodo.
Inserción en el árbol B Si no puede encontrar la posición e insertarla directamente, se destruirán los requisitos en la definición del árbol B. 1. Posicionamiento: busque el nodo que no es hoja en la capa más baja (busque el nodo que no sea hoja, tome el nodo que no sea hoja de la capa anterior) 2. Inserción: el número de palabras clave en cada nodo que no falla está dentro del intervalo [m/2 toma el límite superior -1, m-1] y se puede insertar directamente; Cuando el número de palabras clave de nodo insertadas es mayor que m-1, el nodo se divide. Método de división: tome el límite superior de la posición media m/2 y divida la palabra clave en dos partes. La parte izquierda se incluye en el nodo original y la parte derecha se coloca en el nuevo nodo en la posición media; m/2 toma el límite superior) para insertar el nodo padre del nodo original y así sucesivamente;
Eliminación del árbol B Cuando la palabra clave eliminada k no está en el nodo terminal (el nodo no hoja más bajo), k puede reemplazarse por el predecesor (sucesor) de k k', y luego k' se elimina en el nodo correspondiente y se convierte en una palabra clave. en el nodo terminal; Cuando la palabra clave eliminada está en el nodo terminal (el nodo no hoja más bajo), existen tres situaciones: 1. Eliminar palabras clave directamente: el número de palabras clave ≥ m/2 toma el límite superior -1 2. Los hermanos son suficientes: el número de palabras clave = m/2, tome el límite superior -1, y el número de sus palabras clave hermanas izquierda y derecha (adyacentes) es mayor o igual a m/2, tome el límite superior, luego sus hermanos, padres y él mismo utilizan el método de transposición padre-hijo para lograr el equilibrio. 3. No hay suficientes hermanos: el número de palabras clave eliminadas = m/2 y el límite superior es -1. En este momento, los nodos de sus hermanos izquierdo y derecho (adyacentes) también son = m/2 y el límite superior. es 1. Luego, las palabras clave se fusionarán con las palabras clave en el nodo hermano izquierdo (derecho) y el nodo principal después de la eliminación. El número de palabras clave en el nodo principal se reduce en uno. el nodo raíz se puede reducir directamente a 0 (eliminar el nodo raíz). El nuevo nodo se llama raíz.
Propiedades relacionadas con el árbol B Es necesario comprender la relación entre el número de palabras clave y el número de subárboles: el número de palabras clave es 1 menos que el número de subárboles. Todos los nodos no terminales, excepto el nodo raíz, tienen al menos m/2 subárboles de límite superior (m/2 límite superior - 1 palabra clave) Las palabras clave en los nodos están ordenadas en orden ascendente de izquierda a derecha. Es decir, m/2 toma el límite superior -1≤n≤m-1, es decir, [m/2 toma el límite superior -1, m-1]
árbol B
B-tree es un árbol deformado de B-tree que aparece en respuesta a las necesidades de la base de datos. Un árbol B de orden m satisface las siguientes condiciones: 1. Cada nodo de rama tiene como máximo m subárboles 2. El nodo raíz no hoja tiene al menos dos subárboles. 3. La cantidad de subárboles de un nodo es igual a la cantidad de palabras clave 4. Todos los nodos hoja contienen todas las palabras clave y punteros a los registros correspondientes. Las palabras clave están organizadas en orden de tamaño en los nodos hoja, y los nodos hoja adyacentes están conectados entre sí en orden de tamaño.
Principales diferencias con los árboles B: 1. Un nodo con n palabras clave en el árbol B tiene n subárboles, y cada palabra clave corresponde a un subárbol; n nodos de palabras clave en el árbol B contienen n-1 subárboles. 2. El rango del número n de palabras clave para cada nodo en el árbol B es [m/2 toma el límite superior, m] (en relación con los límites superior e inferior del número n de palabras clave de nodos en el árbol B más 1 ); nodo raíz B: [1,m],B:[1,m-1] 3. En el árbol B, los nodos hoja contienen información (todas las palabras clave) y los nodos que no son hoja solo sirven como índices. 4. Cada búsqueda en el árbol B, exitosa o no, es una ruta desde el nodo raíz hasta el nodo hoja.
tabla de picadillo
Conceptos básicos de tablas hash.
Debido a la búsqueda lineal anterior en tabla y árbol, no existe una relación definida entre la posición del registro en la tabla y la clave del registro. Por lo tanto, al buscar registros en estas tablas, es necesario comparar una serie de palabras clave. Este método de búsqueda se basa en la comparación y la eficiencia de la búsqueda depende del número de comparaciones. Función hash: la función Hash (clave) = Addr que asigna las palabras clave en la tabla de búsqueda a la dirección correspondiente a la palabra clave, es decir, utilizando las características de la palabra clave en sí y utilizando la menor búsqueda de "comparación" posible. La función hash puede asignar varias palabras clave diferentes a la misma dirección, lo que se denomina "colisión". Estas diferentes palabras clave que chocan se denominan sinónimos. Por un lado, una función hash bien diseñada minimizará los conflictos; por otro, los conflictos son inevitables y se deben diseñar métodos para abordarlos. Tabla hash: una estructura de datos a la que se accede directamente en función de palabras clave. En otras palabras, la tabla hash establece una relación de mapeo directo entre palabras clave y direcciones de almacenamiento. Idealmente, la complejidad temporal de buscar en una tabla hash es O(1), lo que significa que no tiene nada que ver con la cantidad de elementos.
Cómo construir una función hash
1. El dominio de definición de la función hash debe incluir todas las claves de almacenamiento y el rango de valores depende del tamaño o rango de direcciones de la tabla hash. 2. Las direcciones calculadas por la función hash deben distribuirse uniformemente en todo el espacio de direcciones con la misma probabilidad, reduciendo así la aparición de conflictos. 3. La función hash debe ser lo más simple posible y poder calcular el hash correspondiente a cualquier palabra clave en poco tiempo.
1. Método de direccionamiento directo Tome directamente el valor de una función lineal de la palabra clave como dirección hash. H(tecla)=tecla o H(tecla)=ax tecla b Es consistente con la situación de que la distribución de palabras clave es básicamente continua, si la distribución de palabras clave es discontinua y hay muchos espacios vacíos, se desperdiciará espacio de almacenamiento.
Métodos de manejo de conflictos
Búsqueda de hash y análisis de rendimiento
8 tipo
Conceptos básicos de clasificación.
Estabilidad del algoritmo: hay dos elementos Ri y Rj en la lista a ordenar, y sus palabras clave correspondientes son las mismas keyi = keyj. Si las posiciones relativas de Ri y Rj permanecen sin cambios después de usar un determinado algoritmo de clasificación, entonces se realiza la clasificación. El algoritmo es estable; de lo contrario, no lo será. La estabilidad no refleja la calidad del algoritmo, solo describe la naturaleza del algoritmo. Los algoritmos de clasificación se pueden dividir en dos categorías según si los elementos de datos están completamente en la memoria durante el proceso de clasificación: 1. Clasificación interna: durante la clasificación, todos los elementos se almacenan en la memoria para su clasificación. 2. Clasificación externa: durante la clasificación, todos los elementos no se pueden almacenar en la memoria al mismo tiempo. Deben moverse continuamente entre la memoria interna y externa de acuerdo con los requisitos durante el proceso de clasificación. En general, la clasificación interna requiere comparación y movimiento, pero no toda clasificación interna requiere comparación, como la clasificación por base.
tipo de inserción Cada vez que un registro a ordenar se inserta en la subsecuencia previamente grabada de acuerdo con el tamaño de la palabra clave.
clasificación por inserción directa
Secuencia ordenada L[1..i-1]L(i) Secuencia desordenada L[i 1…n] void InsertSort(ELemType A[],int n){ int i, j; for(i=2;i<=n;i){//Inserte los siguientes n-1 números al frente si(A[i]<A[i-1]){ A[0] = A[i];//Copiar como centinela, A[0] no almacena ningún elemento for(j=i-1;A[0]<A[j];j--){//Encuentre la posición de inserción de atrás hacia adelante A[j 1] = A[j]; A[j 1] = A[0]; } } Complejidad espacial O (1), complejidad espacial O (n ^ 2) Mejor caso: los elementos de la tabla ya están ordenados, tiempo O(n) Peor de los casos: el orden de los elementos en la tabla se invierte, tiempo O(n^2) Dado que cada comparación es de atrás hacia adelante, es estable. Adecuado para listas secuenciales y listas vinculadas (puede encontrar la posición del elemento especificado de adelante hacia atrás) Adecuado para tablas de clasificación básicamente ordenadas con un volumen de datos pequeño
clasificación de media inserción
void InsertSort(ElemType A[],int n){ int i,j,bajo,alto,medio; para(i=2;i<=n;i){ A[0] =A[i]; bajo=1;alto=i-1; mientras(bajo<=alto){ medio = (bajo alto)/2; if(A[mid]>A[0])high=mid-1; de lo contrario, bajo = medio 1; } para(j=i-1;j>=alto 1;j--) A[j 1] = A[j]; A[alto 1]=A[0]; } } Complejidad del tiempo O (n ^ 2) El número de comparaciones no tiene nada que ver con el estado inicial de la lista a ordenar, y solo depende del número n de elementos de la lista; El número de movimientos está relacionado con el estado inicial de la lista a ordenar.
clasificación de colinas
Primero divida la tabla de clasificación en varias subtablas (los registros con el mismo incremento forman una subtabla di 1 = di/2, y el último incremento es igual a 1), realice la clasificación por inserción directa en cada subtabla, cuando toda la tabla Cuando los elementos están básicamente en orden, se realiza una clasificación por inserción directa en todos los registros. void ShellSort(ELemType A[],int n){ // A [0] es una unidad de almacenamiento temporal, no un centinela. Cuando j <= 0, se alcanza la posición de inserción. for(dk=n/2;dk>=1;dk/=2)//cambio de tamaño de paso para(i=di 1;i<=n;i) if(A[i]<A[i-dk]){//A[i] debe insertarse en la subtabla incremental ordenada A[0]=A[j];//almacenado temporalmente en A[0] para(j=i-dk;j>0&&A[0]<A[j];j-=dk) A[j dk]=A[j]; } } No hay una conclusión definitiva sobre la complejidad del tiempo, pero se cree que es O(n^2) Inestabilidad de clasificación Adecuado para tablas de secuencia
tipo de intercambio Intercambie las posiciones de los dos registros en la secuencia según los resultados de la comparación de las palabras clave
Ordenamiento de burbuja
Compare los valores de los elementos adyacentes de atrás hacia adelante (de adelante hacia atrás) e intercámbielos si están en orden inverso. Las palabras clave flotan gradualmente hacia la superficie como burbujas. void BubbleSort(ELemType A[],int n){ for(int i=0;i<n-1;i){//n-1 veces bandera = falso; para(j=n-1;j>i;j--) if(A[j-1]>A[j]){//Si es orden inverso intercambiar(A[j-1],A[j]); bandera = verdadero; } if(flag==false)//La bandera del fin de la clasificación de burbujas: ni un solo intercambio devolver; } } Espacio O(1), tiempo O(n^2) Estabilidad: los elementos no se intercambiarán cuando sean iguales, estables La secuencia producida por la clasificación de burbujas está ordenada globalmente, lo que no es equivalente al ordenamiento local de la clasificación por inserción.
Ordenación rápida Basado en el pensamiento de divide y vencerás
Elija cualquier elemento de la lista para ordenarlo como pivote (generalmente el primer elemento) y divida la secuencia ordenada en dos partes independientes L[1..k-1] L(k) L[k 1, ...n] , de modo que la primera mitad sea más pequeña que el pivote y la segunda mitad sea más grande que el pivote, y el pivote se coloca en la posición final L (k). Este proceso se denomina clasificación rápida de una sola pasada (división de una sola pasada). void QuickSort(ELemType A[],int bajo,int alto){ if(low<high){//Condición para salto recursivo int pivotpos = Partición(A,baja,alta);//Partición Ordenación rápida(A,baja,pivotpos-1); QuickSort(A,puntos de pivote 1,alto); } } El rendimiento y la clave del algoritmo de clasificación rápida provienen de la operación de partición. Hay muchas versiones de la operación de partición. Aquí, el primer elemento se utiliza como pivote para la partición. int Partición(ElemType A[],int bajo,int alto){ ElemType pivote=A[bajo]; while(low<high){//Condición de interrupción del bucle while(bajo<alto && A[alto]>=pivote) --alto; A[bajo]=A[alto]; whilie(bajo<alto && A[bajo]<=pivote) bajo; A[alto] = A[bajo]; } A[bajo] = pivote; retorno bajo; } Espacio: se requiere una pila de trabajo recursiva, preferiblemente O (log2n), promedio O (log2n) (dividido por la mitad tanto como sea posible, como un árbol binario completo), En el peor de los casos, O (n) (el número de elementos en las dos áreas divididas es n-1 y 0, se requieren n-1 llamadas recursivas y la profundidad de la pila es O (n)). Tiempo: el tiempo de ejecución de la clasificación rápida está relacionado con si la división es simétrica. El peor de los casos es mitad n-1 y mitad 0. La asimetría máxima ocurre en cada nivel de recursividad, que corresponde a la secuencia ordenada inicial (secuencial o inversa). orden), es O(n^2) Estabilidad: Inestable, los mismos elementos se transferirán a diferentes partes y se cambiarán las posiciones relativas. Nota: La clasificación rápida no produce una subsecuencia ordenada, pero el elemento pivote se coloca en la posición final en cada pasada. Mejore la eficiencia: 1. Seleccione elementos pivote que puedan dividir la secuencia tanto como sea posible 2. Seleccione aleatoriamente elementos pivote La división más ideal: ambos problemas planteados son más pequeños que n/2, el tiempo es O (nlog2n) y el tiempo de ejecución de la clasificación rápida está muy cerca del mejor de los casos en promedio. Quicksort es el algoritmo de clasificación con el mejor rendimiento promedio entre todos los algoritmos de clasificación internos.
clasificación de selección Cada paso selecciona el elemento más pequeño de la palabra clave en la secuencia original como el i-ésimo elemento de la subsecuencia ordenada hasta que pase n-1
Orden de selección simple
void SelectSort(ElemType A[],int n){ for(i=0;i<n-1;i)//Un total de n-1 veces min=i;//Registra la posición mínima del elemento para(j=i 1;j<n;j) si(A[j]<A[min]) mín = j; if(min!=i) swap(A[i],A[min]); } } Espacio O(1), tiempo O(n^2) Estabilidad: puede provocar que cambie la posición relativa de las palabras clave que contienen los mismos elementos, lo cual es inestable.
clasificación de montón
El montón es un objeto de matriz de un árbol. Una matriz unidimensional puede considerarse como un árbol binario completo que satisface las propiedades. 1.L(i)>=L(2i) y L(i)>=L(2i 1) o 2.L(i)<=L(2i) y L(i)<=L(2i 1) ( 1<=i<=n/2 (tomar el límite); Aquellos que satisfacen 1. se consideran montones de raíces grandes (montones superiores grandes), y aquellos que satisfacen 2. se consideran montones de raíces pequeños (montones superiores pequeños). Clasificación del montón: primero, n elementos de la matriz se integran en un montón inicial, después de generar el elemento superior del montón, el elemento inferior del montón se envía a la parte superior del montón; cumple con las propiedades de un montón raíz grande y el montón se destruye. El elemento superior del montón se mueve a la parte superior del montón. Ajústelo hacia abajo para que pueda continuar manteniendo la naturaleza de un montón superior grande. Luego genere el elemento superior de la torre y repita hasta que solo quede un elemento en el montón. Algoritmo de clasificación de montón: void HeapSort(ElemType A[],int len){ BuildMaxHeap(A,len); para(i=len;i>1;i--){ Swap(A[i],A[1]);// Genera el elemento superior del montón e intercámbialo con el elemento inferior del montón HeadAdjust(A,1,i-1);//Organiza los elementos i-1 restantes en una pila } } La clasificación del montón es adecuada para situaciones en las que hay muchas palabras clave (del orden de cientos de millones) Ejemplo: para seleccionar los 100 valores máximos principales entre 100 millones de números, primero use una matriz de 100, lea los primeros 100 números, construya un pequeño montón superior y luego lea los números restantes en secuencia si son menores que. en la parte superior del montón, deséchelos. De lo contrario, reemplace la parte superior del montón con este número y cambie el tamaño del montón. Una vez leídos los datos, los 100 números del montón son los requeridos. Eficiencia espacial O (1), eficiencia temporal O (nlog2n) inestable
1. Cómo construir una secuencia desordenada en un montón inicial Filtre el subárbol con el (n/2 delimitado) -ésimo nodo como raíz (si es un montón raíz grande, compare la palabra clave del nodo raíz y el tamaño de sus palabras clave del subárbol izquierdo y derecho, e intercámbielas si no cumplen las reglas del montón raíz grande), convierta este subárbol en un montón. Luego, los subárboles enraizados en cada nodo (n/2 menos el límite -1,1) se filtran en secuencia. Cuando se ve como un árbol binario completo, el nodo raíz se ajusta gradualmente de abajo hacia arriba (las claves se intercambian entre el nodo raíz y los nodos secundarios en la matriz, y el nodo raíz se ajusta de derecha a izquierda (se intercambian palabras clave); void BuildMaxHeap(ElemType A[],int len){ for(int i=len/2;i>0;i--)//De i=n/2 a 1, ajuste el montón repetidamente Ajuste de cabeza(A,i,len); } void HeadAdjust(ElemType A[],int k,int len){ //HeadAdjust ajusta el subárbol con el elemento k como raíz A[0]=A[k]; for(i=2*k;i<=len;i*=2){//Filtrar hacia abajo a lo largo de los nodos secundarios con clave más grande if(i<len && A[i]<A[i 1]) i ;//Obtiene el subíndice del nodo secundario con una clave más grande if(A[0]>=A[i]) break;//Fin del filtrado demás{ A[k] = A[i];//Ajustar A[i] al nodo padre k=i;//Modifica el valor de k para continuar filtrando hacia abajo } } A[k]=A[0]; } El tiempo de ajuste está relacionado con la altura del árbol, O (h), y la complejidad del tiempo es O (n). Se puede incorporar un número desordenado en un montón en tiempo lineal.
2. Después de generar el elemento superior del montón, ¿cómo ajustar los elementos restantes en un nuevo montón? Después de generar el elemento superior del montón, el último elemento del montón se intercambia con el elemento superior del montón. En este momento, las propiedades del montón se destruyen y se requiere un filtrado descendente. Filtrar de arriba a abajo (intercambiar palabras clave del nodo raíz y palabras clave del subárbol)
inserción de montón Inserta la cola y ajusta de abajo hacia arriba.
Combinar clasificación y clasificación por base
fusionar ordenar El significado de fusionar es combinar dos o más listas ordenadas en una nueva lista ordenada.
Suponiendo que la tabla a ordenar contiene n registros, se puede considerar como n subtablas ordenadas, cada una con una longitud de 1, y luego fusionarlas en pares para obtener n/2 registros acotados superiores con una longitud de 2 o 1. Lista de secuencia; continúe fusionando de dos en dos hasta que se fusione en una lista ordenada de longitud n. Este método se llama clasificación por fusión bidireccional. ElemType *B=(ElemType*)malloc((n 1)*sizeof(ElemType));//Matriz auxiliar B void Merge(ElemType A[],int bajo,int medio,int alto){ //La tabla A[low..mid]A[mid 1..high] está ordenada, combínalas en una lista ordenada para(int k=bajo;k<=alto;k) B[k]=A[k]; for(i=bajo;j=medio 1;k=i,i<=medio&&j<=alto;k ){ si(B[i]<=B[j]) A[k]=B[i]; de lo contrario A[k]=B[j]; } mientras(i<=medio) A[k ]=B[i ]; mientras(j<=alto) A[k ]=B[j ]; } void MergeSort(ELemType A[],int bajo,int alto){ si(bajo<alto){ int medio=(bajo alto)/2; MergeSort(A,bajo,medio); MergeSort(A,mediados de 1,alto); Fusionar(A,bajo,medio,alto); } } Eficiencia espacial: n unidades de espacio auxiliar, complejidad espacial O (n) Eficiencia del tiempo: cada fusión es O (n), y se requieren fusiones de límite superior log2n. La complejidad del tiempo del algoritmo es O (nlog2n). Estabilidad: la operación Merge() no cambiará el orden relativo de los registros con la misma palabra clave, estable
En términos generales, para la fusión de N elementos de k vías, el número de veces de clasificación m satisface k^m=N, por lo que m=logkN, y como m es un número entero, m=logkN toma el límite superior
Ordenación por base Utilice la idea de clasificación de varias palabras clave para ordenar palabras clave lógicas individuales
Ordene según el tamaño de las palabras clave. La palabra clave de cada nodo aj consta de d tuplas, kd-1j es la palabra clave principal y kj0 es la palabra clave secundaria. Para lograr la clasificación de varias palabras clave, normalmente existen dos métodos: 1. Primero el bit más significativo (MSD), divida varias subsecuencias más pequeñas capa por capa de acuerdo con el peso decreciente de las palabras clave y, finalmente, todas las subsecuencias se conectan en una secuencia ordenada. 2. Primero el dígito más bajo (LSD), ordenado en orden ascendente por peso de palabra clave para formar una secuencia ordenada. funcionar: 1. Distribución: Deje vacía la cola Q0...Qr-1 y luego agréguela a la cola correspondiente según el bit correspondiente de cada nodo. 2. Colección: conecte los nodos de la cola de Q0..Qr-1 de un extremo a otro para obtener una nueva secuencia de nodos para formar una nueva tabla lineal. Ejemplo: para ordenar secuencias por debajo de 1000, primero determine la base r (puede considerarse como base r. La base de 1000 es 10, por lo que se necesitan 10 colas de cadena durante el proceso de clasificación). Los números inferiores a 1000 son tres dígitos, por lo que se requieren tres operaciones de "distribución" y "recopilación"; todos los registros con la misma palabra clave más baja (dígito de unidades) se asignan a una cola y luego se realiza la operación de "recopilación". Eficiencia del espacio: el espacio de almacenamiento auxiliar requerido para un viaje de clasificación es r (r colas: r punteros de cabecera de cola, r punteros de cola de cola), O (r) Eficiencia del tiempo: d pases de asignación y recolección, un paso de asignación requiere O (n), un paso de recolección requiere O (r), la complejidad del tiempo es O (d (n r)), independientemente del estado inicial de la secuencia . Estabilidad: la clasificación Radix en sí requiere que la clasificación bit a bit sea estable, por lo que es estable.
Comparación y aplicación de varios algoritmos de clasificación interna.
Comparación de algoritmos de clasificación interna.
Aplicación del algoritmo de clasificación interna.
1. Si n es pequeño, se puede utilizar la ordenación por inserción directa o la ordenación por selección simple. Dado que la ordenación por inserción directa requiere más movimientos de registros que la ordenación por selección simple, cuando el registro en sí tiene una gran cantidad de información, la ordenación por selección simple es mejor. 2. Si el estado inicial del archivo está básicamente ordenado por palabras clave, es apropiado utilizar la inserción directa o la clasificación por burbujas. 3. Si n es grande, utilice el método de clasificación O (nlog2n): clasificación rápida, clasificación en montón o clasificación por combinación. En la actualidad, la clasificación rápida se considera el mejor método de clasificación interna basada en comparación. Cuando las palabras clave que se van a clasificar se distribuyen aleatoriamente, la clasificación rápida tiene el tiempo promedio más corto y requiere menos espacio auxiliar que la clasificación rápida. no ocurrirá. Pero ambas clasificaciones son inestables. Si se requiere un algoritmo O(nlog2n) estable, se utiliza la ordenación por fusión, pero no se recomienda fusionar pares de un solo registro. Generalmente se pueden usar en combinación con la ordenación por inserción directa: primero use la ordenación por inserción directa para obtener la subdivisión ordenada más larga. . archivos y luego fusionarlos de dos en dos. La clasificación por inserción directa es estable y la clasificación por combinación mejorada también es estable. 4. En el método de clasificación basado en comparación, después de cada comparación de dos tamaños de palabras clave, solo ocurren dos transferencias posibles. Por lo tanto, se puede usar un árbol binario para describir el proceso de decisión de comparación. Se puede demostrar que: cuando hay n archivos. Cuando las palabras clave se distribuyen aleatoriamente, cualquier algoritmo de clasificación que se base en la "comparación" requiere al menos O (nlog2n) tiempo. 5. Si n es muy grande y la cantidad de palabras clave registradas es pequeña y se puede descomponer, es mejor utilizar la clasificación por base. 6. Cuando el registro en sí tiene una gran cantidad de información, para evitar perder mucho tiempo moviendo el registro, se puede utilizar una lista vinculada como estructura de almacenamiento.
clasificación externa
Conceptos básicos de algoritmos de clasificación externos.
La clasificación realizada en la memoria se denomina clasificación interna. En muchas aplicaciones, es necesario ordenar archivos grandes y no se puede copiar el archivo completo en la memoria para ordenarlo. Por lo tanto, los registros que se van a clasificar deben almacenarse en una memoria externa. Durante la clasificación, los datos se transfieren a la memoria parte por parte para la clasificación. Durante el proceso de clasificación, se requieren múltiples intercambios entre la memoria y el almacenamiento externo. Esta clasificación se llama clasificación externa.
método de clasificación externo
El costo de tiempo durante el proceso de clasificación externa considera principalmente la cantidad de accesos al disco, es decir, la cantidad de IO La clasificación externa generalmente utiliza clasificación por combinación: 1. Según el tamaño del búfer de memoria, divida los archivos en el almacenamiento externo en subarchivos de longitud l, léalos en la memoria en secuencia y use el método de clasificación interno para ordenarlos y escriba los subarchivos ordenados. obtenidos después de volver a clasificarlos en el almacenamiento externo, estos subarchivos ordenados se denominan segmentos fusionados o cadenas secuenciales; 2. Combine estos segmentos fusionados uno por uno, de modo que los segmentos fusionados aumenten gradualmente de pequeño a grande hasta obtener el archivo ordenado completo. La fusión de una sola pasada se refiere a fusionar todos los segmentos del mismo nivel actual en uno Tiempo total de clasificación externa = tiempo requerido para la clasificación interna, tiempo de lectura y escritura de información externa, tiempo requerido para la fusión interna El tiempo para leer y escribir información del almacenamiento externo es mucho más largo que el tiempo para la clasificación interna y la fusión interna. La lectura y escritura de información del almacenamiento externo se basa en "bloques de disco". Una combinación requiere el número total de registros/registros de uno. El bloque de disco se leerá y escribirá varias veces. Por lo tanto, el número total de lecturas y escrituras necesarias: 2*número total de registros/número de registros en un bloque de disco*n (número de veces) número total de registros/número de registros. en un bloque de disco (la clasificación interna también requiere una lectura y escritura completa) Para r segmentos de fusión iniciales, realice una fusión equilibrada de k vías. La altura del árbol (árbol k-ario invertido) = logkr y el límite superior = el número de pasadas de fusión S. Se puede ver que aumentar el número de rutas de fusión k o reducir el número de segmentos de fusión iniciales r puede reducir el número de pases de fusión S, reduciendo así el número de E/S de disco y mejorando la velocidad de clasificación externa.
Árbol perdedor y fusión equilibrado multidireccional
Aumentar el número de rutas de fusión k puede reducir el número de pasos de fusión S, pero aumentará el tiempo de fusión interna. Al fusionar internamente, seleccionar la palabra clave más pequeña entre k elementos requiere comparaciones k-1, y cada combinación de n elementos requiere comparaciones (n-1) (k-1), S veces. El número de comparaciones necesarias para la fusión es S(n-1)(k-1)=log2r toma el límite superior (n-1)(k-1)/log2k toma el límite superior, donde (k-1)/log2k toma el límite superior y crece con el crecimiento de k. Por lo tanto, no se puede utilizar el algoritmo de ordenación por fusión interno ordinario.
Para evitar que la fusión interna se vea afectada por el aumento de k, se introduce el árbol perdedor. El árbol perdedor es una variante de la clasificación de árboles y puede considerarse como un árbol binario completo. Los k nodos hoja almacenan respectivamente los registros de los k segmentos de fusión que actualmente participan en la comparación durante el proceso de fusión. Los nodos internos se utilizan para registrar los "perdedores" de los nodos izquierdo y derecho, y permiten que los ganadores continúen comparando hasta. el nodo raíz. El nodo raíz es el ganador. S(n-1) log2k toma el límite superior = log2k toma el límite superior (n-1) log2k toma el límite superior = (n-1) log2r toma el límite superior Después de usar el árbol perdedor, el orden de comparación de clasificación de fusión interna no tiene nada que ver con k, pero el número de rutas de fusión k está restringido por el espacio de memoria, lo que puede reducir la capacidad del búfer dentro de un determinado espacio. Si el valor k es demasiado grande, aunque se reducirá el número de pasadas de fusión, el número de IO seguirá aumentando. ps: en comparación con el árbol ganador, al reconstruir el árbol ganador, debe comparar con los nodos hermanos y luego cambiar los nodos principales; al reconstruir el árbol perdedor, solo necesita subir completamente para comparar los nodos principales;
Ordenación por selección de reemplazo (generando segmentos de fusión iniciales)
Para reducir r, r=n/l toma un límite superior, por lo que necesitamos encontrar una manera de generar un segmento de fusión inicial más largo l. Supongamos que el archivo inicial a ordenar es FI, el archivo de salida del segmento de fusión inicial es FO, el área de trabajo de la memoria es WA, FO y WA están inicialmente vacíos y WA puede acomodar w registros. Los pasos del algoritmo de selección de reemplazo son los siguientes: 1. Ingrese w registros de FI al espacio de trabajo WA 2. Seleccione el registro con el valor mínimo de palabra clave de WA y regístrelo como MINIMAX. 3. Grabar MINIMAX a FO 4. Si FI no está vacío, envíe el siguiente registro de FI a WA 5. Seleccione el registro de palabra clave más pequeño de todos los registros en WA con palabras clave mayores que las palabras clave del registro MINIMAX como el nuevo registro MINIMAX. 6. Repita los pasos 3 a 5 hasta que no se pueda seleccionar ningún MINIMAX nuevo en WA, obtenga un segmento de fusión inicial y envíe una bandera de fin del segmento de fusión a FO. 7. Repita los pasos 2 a 6 hasta que WA esté vacío y se obtengan todos los segmentos fusionados iniciales. El proceso de selección de registros MINIMAX en WA requiere el uso de árboles perdedores.
mejor árbol de fusión
Después de ordenar el archivo por selección de sustitución, se obtienen segmentos fusionados iniciales de diferentes longitudes. ¿Cómo organizar el orden de fusión de los segmentos de fusión iniciales con diferentes longitudes para minimizar la cantidad de IO? Dibuje el árbol de combinación correspondiente. La longitud de ruta ponderada WPL del árbol es el número total de registros leídos durante el proceso recursivo. Extienda la idea del árbol de Huffman al caso del árbol k-ary. En el árbol de combinación, combine primero el segmento de combinación inicial con una pequeña cantidad de registros y deje que el segmento de combinación inicial con una gran cantidad de registros. fusionado por último, se puede establecer el IO total. El mejor árbol de fusión con el menor número de veces. Método para construir el mejor árbol de fusión: si el segmento de fusión inicial no es suficiente para formar un árbol k-ario estricto, es necesario agregar un "segmento virtual" con una longitud de 0. ¿Cómo determinar la cantidad de segmentos virtuales a agregar? Un árbol k-ario estricto tiene n0=(k-1)nk 1. Si (n0-1)%(k-1)=0, entonces el libro de fusión k-fork se puede construir con nk nodos internos. Si (n0-1)%(k-1)=u!=0, entonces se pueden agregar segmentos de fusión vacíos k-u-1 para crear un árbol de fusión
La red AOV y la red AOE son ambas DAG, y los bordes y vértices de las dos tienen significados diferentes. Los bordes en la red AOV no tienen pesos y solo representan el contexto entre los vértices; los bordes en la red AOE tienen pesos y representan el costo de completar la actividad.
La diferencia entre Dijkstra y Prim: la suma de todos los bordes en el árbol de expansión mínima de Prim debe ser la más pequeña, solo los pesos de los nodos adyacentes en el MST (árbol de ruta más corta del punto unitario) de Dijkstra no son necesariamente menores que el; El gráfico original AB es más corto porque se suma la distancia al punto fuente; la diferencia entre los dos radica en la operación de relajación. Además, Prim solo se puede usar para gráficos no dirigidos, y Dijkstra puede tener \ gráficos no dirigidos (los gráficos no dirigidos se convierten en gráficos dirigidos, ij bilaterales)
El recorrido de la matriz de adyacencia es único, pero el recorrido de la lista de adyacencia no es único.
La conectividad se analiza en gráficos no dirigidos y la conectividad fuerte se analiza en gráficos dirigidos.