contente
Prefácio da Estrutura de Dados
1. A complexidade do algoritmo
2.1 O conceito de complexidade de tempo
2.2 Notação Assintótica para Big O
2.3 Existem melhores, médias e piores casos para complexidade de tempo
2.4 Exemplos comuns de cálculo de complexidade de tempo
Nota: o tempo é acumulado (se foi para sempre), o espaço não é acumulado (reutilizável)
4. Complexidade de tempo comum e complexidade de exercícios
Prefácio da Estrutura de Dados
O que é uma estrutura de dados?
Uma estrutura de dados é uma maneira de um computador armazenar e organizar dados e se refere a uma coleção de elementos de dados que possuem um ou mais relacionamentos específicos entre si.
O que é um algoritmo?
Algoritmo: Um processo computacional bem definido que recebe um ou um conjunto de valores como entrada e produz um ou um conjunto de valores como saída. Simplificando, um algoritmo é uma série de etapas computacionais usadas para converter dados de entrada em resultados de saída.
1. A complexidade do algoritmo
Depois que o algoritmo é escrito em um programa executável, são necessários recursos de tempo e recursos de espaço (memória) para ser executado. Portanto, para medir a qualidade de um algoritmo, ele geralmente é medido a partir de duas dimensões de tempo e espaço, a saber, complexidade de tempo e complexidade de espaço .
A complexidade de tempo mede principalmente a rapidez com que um algoritmo é executado, enquanto a complexidade de espaço mede principalmente o espaço extra necessário para executar um algoritmo. Nos primórdios do desenvolvimento do computador, a capacidade de armazenamento dos computadores era muito pequena. Por isso, está muito preocupado com a complexidade do espaço. Mas após o rápido desenvolvimento da indústria de computadores, a capacidade de armazenamento do computador atingiu um nível muito alto. Portanto, não precisamos mais prestar atenção especial à complexidade do espaço de um algoritmo.
2. Complexidade de tempo
2.1 O conceito de complexidade de tempo
A complexidade de tempo de um algoritmo é uma função que descreve quantitativamente o tempo de execução do algoritmo. O tempo que leva para um algoritmo ser executado, em teoria, não pode ser calculado, somente quando você executa seu programa em uma máquina você pode conhecê-lo. O tempo gasto por um algoritmo é proporcional ao número de execuções das instruções nele, e o número de execuções das operações básicas no algoritmo é a complexidade de tempo do algoritmo.
Por favor, conte quantas vezes a instrução ++count em Func1 é executada no total?
void Func1(int N)
{
int count = 0;
for (int i = 0; i < N ; ++ i)
{
for (int j = 0; j < N ; ++ j)
{
++count;
}
}
for (int k = 0; k < 2 * N ; ++ k)
{
++count;
}
int M = 10;
while (M--)
{
++count;
}
printf("%d\n", count);
}
Encontrar a expressão matemática entre uma afirmação básica e o tamanho do problema N é calcular a complexidade de tempo do algoritmo.
N=10 | F(N)= 130 |
N=100 | F(N)= 10210 |
N=1000 | F(N)= 1002010 |
N = 10.000 | F(N)= 100.020.010 |
Podemos descobrir que quando N é maior, o efeito de 2*N+10 no resultado é menor, e N^2 é responsável pela maior parte do resultado. Se usarmos F(N) para representar a complexidade de tempo, cada algoritmo precisa calcular cuidadosamente a função correspondente, o que é muito complicado. Na prática, quando calculamos a complexidade de tempo, não precisamos necessariamente calcular o número exato de execuções , mas apenas um número aproximado de execuções (ordem de magnitude estimada) é necessário, então usamos a notação assintótica de O grande.
2.2 Notação Assintótica para Big O
Notação Big O: é uma notação matemática usada para descrever o comportamento assintótico de uma função.
Derive o método big-O:
1. Substitua todas as constantes aditivas em tempo de execução pela constante 1
2. Na função de tempos de execução modificados, apenas os termos de ordem mais alta são mantidos
3. Se o termo de ordem mais alta existir e não for 1, remova a constante multiplicada por este termo. O resultado é uma grande ordem O
Depois de usar a representação assintótica de O grande, a complexidade de tempo de Func1 é: O(N^2)
N=10 | F(N)= 100 |
N=100 | F(N)= 10.000 |
N=1000 | F(N)= 1.000.000 |
N = 10.000 | F(N)= 100.000.000 |
Os itens que têm pouco efeito no resultado são removidos e o número de execuções é exibido de forma concisa e clara
Calcule a complexidade de tempo de Func3?
void Func3(int N, int M)
{
int count = 0;
for (int k = 0; k < M; ++ k)
{
++count;
}
for (int k = 0; k < N ; ++ k)
{
++count;
}
printf("%d\n", count);
}
Resposta: O(M+N), em circunstâncias normais, N é usado como desconhecido, mas M+N não sabe o tamanho (desconhecido), e tanto M quanto N precisam ser deixados
Se for declarado que N é muito maior que M, é O(N); se M é muito maior que N, é O(M); se N é o mesmo que M, O(N) ou O(M ) pode ser usado
2.3 Existem melhores, médias e piores casos para complexidade de tempo
Pior caso: número máximo de execuções para qualquer tamanho de entrada (limite superior)
Caso médio: número desejado de execuções para qualquer tamanho de entrada
Melhor caso: número mínimo de execuções para qualquer tamanho de entrada (limite inferior)
Exemplo: Pesquisar uma matriz de comprimento N para um dado x Melhor caso: 1 encontrar Pior caso: N encontrar Caso médio: N/2 encontrar
Na prática, a preocupação geral é a operação do pior caso do algoritmo, então a complexidade de tempo de busca de dados no array é O(N)
2.4 Exemplos comuns de cálculo de complexidade de tempo
Calcule a complexidade de tempo de Func2?
void Func2(int N)
{
int count = 0;
for (int k = 0; k < 2 * N ; ++ k)
{
++count;
}
int M = 10;
while (M--)
{
++count;
}
printf("%d\n", count);
}
Resposta: O (N)
Regra de ordem O grande: 2. Na função de tempos de execução modificada, mantenha apenas o termo de ordem mais alta 3. Se o termo de ordem mais alta existir e não for 1, remova a constante multiplicada por este termo. O resultado é uma ordem big-O. Quando N tende ao infinito, N representa a ordem de grandeza (bilionários, 100 milhões e 200 milhões ainda são bilionários), +10 tem pouco efeito sobre ele, e o coeficiente é removido ao mesmo tempo
Calcule a complexidade de tempo de Func4?
void Func4(int N)
{
int count = 0;
for (int k = 0; k < 100; ++ k)
{
++count;
}
printf("%d\n", count);
}
Resposta: O(1)
Desde que seja constante, a complexidade de tempo é substituída por 1. Regra da ordem grande O: 1. Substitua todas as constantes aditivas em tempo de execução pela constante 1
Complexidade de tempo de computação strchr?
const char * strchr ( const char * str, int character );//在字符串中查找一个字符
Resposta: O(N), a melhor operação básica é realizada uma vez, a pior é N vezes, a complexidade de tempo é geralmente a pior e a complexidade de tempo é O(N)
Calcular a complexidade de tempo da classificação de bolhas?
void BubbleSort(int* a, int n)
{
assert(a);
for (size_t end = n; end > 0; --end)
{
int exchange = 0;
for (size_t i = 1; i < end; ++i)
{
if (a[i-1] > a[i])
{
Swap(&a[i-1], &a[i]);
exchange = 1;
}
}
if (exchange == 0)
break;
}
}
Resposta: O (N ^ 2)
A complexidade do tempo de cálculo não pode levar o loop numérico, que não é necessariamente preciso, devendo ser calculado de acordo com a ideia do algoritmo.
O bubble sort compara N-1 vezes, o número exato de vezes: F(N)=N-1+N-2+N-3+...2+1 A operação básica é melhor executada N vezes, e a pior é realizado ( N*(N-1)/2 vezes (sequência aritmética), derivando o método de ordem O grande + complexidade de tempo é geralmente o pior, a complexidade de tempo é O(N^2), o melhor caso é O (N)
Calcular a complexidade de tempo de BinarySearch (pesquisa binária)?
int BinarySearch(int* a, int n, int x)
{
assert(a);
int begin = 0;
int end = n-1;
// [begin, end]:begin和end是左闭右闭区间,因此有=号
while (begin <= end)
{
int mid = begin + ((end-begin)>>1);
if (a[mid] < x)
begin = mid+1;
else if (a[mid] > x)
end = mid-1;
else
return mid;
}
return -1;
}
Resposta: O(logN)
A operação básica é melhor executada uma vez que O(1), o pior caso é que não pode ser encontrado ou há apenas um número para encontrar, quando o último número é encontrado, ×2, x2 e, finalmente, o array original é obtido . Da mesma forma, quantas vezes são dobradas ao meio Basta dividir alguns 2, assumindo que a busca pela metade é x vezes, 2^x=N
Como não é fácil escrever logaritmos no texto, abreviamos como logN, o que significa que a base é 2 e o logaritmo é N na análise do algoritmo. Em alguns lugares, será escrito como lgN (em matemática, lgN é baseado em 10, essa forma de escrever é ambígua, é melhor escrevê-lo como logN)
Complexidade de tempo de cálculo Fac recursivo fatorial?
long long Fac(size_t N)
{
if(0 == N)
return 1;
return Fac(N-1)*N;
}
Resposta: O () N)
Através de cálculo e análise, verifica-se que a recursão é N+1 vezes no total, a recursão interna é constante vezes, Fac(N) Fac(N-1) Fac(N-2)... A complexidade de tempo é O (N).
Complexidade de tempo de cálculo de Fibonacci recursiva de Fibonacci?
long long Fib(size_t N)
{
if(N < 3)
return 1;
return Fib(N-1) + Fib(N-2);
}
Resposta: O (2 ^ N)
Sequência proporcional, 2^0+2^1+...+2^(N-2)=2^(N-1)-1
3. Complexidade do espaço
A complexidade do espaço também é uma expressão matemática, que é uma medida da quantidade de espaço de armazenamento temporariamente ocupado por um algoritmo durante sua operação.
A complexidade do espaço não é quantos bytes o programa ocupa, pois isso não faz muito sentido, então a complexidade do espaço é o número de variáveis. As regras de cálculo da complexidade do espaço são basicamente semelhantes à complexidade prática, e também usam a notação assintótica big-O.
Nota: O espaço de pilha (parâmetros de armazenamento, variáveis locais, algumas informações de registro, etc.) exigido pela função para execução foi determinado durante a compilação, portanto, a complexidade do espaço é determinada principalmente pelo espaço adicional explicitamente solicitado pela função em tempo de execução.
Nota: o tempo é acumulado (se foi para sempre), o espaço não é acumulado (reutilizável)
Calcular a complexidade do espaço do BubbleSort?
void BubbleSort(int* a, int n)
{
assert(a);
for (size_t end = n; end > 0; --end)
{
int exchange = 0;
for (size_t i = 1; i < end; ++i)
{
if (a[i-1] > a[i])
{
Swap(&a[i-1], &a[i]);
exchange = 1;
}
}
if (exchange == 0)
break;
}
}
Resposta: O(1)
Uma quantidade constante de espaço extra é usada, então a complexidade do espaço é O(1). A matriz de N números fornecida não está incluída em nosso algoritmo. A complexidade do espaço é calculada apenas devido à abertura deste algoritmo. Existem apenas quatro temporária A variável é criada por causa do algoritmo, embora a troca seja destruída, ela ainda usa o espaço original.
Calcule a complexidade do espaço de Fibonacci?
long long* Fibonacci(size_t n)
{
if(n==0)
return NULL;
long long * fibArray = (long long *)malloc((n+1) * sizeof(long long));
fibArray[0] = 0;
fibArray[1] = 1;
for (int i = 2; i <= n ; ++i)
{
fibArray[i] = fibArray[i - 1] + fibArray [i - 2];
}
return fibArray;
}
Resposta: A complexidade do espaço é O(N)
malloc abre n+1 espaços por causa do algoritmo
Complexidade espacial do cálculo Fac recursivo fatorial?
long long Fac(size_t N)
{
if(N == 0)
return 1;
return Fac(N-1)*N;
}
Resposta: A complexidade do espaço é O(N)
Abra os quadros de pilha, abra um total de n+1 quadros de pilha, cada quadro de pilha é O(1)
Calcular a complexidade do espaço de Fibonacci recursiva Fib?
long long Fib(size_t N)
{
if(N < 3)
return 1;
return Fib(N-1) + Fib(N-2);
}
Resposta: A complexidade do espaço é O(N)
A função primeiro calcula Fib (N) Fib (N-1) Fib (N-2) .., e depois vai para a direita para calcular, a função recua para baixo e a função é destruída após o retorno, e a complexidade do espaço é EM)
void test1()
{
int a = 0;
printf("%p\n", &a);
}
void test2()
{
int b = 0;
printf("%p\n", &b);
}
int main()
{
test1();
test2();
return 0;
}
Você pode ver que o endereço é o mesmo. Para obter detalhes, consulte meu artigo sobre o quadro de pilha de funções Artigo extra do salvador da linguagem C (a criação e destruição de quadros de pilha de funções) - Programador procurado
4. Complexidade de tempo comum e complexidade de exercícios
114514 | O(1) | ordem constante |
3n+4 | EM) | Ordem linear |
3n^2+4n+5 | O(N^2) | Ordem quadrada |
3log(2)n+4 | O (logN) | logarítmico |
2n+3nlog(2)n+14 | O (nlogN) | pedido nlog |
n^3+2n^2+4n+6 | O(N^3) | ordem cúbica |
2^n | O(2^N) | Ordem exponencial |
4.1 Leetcode: O array
nums
contém todos os inteiros de0
atén
, mas um está faltando. Por favor, escreva o código para encontrar esse número inteiro ausente. Você tem uma maneira de fazer isso em tempo O(n)?
Ideias:
É necessário que seja preenchido em complexidade de tempo O(N), podemos usar a característica de XOR para completar, o XOR de dois números idênticos juntos é 0. Desde que o conteúdo seja o mesmo, XOR não se importa com a ordem, apenas deixe os números ausentes e a matriz correspondente XOR
int missingNumber(int* nums, int numsSize){
int x=0;
for(int i=0;i<numsSize;i++)
{
x ^= nums[i];
}
for(int j=0;j<numsSize+1;j++)
{
x^=j;
}
return x;
}
Ideias:
Se usarmos o método usual, a complexidade de tempo é de pelo menos O(N), percorrendo o array uma vez, só podemos usar o método de lançamento de três etapas para sair desse problema (quase difícil de pensar)
void reverse(int*nums,int left,int right)
{
while(left<right)
{
int tmp=nums[left];
nums[left]=nums[right];
nums[right]=tmp;
--right;
++left;
}
}
void rotate(int* nums, int numsSize, int k){
k%=numsSize;
reverse(nums,numsSize-k,numsSize-1);
reverse(nums,0,numsSize-k-1);
reverse(nums,0,numsSize-1);
}