Contar células de uma lista encadeada

13. Re: Contar células de uma lista encadeada

Dalison
dalison

(usa Slackware)

Enviado em 20/04/2019 - 13:08h

Quando botei as linhas código para verificar scanf, está fazendo com que tenha que digitar duas vezes. Como se eu tivesse colocado dois scanf.


  


14. Re: Contar células de uma lista encadeada

Paulo
paulo1205

(usa Ubuntu)

Enviado em 21/04/2019 - 02:26h

dalison escreveu:

O programa tá funcionando bem. Fiz quase todos os ajustes (pelo menos eu acho). A questão de iniciar a variável cont em contaRecursiva que atualmente é só conta, gokernel já tinha mim dito. Eu tenho 10 questões pra resolver. Pretendo fazer tudo em um único programa. Na primeira questão quer que eu conte o número de células de forma recursiva e interativa. Só consegui de forma interativa e mesmo assim não sei se tá certo. Sobre o uso da função malloc, estou com medo de remove-la e dá bronca. Segue abaixo o código atualizado.

#include <stdio.h>
#include <stdlib.h>

typedef struct registro celula;
struct registro
{
int x;
celula *prox;
};

celula* iniciar();
void insere(int n, celula *p);
void conta(celula *p);
void altura(int n, celula *p);

int main(void)
{
int n1, cont;
celula *lista = iniciar();
int tecla = 10;

while(tecla != 0)
{
printf("====== MENU ======\n");
printf("1 - para inserir um número\n");
printf("2 - para ver a quantidade de elementos\n");
printf("3 - contar elementos de forma recursiva\n");
scanf("%d", &tecla);


Você não removeu a leitura feita sem verificação do valor de retorno de scanf(). Você só acrescentou, mais abaixo, uma repetição da mesma leitura com verificação (e ainda o fez com erro).

        fflush(stdin); 


Eu recomendo que você evite fortemente a construção “fflush(stdin)”. O padrão do C só define o comportamento de fflush() para streams de saída de dados, não para streams de entrada, como stdin. Embora existam implementações que estendam o padrão para provocar o descarte de dados até o fim da próxima linha, isso não é uma regra universal — um sistema pode muito bem ter um comportamento diferente, inclusive causando erro de execução ou corrupção de dados.

Se você quiser ter certeza de que o usuário digitou uma linha contendo apenas um número e quiser consumir a marca de fim de linha, para que a próxima leitura não se confunda encontrando tal marca, eis uma forma de fazer.
// Usando fgets().

char line[256]; // Ou outro tamanho qualquer.
int valor;
char ch;
if(
fgets(line, sizeof line, stdin)!=NULL && // Lê linha completa (desde que tenha menos do que (sizeof line) caracteres).
sscanf(line, "%d%c", &valor, &ch)==2 && // Duas conversões.
ch=='\n' // Fim de linha logo após o número.
){
// Leitura bem sucedida.
// Usa o valor lido e gravado em ‘valor’.
}
else{
fprintf(stderr, "Erro de leitura.\n");
exit(1);
}


        printf("\n");
if(scanf("%d", &tecla) == 1)


Você inverteu a condição do teste. Você está indicando erro justamente quando a função a conversão é bem sucedida.

        {
fprintf(stderr, "Erro de leitura. Abortando programa.");
exit(1);
}
if(tecla == 1)
{
printf("Digite um numero\n");
scanf("%d", &n1);
if(scanf("%d", &n1) == 1)
{
fprintf(stderr, "Erro de leitura. Abortando programa.");
exit(1);
}
exit(1);
fflush(stdin);
insere(n1, lista);
printf("\n");
}
else if(tecla == 2)
{
conta(lista);
}
else if(tecla == 3)
{
altura(n1, lista);
}
}
}

celula* iniciar()
{
celula *lista = (celula *) malloc(sizeof(celula));


Eu vi que você enviou outra postagem falando que tem receio de tirar a conversão e fazer como eu lhe indiquei.

Eu não sei o quanto seu professor realmente sabe de C (a julgar por algumas outras coisas no código, que você possivelmente aprendeu com ele, não parece ser grande coisa). Eis um fato da vida: algumas vezes você vai ter professores ruins. E nem sempre vale realmente a pena mostrar a um professor que ele peca por sua falta de conhecimento adequado, pois não raramente ele vai querer se vingar do portador da informação que lhe é inconveniente, em vez de melhorar a si mesmo.

Se você realmente acha que seu professor é um desses maus professores, faça do jeito como ele ensinou. Contudo, não fique você mesmo preso ao lixo que ele pode lhe estar passando. Assim que o semestre acabar, use aquilo que você pode aprender com quem sabe mais do que ele (e não falo de mim, especificamente — a Internet mesma está cheia de excelente informação de excelentes profissionais que lidam diariamente com ferramentas que seu professor talvez só use em sala de aula; aprenda com esses profissionais, não com o burocrata da cátedra).

Voltando à alocação, especificamente, compare as duas construções.
celula *lista=(celula *)malloc(sizeof(celula));
// Especifica manualmente o mesmo tipo em três locais diferentes: na declaração, numa conversão de
// valor antes da atribuição, e no cálculo de tamanho.

celula *lista=malloc(sizeof *lista);
// Especifica o tipo apenas na declaração. Para a atribuição, usa uma conversão automática garantida
// pela linguagem. Para o cálculo de tamanho, faz com que o compilador infira o tipo do dado cujo tamanho
// tem de ser calculado, a partir do próprio ponteiro que está recebendo a memória alocada.


Qual das duas dá mais trabalho de digitar? Qual das duas requer mais atenção, caso se decida alterar o tipo da variável? Qual das duas é mais propensa a erros? Qual das duas executa trabalho que o próprio compilador poderia executar? Qual é a mais racional de se usar?

Acho que não tem comparação.

    lista->prox = NULL;
return lista;
}


Eu acho essa implementação sub-ótima. Note como ela aloca um nó inteiro, mas não preenche nenhuma informação, a não ser o ponteiro para o próximo, e esse próximo nó é que é, na verdade, o primeiro nó da sua lista.

Eu acho estranho que uma lista supostamente vazia precise alocar um nó. Não só estranho, mas incoerente, porque do segundo nó em diante (que será o seu “primeiro”), o ponteiro nulo já indica o fim da lista. Por que essa mesma indicação não vale já desde o primeiro nó?

Eu até entendo que, no seu caso, o desperdício não será tão grande, já que o espaço desperdiçado no nó será apenas o de uma variável inteira. Mas o importante é a questão de princípio. Não só porque mais tarde, num programa real, você pode estar trabalhando com um computador com sérias restrições de memória (como um microcontrolador de hardware embarcado) ou com uma estrutura de dados bem maior e com muitos campos, mas sim porque é fácil fazer a coisa certa, bastando umas pequenas modificações nas funções de inicialização, inserção e consulta, conforme eu mostrarei abaixo.

void insere(int n, celula *p)
{
celula *nova = (celula*) malloc(sizeof(celula));
nova->x = n;
nova->prox = p->prox;
p->prox = nova;
}

void conta(celula *p)
{
int cont = 0;
celula *aux;
if(p != NULL)
{
for(aux = p; aux->prox != NULL; aux = aux->prox)
{
cont++;
}
printf("%d celulas\n", cont);
}
}

void altura(int n, celula *p)
{
celula *aux;
celula *atual;
int cont = 0;
aux = p;

while(aux != NULL && aux->x != n)
{
aux = aux->prox;
}
for(atual = aux; atual != NULL; atual = atual->prox)
{
cont++;
}
printf("%d celulas para o final da lista.", cont);

}


Eis como fazer uma lista sem desperdício.

// A inicialização é tão trivial que chega a ser redundante.
celula *inicia(void){ return NULL; }

// Insere no início da lista (como você fez). Retorna true se inserção bem sucedida; false para erro.
// (Note o uso de ponteiro para ponteiro.)
bool insere(celula **primeiro, int valor){
celula *nova=malloc(sizeof *nova);
if(!nova)
return false;
nova->x=valor;
nova->prox=*primeiro;
*primeiro=nova;
return true;
}

// Conta quantidade de nós da lista.
// (Note que eu não apenas uso ponteiro simples, mas ainda o faço apontar para dados constantes, já
// que não vou alterá-los, mas apenas consultá-los.)
unsigned conta_celulas(const celula *primeiro){
unsigned result;
// Note que eu não uso variável auxiliar, mas altero o valor de ‘primeiro’. Isso é seguro, porque em C
// todos os parâmetros de função recebem cópias dos argumentos; alterar aqui não vai mexer no valor
// de quem chamou a função.
for(result=0; primeiro!=NULL; primeiro=primeiro->prox)
++result;
return result;
}

unsigned conta_celulas_recursivo(const celula *primeiro){
return primeiro==NULL? 0: 1+conta_celulas_recursivo(primeiro->prox);
}

void esvazia(celula **primeiro){
while(*primeiro){
celula *primeiro_velho=*primeiro;
*primeiro=(*primeiro)->prox;
free(primeiro_velho);
}
}


// Modo de uso.
int main(void){
celula *lista=inicia();
printf("Tamanho inicial: %u.\n", conta_celulas(lista));
insere(&lista, 1);
insere(&lista, 2);
insere(&lista, 3);
printf("Tamanho após inserir 3 elementos: %u.\n", conta_celulas(lista));
insere(&lista, 4);
printf("Tamanho após inserir quarto elemento: %u.\n", conta_celulas_recursivo(lista));
esvazia(&lista);
printf("Tamanho após esvaziar: %u.\n", conta_celulas_recursivo(lista));
insere(&lista, 5);
printf("Tamanho após inserir um elemento novo: %u.\n", conta_celulas(lista));
esvazia(&lista);
printf("Tamanho após esvaziar novamente: %u.\n", conta_celulas_recursivo(lista));
}



... “Principium sapientiae timor Domini, et scientia sanctorum prudentia.” (Proverbia 9:10)


15. Re: Contar células de uma lista encadeada

Dalison
dalison

(usa Slackware)

Enviado em 21/04/2019 - 16:55h

Pera aí. Preciso de tempo pra processar... Não entendi porque insere retorna um valor booleano. Mas o resto eu acho que entendi.



01 02



Patrocínio

Site hospedado pelo provedor RedeHost.
Linux banner

Destaques

Artigos

Dicas

Tópicos

Top 10 do mês

Scripts