[Estrutura de dados] Lista vinculada única de lista vinculada

Índice

Conceitos e operações básicas:

Método e princípio de implementação

Cenários de aplicação e precauções de uso

Análise de algoritmo e complexidade:

Comparação com outras estruturas de dados:


Conceitos e operações básicas:

Uma lista vinculada individualmente é uma estrutura de dados linear comum que consiste em vários nós, e cada nó contém duas partes: um elemento de dados e um ponteiro para o próximo nó. Cada nó possui apenas um ponteiro, geralmente chamado de próximo ponteiro, que aponta para o sucessor do nó. O nó principal é um nó adicional adicionado antes do primeiro nó, que não contém elementos de dados, mas contém um ponteiro para o primeiro nó.

A lista vinculada individualmente pode representar uma sequência de qualquer comprimento e pode inserir e excluir nós dinamicamente, tornando-a flexível e eficiente. Mas como cada nó possui apenas um ponteiro, a lista vinculada individualmente só pode ser percorrida de frente para trás, e não inversamente.

Os nós da lista vinculada individualmente são descritos na linguagem C# como:

namespace DataStructLibrary
{
    class SNode<T>
    {
        private T data;//数据域
        private SNode<T> next;//引用域
        public SNode(T val,SNode<T> p)
        {
            data = val;
            next = p;
        }

        public SNode(SNode<T> p)
        {
            next = p;

        }

        public SNode(T val)
        {
            data = val;
            next = null;
        }

        public SNode()
        {
            data = default(T);
            next = null;
        }

        public T Data
        {
            get { return data; }
            set { data = value; }
        }

        public SNode<T> Next
        {
            get { return next; }
            set { next = value; }
        }
    }
}

As operações a seguir são comumente usadas em listas vinculadas individualmente:

  1. Criar lista vinculada: crie uma lista vinculada vazia, com ou sem nó principal.

etapa operar
1 Declare uma variável inicial do tipo de nó para apontar para o primeiro nó da lista vinculada individualmente
2

Atribua o valor da variável inicial como nulo no construtor da lista vinculada individualmente

     2. Inserir nó: insira um novo nó na posição especificada (como início, final ou meio) da lista vinculada.

         2.1 Insira um novo nó no início da lista vinculada individualmente

        2.2 Insira um nó entre dois nós na lista vinculada

        2.3 Insira um novo nó no final da lista vinculada

Inserir um nó no final da lista ligada individualmente é um caso especial de inserir um nó entre dois nós da lista ligada.Quando o actual é nulo e o anterior aponta para o último nó,o novo nó pode ser inserido no final do nó. lista vinculada. Se em alguns casos estiver muito claro que o nó será inserido no final da lista vinculada, as seguintes etapas do algoritmo podem ser executadas.

etapa operar
1

Alocar memória para novos nós e atribuir valores aos campos de dados

2

Encontre o último nó da lista e marque-o como atual

3

Aponte o próximo campo de corrente para o novo nó

4

É o próximo campo do nó apontando para nulo, liberando o espaço atual

      3. Excluir operação

      Para excluir um nó especificado de uma lista vinculada individualmente, primeiro determine se a lista está vazia. Se não estiver vazio, o nó especificado deve ser pesquisado primeiro e, se o nó especificado for encontrado, ele será excluído, caso contrário, será exibida uma mensagem informando que o nó correspondente não foi encontrado. Quando o nó excluído é encontrado, o nó especificado é excluído da lista vinculada individualmente, geralmente dividida nas três situações a seguir:

         3.1 Exclua o nó principal da lista vinculada individualmente

etapa operar
1

marque o primeiro nó da lista como o nó atual

2

Use start para apontar para o próximo nó na lista vinculada individualmente

3

memória livre marcada como nó atual

         3.2 Exclua o nó entre dois nós na lista vinculada individualmente

         3.3 Exclua o nó final da lista vinculada individualmente

         No algoritmo acima para exclusão de nós entre dois nós na lista vinculada unidirecional, se após a operação de pesquisa, o nó atual apontar para o último nó da lista, significa que o nó a ser excluído é o último nó em a lista. O algoritmo também pode excluir nós no final de expressões de cadeia unidirecional. Portanto, não há necessidade de criar especificamente um algoritmo para remover nós no final de uma lista vinculada individualmente.

     4. Pegue os elementos da tabela e localize os elementos

     A busca de elementos da tabela e a localização de elementos referem-se à busca do nó correspondente ao número ou valor de sequência de acordo com o número de sequência ou valor do nó fornecido. O processo específico é o seguinte:

 etapa

operar

1

Marque o nó inicial da lista vinculada individualmente como o nó atual atual

2

Se a lista vinculada individualmente não for uma lista vinculada vazia, compare se o número de série ou valor a ser pesquisado é igual ao número de série ou valor apontado pela referência atual, caso contrário, o atual aponta para o próximo nó e quando o nó é encontrado, retorna atual

3

Quando current é nulo, significa que o nó especificado não foi encontrado

      5. Lista vinculada reversa: organize os nós na lista vinculada em ordem inversa.

      6. Anexar lista vinculada: anexa o cabeçalho de outra lista vinculada a esta lista vinculada.

As operações acima são as operações mais básicas de uma lista vinculada individualmente, por meio da qual muitas outras estruturas de dados, como pilhas, filas e tabelas hash, podem ser implementadas. Deve-se notar que algumas condições de contorno e condições anormais precisam ser tratadas ao realizar operações de lista vinculada para garantir o funcionamento normal da lista vinculada.

Outras operações relacionadas à tabela linear, como calcular o comprimento da tabela, julgar que ela está vazia, etc., são relativamente simples de implementar na tabela sequencial, consulte o seguinte código C# para a lista vinculada única:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace DataStructLibrary
{
    class SingleLinkList<T>:ILinarList<T>
    {
        public SNode<T> start;//单链表的头引用
        int length;//单链表长度
        /// <summary>
        /// 初始化单链表
        /// </summary>
        public SingleLinkList()
        {
            start = null;
        }


        /// <summary>
        /// 在单链表的末尾追加数据元素 data
        /// </summary>
        /// <param name="data"></param>
        public void InsertNode(T data)
        {
            if(start == null)
            {
                start = new SNode<T>(data);
                length++;
                return;
            }
            SNode<T> current = start;
            while(current.Next != null)
            {
                current = current.Next;
            }
            current.Next = new SNode<T>(data);
            length++;
        }

        /// <summary>
        /// 在单链表的第i个数据元素的位置前插入一个数据元素data
        /// </summary>
        /// <param name="data"></param>
        /// <param name="i"></param>
        public void InsertNode(T data, int i)
        {
            SNode<T> current;
            SNode<T> previous;
            if(i<1 || i> (length + 1))
            {
                Console.WriteLine("Position is error!");
                return;
            }
            SNode<T> newNode = new SNode<T>(data);
            //在空链表或第一个元素钱插入第一个元素
            if (i == 1)
            {
                newNode.Next = start;
                start = newNode;
                length++;
                return;
            }
            //单链表的两个元素中间插入一个元素
            current = start;
            previous = null;
            int j = 1;
            while(current != null && j<i)
            {
                previous = current;
                current = current.Next;
                j++;
            }
            if (j == i)
            {
                previous.Next = newNode;
                newNode.Next = current;
                length++;
            }
        }

        /// <summary>
        /// 删除单链表的第i个数据元素
        /// </summary>
        /// <param name="i"></param>
        public void DeleteNode(int i)
        {
            if(IsEmpty() || i < 1)
            {
                Console.WriteLine("Link is empty or Position is error");
            }
            SNode<T> current = start;
            if(i == 1)
            {
                start = current.Next;
                length--;
                return;
            }
            SNode<T> previus = null;
            int j = 1;
            while(current.Next != null && j<i)
            {
                previus = current;
                current = current.Next;
                j++;
            }
            if (j == i)
            {
                previus.Next = current.Next;
                current = current.Next;
                length--;
                return;
            }
            //第i个节点不存在
            Console.WriteLine("the ith node is not exist!");

        }

        /// <summary>
        /// 获取单链表的第i个数据元素
        /// </summary>
        /// <param name="i"></param>
        /// <returns></returns>
        public T SearchNode(int i)
        {
            if (IsEmpty())
            {
                Console.WriteLine("List is empty");
                return default(T);
            }
            SNode<T> current = start;
            int j = 1;
            while (current.Next != null && j<i)
            {
                current = current.Next;
                j++;
            }
            if (j == i)
            {
                return current.Data;
            }
            //第i个节点不存在
            Console.WriteLine("the ith node is not exist!");
            return default(T);

        }

        /// <summary>
        /// 在单链表中查找值为data的数据元素
        /// </summary>
        /// <param name="data"></param>
        /// <returns></returns>
        public T SearchNode(T data)
        {
            if (IsEmpty())
            {
                Console.WriteLine("List is empty");
                return default(T);
            }
            SNode<T> current = start;
            int i = 1;
            while(current != null && !current.Data.ToString().Contains(data.ToString()))
            {
                current = current.Next;
                i++;
            }
            if(current != null)
            {
                return current.Data;
            }
            return default(T);
        }

        /// <summary>
        /// 获取单链表的长度
        /// </summary>
        /// <returns></returns>
        public int GetLength()
        {
            return length;
        }

        /// <summary>
        /// 该函数将链表头节点反转后,重新作为链表的头节点。算法使用迭代方式实现,遍历链表并改变指针指向
        /// 例如链表头结点start:由原来的
        /// data:a,
        /// next:[
        ///      data:b,
        ///      next:[
        ///           data:c,
        ///           next:null
        ///          ]
        ///     ] 
        ///翻转后的结果为:
        /// data:c,
        /// next:[
        ///      data:b,
        ///      next:[
        ///           data:a,
        ///           next:null
        ///          ]
        ///     ] 
        /// </summary>
        public void ReverseList()
        {
            if (length ==1 || this.start == null)
            {
                return;
            }
            //定义 previous next 两个指针
            SNode<T> previous = null;
            SNode<T> next = null;
            SNode<T> current = this.start;
            //循环操作
            while (current != null)
            {
                //定义next为Head后面的数,定义previous为Head前面的数
                next = current.Next;
                current.Next = previous;//这一部分可以理解为previous是Head前面的那个数。
                //然后再把previous和Head都提前一位
                previous = current;
                current = next;
            }
            this.start = previous;
            //循环结束后,返回新的表头,即原来表头的最后一个数。
            return;
        }



        /// <summary>
        /// 将另外一个链表头部追加到本链表
        ///  /// 例如链表头结点start:由原来的
        /// data:a,
        /// next:[
        ///      data:b,
        ///      next:[
        ///           data:c,
        ///           next:null
        ///          ]
        ///     ] 
        ///翻转后的结果为:
        /// data:a,
        /// next:[
        ///      data:b,
        ///      next:[
        ///           data:c,
        ///           next:[
        ///                 data:x,
        ///                 next:[
        ///                       data:y,
        ///                       next:[
        ///                             data:z,
        ///                             next:null
        ///                            ]
        ///              
        ///                      ]
        ///               ]
        ///          ]
        ///     ] 
        /// </summary>
        /// <param name="nodeB">
        /// 追加的单链表头部数据
        /// 例如:
        /// data:x,
        /// next:[
        ///      data:y,
        ///      next:[
        ///           data:z,
        ///           next:null
        ///          ]
        ///     ]
        /// </param>
        public void Append(SNode<T> nodeB)
        {
            if (nodeB == null)
            {
                return;
            }
            InsertNode(nodeB.Data);
            SNode<T> tmpNode = nodeB.Next;
            while (tmpNode != null)
            {
                InsertNode(tmpNode.Data);
                tmpNode = tmpNode.Next;
            }

            return;
        }

        /// <summary>
        /// 清空单链表
        /// </summary>
        public void Clear()
        {
            start = null;
        }

        /// <summary>
        /// 判断单链表是否为空
        /// </summary>
        /// <returns></returns>
        public bool IsEmpty()
        {
            if(start == null)
            {
                return true;
            }
            return false;
        }

    }
}

Método e princípio de implementação

O método de implementação e o princípio da lista vinculada simples incluem principalmente os seguintes aspectos:

  1. Nó: Um nó em uma lista vinculada individualmente consiste em duas partes, a saber, o campo de dados e o campo de ponteiro. O campo de dados armazena os elementos de dados do nó e o campo de ponteiro aponta para o próximo nó. Os nós podem ser representados por estruturas.

  2. Nó principal: O nó principal é um nó adicional adicionado na frente do primeiro nó na lista vinculada. Ele não contém elementos de dados, mas contém um ponteiro para o primeiro nó. A função do nó principal é simplificar a operação da lista vinculada, tornando mais convenientes operações como inserção e exclusão.

  3. Operação de ponteiro: A conexão entre os nós da lista vinculada é realizada por meio de ponteiros. Cada nó possui um ponteiro apontando para o próximo nó, e o relacionamento do ponteiro entre os nós precisa ser modificado ao inserir ou excluir nós.

  4. Atravessando uma lista vinculada: Atravessar uma lista vinculada refere-se a visitar todos os nós da lista vinculada em ordem. Ao percorrer a lista vinculada, você pode ler ou modificar o valor dos dados no nó e também calcular o comprimento da lista vinculada e encontrar um determinado nó.

  5. Gerenciamento de memória: o espaço de memória precisa ser alocado ou liberado ao inserir e excluir nós. Para evitar problemas como vazamentos de memória e lançamentos repetidos, é necessário gerenciar razoavelmente o espaço de memória.

  6. Processamento de condições de limite: Algumas condições de limite e condições anormais precisam ser tratadas ao executar operações de lista vinculada, como a lista vinculada está vazia, a posição de inserção está fora do intervalo, etc., para garantir a operação normal da lista vinculada.

Compreender o método de implementação e o princípio da lista vinculada individualmente é muito útil para compreender o desempenho e a otimização da lista vinculada. Ao mesmo tempo, é necessário prestar atenção ao design e otimização razoáveis ​​de acordo com a situação real na implementação específica para melhorar a eficiência e a facilidade de manutenção do código.

Cenários de aplicação e precauções de uso

A lista vinculada individualmente é uma estrutura de dados comum, frequentemente usada nos seguintes cenários de aplicação:

  1. Implementar pilhas e filas: listas vinculadas individualmente podem ser usadas para implementar pilhas e filas. Em uma pilha, os elementos só podem entrar e sair do topo da pilha; em uma fila, os elementos só podem entrar no final da fila e sair do início da fila. Essas duas estruturas de dados podem ser facilmente realizadas usando as operações de inserção inicial e de inserção final da lista vinculada individualmente.

  2. Alocação de memória: No gerenciamento de memória do computador, listas vinculadas individualmente são frequentemente usadas como estruturas de dados para alocação dinâmica de memória. Através da conexão de ponteiro entre os nós da lista vinculada, os blocos de memória podem ser alocados e liberados dinamicamente.

  3. Listas de reprodução de áudio e vídeo: listas vinculadas individualmente podem ser usadas para implementar listas de reprodução de áudio e vídeo. Cada nó representa um arquivo de áudio ou vídeo e mantém a posição do próximo arquivo. Ao percorrer a lista vinculada, o áudio e o vídeo de toda a lista podem ser reproduzidos sequencialmente.

  4. Encontrar estruturas circulares: Listas encadeadas individualmente também podem ser usadas para lidar com problemas relacionados a estruturas circulares, como julgar se uma lista encadeada tem um anel, encontrar a entrada do anel, etc.

  5. Estratégia de eliminação de cache: No sistema de cache, quando o espaço de cache está cheio, alguns dados precisam ser eliminados para liberar espaço. Listas vinculadas individualmente podem ser usadas para manter itens de dados no cache enquanto registra seu uso. Quando os dados precisam ser eliminados, o item de dados usado menos recentemente pode ser selecionado para eliminação, ou seja, o nó no final da lista vinculada individualmente é excluído.

Os seguintes pontos precisam ser observados ao usar uma lista vinculada individualmente:

  1. Problema de ponteiro nulo: problemas de ponteiro nulo são propensos a ocorrer em operações de lista vinculada, como acessar uma lista vinculada vazia ou um nó que não existe. Para evitar esses problemas, é necessário realizar julgamento nulo nos parâmetros de entrada.

  2. Problemas de gerenciamento de memória: Ao inserir e excluir nós, o espaço de memória precisa ser alocado ou liberado. Se o gerenciamento não for feito corretamente, podem ocorrer problemas como vazamentos de memória ou liberações repetidas. Esses problemas podem ser resolvidos usando um mecanismo de coleta de lixo ou gerenciando manualmente o espaço de memória.

  3. Problema de condição de limite: Algumas condições de limite e condições anormais precisam ser tratadas ao executar operações de lista vinculada, como a lista vinculada está vazia, a posição de inserção está fora do intervalo, etc., para garantir a operação normal da lista vinculada.

  4. Problemas de desempenho: Ao lidar com dados em grande escala, as listas vinculadas individualmente podem apresentar alguns problemas de desempenho, como velocidade lenta de acesso aleatório e grande sobrecarga de espaço. Portanto, em aplicações práticas, é necessário selecionar uma estrutura de dados adequada de acordo com a situação real.

Análise de algoritmo e complexidade:

  1. Compreenda os algoritmos e a análise de complexidade de listas vinculadas individualmente, como julgar se uma lista vinculada tem um anel, como reverter uma lista vinculada, como mesclar duas listas vinculadas ordenadas, etc. Dominar esses algoritmos pode melhorar suas habilidades de programação e, ao mesmo tempo, aprofundar sua compreensão de listas vinculadas individualmente.

O algoritmo de lista vinculada individualmente e a análise de complexidade incluem principalmente os seguintes aspectos:

  1. Atravessando uma lista vinculada: Atravessar uma lista vinculada é a operação básica de acesso a todos os nós da lista vinculada, que pode ser implementada por meio de loops ou recursão. A complexidade de tempo é O(n), onde n é o comprimento da lista vinculada.

  2. Encontre o nó especificado: pesquise o nó especificado na lista vinculada, você pode pesquisar de acordo com a posição do nó ou a palavra-chave do nó. A complexidade de tempo da pesquisa linear é O(n), e a complexidade de tempo da pesquisa binária é O(logn).

  3. Inserir nó: Para inserir um novo nó na lista vinculada, o relacionamento do ponteiro entre os nós precisa ser modificado. A complexidade de tempo é O(1) se for inserida no início ou no final, caso contrário, O(n).

  4. Excluir nó: para excluir um nó da lista vinculada, você precisa modificar o relacionamento do ponteiro entre os nós. Se a exclusão for o nó principal ou o nó final, a complexidade de tempo será O(1), caso contrário, será O(n).

  5. Lista vinculada reversa: para organizar os nós na lista vinculada em ordem inversa, o relacionamento do ponteiro entre os nós precisa ser modificado. Pode ser implementado usando iteração ou recursão, e a complexidade de tempo é O(n).

  6. Mesclar lista vinculada: para mesclar duas listas vinculadas ordenadas em uma nova lista vinculada ordenada, você precisa comparar os nós das duas listas vinculadas, um por um, e mesclá-los. A complexidade de tempo é O(m+n), onde m e n representam os comprimentos das duas listas vinculadas, respectivamente.

Deve-se notar que ao executar o algoritmo de lista vinculada, é necessário prestar atenção ao lidar com algumas condições de contorno e condições anormais, como a lista vinculada está vazia, a posição de inserção está fora do intervalo e assim por diante. Ao mesmo tempo, na implementação real, o design e a otimização razoáveis ​​devem ser realizados de acordo com a situação real para melhorar a eficiência e a facilidade de manutenção do código.

Comparação com outras estruturas de dados:

  1. Aprenda como as listas vinculadas individualmente se comparam a outras estruturas de dados, como matrizes, listas duplamente vinculadas, etc. Compreender suas vantagens e desvantagens e cenários aplicáveis ​​pode selecionar melhor a estrutura de dados apropriada para resolver problemas práticos.

A comparação entre listas vinculadas simples e outras estruturas de dados inclui principalmente os seguintes aspectos:

  1. Matrizes: tanto matrizes quanto listas vinculadas individualmente podem ser usadas para representar sequências, mas são implementadas de maneira diferente. Um array é uma alocação contínua de espaço de armazenamento na memória e qualquer elemento pode ser acessado diretamente. A lista vinculada individualmente precisa conectar nós por meio de ponteiros e só pode ser percorrida de frente para trás. Como cada nó da lista vinculada individualmente possui apenas um ponteiro, a sobrecarga de espaço é pequena; enquanto o array precisa pré-alocar um certo tamanho de espaço de memória, e algum espaço pode ser desperdiçado ao inserir ou excluir elementos.

  2. Pilhas e filas: Pilhas e filas são duas estruturas de dados comuns que podem ser implementadas usando matrizes ou listas vinculadas individualmente. Pilhas e filas implementadas com arrays podem ser acessadas aleatoriamente, mas outros elementos precisam ser movidos ao inserir ou excluir elementos, e a complexidade de tempo é O(n). A pilha e a fila implementadas por uma lista vinculada individualmente podem realizar operações de inserção e exclusão no início ou no final, e a complexidade de tempo é O (1).

  3. Tabela hash: Uma tabela hash é uma estrutura de dados baseada em uma função hash, que pode localizar e modificar elementos rapidamente. Na implementação de tabelas hash, matrizes são geralmente usadas para armazenar elementos, e listas vinculadas ou outras estruturas de dados são usadas para lidar com colisões de hash. A lista vinculada individualmente pode ser usada como uma implementação de armazenamento encadeado na tabela hash, mas como só pode ser percorrida de frente para trás, pode afetar a eficiência da consulta da tabela hash.

  4. Árvore vermelho-preto: A árvore vermelho-preto é uma árvore de pesquisa binária com equilíbrio automático, que pode realizar operações rápidas de inserção, exclusão e pesquisa. Em comparação com estruturas de dados, como listas vinculadas individualmente, as árvores rubro-negras têm menor complexidade de tempo e suportam funções avançadas, como pesquisa de intervalo e operações de classificação. Mas, correspondentemente, também é mais complicado de implementar e precisa lidar com diversas operações de rotação e coloração.

Resumindo, a lista vinculada individualmente é adequada para representar sequências e suporta operações dinâmicas de inserção e exclusão com menos sobrecarga de espaço. Em comparação com matrizes, a complexidade de tempo de inserção e exclusão de elementos é menor; em comparação com outras estruturas de dados, como pilhas e filas, é mais conveniente realizar operações de inserção e exclusão no meio. No entanto, tem baixo desempenho em operações como consulta e classificação e não é tão bom quanto estruturas de dados avançadas, como árvores rubro-negras.

PS: Se houver algum erro ou omissão, corrija-me

Acho que você gosta

Origin blog.csdn.net/beenles/article/details/131110584
Recomendado
Clasificación