[Estructura de datos] Lista enlazada única de lista enlazada

Tabla de contenido

Conceptos y operaciones básicos:

Método y principio de implementación.

Escenarios de aplicación y precauciones de uso

Análisis de algoritmos y complejidad:

Comparación con otras estructuras de datos:


Conceptos y operaciones básicos:

Una lista enlazada individualmente es una estructura de datos lineal común que consta de varios nodos, y cada nodo contiene dos partes: un elemento de datos y un puntero al siguiente nodo. Cada nodo tiene sólo un puntero, normalmente llamado puntero siguiente, que apunta al sucesor del nodo. El nodo principal es un nodo adicional agregado antes del primer nodo, que no contiene elementos de datos, pero contiene un puntero al primer nodo.

La lista enlazada individualmente puede representar una secuencia de cualquier longitud y puede insertar y eliminar nodos dinámicamente, lo que la hace flexible y eficiente. Pero dado que cada nodo tiene solo un puntero, la lista enlazada individualmente solo se puede recorrer de adelante hacia atrás, no hacia atrás.

Los nodos de la lista enlazada individualmente se describen en lenguaje 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; }
        }
    }
}

Las siguientes son operaciones de uso común en listas enlazadas individualmente:

  1. Crear lista vinculada: cree una lista vinculada vacía, con o sin un nodo principal.

paso funcionar
1 Declarar una variable de inicio de tipo de nodo para que apunte al primer nodo de la lista enlazada individualmente
2

Asigne el valor de la variable de inicio a nulo en el constructor de la lista enlazada individualmente

     2. Insertar nodo: inserte un nuevo nodo en la posición especificada (como al principio, al final o en el medio) de la lista vinculada.

         2.1 Insertar un nuevo nodo al comienzo de la lista enlazada individualmente

        2.2 Insertar un nodo entre dos nodos en la lista vinculada

        2.3 Insertar un nuevo nodo al final de la lista vinculada

Insertar un nodo al final de la lista enlazada individualmente es un caso especial de insertar un nodo entre dos nodos de la lista enlazada. Cuando el actual es nulo y el anterior apunta al último nodo, el nuevo nodo se puede insertar al final de la lista enlazada individualmente. lista enlazada. Si en algunos casos está muy claro que el nodo se insertará al final de la lista vinculada, se pueden realizar los siguientes pasos del algoritmo.

paso funcionar
1

Asigne memoria para nuevos nodos y asigne valores a campos de datos

2

Encuentre el último nodo en la lista, márquelo como actual

3

Apunte el siguiente campo de corriente al nuevo nodo

4

Es el siguiente campo del nudo que apunta a nulo, liberando el espacio actual.

      3. Eliminar operación

      Para eliminar un nodo específico de una lista enlazada individualmente, primero determine si la lista está vacía. Si no está vacío, primero se debe buscar el nodo especificado y, si se encuentra, se eliminará; de lo contrario, aparecerá un mensaje indicando que no se encuentra el nodo correspondiente. Cuando se encuentra el nodo eliminado, el nodo especificado se elimina en la lista enlazada individualmente, generalmente dividida en las siguientes tres situaciones:

         3.1 Eliminar el nodo principal de la lista enlazada individualmente

paso funcionar
1

marcar el primer nodo de la lista como el nodo actual

2

Utilice start para apuntar al siguiente nodo en la lista enlazada individualmente

3

memoria libre marcada como nodo actual

         3.2 Eliminar el nodo entre dos nodos en la lista enlazada individualmente

         3.3 Eliminar el nodo de cola de la lista enlazada individualmente

         En el algoritmo anterior para eliminar nodos entre dos nodos en la lista enlazada unidireccional, si después de la operación de búsqueda, el nodo actual apunta al último nodo de la lista, significa que el nodo que se eliminará es el último nodo en la lista. El algoritmo también puede eliminar nodos al final de expresiones de cadena unidireccionales. Por lo tanto, no es necesario crear específicamente un algoritmo para eliminar nodos al final de una lista enlazada individualmente.

     4. Tome elementos de la tabla y ubique elementos.

     Obtener elementos de la tabla y ubicar elementos se refiere a buscar el nodo correspondiente al número o valor de secuencia de acuerdo con el número de secuencia o valor de nodo dado. El proceso específico es el siguiente:

 paso

funcionar

1

Marque el nodo inicial de la lista enlazada individualmente como el nodo actual actual

2

Si la lista enlazada única no es una lista enlazada vacía, compare si el número de serie o el valor a buscar es igual al número de serie o valor señalado por la referencia actual; de lo contrario, la corriente apunta al siguiente nodo y cuando Se encuentra el nodo, devuelve la corriente.

3

Cuando actual es nulo, significa que no se encontró el nodo especificado

      5. Lista enlazada inversa: organice los nodos en la lista enlazada en orden inverso.

      6. Agregar lista vinculada: agregue el encabezado de otra lista vinculada a esta lista vinculada.

Las operaciones anteriores son las operaciones más básicas de una lista enlazada individualmente, a través de las cuales se pueden implementar muchas otras estructuras de datos, como pilas, colas y tablas hash. Cabe señalar que es necesario abordar algunas condiciones límite y condiciones anormales al realizar operaciones de lista vinculada para garantizar el funcionamiento normal de la lista vinculada.

Otras operaciones relacionadas con la tabla lineal, como calcular la longitud de la tabla, juzgar que está vacía, etc., son relativamente simples de implementar en la tabla secuencial; consulte el siguiente código C# para la lista de enlace simple:

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 y principio de implementación.

El método de implementación y el principio de la lista enlazada individualmente incluyen principalmente los siguientes aspectos:

  1. Nodo: un nodo en una lista enlazada individualmente consta de dos partes, a saber, el campo de datos y el campo de puntero. El campo de datos almacena los elementos de datos del nodo y el campo de puntero apunta al siguiente nodo. Los nodos se pueden representar mediante estructuras.

  2. Nodo principal: el nodo principal es un nodo adicional agregado delante del primer nodo en la lista vinculada. No contiene elementos de datos, pero contiene un puntero al primer nodo. La función del nodo principal es simplificar el funcionamiento de la lista vinculada, haciendo que operaciones como la inserción y eliminación sean más convenientes.

  3. Operación de puntero: la conexión entre nodos en la lista vinculada se realiza mediante punteros. Cada nodo tiene un puntero que apunta al siguiente nodo y la relación del puntero entre nodos debe modificarse al insertar o eliminar nodos.

  4. Atravesar una lista vinculada: Atravesar una lista vinculada se refiere a visitar todos los nodos de la lista vinculada en orden. Al atravesar la lista vinculada, puede leer o modificar el valor de los datos en el nodo, y también puede calcular la longitud de la lista vinculada y encontrar un nodo determinado.

  5. Gestión de memoria: es necesario asignar o liberar espacio de memoria al insertar y eliminar nodos. Para evitar problemas como pérdidas de memoria y liberaciones repetidas, es necesario administrar el espacio de memoria de manera razonable.

  6. Procesamiento de condiciones de límite: algunas condiciones de límite y condiciones anormales deben abordarse al realizar operaciones de lista vinculada, como la lista vinculada está vacía, la posición de inserción está fuera de rango, etc., para garantizar el funcionamiento normal de la lista vinculada.

Comprender el método de implementación y el principio de la lista enlazada individualmente es muy útil para comprender el rendimiento y la optimización de la lista enlazada. Al mismo tiempo, es necesario prestar atención a un diseño y optimización razonables de acuerdo con la situación real en la implementación específica para mejorar la eficiencia y la capacidad de mantenimiento del código.

Escenarios de aplicación y precauciones de uso

La lista enlazada individualmente es una estructura de datos común, que se utiliza a menudo en los siguientes escenarios de aplicación:

  1. Implementar pilas y colas: se pueden utilizar listas enlazadas individualmente para implementar pilas y colas. En una pila, los elementos sólo pueden entrar y salir desde la parte superior de la pila; en una cola, los elementos sólo pueden entrar desde el final de la cola y salir desde el principio de la cola. Estas dos estructuras de datos se pueden implementar fácilmente utilizando las operaciones de inserción de encabezado y de inserción de cola de la lista enlazada individualmente.

  2. Asignación de memoria: en la gestión de la memoria de la computadora, las listas enlazadas individualmente se utilizan a menudo como estructuras de datos para la asignación de memoria dinámica. A través de la conexión de puntero entre los nodos de la lista vinculada, los bloques de memoria se pueden asignar y liberar dinámicamente.

  3. Listas de reproducción de audio y vídeo: las listas enlazadas individualmente se pueden utilizar para implementar listas de reproducción de audio y vídeo. Cada nodo representa un archivo de audio o vídeo y mantiene la posición del siguiente archivo. Al recorrer la lista vinculada, el audio y el video de toda la lista se pueden reproducir secuencialmente.

  4. Encontrar estructuras circulares: las listas enlazadas individualmente también se pueden utilizar para resolver problemas relacionados con estructuras circulares, como juzgar si una lista enlazada tiene un anillo, encontrar la entrada del anillo, etc.

  5. Estrategia de eliminación de caché: en el sistema de caché, cuando el espacio de caché está lleno, es necesario eliminar algunos datos para dejar espacio. Se pueden utilizar listas enlazadas individualmente para mantener elementos de datos en la caché mientras se registra su uso. Cuando es necesario eliminar datos, se puede seleccionar para eliminación el elemento de datos utilizado menos recientemente, es decir, se elimina el nodo al final de la lista enlazada individualmente.

Se debe prestar atención a los siguientes puntos al utilizar una lista enlazada individualmente:

  1. Problema de puntero nulo: es probable que se produzcan problemas de puntero nulo en operaciones de listas vinculadas, como acceder a una lista vinculada vacía o a un nodo que no existe. Para evitar estos problemas, es necesario realizar un juicio nulo sobre los parámetros de entrada.

  2. Problemas de administración de memoria: al insertar y eliminar nodos, es necesario asignar o liberar espacio de memoria. Si la administración no se realiza correctamente, pueden ocurrir problemas como pérdidas de memoria o liberaciones repetidas. Estos problemas se pueden resolver utilizando un mecanismo de recolección de basura o administrando manualmente el espacio de memoria.

  3. Problema de condición de límite: algunas condiciones de límite y condiciones anormales deben abordarse al realizar operaciones de lista vinculada, como la lista vinculada está vacía, la posición de inserción está fuera de rango, etc., para garantizar el funcionamiento normal de la lista vinculada.

  4. Problemas de rendimiento: cuando se trata de datos a gran escala, las listas enlazadas individualmente pueden tener algunos problemas de rendimiento, como una velocidad de acceso aleatorio lenta y una gran sobrecarga de espacio. Por lo tanto, en aplicaciones prácticas, es necesario seleccionar una estructura de datos adecuada de acuerdo con la situación real.

Análisis de algoritmos y complejidad:

  1. Comprender los algoritmos y el análisis de complejidad de las listas enlazadas individualmente, como cómo juzgar si una lista enlazada tiene un anillo, cómo invertir una lista enlazada, cómo fusionar dos listas enlazadas ordenadas, etc. Dominar estos algoritmos puede mejorar sus habilidades de programación y al mismo tiempo profundizar su comprensión de las listas enlazadas individualmente.

El algoritmo de lista individualmente enlazada y el análisis de complejidad incluyen principalmente los siguientes aspectos:

  1. Atravesar una lista vinculada: atravesar una lista vinculada es la operación básica de acceder a todos los nodos en la lista vinculada, que se puede implementar mediante bucles o recursividad. La complejidad temporal es O (n), donde n es la longitud de la lista vinculada.

  2. Encuentre el nodo especificado: busque el nodo especificado en la lista vinculada, puede buscar según la posición del nodo o la palabra clave del nodo. La complejidad temporal de la búsqueda lineal es O (n) y la complejidad temporal de la búsqueda binaria es O (logn).

  3. Insertar nodo: para insertar un nuevo nodo en la lista vinculada, es necesario modificar la relación del puntero entre los nodos. La complejidad del tiempo es O(1) si se inserta al principio o al final; en caso contrario, O(n).

  4. Eliminar nodo: para eliminar un nodo en la lista vinculada, debe modificar la relación del puntero entre los nodos. Si la eliminación es el nodo principal o el nodo final, la complejidad temporal es O (1); de lo contrario, es O (n).

  5. Lista enlazada inversa: para organizar los nodos en la lista enlazada en orden inverso, es necesario modificar la relación del puntero entre los nodos. Se puede implementar mediante iteración o recursividad y la complejidad del tiempo es O (n).

  6. Fusionar lista vinculada: para fusionar dos listas vinculadas ordenadas en una nueva lista vinculada ordenada, debe comparar los nodos de las dos listas vinculadas uno por uno y fusionarlos. La complejidad temporal es O (m+n), donde myn representan las longitudes de las dos listas enlazadas respectivamente.

Cabe señalar que al realizar el algoritmo de lista vinculada, es necesario prestar atención a algunas condiciones límite y condiciones anormales, como que la lista vinculada está vacía, la posición de inserción está fuera de rango, etc. Al mismo tiempo, en la implementación real, se deben realizar un diseño y una optimización razonables de acuerdo con la situación real para mejorar la eficiencia y la mantenibilidad del código.

Comparación con otras estructuras de datos:

  1. Aprenda cómo se comparan las listas enlazadas individualmente con otras estructuras de datos, como matrices, listas doblemente enlazadas, etc. Comprender sus ventajas y desventajas y los escenarios aplicables puede seleccionar mejor la estructura de datos adecuada para resolver problemas prácticos.

La comparación entre la lista enlazada individualmente y otras estructuras de datos incluye principalmente los siguientes aspectos:

  1. Matrices: tanto las matrices como las listas enlazadas individualmente se pueden usar para representar secuencias, pero se implementan de manera diferente. Una matriz es una asignación continua de espacio de almacenamiento en la memoria y se puede acceder directamente a cualquier elemento. La lista enlazada individualmente necesita conectar nodos mediante punteros y solo se puede atravesar de adelante hacia atrás. Dado que cada nodo de la lista enlazada individualmente tiene solo un puntero, la sobrecarga de espacio es pequeña, mientras que la matriz necesita preasignar un cierto tamaño de espacio de memoria y es posible que se desperdicie algo de espacio al insertar o eliminar elementos.

  2. Pilas y colas: las pilas y las colas son dos estructuras de datos comunes que se pueden implementar mediante matrices o listas enlazadas individualmente. Se puede acceder aleatoriamente a las pilas y colas implementadas con matrices, pero es necesario mover otros elementos al insertar o eliminar elementos, y la complejidad del tiempo es O (n). La pila y la cola implementadas por una lista enlazada individualmente pueden realizar operaciones de inserción y eliminación al principio o al final, y la complejidad del tiempo es O (1).

  3. Tabla hash: una tabla hash es una estructura de datos basada en una función hash, que puede encontrar y modificar elementos rápidamente. En la implementación de tablas hash, las matrices generalmente se usan para almacenar elementos y las listas vinculadas u otras estructuras de datos se usan para manejar colisiones hash. La lista enlazada individualmente se puede utilizar como una implementación del almacenamiento encadenado en la tabla hash, pero como solo se puede recorrer de adelante hacia atrás, puede afectar la eficiencia de las consultas de la tabla hash.

  4. Árbol rojo-negro: el árbol rojo-negro es un árbol de búsqueda binario autoequilibrado que puede realizar operaciones rápidas de inserción, eliminación y búsqueda. En comparación con estructuras de datos como listas enlazadas individualmente, los árboles rojo-negro tienen una menor complejidad temporal y admiten funciones avanzadas como búsqueda de rango y operaciones de clasificación. Pero, en consecuencia, también es más complicado de implementar y necesita lidiar con varias operaciones de rotación y coloración.

En resumen, la lista enlazada individualmente es adecuada para representar secuencias y admite operaciones dinámicas de inserción y eliminación con menos espacio adicional. En comparación con las matrices, la complejidad temporal de insertar y eliminar elementos es menor; en comparación con otras estructuras de datos como pilas y colas, es más conveniente realizar operaciones de inserción y eliminación en el medio. Sin embargo, tiene un rendimiento deficiente en operaciones como consultas y clasificación, y no es tan bueno como estructuras de datos avanzadas como los árboles rojo-negro.

PD: Si hay algún error u omisión, corríjame.

Supongo que te gusta

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