A estrutura de dados mais forte da história ---- implementação de simulação C de lista vinculada individualmente (ilustração + código)

3.1 Conceito e estrutura da lista encadeada

Conceito: Lista encadeada é uma estrutura de armazenamento não consecutiva e não sequencial na estrutura física de armazenamento.A ordem lógica dos elementos de dados é realizada através da ordem de encadeamento dos ponteiros na lista encadeada.

imagem-20220312183503652

Perceber:

1. Como pode ser visto na figura acima, a estrutura da cadeia é logicamente contínua, mas não necessariamente fisicamente contínua
2. Na realidade, os nós são geralmente solicitados a partir do heap.
3. A partir do espaço solicitado no topo, é é alocado de acordo com uma determinada estratégia, e o espaço para os dois aplicativos pode ou não ser contínuo.

3.2 Classificação de listas vinculadas

Na prática, a estrutura das listas encadeadas é muito diversificada, existem 8 estruturas de listas encadeadas em combinação das seguintes situações:

1. Lista vinculada unidirecional ou bidirecional

2. Lista encadeada com ou sem cabeça

3. Lista encadeada circular ou acíclica

Existem dois mais comumente usados: lista encadeada acíclica unidirecional sem cabeça, lista encadeada circular bidirecional encabeçada

  1. Lista encadeada acíclica sem cabeça: A estrutura é simples e geralmente não é usada para armazenar dados sozinho. Na prática, é mais uma subestrutura de outras estruturas de dados, como baldes de hash, listas de adjacência de gráficos e assim por diante. Além disso, essa estrutura aparece muito na entrevista da prova escrita.
  2. Lista encadeada duplamente circular: A estrutura é a mais complexa e geralmente é usada para armazenar dados separadamente. As estruturas de dados de lista encadeada usadas na prática são todas as listas encadeadas duplamente circulares. Além disso, embora a estrutura dessa estrutura seja complexa, descobriremos que a estrutura trará muitas vantagens depois de usar o código para implementá-la, mas a implementação é simples e saberemos depois que o código for implementado.

3.3 Implementação de Lista Linked Headless + One-way + Acyclic Linked List Adição, Exclusão, Pesquisa e Implementação de Modificação

3.3.1 Definição de Lista Vinculada

typedef int SLTDataType;//
typedef struct SListNode
{
	int data;//val,存储的数据,此处假设存储的数据为int型
	struct SListNode* next;//存储下一个节点的位置
}SListNode,SLN;

3.3.2 Impressão de dados de lista vinculada

void SListPrint(SListNode* phead)
{
	SListNode* cur = phead;
	while (cur != NULL)
	{
		printf("%d->", cur->data);
		cur = cur->next;
	}
	printf("NULL\n");
}

3.3.3 Inserção final da lista vinculada

void SListPushBack(SListNode** pphead, SLTDataType x)
{
	SListNode* newnode = BuySListNode(x);
	if (*pphead == NULL)
	{
		*pphead = newnode;
	}
	else
	{
		//找尾
		SListNode* tail = *pphead;
		while (tail->next != NULL)
		{
			tail = tail->next;
		}
		tail->next = newnode;
	}
}

No processo de encontrar o final, certifique-se de não escrever o seguinte código:

while(tail!=NULL)
{
	tail = tail->next;
}
tail->next = newnode;

imagem-20220315113511719

Claro, a descrição acima é o caso de deleção de cauda.

A inserção de cauda é realmente semelhante. A inserção de cauda é como o código acima. Quando não tail!=NULLé estabelecida, a cauda é igual a vazia, e então a operação de atribuição é realizada. tail->next = newnodeEsta linha de código é equivalente ao seguinte código:

(*tail).next, aqui equivale a desreferenciar o ponteiro nulo, que na verdade é um acesso ilegal, e também tenta modificar ilegalmente os dados na memória não autorizada, o que inevitavelmente levará ao travamento do programa. E não armazena o endereço do novo nó no próximo nó anterior.

Este lugar precisa entender o princípio fundamental da travessia da lista vinculada:

imagem-20220315113946661

A lista encadeada é um espaço de dados relativamente estático armazenado na área de heap. Percorremos alterando os dados na cauda da variável local na área da pilha (ou seja, o endereço de cada nó da lista encadeada). A razão pela qual podemos acessá-la através da variável tail E o motivo de modificar os dados do nó é porque o tipo de dado tail é SListNode*, ou seja, o ponteiro para o nó. O tipo do ponteiro determina o tipo de dado que pode ser acessado desreferenciando o ponteiro, então *tail pode acessar os dados do nó na área de heap e pode ser modificado.

3.3.4 Aplicação dinâmica do espaço de lista vinculada

SListNode* BuySListNode(SLTDataType x)
{
	SListNode* newnode = (SListNode*)malloc(sizeof(SListNode));
	if (newnode == NULL)
	{
		printf("malloc fail\n");
		exit(-1);
	}
	else
	{
		newnode->data = x;
		newnode->next = NULL;
	}
	return newnode;
}

3.3.5 Cabeçalho da lista vinculada

void SListPushFront(SListNode** pphead, SLTDataType x)
{
	SListNode* newnode = BuySListNode(x);
	newnode->next = *pphead;
	*pphead = newnode;
}

3.3.6 Exclusão da cauda da lista vinculada

Três situações precisam ser consideradas:

  1. nulo
  2. um nó
  3. vários nós

Duas maneiras de escrever:

O primeiro:

void SListPopBack(SListNode** pphead)
{
	assert(pphead);
	if (*pphead == NULL)//空链表
	{
		return;
	}
	else if ((*pphead)->next == NULL)//一个节点
	{
		free(*pphead);//*pphead就是plist的值
		*pphead = NULL;
	}
	else//多个节点
	{
		SListNode* tail = *pphead;
		SListNode* prev = NULL;//为什么要置为空呢?因为这个地方相当于是指向第一个节点之前的节点,这个节点并不存在,设为空
		while (tail->next != NULL)
		{
			prev = tail;
			tail = tail->next;
		}
		free(tail);
		tail = NULL;
		prev->next = NULL;
	}
}

imagem-20220315191516068

Desta forma, não há problema quando se depara com apenas um nó.

O segundo:

void SListPopBack(SListNode** pphead)
{
	assert(pphead);
	if (*pphead == NULL)//空链表
	{
		return;
	}
	else if ((*pphead)->next == NULL)//一个节点
	{
		free(*pphead);//*pphead就是plist的值
		*pphead = NULL;
	}
	else//多个节点
	{
		SListNode* tail = *pphead;
		while (tail->next->next != NULL)
		{
			tail = tail->next;
		}
		free(tail->next);//释放尾节点
		tail->next = NULL;//将新尾节点的next置为NULL
	}
}

imagem-20220315192821887

3.3.7 Exclusão do cabeçalho da lista vinculada

void SListPopFront(SListNode** pphead)
{
	assert(pphead);
	if (*pphead == NULL)//空链表
	{
		return;
	}
	else//非空链表
	{
		SListNode* next = (*pphead)->next;//next作为临时变量存放的是被删除的节点中next存储的第二个节点的地址
		free(*pphead);
		*pphead = next;
	}
}

imagem-20220315211854659

3.3.7 Pré-inserção em qualquer posição na lista encadeada

void SListInsertBefore(SListNode** pphead, SListNode* pos,SLTDataType x)
{
	assert(pphead);
	if (*pphead == pos)//pos是第一个节点,相当于头插
	{
		SListPushFront(pphead, x);
	}
	else
	{
		SListNode* prev = *pphead;
		while (prev->next != pos)
		{
			prev = prev->next;
		}
		SListNode* newnode = BuySListNode(x);
		prev->next = newnode;
		newnode->next = pos;
	}
}

imagem-20220315221207436

3.3.8 Pós-inserção em qualquer posição na lista vinculada

Duas implementações:

método um:

void SListInsertAfter(SListNode* pos, SLTDataType x)
{
	assert(pos);
	SListNode* newnode = BuySListNode(x);
	
	newnode->next = pos->next;
	pos->next = newnode;
	//这两行代码顺序是固定的,只能这个顺序,无法进行改变
}

Ícone:

imagem-20220316165627436

Método dois:

void SListInsertAfter(SListNode* pos, SLTDataType x)
{
	assert(pos);
	SListNode* next = pos->next;
	SListNode* newnode = BuySListNode(x);

	newnode->next = next;
	pos->next = newnode;
	//这两行代码可以任意改变顺序,谁先谁后都不影响最后的结果
}

Ícone:

imagem-20220316170549333

3.3.8 Exclusão de qualquer posição na lista vinculada

void SListErase(SListNode** pphead, SListNode* pos)
{
	assert(pphead);
	assert(pos);
	if (pos == *pphead)//当pos为头节点的时候
	{
		SListPopFront(pphead);
	}
	else//当pos为非头节点的时候
	{
		SListNode* prev = *pphead;
		while (prev->next != pos)
		{
			prev = prev->next;
		}
		prev->next = pos->next;
		free(pos);
		pos = NULL;
	}
}

Ícone:

imagem-20220316173644510

3.3.9 Pré-exclusão em qualquer posição na lista vinculada

void SListEraseBefore(SListNode** pphead, SListNode* pos)//pos即为任意位置
{
	assert(pphead);
	assert(pos);
	if (pos == *pphead)
	{
		return;
	}
	else if(pos==(*pphead)->next)
	{
		SListPopFront(pphead);
	}
	else
	{
		SListNode* prev = *pphead;//prev用来存储pos的前一个位置的前一个位置
		while (prev->next->next != pos)
		{
			prev = prev->next;
		}
		SListNode* next = prev->next;//保存pos前一个节点的地址
		prev->next = prev->next->next;//将prev和pos的两个节点进行连接
		free(next);//删除pos的前一个节点
	}
}

imagem-20220316180318172

3.3.10 Pós-exclusão de qualquer posição na lista vinculada

void SListEraseAfter(SListNode* pos)
{
	assert(pos);
	SListNode* next = pos->next;
	if (next == NULL)//当pos是最后一个节点的时候
	{
		return;
	}
	else
	{
		pos->next = next->next;
		free(next);
		next = NULL;
	}
}

Ícone:

imagem-20220316191805389

3.3.11 Destruição da lista vinculada

void SListDestory(SListNode** pphead)
{
	assert(pphead);
	SListNode* cur = *pphead;
	SListNode* next = *pphead;//是为了存储cur下一个节点的地址,因为free(cur)之后,cur指针指向的内存中的数据可能已经称为乱码了,即不能再正常的使用
	while (cur)
	{
		next = cur->next;
		free(cur);
		cur = next;
	}
	*pphead = NULL;
}

3.3.12 Resumo das Listas Vinculadas

Resumo: Estrutura de lista vinculada individualmente, adequada para exclusão de plugue de cabeça. Inserção e exclusão no final ou em algum lugar no meio não são adequadas. Se você deseja usar uma estrutura de lista vinculada para armazenar dados separadamente, é mais adequado usar uma lista duplamente vinculada.

O significado do aprendizado de listas vinculadas individualmente:

  1. A lista vinculada individualmente será usada como uma subestrutura de estruturas de dados complexas que aprenderemos mais tarde (lista de adjacências de gráfico, balde de hash)
  2. Haverá muitas questões práticas clássicas na lista encadeada, e haverá muitas questões relacionadas na entrevista do teste escrito.

Acho que você gosta

Origin blog.csdn.net/m0_57304511/article/details/123549365
Recomendado
Clasificación