Salvador da estrutura de dados [complexidade do tempo, complexidade do espaço--1]

contente

Prefácio da Estrutura de Dados

1. A complexidade do algoritmo

2. Complexidade de tempo

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

3. Complexidade do espaço

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 numscontém todos os inteiros de 0até 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;
}

 Girar matriz 

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);

}

Acho que você gosta

Origin blog.csdn.net/weixin_63543274/article/details/124237885
Recomendado
Clasificación