Galeria de mapas mentais estrutura de dados
Este é um mapa mental sobre estruturas de dados, incluindo tabelas lineares, pilhas e filas, strings, árvores e árvores binárias, pesquisa, classificação, etc.
Editado em 2021-09-25 14:52:15Microbiologia medica, Infezioni batteriche e immunità riassume e organizza i punti di conoscenza per aiutare gli studenti a comprendere e ricordare. Studia in modo più efficiente!
La teoria cinetica dei gas rivela la natura microscopica dei fenomeni termici macroscopici e le leggi dei gas trovando la relazione tra quantità macroscopiche e quantità microscopiche. Dal punto di vista del movimento molecolare, vengono utilizzati metodi statistici per studiare le proprietà macroscopiche e modificare i modelli di movimento termico delle molecole di gas.
Este é um mapa mental sobre uma breve história do tempo. "Uma Breve História do Tempo" é um trabalho científico popular com influência de longo alcance. Ele não apenas introduz os conceitos básicos da cosmologia e da relatividade, mas também discute os buracos negros e a expansão. Do universo. questões científicas de ponta, como inflação e teoria das cordas.
Microbiologia medica, Infezioni batteriche e immunità riassume e organizza i punti di conoscenza per aiutare gli studenti a comprendere e ricordare. Studia in modo più efficiente!
La teoria cinetica dei gas rivela la natura microscopica dei fenomeni termici macroscopici e le leggi dei gas trovando la relazione tra quantità macroscopiche e quantità microscopiche. Dal punto di vista del movimento molecolare, vengono utilizzati metodi statistici per studiare le proprietà macroscopiche e modificare i modelli di movimento termico delle molecole di gas.
Este é um mapa mental sobre uma breve história do tempo. "Uma Breve História do Tempo" é um trabalho científico popular com influência de longo alcance. Ele não apenas introduz os conceitos básicos da cosmologia e da relatividade, mas também discute os buracos negros e a expansão. Do universo. questões científicas de ponta, como inflação e teoria das cordas.
Estruturas de Dados 8.26
1. Introdução
1.1 Conceitos básicos de estrutura de dados
1.1.1 Conceitos básicos e terminologia
1.Dados
É um portador de informação, uma coleção de símbolos que podem ser inseridos em um computador e reconhecidos e processados por um programa de computador.
2. Elementos de dados
É a unidade básica de dados. Um elemento de dados pode ser composto de vários itens de dados.
Item de dados: A menor unidade indivisível que constitui um elemento de dados
3. Objetos de dados
Uma coleção de elementos de dados com a mesma natureza, que é um subconjunto de dados (por exemplo, o objeto de informações do aluno é um subconjunto das informações gerais do aluno e do curso)
4.Tipo de dados
É o nome geral de um conjunto de valores e de um conjunto de operações definidas neste conjunto (como uma variável do tipo int, cujo conjunto de valores é um número inteiro em um determinado intervalo (o tamanho do intervalo varia dependendo da máquina ), e as operações nele são definidas como adição, subtração, multiplicação, divisão e tomada de Módulo e outras operações)
1. Tipo atômico
Um tipo de dados cujo valor não pode ser subdividido
2. Tipo de estrutura
Um tipo de dados cujo valor pode ser dividido em vários (componentes)
3. Estrutura de dados abstrata ADT
Refere-se a um modelo matemático e a um conjunto de operações definidas no modelo (apenas as características lógicas são enfatizadas)
Ilustração 1-4
1.1.2 Estrutura de dados e três elementos
estrutura de dados
É uma coleção de elementos de dados que possuem um relacionamento específico entre si
Três elementos
1. Estrutura lógica dos dados
estrutura linear
Tabela linear geral
tabela linear restrita
pilha, fila
corda
Promoção de mesa linear
variedade
estrutura não linear
juntar
estrutura de árvore
estrutura gráfica
2. Estrutura de armazenamento de dados (imagem, estrutura física)
armazenamento sequencial
Vantagens: 1. O acesso aleatório pode ser alcançado 2. Cada elemento ocupa menos espaço de armazenamento Desvantagens: 1. Apenas um bloco adjacente inteiro de unidades de armazenamento pode ser usado, por isso é fácil gerar mais fragmentos externos;
armazenamento em cadeia
Vantagens: 1. Não requer que elementos logicamente adjacentes sejam fisicamente adjacentes, portanto não ocorrerá fragmentação. Desvantagens: 1. Ponteiros consomem espaço extra 2. Acesso aleatório não pode ser alcançado, apenas acesso sequencial;
Armazenamento de índice
Ao armazenar informações do elemento, uma tabela de índice adicional é criada e cada item na tabela de índice é chamado de item de índice (chave, endereço). Vantagens: 1. Recuperação rápida: 1. A tabela de índice adicional ocupa espaço de armazenamento adicional; 2. Ao adicionar ou excluir dados Você também precisa modificar a tabela de índices, o que leva muito tempo.
Armazenamento de hash
Calcule diretamente o endereço de armazenamento do elemento (armazenamento hash) com base na palavra-chave do elemento Vantagens: 1. A operação de adição, exclusão e verificação de nós é muito rápida.; será um conflito de unidade de armazenamento de elemento, e a resolução do conflito aumentará a sobrecarga de tempo e espaço.
3. Operações de dados
Definição de operações
Tendo em vista a estrutura lógica, indique a função de operação
Implementação de operações
Aponte as etapas de operação específicas para a estrutura de armazenamento
1.2 Algoritmos e avaliação de algoritmos
É uma descrição da solução de um problema específico e é uma sequência finita de instruções.
complexidade de tempo
Quando o tamanho do problema é n, o tempo de execução é proporcional a O(1<log2n<n<nlog2n<n^2<n3<2^n<n!<n^n)
complexidade do espaço
O tamanho do problema é n, espaço extra além da entrada e do programa, o algoritmo funciona no local: o espaço auxiliar exigido pelo algoritmo é constante, ou seja, O(1);
2Tabela linear
2.1 Definição de tabela linear
Uma sequência finita de n elementos de dados com o mesmo tipo de dados
2.2 Representação sequencial de tabelas lineares
Tabela de sequência
inserir
Bool ListInsert(SqList &L,int i,ElemType e){ If(i < 1 || i > L.length 1) retorna falso; If(L.length >= MaxSize) retorna falso; For(int j = L.length;j>=i;j- -) //L.length é a posição após o último elemento no subscrito do array L.dados[j] = L.dados[j-1]; L.dados[i-1] = e; L.comprimento; retornar verdadeiro; }
Complexidade de tempo Melhor pior média O(1) O(n) O(n)
excluir
Bool ListDelete(SqList &L,int i,ElemType &e){ If(i<1 || i>L.length) retorna falso; e = L.dados[i-1]; For(int j = i;j<L.comprimento;j) L.dados[j-1] = L.dados[j]; L.comprimento - -; Retorne verdadeiro; }
O(1) O(n) O(n)
Pesquise por valor (pesquise sequencialmente)
Int LocateElem(SqList L,ElemType e){ For(int i=0;i<L.comprimento;i) If(L.data[i] == e) retornar i 1; Retornar 0; }
O(1) O(n) O(n)
2.3 Representação encadeada de tabelas lineares
Lista única typedef estrutura LNode{ Dados ElemType; estrutura LNode *próximo; }LNode,*LinkList;
Crie uma lista vinculada individualmente
Método de inserção de cabeça
LinkLIst List_HeadInsert(LinkList &L){ LNode *s;int x; L = (LNode*)malloc(sizeof(LNode)); L->próximo = NULO; scanf(“%d”,&x); enquanto(x! = 9999){ s = (LNode*)malloc(sizeof(LNode)); s->dados = x;s->próximo = L->próximo; L->próximo = s; s->dados = x; scanf(“%d”,&x); } retornar eu;}
método de inserção de cauda
LinkList List_TailInsert(LinkList &L){ interno x; L = (LNode*)malloc(sizeof(LNode)); LNode *s,*r = L; scanf(“%d”,&x); enquanto(x! = 9999){ s = (LNode*)malloc(sizeof(LNode)); s->dados = x; r->próximo = s; s = r; scanf(“%d”,&x); } r-próximo = NULO; retornar L: }
Encontre nós por número de série
LNode *GetElem(LinkList L,int i){ int j = 1; LNode *p = L->próximo; se(i == 0) retornar L; if(i < 1) retornar NULO; enquanto(p && j<i){ p = p ->próximo; j; } retornar p; }
Encontre o nó da tabela de nós por valor
LNode *LocateElem(LinkList L,ElemType e){ LNode *p = L->próximo; while(p != NULL && p->dados != e) p = p->próximo; retornar p; }
Inserir nó
p = GetElem(L,i-1); s->próximo = p->próximo; p->próximo = s; (i-1 é p, inserido após p)
Tempo O(n)
s->próximo = p->próximo; p->próximo = s; temp = p->dados; p->dados = s->dados; s->dados = temp; (i é p, insere e troca dados após p e implementa p inserção direta)
Tempo O(1)
Excluir nó
p = GetElem(L,i-1); q = p->próximo; p->próximo = p->próximo->próximo; grátis(q);
Tempo O(n)
q = p->próximo; p->dados = q->dados; p->próximo = p->próximo->próximo; grátis(q);
Tempo O(1)
Pergunte o comprimento da mesa
O comprimento da tabela não inclui o nó principal
Sobre)
Lista duplamente vinculada typedef estrutura DNode{ Dados ElemType; struct DNode *antes,*próximo; }DNode,*DLinkList;
Inserir nó
s->próximo = p->próximo; s->próximo->anterior = s; s->anterior = p; p->próximo = s;
O(1)
Excluir nó
p->anterior->próximo = p->próximo; p->próximo->anterior = p->anterior; grátis(p);
O(1)
lista vinculada circular Recursos óbvios: O ponteiro do último nó não é NULL. A condição nula é se o nó principal é igual ao ponteiro principal, não NULL; isto é, if (L->next == L);
Lista circular unida individualmente
Lista circular duplamente vinculada
lista vinculada estática Use matrizes para descrever a estrutura de armazenamento vinculada de uma lista linear. Características: O ponteiro é o endereço relativo do nó (subscrito do array), também conhecido como cursor. Assim como a lista de sequências, a lista vinculada estática também precisa pré-alocar um espaço de memória contínuo. #define MaxSize 50 estrutura typedef{ Dados ElemType; próximo; }SLinkList[MaxSize];
3 pilhas e filas
pilha Número Cattelan: 1/n 1(C2n n)
Armazenamento sequencial da pilha (pilha sequencial) #define MaxSize 50 estrutura typedef{ Dados ElemType[MaxSize]; int top; //ponteiro para o topo da pilha }SqStack;
inicialização voidInitStack(SqStack &S){ topo = -1; }
A pilha está vazia bool StackEmpty(SqStack S){ if(S.top == -1) retornar verdadeiro; caso contrário, retorne falso; }
empurrar para a pilha bool Push(SqStack &S,ElemType x){ if(S.top == MaxSize-1) retorna falso; S.data[ S.top] = x; //top é inicializado como -1 retornar verdadeiro; }
pop bool Pop(SqStack &S,ElemType &x){ if(S.top == -1) retornar falso; x = S.dados[S.top- -]; retornar verdadeiro; }
Leia o elemento superior da pilha bool GetTop(SqStack S,ElemType &x){ if(S.top == -1) retornar falso; x = S.dados[S.top]; retornar verdadeiro; }
Pilha compartilhada: aproveitando a posição relativamente inalterada da parte inferior da pilha, deixe duas pilhas sequenciais compartilharem um espaço de matriz unidimensional. duas pilhas se estendem em direção ao meio do espaço compartilhado. A pilha compartilhada pode fazer uso mais eficiente do espaço de armazenamento. Os dois espaços de pilha se ajustam e o estouro ocorre apenas quando todo o espaço de armazenamento está cheio.
Inicial: top0 = -1 top1 = MaxSize pilha cheia: somente quando dois ponteiros de pilha são adjacentes top1 - top0 = 1;
pilha de corrente typedef estrutura Linknode{ Dados ElemType; estrutura Linknode *próximo; }*LinkStack;
fila
Armazenamento de pedidos em fila #define MaxSize 50 estrutura typedef{ Dados ElemType[MaxSize]; interno dianteiro, traseiro; }SqQueue;
fila sequencial
Condição de fila vazia: Q.front == Q.rear ==0; Observe que Q.rear == MaxSize não pode ser usado como condição para julgar que a equipe está cheia e pode ser um falso overflow.
fila circular As operações de desenfileiramento, junção e completa da fila circular devem ter%; a condição vazia é apenas Q.rear == Q.front;
inicialização void InitQueue(SqQueue &Q){ Q.traseiro = Q.frontal = 0; }
A equipe está vazia bool isEmpty(SqQueue Q){ if(Q.rear == Q.front) retorna verdadeiro; caso contrário, retorne falso; }
Junte-se à equipe bool EnQueue(SqQueue &Q,ElemType x){ if((Q.rear 1)%MaxSize == Q.front) retorna falso; Q.dados[Q.traseiro] = x; Q.traseiro = (Q.traseiro 1)%MaxSize; retornar verdadeiro; }
Retirar da fila bool DeQueue(SqQueue &Q,ElemType &x){ if(Q.rear == Q.front) retorna falso; x = Q.dados[Q.front]; Q.front = (Q.front 1)%MaxSize; retornar verdadeiro; }
Armazenamento em cadeia de filas (fila em cadeia) typedef estrutura LNode{ Dados ElemType; estrutura LNode* próximo; }LNode; estrutura typedef{ LNode *dianteiro,*traseiro; }LinkQueue; A fila em cadeia é adequada para situações em que os elementos de dados mudam relativamente grandes e não há situação em que a fila esteja cheia e cause estouro.
inicialização void InitQueue(LinkQueue &Q){ Q.front = Q.rear = (LinkQueue*)malloc(sizeof(LinkQueue)); //Crie o nó principal Q.front ->próximo = NULL; }
A equipe está vazia bool IsEmpty(LinkQueue&Q){ if(Q.front == Q.rear) return true;//Com nó principal caso contrário, retorne falso; }
Junte-se à equipe void EnQueue(LinkQueue &Q,ElemType x){ LinkNode *s = (LinkNode*)malloc(sizeof(LinkNode)); s->data = x; s->next = NULL;//Cria um novo nó e insere-o no final da cadeia Q.rear->próximo = s; Q.traseira = s; }
Retirar da fila bool DeQueue(LinkQueue &Q,ElemType &x){ if(Q.rear == Q.front) retorna falso; LinkNode *p = Q.front->próximo; x = p->dados; Q.front->próximo = p->próximo; if(Q.rear == p) Q.rear = Q.front;//A fila original possui apenas um nó grátis(p); retornar verdadeiro; }
Fila dupla: uma fila que permite que ambas as extremidades executem operações de enfileiramento e desenfileiramento. Sua estrutura lógica de elemento ainda é uma estrutura linear, as duas extremidades da fila são chamadas de front-end e back-end, respectivamente.
Deque restrito de entrada
Permitir inserções e exclusões em uma extremidade, mas somente exclusões na outra extremidade
Deque restrito de saída
Permitir inserções e exclusões em uma extremidade, mas apenas inserções na outra extremidade
Aplicações de pilhas e filas
Aplicação de correspondência de pilha entre colchetes
[]()([][]()) é o formato correto; (] [) é o formato errado;
Defina uma pilha vazia e leia os parênteses em sequência; se for um parêntese direito, retire o elemento superior da pilha para inspeção. Se corresponder, retire-o da pilha, caso contrário, um erro será relatado; parêntese esquerdo, coloque-o na pilha no final do algoritmo, verifique se a pilha está vazia. Se estiver vazia, ela corresponderá.
Aplicação de pilha na avaliação de expressão
Expressões infixas: dependem da precedência do operador, mas também lidam com parênteses Expressões postfix: precedência do operador já considerada, sem parênteses (travessia pós-ordem da árvore de expressões) Converter expressão infixa em expressão pós-fixada
Processando expressões pós-fixadas: verifique cada item da expressão sequencialmente e, se for um operando, coloque-o na pilha; se for um operador, saia continuamente dos dois operandos Y e X da pilha para formar X<OP>Y, e colocar Os resultados do cálculo são colocados de volta na pilha
Aplicação de pilha em recursão
Aplicação de fila em travessia hierárquica
O nó raiz entra na fila; se a fila estiver vazia (todos os nós foram processados), a travessia termina, caso contrário, o primeiro nó raiz na fila é retirado da fila e acessado. o O filho da esquerda entra na equipe se tiver um filho da direita, adiciona o filho da direita à equipe e volta para a etapa anterior;
Aplicação de filas em sistemas computacionais
1. Resolva o problema de incompatibilidade de velocidade entre o host e os dispositivos externos; 2. Resolver problemas de competição de recursos causados por múltiplos usuários
1. Host e impressora: Configure um buffer de dados de impressão Quando o buffer estiver cheio, o host pausará a saída. A impressora retirará o fcfs da fila e enviará uma solicitação ao host após a impressão.
2. Alocação de recursos de CPU: FCFS
Armazenamento compactado de matrizes especiais Resolução de problemas: De acordo com as condições correspondentes, faça um esboço e faça uma dedução agora. O principal é entender a lógica de armazenamento ao invés de memorizar fórmulas.
variedade Uma sequência finita de n elementos de dados do mesmo tipo Limite da dimensão: o intervalo de valores do subscrito Matrizes são uma generalização de tabelas lineares. Uma matriz de um bit pode ser vista como uma tabela linear, e uma matriz bidimensional pode ser vista como uma tabela linear cujos elementos também são tabelas lineares de comprimento fixo e assim por diante; array é definido, suas dimensões e limites não mudam mais, portanto, além da inicialização e destruição, o array terá apenas operações de acesso e modificação de elementos, o array bidimensional é armazenado fisicamente como mapeado para um bit; variedade!
Estrutura de armazenamento de matriz linha principal; coluna principal
Armazenamento compactado de matrizes Armazenamento compactado: apenas um espaço de armazenamento é alocado para vários elementos com o mesmo valor e nenhum espaço de armazenamento é alocado para zero elementos. Matrizes especiais: possuem muitos elementos de matriz idênticos ou zero elementos, com distribuição regular
Matriz simétrica
A[1 n][1 n] é armazenado em B[n(n 1)/2] Desenvolva a fórmula de um lado e troque i e j do outro lado
matriz triangular
A[1 n][1 n] é armazenado em B[n(n 1)/2 1] Após deduzir a fórmula de um lado, o outro lado armazena apenas um número em n(n 1)/2 Analisando a situação específica, isso porque o subscrito começa do zero.
matriz triangular superior
matriz triangular inferior
matriz tridiagonal
k = 2i j-3 Quando o subscrito k de uma matriz B de um bit, i=(k 1)/3 1 é limitado, j=k-2i 3 ! ! ! Não memorize de cor! ! ! Para entender, esboce as linhas correspondentes de uma matriz tridiagonal! ! !
matriz esparsa O número de elementos diferentes de zero t na matriz é muito pequeno em relação ao número de elementos da matriz s, ou seja, a matriz com s>>t é chamada de matriz esparsa. Por exemplo, uma matriz 100x100 possui menos de 100 elementos diferentes de zero.
Use o método tripleto (rótulo de linha, rótulo de coluna, valor) ou lista vinculada
4 espetos
Definição e implementação de string
Definição de string: String é chamada de string. Os objetos de processamento não numérico em computadores são basicamente dados de string. String (String) S; uma subsequência composta por quaisquer múltiplas strings de caracteres consecutivas em uma string é chamada de substring da string, e a string correspondente contendo a substring é chamada de string principal. A estrutura lógica de uma string é muito semelhante à de uma tabela linear. A única diferença é que seu objeto de dados é limitado a um conjunto de caracteres; o objeto operacional de uma tabela linear é um único elemento, enquanto o objeto operacional de uma string; é uma substring!
estrutura de armazenamento de strings
Armazenamento sequencial de comprimento fixo #define MaxLen 255 estrutura typedef{ char ch[MaxLen]; comprimento interno; }SString; Você também não pode registrar o comprimento e usar ‘\0’, que não está incluído no comprimento da string, para indicar o comprimento da string.
Representação de armazenamento alocado em heap (alocação dinâmica) estrutura typedef{ char*ch; comprimento interno; }HString; Na linguagem C, o espaço alocado dinamicamente é armazenado no heap e gerenciado por malloc() free().
Representação de armazenamento em cadeia de blocos: cada nó é chamado de bloco, e um bloco pode armazenar um ou vários caracteres, toda a lista vinculada é chamada de estrutura de cadeia de blocos; Quando o último nó ocupa menos de um bloco, geralmente é preenchido com "#"
Operações básicas de string Conjunto mínimo de operações: atribuição de strings, comparação de strings, localização de comprimento de strings, concatenação de strings e localização de substrings; Ou seja, essas operações não podem ser implementadas usando outras operações de string.
Atribuição de string StrAssign(&T,chars) atribui o valor de T a chars
Comparação de strings StrCompare(S,T) if S>T,return>0;S=T,return 0;S<T,return<0
Encontre o comprimento da string StrLength(S) retorna o número de elementos S
Concatenação de série Concat(&T,S1,S2) usa T para retornar uma nova string formada pela concatenação de S1 e S2.
Encontre a substring SubString(&Sub,S,pos,len) Use Sub para retornar a substring começando no caractere pos da string S e continuando com o comprimento de len
correspondência de padrão de string A operação de posicionamento de substrings é geralmente chamada de correspondência de padrões de strings. O que é encontrado é a posição da substring (geralmente chamada de string padrão) na string principal.
Algoritmo simples de correspondência de padrões
índice interno(SString S,SString T){ int i=1,j=1; enquanto(i<=S.comprimento && j<=T.comprimento){ if(S.ch[i] == T.ch[j]){ eu j ; } outro{ eu = i-j 1 1;j = 1; } if(j > T.comprimento) return i - T.comprimento; caso contrário, retorne 0; } Pior complexidade de tempo O(mn)
Algoritmo de correspondência de padrões aprimorado – algoritmo KMP Durante o processo de correspondência, as informações de correspondência já obtidas são usadas para que o ponteiro da string principal i não volte mais, mas continue a corresponder para trás a partir da posição atual. O cálculo do número de dígitos para deslizar o modo para trás está relacionado apenas à estrutura do próprio modo! Não tem nada a ver com a string principal Embora o modo normal seja O(m n), a complexidade de tempo reconhecida do algoritmo KMP é O(n m)
Conceitos relacionados: Prefixo: Todas as substrings de cabeçalho da string, exceto o último caractere; Sufixo: todas as substrings finais da string, exceto o primeiro caractere; Correspondência parcial (PM): o comprimento igual mais longo do prefixo e do sufixo da string. Exemplo: 'ababa' Atenção! ! A partir do primeiro caractere da string principal, divida todas as substrings e, em seguida, analise o comprimento do prefixo e do sufixo e o sufixo e sufixo iguais mais longos, respectivamente. O prefixo e sufixo 'a' é um conjunto vazio e o maior comprimento igual de prefixo e sufixo é 0 'ab' prefixo 'a' sufixo 'b' O maior comprimento de correspondência igual é 0 'aba' prefixo 'a' 'ab' sufixo 'a' 'ba' {'a' 'ab'} ^ {'a' 'ba'} = 'a', o maior comprimento igual do sufixo é 1 Prefixo ‘abab’ ‘a’ ‘ab’ ‘aba’ sufixo ‘b’ ‘ab’ ‘bab’, o prefixo igual mais longo e o comprimento do sufixo são 2 'ababa' prefixo 'a' 'ab' 'aba' 'abab' sufixo 'a' 'ba' 'aba' 'baba' ,{ } ^ { } = {'a' 'aba'}, o maior comprimento igual do sufixo 3 Portanto, o valor de correspondência parcial da string final é 00123
Ao usar o algoritmo KMP, primeiro escreva a tabela PM da string padrão (substring). Quando a substring não corresponder à string principal, encontre o valor PM do último elemento correspondente da substring na tabela PM e, em seguida, deixe o valor PM. substring A distância retrocedida (número de caracteres correspondentes - valor PM do último caractere correspondente); quando ocorre uma incompatibilidade em uma determinada viagem, se o valor de correspondência parcial da parte correspondente for 0, significa que não há sufixos iguais e sufixos na sequência correspondente. O número de dígitos movidos é o maior (comprimento correspondente - 0) e é movido diretamente para a posição apontada pela string principal neste momento para a próxima comparação. Mover = (j-1) - PM[j-1];
Geração e modificação do próximo array: Para facilitar a incompatibilidade, em vez de esperar por um caractere, verifique diretamente o valor PM da posição atual do caractere incompatível, de modo que todos os elementos no PM sejam movidos 1 posição para a direita, a primeira posição seja preenchida com -1 , e a última posição é ignorada; Chamado de next array, Move = (j-1)-next[j] Neste momento, j retorna para j = j-Move = next[j] 1; Em seguida, adicione 1 a todos os próximos e atualize o próximo array para que j = next[j] (neste momento, a primeira posição do próximo array seja 0) O significado da próxima matriz final: Quando o j-ésimo caractere da substring não corresponder, pule para a próxima posição [j] da substring para a próxima rodada de correspondência! ! !
Codifique usando o próximo array final: int index_KMP(String T,String S,int próximo[]){ int i=1,j=1; enquanto(i<=S.comprimento && j<=T.comprimento){ if(S.ch[i] == T.ch[j]){i ;j ;} senão j = próximo[j]; } if(j > T.length) retorna i-T.length; caso contrário, retorne 0; }
Otimização adicional do algoritmo KMP Exemplo: Quando a string principal 'aaabaaaab' e a string padrão 'aaaab' são correspondidas, a quarta posição é incompatível. Se a matriz next[j] acima for usada, ela será movida uma para a esquerda a cada vez e depois comparada; mas Pj = Pnext[ aparecerá j], resultando em comparação redundante; Solução: Certifique-se de que Pj = Pnext[j] não deve aparecer; caso apareça, execute a recursão novamente, corrija next[j] para next[next[j]] e nomeie-o nextval[j]
5 árvores e árvore binária
árvores e floresta
conceito
Uma árvore é um conjunto finito de n (n>0) nós. Quando n=0, é chamada de árvore vazia, pois possui apenas um nó raiz, e os nós restantes podem ser divididos em conjuntos finitos disjuntos, chamados de subárvores; 1. O nó raiz da árvore não tem antecessor, e os outros nós além do nó raiz têm um e apenas um predecessor 2. Todos os nós na árvore podem ter zero ou mais sucessores, a árvore é adequada para representar dados com um; estrutura hierárquica; 3. Terminologia: 1. Antepassado-descendente (pode abranger vários níveis, desde que exista uma relação descendente-filho-pai-irmão (com o mesmo nó pai); 2. O número de filhos de um nó na árvore é chamado de grau do nó; o grau máximo de um nó na árvore é o grau da árvore; 3. Nós com grau > 0 são chamados de nós ramificados (nós não terminais); nós com grau 0 são chamados de nós folhas (nós terminais); o número de ramificações de um nó ramificado é o grau do nó ramificado; 4. O nível dos nós é definido a partir da raiz da árvore, sendo o nó raiz os nós de primeiro nível cujos nós pais estão no mesmo nível são primos um do outro; A profundidade de um nó é acumulada camada por camada, começando no nó raiz e de cima para baixo (imagine a raiz da árvore ficando mais profunda para baixo) A altura do nó é acumulada camada por camada começando no nó folha e de baixo para cima (parece muito alta de baixo para cima) A altura ou profundidade de uma árvore é o número máximo de níveis de nós na árvore; 4. Árvores ordenadas e árvores não ordenadas, árvores ordenadas não são intercambiáveis 5. Caminho e comprimento do caminho: O caminho é composto pela sequência de nós passados entre os dois nós, e o comprimento do caminho é o número de arestas passadas! 6. Floresta: um conjunto de árvores separadas. Enquanto o nó raiz for excluído, a árvore se tornará uma floresta; inversamente, enquanto um nó raiz for adicionado a m árvores, a floresta se tornará uma árvore;
natureza
natureza: 1. O número de nós da árvore é igual à soma dos graus de todos os nós (número total de ramos) 1 (nó raiz) 2. O i-ésimo nível de uma árvore de grau m tem no máximo m^i-1 nós (i>1) 3. Uma árvore m-ária com altura h tem no máximo (m^i - 1)/(m-1) nós. 4. A altura mínima de uma árvore m-ária com n nós é
estrutura de armazenamento
1. Armazenamento sequencial: Notação parental: uma estrutura grande define uma estrutura pequena, e a estrutura pequena são os dados e os ponteiros pais; #define Max_Tree_SIze 100 estrutura typedef{ Dados ElemType; pai interno; }PTNode; estrutura typedef{ Nós PTNode[Max_Tree_Size]; intn; }PTree; Essa estrutura aproveita o fato de que cada nó (exceto o nó raiz) possui apenas um pai e pode obter rapidamente o nó pai de cada nó, mas requer percorrer toda a árvore para encontrar os filhos do nó.
2. Na representação filho: conecte os nós filhos de cada nó com uma única lista vinculada para formar uma estrutura linear. Existem n listas vinculadas para n nós (a lista vinculada filho de um nó folha é uma lista vinculada vazia); Este método é muito simples para encontrar filhos, mas encontrar pais requer percorrer as n listas vinculadas de filhos apontadas pelos ponteiros da lista vinculada de filhos em n nós; Semelhante à tabela crítica
3. Representação de irmão filho: também conhecida como representação de árvore binária. typedef estrutura CSNode{ Dados ElemType; struct CSNode *primeirofilho,*próximo; }CSNode,*CSTree; Implemente convenientemente a operação de conversão de uma árvore em uma árvore binária A criança da esquerda tem um irmão
Operações: Conversão de árvores, florestas e árvores binárias
A conversão entre árvores e árvores binárias; tanto as árvores quanto as árvores binárias podem ser representadas por listas vinculadas binárias. A estrutura física é a mesma, mas as regras para converter uma árvore em uma árvore binária: filho esquerdo e irmão direito. Como o nó raiz não tem irmãos, o nó raiz não tem subárvore direita. Etapas de conversão de caligrafia: 1. Conecte uma linha entre os nós irmãos 2. Mantenha apenas a conexão entre cada nó e seu primeiro filho e use outro Apague todas as conexões dos filhos; 3. Tome a raiz da árvore como eixo e gire 45 graus no sentido horário
Conversão de floresta em árvore binária 1. Primeiro converta cada árvore da floresta em uma árvore binária 2. A raiz de cada árvore é considerada um relacionamento entre irmãos e as árvores subsequentes são sequencialmente consideradas como o nó direito da árvore anterior. 3. Gire a raiz da primeira árvore 45 graus no sentido horário
Converta uma árvore binária em uma floresta: 1. Desconecte a subárvore direita do nó raiz, por sua vez, até que haja apenas um nó raiz sem uma subárvore direita. 2. Converta todas as árvores binárias desconectadas em árvores para obter a floresta original Converter uma árvore binária em uma árvore ou floresta é único
Converter árvore binária em árvore 1. Remova todos os nós direitos, nós direitos e nós direitos da subárvore esquerda do nó raiz. . . todos como filhos da raiz 2. Ajuste por sua vez
Atravessar
travessia de árvore
Percurso raiz primeiro Equivalente à travessia de pré-ordem da árvore binária correspondente
travessia de raiz traseira Equivalente ao percurso em ordem da árvore binária correspondente! ! ! ! !
Passagem de nível
Viajando pela floresta
Pré-encomenda de travessia da floresta Da frente para trás, cada árvore é percorrida primeiro pela raiz.
Atravessando a floresta em sequência Percorra a raiz de cada árvore da frente para trás (a floresta diz que a travessia em ordem é relativa à árvore binária correspondente)
Aplicação de árvores - busca sindical
Union-find é uma representação de conjunto simples que suporta 3 operações A definição da estrutura do conjunto de pesquisa sindical: #define TAMANHO 100 int UFSets[TAMANHO]; Um conjunto na busca de união é uma árvore e vários conjuntos formam uma floresta.
Operação de inicialização (S é um conjunto de pesquisa de união), inicializa cada elemento do conjunto S em um subconjunto com apenas um único elemento, S é o conjunto inteiro, que é uma floresta, armazenado na matriz de representação pai (ambos são - 1 neste momento) void Inicial(int S[]){ for(int i=0;i<tamanho;i) S[i] = -1; }
A operação Find encontra a subcoleção onde o único elemento x está localizado no conjunto S e retorna o nome da subcoleção. int Encontrar(int S[],int x){ while(S[x]>=0)//Loop para encontrar a raiz de x x = S[x];//Atribuir x à raiz de x retornar x; }
A operação de união mescla o subconjunto root2 no conjunto S em root1. Requer que root1 e root2 não se cruzem, caso contrário, não serão mesclados. void União(int S[],int Raiz1,int Raiz2){ //Exige que Root1 e Root2 sejam diferentes S[Root2] = Root1; //Conecta Root2 a outro Root1; } O valor do nó raiz de cada árvore na floresta na matriz de representação pai é negativo e o valor absoluto é o número total de nós sob a raiz.
Árvore binária
Conceito: Cada nó possui no máximo duas subárvores e a ordem não pode ser invertida. A ordem dos nós em uma árvore binária não é relativa a outro nó, ela é determinada. (diferente de uma árvore com grau 2); Várias árvores binárias especiais: 1. Árvore binária completa: cada nível contém o maior número de nós 2. Árvore binária completa: os números de sequência 1-n correspondem completamente aos nós na árvore binária completa 3. As chaves de todos os nós na subárvore esquerda da árvore de classificação binária são menores que as chaves do nó raiz e as chaves à direita são maiores que a raiz. 4. Árvore binária balanceada: A diferença de profundidade entre a subárvore esquerda e a subárvore direita de qualquer nó da árvore não excede 1 5. Dê dicas sobre a árvore binária
Clue árvore binária (desconhecida, especialmente expandida) typedef estrutura TreadNode{ Dados ElemType; struct TreadNode *lchild,*rchild; int ltag,rtag; }ThreadNode,*ThreadTree; Uma lista vinculada binária composta por uma estrutura de nó como a estrutura de armazenamento da árvore binária é chamada de lista vinculada de pistas; os ponteiros que apontam para o predecessor e o sucessor do nó são chamados de pistas; Uma árvore binária com pistas é chamada de árvore binária de pistas
Características e definição: Use o ponteiro nulo da árvore binária para armazenar o ponteiro predecessor ou sucessor, de modo que possa ser percorrido tão facilmente quanto uma lista vinculada. A árvore binária Clue acelera a localização do antecessor e sucessor de um nó! Regulamento: Se não houver subárvore esquerda, deixe lchild apontar para seu nó predecessor; se não houver subárvore direita, deixe rchild apontar para seu nó sucessor; (lchild,ltag,data,rtag,rchild) ltag = 0,lchild refere-se ao filho esquerdo, ltag = 1,lchild refere-se ao nó predecessor rtag = 0, rchild refere-se ao filho certo, rtag = 1, rchild refere-se ao sucessor do nó
Construção de árvore binária de pistas em ordem A essência da pista de uma árvore binária é percorrer a árvore binária uma vez void InTread(ThreadTree &p,ThreadTree &pre){ se(p!=NULO){ InThread(p->lfilho,pré); if(p->lfilho == NULO){ p->lchild = pré; p->ltag = 1; } if(pré!=NULL && pré->próximo->rchild == NULL){ pré->criança = p; pré->rtag = 1; } pré = p; InThread(p->rchild,pré); } } void CreateInThread(ThreadTree T){ ThreadTree pré = NULL; se(T!=NULO){ InThread(T,pré); pré->rchild = NULL; pré->rtag = 1; } } Você também pode adicionar um nó principal à lista de dicas da árvore binária, deixe lchild referir-se ao nó raiz da árvore binária e rchild referir-se ao último nó visitado da árvore binária; Além disso, o lchild do primeiro nó visitado e o rchild do último nó visitado na árvore binária apontam para o nó principal. Torna-se uma lista vinculada de pistas bidirecional, o que facilita a travessia da árvore binária de pistas de frente para trás e de trás para frente.
Travessia de árvores binárias com pistas em ordem Os nós da árvore binária de dicas em ordem ocultam as informações do predecessor e do sucessor da árvore binária de dicas. 1. Encontre o primeiro nó na sequência ordenada na árvore binária de dicas ordenadas: ThreadNode *Firstnode(ThreadNode *p){ enquanto(p->ltag == 0) p = p->lchild; retornar p; }//Encontre o nó inferior esquerdo (não necessariamente um nó folha) 2. Encontre o sucessor do nó p na árvore binária de dicas em ordem na sequência em ordem ThreadNode *Nextnode(ThreadNode *p){ if(p->rtag == 0) return Primeironode(p->rchild); senão retorne p->rchild; } 3. Função principal void Inorder(ThreadNode *T){ for(ThreadNode *p = Primeironó(T);p!=NULL;p-Nextnode(p)) visitar(p); }
Árvore binária de pistas de pré-encomenda e árvore binária de pistas de pré-encomenda Você só precisa alterar o segmento de código da transformação encadeada e a posição onde as funções recursivas da subárvore esquerda e direita encadeadas são chamadas. A direção que aponta para NULL pode ser diferente, um antecessor refere-se a NULL e um sucessor refere-se a NULL; O antecessor e o sucessor específicos precisam ser analisados detalhadamente de acordo com a ordem de passagem; Especial: Para encontrar o sucessor na árvore binária de dicas pós-ordem, você precisa conhecer os pais do nó, ou seja, você precisa usar uma lista vinculada de tridente com um campo de sinalização como estrutura de armazenamento (a lista vinculada de tridente possui três campos , e um ponteiro para o nó pai é adicionado)
natureza: 1. O número de nós folha de uma árvore binária não vazia é igual ao número de nós com grau 2 1, ou seja, n0 = n2 1 2. O k-ésimo nível de uma árvore binária não vazia possui no máximo 2 ^ (k-1) nós. 3. Uma árvore binária não vazia com altura h possui no máximo 2 ^ h-1 nós. 4. Árvore binária completa: i>1 O nó pai é limitado por i/2. i é um número par, i/2; i é um número ímpar, (i-1)/2; 2i<=n o filho esquerdo de i é 2i, caso contrário não há filho esquerdo 2i 1<=n o filho certo de i é 2i 1, caso contrário não existe filho certo 5. Duas fórmulas: 2^(h-1)-1<n<=2^h-1;2^(h-1) <=n <2^h
Estrutura de armazenamento: 1. Estrutura de armazenamento sequencial: Árvores binárias completas e árvores binárias completas são adequadas para estruturas de armazenamento sequenciais; árvores binárias gerais precisam adicionar muitos nós vazios (0 na matriz é recomendado para iniciar o armazenamento a partir do subscrito 1 da matriz, que está em linha com as propriedades de); uma árvore binária completa i 2. Estrutura de armazenamento em cadeia: A utilização do espaço é maior do que a estrutura de armazenamento sequencial (árvore binária geral) typedef estrutura BiTNode{ Dados ElemType; estrutura BiTNode *lchild,*rchild; }BiTNode,*BiTree;
operar
Atravessar
DFS Cada nó é visitado uma vez e apenas uma vez. A complexidade de tempo e a complexidade de espaço são ambas O(n). A profundidade da pilha de trabalho recursiva é a profundidade da árvore.
passagem de pré-encomenda void Pré-encomenda(BiTree T){ se(T!=NULO){ visita(T); Pré-Ordem(T->lchild); PréOreder(T->rchild); } } Não recursivo: void Pré-encomenda2(BiTree T){ InitStack(S);BiTree p = T; enquanto(p || !isEmpty(S)){ Se p){ visitar(p);Push(S,p); p = p->lfilho; } outro{ Pop(S,p); p = p->rfilho; } } }
travessia em ordem voidInOrder(BiTree T){ se(T!=NULO){ InOrder(T->lfilho); visita(T); InOrder(T->rchild); } } Não recursivo: voidInOrder2(BiTree T){ InitStack(S);BiTree p = T; while(p || !isEmpty(S)){//p não está vazio ou a pilha não está vazia if(p){ //Todo o caminho para a esquerda Empurrar(S,p); p = p->lfilho; } outro{ Pop(S,p);visita(p); p = p->rchild; //p vai para a subárvore direita } } }
Travessia pós-ordem void PostOrder(BiTree T){ se(T!=NULO){ PostOrder(T->lchild); PostOrder(T->rchild); visita(T); } }
BFS
Passagem de nível void LevelOrder(BiTree T){ InitQueue(Q); BiÁrvore p = T; Enfileirar(Q,p); while(!isEmpty(Q)){ Desenfileirar(Q,p); visitar(p); if(p->lchild != NULL) Enqueue(Q,p->lchild); if(p->rchild != NULL) Enqueue(Q,p->rchild); } }
aplicativo
Árvore de classificação binária BST (árvore de pesquisa binária)
Pesquisa BST BSTNode *BST_Search(BiTree T, chave ElemType){ while(T!=NULL&&key!=T->dados){ if(key<T.data) T = T->lchild; senão T=T->rchild; } retorno T; }
Inserção BST: O nó inserido deve ser um nó folha recém-adicionado int BST_Insert(BiTree &T,KeyType k){ se(T==NULO){ T = (BiTree)malloc(sizeof(BSTNode)); T->chave = k; T->lchild = T->rchild = NULL; retornar 1; } senão if(k==T->key) return 0; senão if(k<T->key) return BST_Insert(T->lchild,k); caso contrário, retorne BST_Insert(T->rchild,k); }
Estrutura do BST void Create_BST(BiTree &T,KeyType str[],int n){ T = NULO; int i =0; enquanto(eu<n){ BST_Insert(T,str[i]); eu; } }
Exclusão do BST
O nó excluído é um nó folha e é excluído diretamente.
Se o nó excluído z tiver apenas uma subárvore esquerda ou subárvore direita, deixe sua subárvore esquerda ou subárvore direita substituir z's
Se o nó excluído z tiver subárvores esquerda e direita, substitua z pelo sucessor direto (ou predecessor direto) de z e, em seguida, exclua esse sucessor direto (ou predecessor direto) do BST, mudando assim para as duas primeiras situações.
Análise de eficiência de pesquisa BST
A eficiência de busca do BST depende principalmente da altura da árvore Se a diferença de altura entre as subárvores esquerda e direita do BST nunca exceder 1, então essa árvore de classificação binária é chamada de árvore binária balanceada e o comprimento médio da pesquisa é O (log2n); Se o BST for uma única árvore, o comprimento médio da pesquisa é O(n)
O comprimento médio de pesquisa ASL=∑PiCi (i=1,2,3,…,n)Pi é a probabilidade do i-ésimo elemento de dados na tabela de pesquisa, e Ci é o número de comparações que foram feitas ao encontrar o i-ésimo elemento de dados.
Comparação entre BST e pesquisa binária: A árvore de decisão da busca binária é única, mas a busca do BST não é única (a ordem de inserção afeta o formato da árvore); Inserção e exclusão: O objeto da pesquisa binária é uma lista de sequências ordenadas, que requer O(n). O BST não precisa mover o nó, mas apenas modificar o ponteiro para completar, que é O(log2n); Resumo: Quando a tabela ordenada é uma tabela de consulta estática, ela é adequada para armazenamento sequencial de tabelas e usa pesquisa binária; quando a tabela ordenada é uma tabela de consulta dinâmica, uma árvore de classificação binária deve ser selecionada como estrutura lógica;
árvore binária balanceada
Projetado para evitar a redução do desempenho de árvores de classificação binária Definição: O valor absoluto da diferença de altura entre as palavras esquerda e direita de qualquer nó não excede 1, o que é denominado árvore balanceada; A diferença de altura entre a subárvore esquerda e a subárvore direita de um nó é o fator de equilíbrio do nó. O fator de equilíbrio de um nó de árvore binária balanceada só pode ser -1 0 1; árvore vazia.
Inserção em uma árvore binária balanceada: O objeto de cada ajuste é a subárvore mínima desbalanceada, ou seja, a subárvore cujo nó cujo valor absoluto do fator de equilíbrio mais próximo do nó inserido no caminho de inserção é maior que 1 como raiz! A primeira metade do processo de inserção de uma árvore binária balanceada é igual à de uma árvore binária ordenada, mas se for causado um desequilíbrio após a inserção, ele será dividido em quatro situações. (BST melhorado); Aqui LL RR LR RL refere-se à posição do nó inserido em relação ao nó raiz da subárvore mínima desequilibrada (a posição de uma determinada subárvore de um filho)
Rotação balanceada LL (rotação única direita) O nó é inserido no canto inferior esquerdo (não necessariamente imediatamente adjacente) da subárvore esquerda de A (deve ser imediatamente adjacente a ela), e o nó B no primeiro L (o filho esquerdo do nó raiz da subárvore mínima desequilibrada) é girado para a direita para substituir A, e A é como filho direito de B, B originalmente tinha filhos como a subárvore esquerda de A.
Rotação balanceada RR (rotação única esquerda) O nó é inserido no canto inferior direito do filho direito de A. O nó B no primeiro R é girado para a esquerda para substituir A. A é usado como a subárvore esquerda de B, e a subárvore esquerda original de B é usada como o subárvore direita de A. ps: Os modos LL e RR ajudam o filho esquerdo/direito B do nó da subárvore desequilibrada A a "subir"
Rotação balanceada LR (rotação dupla primeiro para a esquerda e depois para a direita) Um nó é inserido na subárvore direita do filho esquerdo do nó A. Primeiro, gire o nó raiz C da subárvore direita do filho esquerdo B de A na direção superior esquerda para a posição de B e, em seguida, gire o nó C na direção superior direita para a posição do nó A.
Rotação balanceada RL (rotação dupla para a direita e depois para a esquerda) Um nó é inserido na subárvore esquerda do filho direito do nó A. Primeiro, gire o nó raiz da subárvore esquerda C do filho direito B do nó A para a posição de B e, em seguida, gire C para a posição do nó A. ps: Quando LR e RL são girados, se o novo nó é inserido na subárvore esquerda de C ou na subárvore direita de C não afeta o processo de rotação. Os modos LR e RL são para ajudar o nó raiz C da subárvore direita/esquerda do filho esquerdo/direito B "superior" da subárvore desequilibrada
Encontre uma árvore binária balanceada O mesmo que pesquisa em árvore ordenada binária; A profundidade máxima de uma árvore binária balanceada contendo n nós é O(log2n), então o comprimento máximo de pesquisa também é O(log2n); O número máximo de comparações necessárias para encontrar uma árvore binária balanceada com um determinado número de nós.
Uma árvore binária balanceada de altura h Existem no máximo 2 ^ h - 1 nós; Existem pelo menos h(n) nós, h(n)=h(n-1) h(n-1) 1,h(0)=0,h(1)=1,h(2)=2c
árvore vermelha preta
Conceito de árvore vermelha e preta
Operações básicas de árvores rubro-negras
propriedades da árvore vermelha preta
Aplicações de árvores rubro-negras
Análise de desempenho da árvore rubro-negra
Árvores de Huffman e codificação de Huffman
Definição de árvore de Huffman A um nó da árvore é atribuído um valor que representa um determinado significado, que é chamado de peso do nó; O produto do comprimento do caminho da raiz da árvore até qualquer nó (número de arestas passadas) e o peso do nó é chamado de comprimento do caminho ponderado do nó; A soma dos comprimentos de caminho ponderados de todos os nós folha na árvore é chamada de comprimento de caminho ponderado da árvore, registrado como WPL (Weighted Path Length of Tree); Em uma árvore binária com n nós folha ponderados, a árvore binária com o menor comprimento de caminho ponderado WPL é chamada de árvore Huffman! Também chamada de árvore binária ótima
A estrutura da árvore de Huffman Dados n nós com pesos w1,w2,..,wn respectivamente, Crie um novo nó raiz e selecione os dois nós com o menor peso de cada vez para formar uma árvore binária. Adicione os pesos dos dois nós e trate-os como os novos pesos do nó raiz. o nó raiz é excluído e um novo nó raiz com peso é adicionado; Repita este processo até que não haja mais nós independentes
Propriedades das árvores Huffman 1. Cada nó inicial eventualmente se torna um nó folha e, quanto menor o peso, maior o comprimento do caminho do nó ao nó raiz. 2. Durante o processo de construção, um total de n-1 novos nós (nós de ramificação dupla) são criados, então o número total de nós da árvore Huffman é 2n-1 3. Cada construção seleciona 2 árvores como filhas do novo nó, portanto não há nenhum nó com grau 1 na árvore de Huffman.
Codificação de Huffman
Vários conceitos relacionados: Codificação de comprimento fixo: representação binária de comprimento igual de cada caractere Codificação de comprimento variável: caracteres diferentes são representados por bits binários de comprimentos diferentes A codificação de comprimento variável é muito melhor do que a codificação de comprimento fixo, porque caracteres com alta frequência de ocorrência podem receber códigos curtos e códigos com baixa frequência podem receber códigos longos, encurtando assim o comprimento médio de codificação dos caracteres e compactando os dados. , A codificação Huffman é uma codificação de compactação de dados amplamente utilizada e muito eficaz Nenhum dos códigos é o código do prefixo, é o código do prefixo, decodifique-o: identifique-o de frente para trás e traduza-o no código original;
A árvore de Huffman desce da raiz, atribuindo 0 a um lado e 1 ao outro lado (ou 0 ou 1 à esquerda e à direita, desde que estejam sempre unificados); O WPL da árvore Huffman pode ser considerado como o comprimento do código binário obtido pela codificação final; As árvores Huffman não são únicas, o WPL deve ser ideal
6 fotos
Conceitos básicos de gráficos
G=(V,E), conjunto de vértices V, conjunto de arestas E(G), representa o conjunto finito não vazio de vértices no grafo G; G ;V| representa o número de vértices, |E| Uma tabela linear pode ser uma tabela vazia e uma árvore pode ser uma árvore vazia, mas um gráfico não pode ser um gráfico vazio; O conjunto de vértices V no gráfico não deve estar vazio e E pode estar vazio.
gráfico direcionado, gráfico não direcionado Gráfico direcionado: E é uma aresta direcionada (arco) e um arco é um par ordenado de vértices, denotado como <v,w> Gráfico não direcionado: E é uma aresta não direcionada (aresta) e uma aresta é um par não ordenado de vértices, registrado como (v, w)
Gráfico simples, gráfico múltiplo Diagrama simples: 1. Não há arestas duplicadas (arestas apontando uma para a outra entre dois nós adjacentes em um gráfico direcionado não são contadas como arestas duplicadas) 2. Não há aresta do vértice para ele mesmo. Parcelas múltiplas: 1. O número de arestas entre dois vértices é maior que 1 2. Permitir que os vértices sejam relacionados entre si através de uma aresta As definições de gráficos múltiplos e gráficos simples são relativas e apenas gráficos simples são discutidos na estrutura de dados.
Gráfico completo (gráfico completo simples) Para gráficos não direcionados, o intervalo de valores de |E| é 0-n(n-1)/2, Um grafo não direcionado com n(n-1)/2 arestas é chamado de grafo completo e há uma aresta entre quaisquer dois vértices; Para gráficos direcionados, o intervalo de valores de |E| é 0-n(n-1), Um grafo direcionado com n(n-1) arcos é chamado de grafo completo direcionado e existe um arco oposto entre quaisquer dois vértices.
subtrama G=(V,E),G'=(V',E'); Se V' é um subconjunto de V e E' é um subconjunto de E, então G' é chamado de subgrafo de G. Se V(G')=V(G) for satisfeito, então G' é chamado de subgrafo gerado de G; Nenhum subconjunto de V e E pode formar um subgrafo G, porque os vértices associados a algumas arestas no subconjunto de E podem não estar neste subconjunto de V.
Conectividade, gráficos conectados e componentes conectados Se existe um caminho do vértice v ao vértice w, então v e w são considerados conectados. Se quaisquer dois vértices em G estiverem conectados, ele se tornará um grafo conectado, caso contrário, será um grafo desconectado. O subgrafo conectado máximo em um gráfico não direcionado é chamado de componente conectado. Um gráfico conectado tem pelo menos n-1 arestas. Se o número de arestas for menor que n-1, deve ser um grafo não conectado; Quantas arestas um gráfico desconectado pode ter no máximo? : O estado crítico de n-1 vértices formando um gráfico completo (adicionando qualquer aresta para formar um gráfico conectado)
Subgrafo conectado máximo: um subgrafo conectado no gráfico G que não está contido em outros subgrafos conectados (o máximo aqui não é o máximo de uma quantidade específica, mas um relacionamento que não está incluído, que na verdade é todos os vértices no subgrafo conectado e na aresta existir) Subgrafo minimamente conectado: A árvore geradora do grafo G é um subgrafo minimamente conectado
Gráfico fortemente conectado, componentes fortemente conectados Em um grafo direcionado, se um par de vértices v para w e w para v têm caminhos, então os dois vértices são fortemente conectados. Se qualquer par de vértices no grafo estiver fortemente conectado, o grafo será chamado de grafo fortemente conectado. O subgrafo fortemente conectado máximo em um gráfico direcionado é chamado de componente fortemente conectado do gráfico direcionado; A situação com o menor número de arestas quando um grafo direcionado é fortemente conectado: pelo menos n arestas são necessárias para formar um ciclo
abrangendo árvore, abrangendo floresta A árvore geradora de um grafo conectado é um subgrafo conectado mínimo que contém todos os vértices do grafo. Se o número de vértices fixos for n, então a árvore geradora terá n-1 arestas. Se uma aresta for cortada da árvore geradora, ela se tornará um subgrafo não conectado; se uma aresta for adicionada, um ciclo será formado; Em um gráfico desconectado, a árvore geradora de componentes conectados constitui a floresta abrangente do gráfico desconectado.
Grau de vértice, grau de entrada e grau de saída Gráfico não direcionado: TD(v), a soma dos graus de todos os vértices de um gráfico não direcionado é igual a 2 vezes o número de arestas Gráfico direcionado: O grau do vértice v é dividido em grau de entrada ID (v) e grau de saída OD (v). O grau do vértice v é igual à soma do grau de entrada e do grau de saída: TD (v). =ID(v) OD(v) , o grau de entrada de todos os vértices de um gráfico direcionado é igual ao grau de saída igual ao número de arestas
Bian's Quanhe.com Cada aresta pode ser marcada com um valor numérico que possui um determinado significado, que é chamado de peso da aresta. O grafo com arestas ponderadas é chamado de grafo ponderado, também conhecido como rede.
Gráfico denso, gráfico esparso Um grafo com poucas arestas é chamado de grafo esparso e o oposto é chamado de grafo denso. Geralmente, quando G satisfaz |E|<|V|log|V|, G é considerado um grafo esparso.
Caminhos, comprimentos de caminho e loops Caminho: um caminho do vértice vp para vq refere-se à sequência de vértices vp,vi1...vim,vq, O número de arestas em um caminho é chamado de comprimento do caminho Um caminho cujo primeiro e último vértices são iguais é chamado de ciclo ou ciclo Se um grafo tiver n vértices e mais de n-1 arestas, então o grafo deve ter um ciclo.
Caminho simples, loop simples Em uma sequência de caminhos, um caminho cujos vértices não aparecem repetidamente é chamado de caminho simples; Exceto o primeiro vértice e o último vértice, o ciclo no qual os vértices restantes não param nesse gráfico é chamado de ciclo simples.
distância Se o caminho mais curto do vértice u ao vértice v existir, então o comprimento desse caminho é a distância de u a v; Não há caminho de u para v, então a distância é registrada como ∞
árvore direcionada Um gráfico direcionado no qual o grau de entrada de um vértice é 0 e o grau de saída dos vértices restantes é 1 é chamado de árvore direcionada.
Armazenamento gráfico
método de matriz de adjacência
Refere-se ao uso de uma matriz unidimensional para armazenar o relacionamento entre os vértices no gráfico e ao uso de uma matriz bidimensional para armazenar as informações de borda no gráfico (o relacionamento de adjacência entre os vértices. A matriz bidimensional que armazena o). A relação de adjacência entre vértices é chamada de matriz de adjacência. A estrutura de armazenamento de todo o gráfico G: #define MaxVertexNum 100 typedef char VertexType; typedef int EdgeType; estrutura typedef{ VertexType Vex[MaxVertexNum]; //Tabela de vértices EdgeType Edge[MaxVertexNum][MaxVertexNum];//matriz de adjacência, tabela de arestas int vexnum,arcnum; }MGráfico;
Perceber: 1. Em aplicações simples, uma matriz bidimensional pode ser usada diretamente como matriz de adjacência do gráfico. 2. Em um grafo não direcionado, a matriz de adjacência é uma matriz simétrica (única) e pode ser compactada e armazenada. 3. Quando a matriz de adjacência indica apenas se a aresta existe, ElemType pode adotar o tipo de enumeração 0 e 1. 4. A complexidade espacial da representação da matriz de adjacência é O(n^2), n é o número de vértices |V|
Características da representação da matriz de adjacência: 1. O número de elementos diferentes de zero (ou diferentes de ∞) na linha i (ou coluna j) de um gráfico não direcionado representa o grau TD(v) do nó O número de elementos diferentes de zero (ou diferentes de ∞) na i-ésima linha e na j-ésima coluna do gráfico direcionado representa o grau de saída OD (v) e o grau de entrada ID (v) do nó. 2. Usando uma matriz de adjacência para armazenar um gráfico, é fácil determinar se existe uma conexão de arestas entre quaisquer dois vértices no gráfico, mas para determinar quantas arestas existem no gráfico, você precisa detectar cada elemento por linha; e coluna, o que consome muito tempo. 3. Gráficos densos são adequados para representação de armazenamento usando matrizes de adjacência. 4. Suponha que a matriz de adjacência do grafo G seja A, e o elemento A^n[i][j] de A^n seja igual ao número de caminhos de comprimento n do vértice i ao vértice j.
método de lista de adjacências
Quando um gráfico é esparso, o uso do método de lista de adjacências economiza muito espaço. O método de lista de adjacência de gráfico combina métodos de armazenamento sequencial e de armazenamento em cadeia; Crie uma lista vinculada individualmente para cada vértice no gráfico. O nó na i-ésima lista vinculada individualmente é anexado à aresta vi do vértice. Esta lista vinculada individualmente é a lista de arestas do vértice vi (um gráfico direcionado é chamado de. lista de arestas de saída) (a lista de arestas é "Edge" é realmente incrível) Cada nó na tabela de arestas representa uma aresta, mas armazena um número de vértice e omite outro número de vértice (na tabela de vértices) O ponteiro principal da tabela de arestas e as informações de dados de vértice do gráfico G são armazenados sequencialmente (chamadas de tabela de vértices); #define MaxVertexNum 100 typedef struct ArcNode{//nó da tabela de borda int adjvex;//A posição do vértice apontado pelo arco estrutura ArcNode *próximo; //Info Typeinfo; //O peso da borda da rede }ArcNode; typedef estrutura VNode{ Dados VertexType; // informações do vértice struct VNode *first;//Ponteiro para o primeiro arco anexado ao vértice }VNode,AdjList[MaxVertexNum]; estrutura typedef{ AdjList vértices; // lista de adjacências int vexnum,arcnum;//O número de vértices e arcos do gráfico }ALGráfico
Características: 1. Para um grafo não direcionado, o espaço de armazenamento necessário é O(|V| 2|E| (cada aresta aparece duas vezes na lista de adjacências) Para um gráfico direcionado, o espaço de armazenamento necessário é O(|V| |E|); 2. Para gráficos esparsos, o uso de listas de adjacências pode economizar muito espaço de armazenamento. 3. Na lista de adjacências, dado um vértice, é fácil encontrar todas as suas arestas na matriz de adjacência, uma linha precisa ser escaneada, O(n); Se você deseja determinar se existe uma aresta entre determinados dois vértices, você pode encontrá-la rapidamente na matriz de adjacência (um acesso um pouco aleatório, enquanto a matriz de adjacência precisa encontrar outro nó na tabela de arestas correspondente ao nó correspondente); , que é eficiente mais baixo. 4. Em um gráfico direcionado, encontrar o grau externo de um vértice requer apenas o cálculo do número de nós em sua lista de adjacência adjacente, mas encontrar o grau externo requer percorrer todas as listas de adjacência, portanto, a lista de adjacência inversa pode ser usada para; acelerar o grau de entrada de uma solução de vértice. 5. A lista de adjacências do grafo não é única e a ordem de conexão dos nós de borda é arbitrária.
método de lista cruzada
Uma lista reticulada é uma estrutura de armazenamento vinculada de um gráfico direcionado. Cada arco no gráfico possui um nó. O nó do arco possui 5 campos: (tailvex, headvex, hlink, tlink, info) tailvex e headvex são respectivamente os vértices de origem e inserção da aresta hlink e tlink conectam respectivamente os nós de aresta dos mesmos vértices de origem e inserção; O nó vértice possui 3 campos: (dados, primeiro a entrar, primeiro a sair) A lista reticulada não é única, mas uma lista reticulada representa um determinado gráfico Na lista reticulada, é fácil encontrar o grau de entrada de V e o grau de saída de V.
método de lista múltipla de adjacências
Lista múltipla de adjacência é uma estrutura de armazenamento em cadeia de gráfico não direcionado Nós de borda (mark,ivex,ilink,jvex,jlink,info) mark é um campo de sinalização, que pode marcar se esta aresta foi pesquisada; ivex e jvex são as posições dos dois vértices anexados à aresta no gráfico; anexado ao vértice jvex edge info está um campo de ponteiro apontando para várias informações relacionadas à borda; vértice (dados, primeira aresta)
Operações básicas em gráficos
Adjacente(G,x,y);Vizinhos(G,x);InsertVertex(G,x);DeleteVertex(G,x); AddEdge(G,x);RemoveEdge(G,x,y);FirstNeighbor(G,x);NextNeighbor(G,x); Get_edge_value(G,x,y);Set_edge_value(G,x,y,v);
Percurso gráfico Partindo de um determinado vértice, visitar todos os vértices uma e apenas uma vez é a base para resolver problemas de conectividade, ordenação topológica e problemas de algoritmo de caminho crítico; Diferente da árvore, para evitar que o mesmo vértice seja visitado várias vezes, ele precisa ser registrado após percorrer um vértice, e o array auxiliar visitado[]; A travessia do gráfico inclui BFS e DFS Usando a matriz de adjacência, a complexidade do tempo é O(n^2); usando a lista de adjacência, a complexidade do tempo é O(|V| |E|); BFS\DFS requer espaço O(n) (A complexidade de tempo e espaço não tem nada a ver com a seleção do método BFS\DFS, depende principalmente do método de armazenamento)
amplitude primeira pesquisa Travessia hierárquica em forma de árvore Acesse camada por camada, portanto é necessária uma fila auxiliar bool visitado[MAV_VERTEX_NUM]; void BFSTraverse(Gráfico G){ for(int i=0;i<G.vexnum;i) visitado[i] = FALSO; InitQueue(Q); for(int i=0;i<G.vexnum;i) if(!visitado[i]) BFS(G,i); } void BFS(Gráfico G,int v){ visitar(v); visitado[v] = VERDADEIRO; Enfileirar(Q,v); while(!isEmpty(Q)){ for(w=PrimeiroVizinho(G,v);w>=0;w=PróximoVizinho(G,v,w)) if(!visite[w]){ visitar(w); visitado[w] = VERDADEIRO; Enfileirar(Q,w); } } }
BFS resolve o problema do caminho mais curto de fonte única void BFS_MIN_Distance(Gráfico G,int u){ para(i=0;i<G.vexnum;i) //d[i] representa o caminho mais curto de você até i d[i]=∞;//inicializar comprimento do caminho visitado[u]=TRUE; d[u]=0; EnQueue(Q,u); while(!isEmpty(Q)){ DeQueue(Q,u); for(w=PrimeiroVizinho(G,u);w>=0;w=PróximoN oitavo(G,u,w)) if(visitado[w]){ visitado[w]=TRUE; d[w]=d[u] 1;//comprimento do caminho 1 EnQueue(Q,w); } } }
árvore geradora de largura A matriz de adjacência é armazenada de forma única – sua árvore geradora em largura é única O armazenamento da lista de adjacências não é único – sua árvore geradora de largura não é única
primeira pesquisa em profundidade
Semelhante ao percurso de pré-encomenda de uma árvore 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) if(!visitado[v]) DFS(G,v); } void DFS(Gráfico G,int v){ visitar(v); visitado[v] = VERDADEIRO; for(w=PrimeiroVizinho(G,v);w>=0;w=PróximoVizinho(G,v,w)) if(!visitou[w]){ DFS(G,w); } }
Árvores e florestas que abrangem a profundidade Gráficos conectados podem gerar árvores abrangentes e gráficos não conectados podem gerar florestas.
conectividade gráfica
Algoritmos de travessia de gráfico podem ser usados para determinar a conectividade do gráfico. Se um grafo não direcionado for conectado, começando em qualquer vértice, todos os vértices do grafo podem ser visitados com apenas uma travessia; Se um grafo direcionado estiver conectado e houver um caminho do ponto inicial para cada vértice do grafo, então todos os vértices do grafo poderão ser acessados.
Aplicação de diagramas
árvore geradora mínima Se uma aresta for cortada, a árvore geradora se tornará um gráfico desconectado; Se uma aresta for adicionada, um loop será formado ao longo do caminho; A árvore geradora mínima é especial. É necessário que seja a árvore geradora com a menor soma dos pesos das arestas no conjunto da árvore geradora; Quando os pesos de cada aresta são diferentes, a árvore geradora mínima é única, em geral, a árvore geradora mínima não é necessariamente única; A soma dos pesos das arestas da árvore geradora mínima é sempre única e mínima. GENERIC_MST(G){ T = NULO; enquanto T não forma uma árvore geradora; encontre uma aresta de custo mínimo (u, v) e nenhum loop será gerado após adicionar T; T=T∪(você,v); }
Algoritmo Prim (BFS) Semelhante a Dijkstra, inicialmente escolha um vértice do gráfico e adicione-o à árvore T, depois selecione um vértice mais próximo do conjunto atual de vértices em T e adicione o vértice e a aresta à árvore T; após cada operação e o número de arestas é aumentado em 1 até que todos os vértices do gráfico sejam adicionados à árvore T, T é a árvore geradora mínima. Deve haver n-1 arestas em T voidPrim(G,T){ T=conjunto vazio; você={W}; while((V-U)!=empty set){//Se a árvore não contém todos os vértices Seja (u, v) a aresta que forma u∈U e v∈(V-U) e tem o menor peso; T=T∪{(u,v)};//As arestas são classificadas em árvores U=U∪{v};//Os vértices são classificados em árvores } } A complexidade de tempo do algoritmo de Prim é O(|V^2|) e não depende de |E|, portanto é adequado para resolver a árvore geradora mínima de grafos com arestas densas.
Algoritmo de Kruskal Diferente do algoritmo de Prim, que se expande a partir dos vértices para gerar uma árvore geradora mínima, o algoritmo de Kruskal seleciona arestas apropriadas em ordem crescente de pesos para construir uma árvore geradora mínima. Inicialmente, cada vértice forma um componente conectado. Então, na ordem dos pesos das arestas, de pequeno para grande, a aresta que ainda não foi selecionada e tem o menor peso é selecionada continuamente se o vértice anexado à aresta cair em um conectado diferente. componente do componente T, adicione esta aresta a T, caso contrário, descarte esta aresta até que todos os vértices em T estejam em um componente conectado; nulo Kruskal(V,T){ T=V; //Inicializa T, apenas vértices numS=n;//Número de componentes conectados while(numS>1){//Se o número de componentes conectados for maior que 1 Pegue a aresta (v, u) com o menor peso de E se (v e u pertencem a diferentes componentes conectados em T) T=T∪{(v,você)}; numS--; } } } O algoritmo de Kruskal usa um heap para armazenar o conjunto de arestas, portanto, leva apenas O(log|E|) tempo para selecionar a aresta com o menor peso cada vez que adicionar uma nova aresta é semelhante ao processo de resolver uma equivalência; classe, e o método de pesquisa de união pode ser usado Estrutura de dados para descrever T, a complexidade do tempo é O(|E|log|E|), então Kruskal é adequado para gráficos com arestas esparsas e muitos vértices.
caminho mais curto A travessia BFS na figura anterior para resolver o caminho mais curto é para gráficos não ponderados. Aqui discutimos o caminho mais curto com o menor comprimento de caminho ponderado; Todos os algoritmos para resolver o caminho mais curto dependem de uma propriedade: o caminho mais curto entre dois pontos também inclui os caminhos mais curtos entre outros vértices do caminho.
Algoritmo de Dijkstra (BFS) Caminho mais curto de origem única; O algoritmo de Dijkstra define um conjunto S para registrar os vértices do caminho mais curto obtido. Inicialmente, o ponto fonte v0 é colocado em S. Cada vez que um novo vértice vi é incorporado ao conjunto S, o ponto fonte v0 deve ser modificado. para o vértice mais curto atual no valor de comprimento V-S definido não são permitidos; Configure duas matrizes auxiliares: dist[]: Registre o comprimento do caminho mais curto atual do ponto de origem v0 para outros vértices. Estado inicial: Se houver um arco de v0 a vi, então dist[i] é o peso do arco, caso contrário, defina dist[i; ] para ∞. (dist[] só volta um passo de cada vez - ideia BFS) path[]: path[i] representa o nó predecessor do caminho mais curto do ponto de origem ao vértice i. Ao final do algoritmo, o caminho mais curto do ponto fonte v0 até o vértice vi pode ser traçado. Ao resolver manualmente, desenhe uma matriz de dict. O mais importante é adicionar o vértice ao conjunto S toda vez que você selecioná-lo, atualizar a distância do caminho de cada ponto e então dar o próximo passo começando no último vértice do conjunto. S. Complexidade de tempo O(|V|^2)
Algoritmo Floyd O caminho mais curto entre cada par de vértices A recursão produz uma sequência de matriz quadrada de ordem n: A^-1,A^0,..A^N; onde A^k[i][j] representa o comprimento do vértice i a j, e o k-ésimo vértice é usado; como relaxe os vértices do meio A complexidade do tempo é O(|V|^3) (equivalente a executar o algoritmo de Dijkstra uma vez para cada vértice O(|V^2|)*|V|), Arestas com pesos negativos não são permitidas e podem atuar em gráficos direcionados\gráficos não direcionados (convertidos em gráficos direcionados com os mesmos lados bilaterais)
Expressão de descrição de gráfico acíclico direcionado DAG (gráfico de ciclo direcionado): Não há ciclo em um gráfico direcionado. É uma ferramenta eficaz para descrever expressões contendo subexpressões comuns. Por exemplo, uma sequência de expressões infixas pode ser representada por uma árvore binária (o nó raiz armazena operadores e a subárvore armazena operandos). Ao usar um gráfico acíclico direcionado, as mesmas subexpressões podem ser compartilhadas.
sequência topológica Uma sequência composta por vértices de um grafo acíclico direcionado, cada vértice aparece apenas uma vez, e se o vértice A for classificado na frente do vértice B na sequência, então não há caminho de B para A no grafo. Também pode ser entendido como: uma sequência topológica é uma ordenação dos vértices de um grafo acíclico direcionado. Cada rede AOV possui uma ou mais sequências ordenadas topologicamente.
Rede AOV (atividade na rede vértice) Se um DAG for usado para representar um projeto, seus vértices representam atividades, e a aresta direcionada <Vi, Vj> representa um relacionamento que Vi deve preceder Vj, então esse tipo de grafo é chamado de rede com vértices representando atividades. Existem muitos algoritmos para classificação topológica de redes AOV, um dos algoritmos comumente usados é: 1. Selecione um vértice sem predecessor da rede AOV e produza-o 2. Exclua o vértice e todas as arestas direcionadas a partir dele da rede 3. Repita o passo 1.2 até que a rede AOV esteja vazia ou não haja vértices sem predecessor na rede atual (indicando que deve haver um ciclo no gráfico) Como a saída de cada vértice requer a exclusão de sua aresta inicial, a complexidade de tempo da classificação topológica é O(|V| |E|) Além disso, a classificação topológica também pode ser implementada usando DFS (começando nos vértices com um grau de entrada 0 e um grau de saída diferente de 0, um por um, e falhará enquanto o retrocesso ocorrer até que um traço seja concluído ) classificação topológica reversa Comece no vértice com grau externo 0 A rede AOV utiliza armazenamento de matriz de adjacência. Se for uma matriz triangular, deve haver uma sequência topológica e vice-versa.
bool TopologicalSort(Gráfico G){ InitStack(S); for(int i=0;i<G.vexnum;i) if(indegree[i]==0) Push(S,i);//Empurra todos os vértices com grau 0 para a pilha contagem interna=0; while(!isEmpty(S)){ Pop(S,eu); print[count]=i;//Vértices de saída for(p=G.vertices[i].firstarc;p;p=p->nextarc){ //Diminui o grau de entrada de todos os vértices apontados por i em 1 e coloca os vértices cujo grau de entrada é reduzido para 0 na pilha v = p->adjvexo; if(!(--indegree[v])) Empurrar(S,v); } if(contagem<G.vexnum) retorna falso; outro retornar verdadeiro; }
Caminho crítico
Rede AOE (atividade na rede de ponta) Os eventos são representados por vértices, as atividades são representadas por arestas direcionadas e o custo de conclusão da atividade é representado pelo peso na aresta (como o tempo necessário para concluir a atividade) Duas propriedades da rede AOE: 1. Somente após a ocorrência do evento representado por um vértice, as atividades representadas pelas arestas direcionadas a partir do vértice podem começar. 2. O evento em um vértice só pode ocorrer após a conclusão das atividades representadas pelas arestas de entrada de um vértice. Existe apenas um vértice com grau de entrada 0 na rede AOE, denominado vértice inicial (ponto de origem), que representa o início de todo o projeto. A rede AOE também possui apenas um vértice com grau de saída 0; , chamado de vértice final (sink). Representa o final de todo o projeto. Algumas atividades na rede AOE podem ser realizadas em paralelo, mas todas as atividades em todos os caminhos não podem terminar até que sejam concluídas. Portanto, entre todos os caminhos da origem ao destino, o caminho com o maior comprimento é chamado de caminho crítico. . As atividades no caminho são chamadas de atividades críticas (encontrar as atividades críticas equivale a encontrar o caminho crítico). O menor tempo para concluir todo o projeto é a extensão do caminho crítico.
Encontre as principais atividades 1. O primeiro horário de ocorrência do evento vk ve(k) Refere-se ao comprimento do caminho mais longo do ponto de origem v1 ao vértice vk. O primeiro momento de ocorrência do evento vk é o primeiro momento em que todas as atividades iniciadas em vk podem ser iniciadas. Seja vk qualquer sucessor de vj if(ve[j] Peso(vj,vk) > ve(k)) then ve[k]=ve[j] Peso(vj,vk) "Operação de tensão" Ao calcular o valor ve(), proceda de frente para trás e pode ser calculado com base na classificação topológica. 2. O último horário de ocorrência vl(k) do evento vk Refere-se ao último momento vl(k) em que k pode ocorrer sem atrasar a conclusão de todo o projeto. Ao calcular o valor vl(), proceda de trás para frente e pode ser calculado com base na sequência topológica inversa. 3. A primeira hora de início e(i) da atividade ai 4. O último horário de início da atividade ai é l(i) 5. A diferença entre o último horário de início l(i) de uma atividade ai e seu primeiro horário de início e(i) d(i)=l(i)-e(i) Refere-se à margem de tempo para o tempo de conclusão da atividade. Para ser franco, significa quanto tempo a IA pode atrasar. Se d(i)=0, então i é a atividade principal O caminho crítico na rede não é único e, para redes com vários caminhos críticos, aumentar a velocidade das atividades principais em um caminho crítico não pode encurtar a duração de todo o projeto. Isso só pode ser alcançado acelerando as interações principais incluídas em todos. caminhos críticos. O objetivo de encurtar o período de construção.
A compreensão da solução vl(k): ve(k) é o movimento mais diligente do ponto de origem para o ponto de destino vl(k) é o movimento do ponto de destino para o ponto de origem, e o movimento mais próximo do ponto de destino; o ponto de afundamento está selecionado (também basta caminhar em frente, que é a maneira mais preguiçosa, basta arrastá-lo se puder)
7Encontre
Conceitos básicos de pesquisa
Pesquisa: o processo de encontrar elementos de dados que atendam a determinadas condições em um conjunto de dados Tabela de pesquisa (estrutura de pesquisa): uma coleção de dados usados para pesquisa, existem quatro operações comumente usadas em tabelas de pesquisa; 1. Consulte se um elemento de dados específico está na tabela de pesquisa 2. Recupere um elemento de dados específico que atenda às condições 3. Insira um elemento de dados na tabela de pesquisa 4. Exclua um elemento de dados da tabela de pesquisa Tabela de pesquisa estática: uma tabela de pesquisa que envolve apenas uma e duas etapas acima, sem necessidade de modificar dinamicamente a tabela de pesquisa (pesquisa sequencial, meia pesquisa, tabela de pesquisa dinâmica, etc.); , etc.) Palavra-chave: o valor de um item de dados em um elemento de dados que identifica exclusivamente o elemento Duração média da pesquisa: durante o processo de pesquisa, a duração de uma pesquisa refere-se ao número de palavras-chave que precisam ser comparadas. A duração média da pesquisa é o número médio de comparações de palavras-chave em todos os processos de pesquisa.
Pratique ASL ao buscar sucesso e fracasso
Pesquisa sequencial e pesquisa binária
pesquisa sequencial Também conhecida como pesquisa linear, é adequada tanto para listas sequenciais quanto para listas vinculadas. Vantagens: Não há requisitos para armazenamento e ordenação de elementos de dados. Desvantagens: Quando n é grande, a eficiência é baixa;
Pesquisa sequencial de tabelas lineares gerais typedef struct{//estrutura de dados da tabela de pesquisa ElemType *elem; //Endereço base do espaço de armazenamento do elemento int TableLen; //O comprimento da tabela }SSTable; int Search_Seq(SSTable ST, chave ElemType){ ST.elem[0] = chave;//Sentinela for(i=ST.TableLen;ST.elem[i]!=key;--i);//Olha de trás para frente retornar eu; } No algoritmo acima, ST.elem[0] é chamado de sentinela, de modo que o loop interno de Search_Seq não precisa julgar se a matriz sairá dos limites, porque o loop definitivamente saltará quando i==0 for satisfeito. A introdução de sentinelas pode evitar muitas declarações de julgamento desnecessárias, melhorando assim a eficiência do programa.
Pesquisa sequencial em lista ordenada Se for conhecido antes da pesquisa que as palavras-chave na tabela estão em ordem, então, após a falha da comparação, não há necessidade de comparar com a outra extremidade da tabela para retornar a falha da pesquisa, reduzindo assim o comprimento médio da pesquisa do falha na pesquisa. Uma árvore de decisão pode ser usada para descrever o processo de busca de uma lista linear ordenada. Os nós circulares representam os elementos que existem na lista linear ordenada; , então, correspondentemente, existem n 1 nós onde a pesquisa falhou. O comprimento médio da pesquisa (ASL) da pesquisa com falha é (1 2 .. n n)/(n 1); a probabilidade de encontrar cada 'nó com falha' é 1/n 1);
A diferença entre pesquisa sequencial desordenada e ordenada é apenas a diferença quando a pesquisa falha.
Sucesso em ASL=(n 1)/2
Meia pesquisa (pesquisa binária) É adequado apenas para estruturas de armazenamento sequenciais, não é adequado para estruturas de armazenamento em cadeia e requer que os elementos sejam organizados em ordem por palavras-chave. O tempo é O(log2n)
int Binary_Search(SeqList L, chave ElemType){ int baixo = 0, alto = L.TableLen-1, médio; enquanto(baixo<=alto){ médio = (baixo alto)/2; if(L.elem[mid]==key) return mid; senão if(L.elem[mid]<chave){ baixo = médio 1; // médio = (baixo alto)/2; } outro{ alto = médio-1; // médio = (baixo alto)/2; } } retornar -1; }
Pesquisa de bloco (pesquisa de ordem de índice) Absorve as respectivas vantagens da pesquisa sequencial e da pesquisa binária. Possui uma estrutura dinâmica e é adequado para pesquisa rápida; A ideia básica da pesquisa em bloco é dividir a tabela de pesquisa em vários subblocos. Os elementos dentro de um bloco podem ser desordenados, mas os blocos são ordenados (a maior palavra-chave no primeiro grupo é menor que todas as palavras-chave no segundo bloco e assim por diante, crie uma tabela de índice para indexar cada elemento da tabela); Contém a chave máxima de cada bloco e o endereço do primeiro elemento de cada bloco. A tabela de índices é ordenada por chave; O processo de busca de bloco: 1. Determinar o bloco onde se encontra o registro a ser buscado na tabela de índices (busca sequencial ou meia busca) 2. Busca sequencial dentro do bloco Comprimento médio da pesquisa de pesquisa bloqueada: soma do comprimento médio da pesquisa de índice e da pesquisa intra-bloco
Árvore B e árvore B
Árvore B (árvore de pesquisa balanceada multidirecional) (versão melhorada do BST) O número máximo de filhos de todos os nós da árvore B é chamado de ordem da árvore B, geralmente representada por m. Uma árvore B de ordem m é uma árvore vazia ou satisfaz as seguintes características: 1. Cada nó da árvore possui no máximo m subárvores, ou seja, contém no máximo m-1 palavras-chave. 2. Se o nó raiz não for um nó terminal, existem pelo menos duas subárvores 3. Todos os nós não-folha, exceto o nó raiz, têm pelo menos m/2 subárvores de limite superior, ou seja, eles contêm pelo menos m/2 palavras-chave de limite superior-1. 4. A estrutura de todos os nós não-folha é a seguinte (n, P0, K1, P1, K2, P2,..., Kn, Pn) Ki é uma palavra-chave e satisfaz K1<K2<..<Kn Pi é um ponteiro para o nó raiz da subárvore Todos os nós na subárvore apontados pelo ponteiro Pi são maiores que Ki e menores que Ki 1; a palavra-chave no número do nó. 5. Todos os nós folha aparecem no mesmo nível e não carregam nenhuma informação. Na verdade, os ponteiros para esses nós estão vazios.
Altura da árvore B O número de acessos ao disco necessários para a maioria das operações em uma árvore B é proporcional à altura da árvore B. Cada nó na árvore possui no máximo m subárvores e m-1 palavras-chave, e o número de palavras-chave deve satisfazer n≤m^h-1; A h1ª camada tem pelo menos 2(m/2 limite superior)^(h-1) nós, e a h1ª camada é um nó folha que não contém nenhuma informação; Para uma árvore B com n palavras-chave, o nó onde a pesquisa do nó folha falha é n 1, então n 1>=2 (m/2 assume o limite superior)^(h-1), ou seja, h≤log ( m/2 assume o limite superior)((n 1)/2 1)
Pesquisa de árvore B 1. Encontre nós na árvore B 2. Encontre palavras-chave dentro do nó A operação de pesquisa de 1. é executada no disco e a operação de pesquisa de 2. é executada na memória; Depois de encontrar o nó de destino, leia-o na memória e, em seguida, use o método de pesquisa sequencial ou o método de pesquisa binária dentro do nó.
Inserção na árvore B Inserir diretamente após não conseguir encontrar o local destruirá os requisitos na definição da árvore B. 1. Posicionamento: Encontre o nó não folha na camada mais baixa (encontre o nó folha, pegue o nó não folha da camada anterior) 2. Inserção: O número de palavras-chave em cada nó sem falha está dentro do intervalo [m/2 leva o limite superior -1, m-1] e pode ser inserido diretamente; Quando o número de palavras-chave de nó inseridas é maior que m-1, o nó é dividido. Método de divisão: pegue o limite superior da posição intermediária m/2 e divida a palavra-chave em duas partes. A parte esquerda é incluída no nó original e a parte direita é colocada no novo nó na posição intermediária (; m/2 assume o limite superior) para inserir o nó pai do nó original e assim por diante;
Exclusão da árvore B Quando a palavra-chave excluída k não está no nó terminal (o nó não folha mais baixo), k pode ser substituído pelo antecessor (sucessor) de k k', e então k' é excluído no nó correspondente e convertido em uma palavra-chave A situação no nó terminal; Quando a palavra-chave excluída está no nó terminal (o nó não folha mais baixo), há três situações: 1. Exclua palavras-chave diretamente: o número de palavras-chave ≥ m/2 assume o limite superior -1 2. Irmãos são suficientes: o número de palavras-chave = m/2, considere o limite superior -1, e o número de palavras-chave irmãs esquerda e direita (adjacentes) é maior ou igual a m/2, considere o limite superior, então seus irmãos, pais e ele próprio usam o método de transposição pai-filho para alcançar o equilíbrio 3. Não há irmãos suficientes: o número de palavras-chave excluídas = m/2 e o limite superior é -1. Neste momento, os nós de seus irmãos esquerdo e direito (adjacentes) também são = m/2 e o limite superior. é 1. Depois da exclusão, as palavras-chave serão mescladas com as palavras-chave no nó irmão esquerdo (direito) e no nó pai. O número de palavras-chave no nó pai será reduzido em um. o nó raiz pode ser reduzido diretamente para 0 (exclua o nó raiz).
Propriedades relacionadas à árvore B É necessário compreender a relação entre o número de palavras-chave e o número de subárvores: o número de palavras-chave é 1 a menos que o número de subárvores. Todos os nós não terminais, exceto o nó raiz, têm pelo menos m/2 subárvores de limite superior (m/2 limite superior - 1 palavra-chave) As palavras-chave nos nós são ordenadas em ordem crescente, da esquerda para a direita. Ou seja, m/2 assume o limite superior -1≤n≤m-1, ou seja, [m/2 assume o limite superior -1, m-1]
Árvore B
A árvore B é uma árvore deformada da árvore B que aparece em resposta às necessidades do banco de dados. Uma árvore B de ordem m satisfaz as seguintes condições: 1. Cada nó de ramificação possui no máximo m subárvores 2. O nó raiz não folha tem pelo menos duas subárvores 3. O número de subárvores de um nó é igual ao número de palavras-chave 4. Todos os nós folha contêm todas as palavras-chave e ponteiros para os registros correspondentes. As palavras-chave são organizadas em ordem de tamanho nos nós folha, e os nós folha adjacentes são conectados uns aos outros em ordem de tamanho.
Principais diferenças das árvores B: 1. Um nó com n palavras-chave na árvore B possui n subárvores, e cada palavra-chave corresponde a uma subárvore que n nós de palavras-chave na árvore B contêm n-1 subárvores; 2. O intervalo do número n de palavras-chave para cada nó na árvore B é [m/2 leva o limite superior, m] (em relação aos limites superior e inferior do número n de palavras-chave do nó na árvore B mais 1 ); nó raiz B: [1,m],B:[1,m-1] 3. Na árvore B, os nós folha contêm informações (todas as palavras-chave) e os nós não folha servem apenas como índices. 4. Cada pesquisa na árvore B, bem-sucedida ou não, é um caminho do nó raiz ao nó folha.
tabela hash
Conceitos básicos de tabelas hash
Devido à tabela linear anterior e à pesquisa em árvore, não existe uma relação definida entre a posição do registro na tabela e a chave do registro. Portanto, ao buscar registros nessas tabelas, é necessário comparar uma série de palavras-chave. Esse método de busca é baseado na comparação, e a eficiência da busca depende do número de comparações. Função hash: A função Hash(key)=Addr que mapeia as palavras-chave da tabela de consulta para o endereço correspondente à palavra-chave, ou seja, utilizando as características da própria palavra-chave e utilizando o mínimo possível de busca de "comparação". A função hash pode mapear várias palavras-chave diferentes para o mesmo endereço, o que é chamado de 'colisão'. Essas diferentes palavras-chave que colidem são chamadas de sinônimos. Por um lado, uma função hash bem projetada minimizará os conflitos; por outro lado, os conflitos são inevitáveis e devem ser projetados métodos para lidar com eles; Tabela hash: Uma estrutura de dados que é acessada diretamente com base em palavras-chave. Em outras palavras, a tabela hash estabelece uma relação de mapeamento direto entre palavras-chave e endereços de armazenamento. Idealmente, a complexidade de tempo de pesquisa em uma tabela hash é O(1), o que significa que não tem nada a ver com o número de elementos.
Como construir uma função hash
1. O domínio de definição da função hash deve incluir todas as chaves de armazenamento, e o intervalo de valores depende do tamanho ou intervalo de endereços da tabela hash, 2. Os endereços calculados pela função hash devem ser distribuídos uniformemente em todo o espaço de endereçamento com igual probabilidade, reduzindo assim a ocorrência de conflitos. 3. A função hash deve ser o mais simples possível e pode calcular o hash correspondente a qualquer palavra-chave em um curto espaço de tempo.
1. Método de endereçamento direto Tome diretamente o valor de uma função linear da palavra-chave como o endereço hash. H(tecla)=tecla ou H(tecla)=ax tecla b É consistente com a situação em que a distribuição de palavras-chave é basicamente contínua. Se a distribuição de palavras-chave for descontínua e houver muitos espaços vazios, causará um desperdício de espaço de armazenamento;
Métodos de tratamento de conflitos
Pesquisa de hash e análise de desempenho
8 tipo
Conceitos básicos de classificação
Estabilidade do algoritmo: Existem dois elementos Ri e Rj na lista a serem classificados, e suas palavras-chave correspondentes são as mesmas keyi=keyj Se as posições relativas de Ri e Rj permanecerem inalteradas após usar um determinado algoritmo de classificação, então a classificação. o algoritmo é estável, caso contrário não. A estabilidade não reflete a qualidade do algoritmo, mas apenas descreve a natureza do algoritmo. Os algoritmos de classificação podem ser divididos em duas categorias, dependendo se os elementos de dados estão completamente na memória durante o processo de classificação: 1. Classificação interna: Durante a classificação, todos os elementos são armazenados na memória para classificação. 2. Classificação externa: Durante a classificação, todos os elementos não podem ser armazenados na memória ao mesmo tempo. Eles devem ser movidos continuamente entre a memória interna e externa de acordo com os requisitos durante o processo de classificação. Em geral, a classificação interna requer comparação e movimento, mas nem toda classificação interna requer comparação, como a classificação radix
ordenação por inserção Cada vez que um registro a ser classificado é inserido na subsequência capturada anteriormente de acordo com o tamanho da chave
classificação por inserção direta
Sequência ordenada L[1..i-1]L(i) Sequência não ordenada L[i 1…n] void InsertSort(ELemType A[],int n){ int eu,j; for(i=2;i<=n;i){//Insira os seguintes números n-1 na frente if(A[i]<A[i-1]){ A[0] = A[i];//Copia como sentinela, A[0] não armazena nenhum elemento for(j=i-1;A[0]<A[j];j--){//Encontre a posição de inserção de trás para frente A[j 1] = A[j]; A[j 1] = A[0]; } } Complexidade espacial O(1), complexidade espacial O(n^2) Melhor caso: os elementos da tabela já estão classificados, tempo O(n) Pior caso: a ordem dos elementos na tabela é invertida, tempo O(n^2) Como cada comparação é de trás para frente, ela é estável. Adequado para listas sequenciais e listas vinculadas (você pode encontrar a posição do elemento especificada da frente para trás) Adequado para tabelas de classificação basicamente ordenadas com pequena quantidade de dados
classificação de meia inserção
void InsertSort(ElemType A[],int n){ int i,j,baixo,alto,médio; para(i=2;i<=n;i ){ A[0] =A[i]; baixo=1; alto=i-1; enquanto(baixo<=alto){ médio = (baixo alto)/2; if(A[meio]>A[0])alto=meio-1; senão baixo = médio 1; } para (j=i-1;j>=alto 1;j--) A[j 1] = A[j]; A[alto 1]=A[0]; } } Complexidade de tempo O (n ^ 2) O número de comparações não tem nada a ver com o estado inicial da lista a ser ordenada e depende apenas do número n de elementos da lista; O número de movimentos está relacionado ao estado inicial da lista a ser ordenada
Tipo de colina
Primeiro divida a tabela de classificação em várias subtabelas (registros com o mesmo incremento separados formam uma subtabela di 1 = di/2, e o último incremento é igual a 1), execute a classificação por inserção direta em cada subtabela, quando a tabela inteira Quando os elementos estão basicamente em ordem, uma classificação por inserção direta é executada em todos os registros. void ShellSort(ELemType A[],int n){ //A[0] é uma unidade de armazenamento temporário, não uma sentinela. Quando j<=0, a posição de inserção é alcançada. for(dk=n/2;dk>=1;dk/=2)//alteração do tamanho do passo para(i=di 1;i<=n;i) if(A[i]<A[i-dk]){//A[i] precisa ser inserido na subtabela incremental ordenada A[0]=A[j];//armazenado temporariamente em A[0] para(j=i-dk;j>0&&A[0]<A[j];j-=dk) A[j dk]=A[j]; } } Não há uma conclusão definitiva sobre a complexidade do tempo, mas acredita-se que seja O(n^2) Classificando instabilidade Adequado para tabela de sequência
classificação de troca Troque as posições dos dois registros na sequência com base nos resultados da comparação das palavras-chave
Tipo de bolha
Compare os valores dos elementos adjacentes de trás para frente (de frente para trás) e troque-os se estiverem na ordem inversa. Palavras-chave flutuam gradualmente na superfície como bolhas void BubbleSort(ELemType A[],int n){ for(int i=0;i<n-1;i){//n-1 vezes bandeira = falso; para(j=n-1;j>i;j--) if(A[j-1]>A[j]){//Se for ordem inversa trocar(A[j-1],A[j]); bandeira = verdadeiro; } if(flag==false)//O sinalizador do fim da classificação por bolha: nem uma única troca retornar; } } Espaço O(1), tempo O(n^2) Estabilidade: os elementos não serão trocados quando forem iguais, estáveis A sequência produzida pela classificação por bolha é ordenada globalmente, o que não é equivalente à ordenação local da classificação por inserção.
Ordenação rápida Baseado no pensamento de dividir para conquistar
Escolha qualquer elemento na lista para ser classificado como pivô (geralmente o primeiro elemento) e divida a sequência classificada em duas partes independentes L[1..k-1] L(k) L[k 1, ...n] , de modo que a primeira metade seja menor que o pivô e a segunda metade seja maior que o pivô, e o pivô seja colocado na posição final L (k). Este processo é chamado de classificação rápida de uma passagem (divisão de uma passagem). void QuickSort(ELemType A[],int baixo,int alto){ if(low<high){//Condição para salto recursivo int pivotpos = Partição(A,baixa,alta);//Partição QuickSort(A,baixo,pivotpos-1); QuickSort(A,pivotpos 1,alto); } } O desempenho e a chave do algoritmo de classificação rápida vêm da operação de partição. Aqui, o primeiro elemento é usado como pivô para particionamento. int Partition(ElemType A[],int baixo,int alto){ ElemType pivô=A[baixo]; while(low<high){//Condição de saída do loop while(baixo<alto && A[alto]>=pivô) --alto; A[baixo]=A[alto]; whilie(baixo<alto && A[baixo]<=pivô) baixo; A[alto] = A[baixo]; } A[baixo] = pivô; retornar baixo; } Espaço: requer pilha de trabalho recursiva, de preferência O(log2n), média O(log2n) (dividida ao meio tanto quanto possível, como uma árvore binária completa), Pior caso O(n) (o número de elementos nas duas áreas divididas é n-1 e 0, são necessárias n-1 chamadas recursivas e a profundidade da pilha é O(n)). Tempo: O tempo de execução da classificação rápida está relacionado ao fato de a divisão ser simétrica. O pior caso é metade n-1 e metade 0. A assimetria máxima ocorre em cada nível de recursão, que corresponde à sequência ordenada inicial (sequencial ou reversa). ordem), é O (n ^ 2) Estabilidade: Instável, os mesmos elementos serão transferidos para partes diferentes e as posições relativas serão alteradas. Nota: A classificação rápida não produz uma subsequência ordenada, mas o elemento pivô é colocado na posição final em cada passagem. Melhore a eficiência: 1. Selecione elementos pivôs que possam dividir a sequência tanto quanto possível 2. Selecione aleatoriamente elementos pivôs A divisão mais ideal: ambos os problemas de palavras são menores que n/2, o tempo é O(nlog2n) e o tempo de execução da classificação rápida é muito próximo do melhor caso, em média. Quicksort é o algoritmo de classificação com melhor desempenho médio entre todos os algoritmos de classificação interna.
ordenação por seleção Cada passagem seleciona o menor elemento da palavra-chave na sequência original como o i-ésimo elemento da subsequência ordenada até n-1 passagens
Ordenação por seleção simples
void SelectSort(ElemType A[],int n){ for(i=0;i<n-1;i)//Um total de n-1 vezes min=i;//Registra a posição mínima do elemento para(j=i 1;j<n;j) if(A[j]<A[min]) min = j; if(min!=i) troca(A[i],A[min]); } } Espaço O(1), tempo O(n^2) Estabilidade: pode fazer com que a posição relativa das palavras-chave contendo os mesmos elementos mude, o que é instável.
Classificação de pilha
O heap é um objeto array de uma árvore. Um array unidimensional pode ser considerado como uma árvore binária completa, que satisfaz as propriedades. 1.L(i)>=L(2i) e L(i)>=L(2i 1) ou 2.L(i)<=L(2i) e L(i)<=L(2i 1) ( 1<=i<=n/2 (tomar o limite); Aqueles que satisfazem 1. são considerados como grandes pilhas de raiz (grandes pilhas superiores), e aqueles que satisfazem 2. são considerados como pequenas pilhas de raiz (pequenas pilhas superiores). Classificação de heap: primeiro, n elementos na matriz são construídos em um heap inicial após a saída do elemento superior do heap, o elemento inferior do heap é enviado para o topo do heap. atende às propriedades de uma grande pilha raiz e a pilha é destruída. O elemento superior da pilha é movido para o topo da pilha, ajustando-o para baixo para que possa continuar a manter a natureza de uma grande pilha superior. Em seguida, produza o elemento superior da torre e repita até que reste apenas um elemento na pilha. Algoritmo de classificação de heap: void HeapSort(ElemType A[],int len){ BuildMaxHeap(A,len); for(i=len;i>1;i--){ Swap(A[i],A[1]);//Sai do elemento superior do heap e o troca com o elemento inferior do heap HeadAdjust(A,1,i-1);//Organiza os elementos i-1 restantes em uma pilha } } A classificação de heap é adequada para situações onde existem muitas palavras-chave (na ordem de centenas de milhões) Exemplo: para selecionar os 100 valores máximos principais entre 100 milhões de números, primeiro use uma matriz de 100, leia os primeiros 100 números e construa um pequeno heap superior e, em seguida, leia os números restantes em sequência, se forem menores que. o topo da pilha, descarte-os. Caso contrário, substitua o topo da pilha por este número e redimensione a pilha. Após a leitura dos dados, os 100 números na pilha são os necessários. Eficiência espacial O(1), eficiência temporal O(nlog2n) instável
1. Como construir uma sequência não ordenada em um heap inicial Filtre a subárvore com o (n/2 limitado)-ésimo nó como a raiz (se for um heap raiz grande, compare a palavra-chave do nó raiz e o tamanho de suas palavras-chave da subárvore esquerda e direita e troque-as se não atenderem as regras de heap raiz grande), Transforme esta subárvore em um heap. Em seguida, as subárvores enraizadas em cada nó (n/2 menos o limite -1,1) são filtradas adiante. Quando visto como uma árvore binária completa, o nó raiz é gradualmente ajustado de baixo para cima (as chaves são trocadas entre o nó raiz e os nós filhos na matriz, o nó raiz é ajustado da direita para a esquerda (as palavras-chave são trocadas); void BuildMaxHeap(ElemType A[],int len){ for(int i=len/2;i>0;i--)//De i=n/2 a 1, ajuste o heap repetidamente HeadAdjust(A,i,len); } void HeadAdjust(ElemType A[],int k,int len){ //HeadAdjust ajusta a subárvore com o elemento k como raiz A[0]=A[k]; for(i=2*k;i<=len;i*=2){//Filtre ao longo dos nós filhos com chave maior if(i<len && A[i]<A[i 1]) i ;//Obtém o subscrito do nó filho com uma chave maior if(A[0]>=A[i]) break;//Fim da filtragem outro{ A[k] = A[i];//Ajusta A[i] ao nó pai k=i;//Modifique o valor k para continuar filtrando para baixo } } A[k]=A[0]; } O tempo de ajuste está relacionado à altura da árvore, O(h), e a complexidade do tempo é O(n). Um número não ordenado pode ser construído em um heap em tempo linear.
2. Depois de gerar o elemento superior do heap, como ajustar os elementos restantes em um novo heap? Após a saída do elemento superior do heap, o último elemento do heap é trocado pelo elemento superior do heap. Nesse momento, as propriedades do heap são destruídas e a filtragem descendente é necessária. Filtre de cima para baixo (troque palavras-chave do nó raiz e palavras-chave da subárvore)
Inserção de heap Insira a cauda e ajuste de baixo para cima
Mesclar classificação e classificação radix
classificação por mesclagem O significado da fusão é combinar duas ou mais listas ordenadas em uma nova lista ordenada
Supondo que a tabela a ser classificada contém n registros, ela pode ser considerada como n subtabelas ordenadas, cada uma com comprimento 1, e então mescladas em pares para obter n/2 registros com limite superior com comprimento 2 ou 1. Lista de sequências; continue mesclando dois por dois até mesclar em uma lista ordenada de comprimento n. Este método é chamado de classificação por mesclagem bidirecional. ElemType *B=(ElemType*)malloc((n 1)*sizeof(ElemType));//matriz auxiliar B void Merge(ElemType A[],int baixo,int médio,int alto){ //Tabela A[low..mid]A[mid 1..high] estão ordenadas, mescle-as em uma lista ordenada for(int k=baixo;k<=alto;k) B[k]=A[k]; for(i=low;j=mid 1;k=i,i<=mid&&j<=high;k ){ if(B[i]<=B[j]) A[k]=B[i ]; senão A[k]=B[j]; } enquanto(i<=meio) A[k ]=B[i ]; enquanto(j<=alto) A[k ]=B[j ]; } void MergeSort(ELemType A[],int baixo,int alto){ if(baixo<alto){ int médio=(baixo alto)/2; MergeSort(A, baixo, médio); MergeSort(A,médio 1,alto); Mesclar(A,baixo,médio,alto); } } Eficiência espacial: n unidades de espaço auxiliar, complexidade espacial O(n) Eficiência de tempo: cada mesclagem é O(n) e as mesclagens de limite superior log2n são necessárias. A complexidade de tempo do algoritmo é O(nlog2n). Estabilidade: A operação Merge() não alterará a ordem relativa dos registros com as mesmas palavras-chave, estável
De modo geral, para fusão k-way de N elementos, o número de vezes de classificação m satisfaz k ^ m = N, então m = logkN, e como m é um número inteiro, m = logkN assume o limite superior
Classificação de raiz Use a ideia de classificação de várias palavras-chave para classificar palavras-chave lógicas únicas
Classifique com base no tamanho da palavra-chave. A palavra-chave de cada nó aj consiste em d tuplas, kd-1j é a palavra-chave primária e kj0 é a palavra-chave secundária. Para obter a classificação de várias palavras-chave, geralmente existem dois métodos: 1. Primeiro o bit mais significativo (MSD), divida várias subsequências menores, camada por camada, de acordo com o peso decrescente das palavras-chave e, finalmente, todas as subsequências são conectadas em uma sequência ordenada. 2. O dígito mais baixo primeiro (LSD), classificado em ordem crescente pelo peso da palavra-chave para formar uma sequência ordenada. operar: 1. Distribuição: Deixe a fila Q0...Qr-1 vazia e, em seguida, adicione-a à fila correspondente de acordo com o bit correspondente de cada nó. 2. Coleta: Conecte os nós da fila de Q0..Qr-1 de ponta a ponta para obter uma nova sequência de nós para formar uma nova tabela linear. Exemplo: para classificar sequências abaixo de 1000, primeiro determine a base r (pode ser considerada como r base). A base de 1000 é 10, portanto, 10 filas de cadeia são necessárias durante o processo de classificação. Números abaixo de 1000 têm três dígitos, portanto são necessárias três operações de "distribuição" e "coleta"; todos os registros com a mesma palavra-chave mais baixa (dígito de unidades) são atribuídos a uma fila e, em seguida, a operação de "coleta" é executada. Eficiência de espaço: O espaço de armazenamento auxiliar necessário para uma viagem de classificação é r (r filas: r ponteiros de cabeça de fila, r ponteiros de cauda de fila), O (r) Eficiência de tempo: d passagens de alocação e coleta, uma passagem de alocação requer O(n), uma passagem de coleta requer O(r), a complexidade de tempo é O(d(n r)), independentemente do estado inicial da sequência . Estabilidade: a classificação Radix em si exige que a classificação bit a bit seja estável, portanto, é estável!
Comparação e aplicação de vários algoritmos de classificação interna
Comparação de algoritmos de classificação interna
Aplicação do algoritmo de classificação interna
1. Se n for pequeno, a classificação por inserção direta ou a classificação por seleção simples podem ser usadas. Como a classificação por inserção direta requer mais movimentos de registro do que a classificação por seleção simples, quando o próprio registro contém uma grande quantidade de informações, a classificação por seleção simples é melhor. 2. Se o estado inicial do arquivo for basicamente ordenado por palavras-chave, é apropriado usar inserção direta ou classificação por bolha. 3. Se n for grande, use o método de classificação O (nlog2n): classificação rápida, classificação por heap ou classificação por mesclagem. A classificação rápida é considerada o melhor método de classificação interna baseada em comparação no momento. Quando as palavras-chave a serem classificadas são distribuídas aleatoriamente, a classificação rápida tem o tempo médio mais curto e requer menos espaço auxiliar do que a classificação rápida, e a classificação rápida irá. não ocorrer. Mas ambas as classificações são instáveis. Se um algoritmo O(nlog2n) estável for necessário, a classificação por mesclagem será usada, mas a mesclagem de pares de um único registro não é recomendada. Eles geralmente podem ser usados em combinação com a classificação por inserção direta: primeiro use a classificação por inserção direta para obter subdivisões ordenadas mais longas. arquivos e, em seguida, mescle-os dois por dois. A classificação por inserção direta é estável e a classificação por mesclagem aprimorada também é estável. 4. No método de classificação baseado em comparação, após cada comparação de dois tamanhos de palavras-chave, ocorrem apenas duas transferências possíveis. Portanto, uma árvore binária pode ser usada para descrever o processo de decisão de comparação. Quando as palavras-chave são distribuídas aleatoriamente, qualquer algoritmo de classificação que dependa de "comparação" requer pelo menos tempo O(nlog2n). 5. Se n for muito grande e o número de palavras-chave registradas for pequeno e puder ser decomposto, é melhor usar a classificação radix. 6. Quando o próprio registro contém uma grande quantidade de informações, para evitar gastar muito tempo movimentando o registro, uma lista vinculada pode ser usada como estrutura de armazenamento
classificação externa
Conceitos básicos de algoritmos de classificação externa
A classificação realizada na memória é chamada de classificação interna. Em muitos aplicativos, arquivos grandes precisam ser classificados e o arquivo inteiro não pode ser copiado para a memória para classificação. Portanto, os registros a serem classificados precisam ser armazenados na memória externa. Durante a classificação, os dados são transferidos para a memória parte por parte para classificação. Durante o processo de classificação, são necessárias múltiplas trocas entre a memória e o armazenamento externo. Essa classificação é chamada de classificação externa
método de classificação externa
O tempo gasto durante o processo de classificação externa considera principalmente o número de acessos ao disco, ou seja, o número de IOs A classificação externa geralmente usa classificação por mesclagem: 1. De acordo com o tamanho do buffer de memória, divida os arquivos no armazenamento externo em subarquivos de comprimento l, leia-os na memória em sequência e use o método de classificação interna para classificá-los e grave os subarquivos ordenados obtidos após a classificação de volta para o armazenamento externo, esses subarquivos ordenados são chamados de segmentos mesclados ou sequências sequenciais; 2. Mescle esses segmentos mesclados um por um para que os segmentos mesclados aumentem gradualmente de pequeno para grande até que todo o arquivo ordenado seja obtido. A fusão de uma passagem refere-se à fusão de todos os segmentos do mesmo nível atual em um Tempo total de classificação externa = tempo necessário para classificação interna, tempo de leitura e gravação de informações externas, tempo necessário para fusão interna O tempo para leitura e gravação de informações de armazenamento externo é muito maior do que o tempo para classificação interna e fusão interna. A leitura e gravação de informações de armazenamento externo é baseada em "blocos de disco". bloco de disco a ser lido e gravado diversas vezes Portanto, o número total de leituras e gravações necessárias: 2*número total de registros/número de registros em um bloco de disco*n (número de vezes) número total de registros/número de registros. em um bloco de disco (a classificação interna também requer leitura e gravação completas) Para r segmentos de fusão iniciais, execute a fusão balanceada de k vias. A altura da árvore (árvore k-ária invertida) = logkr e o limite superior = o número de passagens de fusão S. Pode-se observar que aumentar o número de caminhos de mesclagem k ou reduzir o número de segmentos de mesclagem iniciais r pode reduzir o número de passagens de mesclagem S, reduzindo assim o número de E/Ss de disco e melhorando a velocidade da classificação externa.
Fusão balanceada multidirecional e árvore perdedora
Aumentar o número de caminhos de fusão k pode reduzir o número de passagens de fusão S, mas aumentará o tempo de fusão interno. Ao mesclar internamente, selecionar a menor palavra-chave entre k elementos requer comparações k-1, e palavras-chave n-1 precisam ser comparadas. Cada mesclagem de n elementos requer (n-1)(k-1) comparações, S vezes. o número de comparações necessárias para a fusão é S(n-1)(k-1)=log2r pega o limite superior (n-1)(k-1)/log2k pega o limite superior, onde (k-1)/log2k pega o limite superior e cresce com o crescimento de k. Portanto, o algoritmo de classificação por mesclagem interna comum não pode ser usado.
Para evitar que a fusão interna seja afetada pelo aumento de k, a árvore perdedora é introduzida. A árvore perdedora é uma variante da classificação de árvores e pode ser considerada uma árvore binária completa. Os k nós folha armazenam respectivamente os registros dos k segmentos de mesclagem que atualmente participam da comparação durante o processo de mesclagem. Os nós internos são usados para registrar os "perdedores" dos nós esquerdo e direito e permitem que os vencedores continuem a comparar até. o nó raiz. O nó raiz é o vencedor. S(n-1) log2k leva o limite superior = log2k leva o limite superior (n-1) log2k leva o limite superior = (n-1) log2r leva o limite superior Depois de usar a árvore perdedora, a ordem de comparação da classificação de mesclagem interna não tem nada a ver com k, mas o número de caminhos de mesclagem k é restringido pelo espaço de memória, o que pode reduzir a capacidade do buffer dentro de um determinado espaço. Se o valor k for muito grande, embora o número de passagens de mesclagem seja reduzido, o número de IOs ainda aumentará. ps: Comparado com a árvore vencedora, ao reconstruir a árvore vencedora, você precisa comparar com os nós irmãos e, em seguida, alterar os nós pais ao reconstruir a árvore perdedora, você só precisa subir até o fim para comparar os nós pais;
Classificação por seleção de substituição (gerando segmentos de mesclagem iniciais)
Para reduzir r, r=n/l assume um limite superior, então precisamos encontrar uma maneira de gerar um segmento de mesclagem inicial mais longo l. Suponha que o arquivo inicial a ser classificado seja FI, o arquivo de saída do segmento de mesclagem inicial seja FO, a área de trabalho da memória seja WA, FO e WA estejam inicialmente vazios e WA possa acomodar w registros. As etapas do algoritmo de seleção de substituição são as seguintes: 1. Insira w registros do FI para o espaço de trabalho WA 2. Selecione o registro com o valor mínimo da palavra-chave de WA e registre-o como MINIMAX 3. Grave MINIMAX em FO 4. Se FI não estiver vazio, envie o próximo registro de FI para WA 5. Selecione o menor registro de palavra-chave de todos os registros em WA com palavras-chave maiores que as palavras-chave do registro MINIMAX como o novo registro MINIMAX. 6. Repita as etapas 3 a 5 até que nenhum novo MINIMAX possa ser selecionado em WA, obtenha um segmento de mesclagem inicial e envie um sinalizador final do segmento de mesclagem para FO. 7. Repita as etapas 2 a 6 até que WA esteja vazio e todos os segmentos mesclados iniciais sejam obtidos. O processo de seleção da gravação MINIMAX no WA requer o uso de árvores perdedoras.
melhor árvore de mesclagem
Depois que o arquivo é classificado por seleção de substituição, são obtidos segmentos iniciais mesclados de diferentes comprimentos. Como organizar a ordem de fusão dos segmentos iniciais de fusão com comprimentos diferentes para minimizar o número de IOs? Desenhe a árvore de mesclagem correspondente. O comprimento do caminho ponderado WPL da árvore é o número total de registros lidos durante o processo recursivo. Estenda a ideia da árvore de Huffman para o caso da árvore k-ária. Na árvore de mesclagem, deixe o segmento de mesclagem inicial com um pequeno número de registros ser mesclado primeiro e o segmento de mesclagem inicial com um grande número de registros. mesclado por último. O IO total pode ser estabelecido A melhor árvore de mesclagem com o menor número de vezes. Método para construir a melhor árvore de mesclagem: Se o segmento de mesclagem inicial não for suficiente para formar uma árvore k-ária estrita, um "segmento virtual" com comprimento 0 precisa ser adicionado. Como determinar o número de segmentos virtuais a serem adicionados? Uma árvore k-ária estrita tem n0=(k-1)nk 1. Se (n0-1)%(k-1)=0, então o livro de mesclagem k-fork pode ser construído com nk nós internos. Se (n0-1)%(k-1)=u!=0, então ku-1 segmentos de mesclagem vazios podem ser adicionados para criar uma árvore de mesclagem
A rede AOV e a rede AOE são ambas DAGs, e as arestas e vértices das duas têm significados diferentes. As arestas da rede AOV não possuem pesos e representam apenas o contexto entre os vértices; as arestas da rede AOE possuem pesos e representam o custo de conclusão da atividade;
A diferença entre Dijkstra e Prim: A soma de todas as arestas na árvore geradora mínima de Prim deve ser a menor, apenas os pesos dos nós adjacentes não são necessariamente menores que o MST de Dijkstra; o gráfico original AB é mais curto porque a distância ao ponto fonte é somada; Além disso, Prim só pode ser usado para gráficos não direcionados, e Dijkstra pode ter \gráficos não direcionados (grafos não direcionados são convertidos em gráficos direcionados, ij bilaterais)
A travessia da matriz de adjacência é única, mas a travessia da lista de adjacências não é única.
A conectividade é discutida em grafos não direcionados, e a conectividade forte é discutida em grafos direcionados.