Lista doblemente enlazada
1. Breve introducción
El significado de una lista doblemente enlazada es que además de la dirección del siguiente nodo, un nodo debe almacenar adicionalmente la dirección del nodo anterior.
De esta manera, asumiendo que conoce la dirección de uno de los nodos, podemos recorrer fácilmente "hacia adelante" y "hacia atrás" en lugar de solo el recorrido "hacia atrás" para las listas enlazadas individualmente.
Por lo tanto, una lista de enlace doble es más flexible que una lista de enlace único. Tardará un poco menos en recorrer, pero costará espacio de almacenamiento adicional. Algunas preguntas pueden ser escandalosas y "avanzarán y retrocederán", por lo que la lista enlazada individualmente no puede completar estas funciones. La lista de doble enlace es un "espacio para el tiempo" muy típico.
Dos. Diagrama de ideas
2.1 Nodo
Para crear un nodo, aún debemos ser similares a una lista enlazada individualmente, porque debe contener prev
data
next
tres partes. prev
Señale la dirección del nodo anterior, next
señale la dirección del siguiente nodo.
2.2 Estructura de lista vinculada
Debido a la estructura de la lista doblemente enlazada, no puedo simplemente almacenar una 头地址
, tenemos que declarar una 尾地址
. En el caso de recorrido hacia adelante, el límite en el que termina el recorrido es *next = tail
; en el caso de recorrido inverso, el límite en el que termina el recorrido es *prev = head
. Además, para ahorrar espacio, podemos agregar un recuento (para contar la longitud de la lista vinculada). Si lo index
insertamos como un identificador, entonces podemos index
obtener la dirección transversal a través de la longitud y comparación de la lista vinculada .
Por supuesto, si le resulta problemático, no necesita crear una lista vinculada y puede utilizar directamente node
los datos del primer nodo para registrar la longitud de la lista vinculada.Este artículo utiliza el primer método.
2.3 Inicialización de la lista enlazada
Es mejor crear una estructura por separado, que contenga head
*tail
len = 0
tres partes, y asignar valores iniciales.
2.4 recorrido de la lista vinculada
Podemos producir en orden positivo o en orden inverso, pero el "comienzo" y la "dirección del proceso" del recorrido son diferentes.
Será mejor que escribamos una función transversal que pueda determinar automáticamente la dirección, de modo que para las siguientes operaciones, podamos sacar la dirección de cualquier nodo y este código se pueda reutilizar.
2.5 Inserción de lista enlazada
La inserción de una lista doblemente enlazada es muy sencilla, solo necesitamos:
- El nodo a insertar
NewNode
se*prev
cambiaNextNode
a*prev
;*next
cambioLastNode
de*next
. - Luego, cambie
LastNode
la*next
suma del nodo anteriorprev
aNewNode
la dirección del nodo insertado , ¡y listo!
2.6 Eliminación de lista vinculada
Necesitamos modificar los dos nodos que lo rodean:
- Para
LastNode
el*next
cambio de su*next
- Para
NextNode
el*prev
cambio es*prev
por lo que no se puede atravesar, entonces el nodo ha sido borrado
2.7 Modificación / Consulta de la lista vinculada
Esta parte es equivalente al recorrido, solo necesita modificar el valor.
Tres. Implementación del código
#include <stdio.h>
#include <stdlib.h>
#define OK 1
#define ERROR 0
typedef struct dataType{
int data1;
int data2;
}dataType; // 数据类
typedef struct nodeType{
struct nodeType *prev;
struct nodeType *next;
dataType data;
}nodeType; // 节点类
typedef struct listType{
nodeType *head;
nodeType *tail;
int len;
}listType; // 链表类
dataType DEFAULTDATA={
0,0}; // 创建一个默认的类,作为数据结构
nodeType* NewNode(dataType data);
listType* ListInit(void);
nodeType* ListGetNode(listType *list, int index);
int ListShow(listType *list, int way);
int ListInsert(listType *list, dataType data, int index);
int ListDelete(listType *list, int index);
int ListModify(listType* list, dataType data, int index);
int ListQuery(listType* list, dataType data, int way);
int main()
{
listType* list = ListInit();
dataType test = {
1,2};
//主函数可以自己修改
ListInsert(list, test, 0);
test.data1 = 2;
ListInsert(list, test, 1);
test.data1 = 3;
ListInsert(list,test,2);
ListShow(list, 0);
ListModify(list, test, 0);
ListShow(list, 0);
printf("i find in %d\n", ListQuery(list, test, 0));
printf("i find in %d\n", ListQuery(list, test, 1));
return 0;
}
nodeType* NewNode(dataType data)
{
nodeType* node = (nodeType*)malloc(sizeof(nodeType));
if(node != NULL)
{
node->data = data; // 把数据的值赋值给节点
node->prev = NULL; // 并初始化节点的信息
node->next = NULL;
return node;
}
exit(1);
}
listType* ListInit(void)
{
listType* list = (listType*)malloc(sizeof(listType));
if (list != NULL)
{
list->head = NewNode(DEFAULTDATA); // 创建两个节点给头尾
list->tail = NewNode(DEFAULTDATA);
list->head->next = list->tail; // 并将头的地址指向尾
list->tail->prev = list->head; // 尾的地址指向头
list->len = 0; // 初始化长度
return list;
}
exit(1);
}
nodeType* ListGetNode(listType *list, int index)
{
nodeType *begin, *end;
int cont = 0;
if (list->len == 0)
{
return list->tail; // 如果链表为空,那么就返回尾节点
}
else
{
if (index > list->len / 2) // 通过1/2判断从前还是后遍历
{
begin = list->tail;
end = list->head;
cont = list->len;
// 因为len刚好比index序号大一,所以尾节点的序号刚好等于len
// 反之,如果是头节点,那么序号就是 -1
while(begin != end && cont > index) // 遍历的边界就是链表的末尾,或者cont= index
{
begin = begin->prev;
--cont; // 遍历节点与更新当前index
}
return begin; // 此时的链表节点,就是index指向的节点,或者表头
}
else
{
begin = list->head;
end = list->tail;
cont = -1;
// 头节点的序号就是-1
while(begin != end && cont < index) // 遍历的边界就是链表末尾或者cont=index
{
begin = begin->next;
++cont; // 这部分和上面一样
}
return begin;
}
}
}
int ListShow(listType *list, int way)
{
nodeType* begin;
nodeType* end;
int cont = 0;
if (way == 1) //way=1时,从后往前遍历,否则都是从前开始遍历
{
begin = list->tail->prev;
end = list->head;
cont = list->len - 1;
// 此时的开始节点是尾节点的前一个,要么是有效的,要么就是头节点
// 所以开始的节点序号是len-1
while (begin != end)
{
printf("No.%d is %d %d\n", cont,begin->data.data1, begin->data.data2);
begin = begin->prev;
--cont;
} // 遍历,并输出信息
printf("----------------\n");
return OK;
}
else
{
begin = list->head->next;
end = list->tail;
cont = 0;
// 从头节点的后一个节点开始,所以序号因该是0
while (begin != end)
{
printf("No.%d is %d %d\n", cont,begin->data.data1, begin->data.data2);
begin = begin->next;
++cont;
} // 同理 都是遍历
printf("----------------\n");
return OK;
}
}
int ListInsert(listType *list, dataType data, int index)
{
nodeType *newNode = NewNode(data); // 先创建一个节点
nodeType *nextNode = ListGetNode(list, index); // 获得index节点的地址
nodeType *lastNode = nextNode->prev; // 插入节点前的节点地址
if (nextNode->prev != NULL)
{
newNode->prev = nextNode->prev; // 修改插入节点的信息
newNode->next = nextNode;
lastNode->next = newNode; // 更新插入节点的前节点信息
nextNode->prev = newNode; // 更新插入节点的后节点信息
list->len += 1; // 一定不要忘记把长度加1
return OK;
}
else
{
return ERROR;
}
}
int ListDelete(listType* list, int index)
{
nodeType *dNode;
nodeType *nextNode;
nodeType *lastNode;
if (list->len == 0)
{
return ERROR;
}
else
{
dNode = ListGetNode(list, index); // 找到要删除的节点
if (dNode->next == NULL || dNode->prev == NULL)
{
return ERROR; // 确保不是头节点或者尾节点
}
nextNode = dNode->next; // 更新前后节点的信息
lastNode = dNode->prev;
lastNode->next = nextNode;
nextNode->prev = lastNode;
list->len -= 1; // 一定不要忘记要修改链表的长度
return OK;
}
}
int ListModify(listType* list, dataType data, int index)
{
nodeType *thisNode = ListGetNode(list, index); //查找节点
if (thisNode->next != NULL || thisNode->prev != NULL) // 确保不是首尾节点
{
thisNode->data = data;
return OK;
}
else
{
return ERROR;
}
}
int ListQuery(listType* list, dataType data, int way)
{
nodeType *begin;
nodeType *end;
int cont = 0;
if (way == 0)
{
begin = list->head->next; // 创建开始与结束节点
end = list->tail;
cont = 0; // index计数
while(begin != end)
{
if (begin->data.data1 == data.data1 && begin->data.data2 == data.data2)
{
return cont; //找到了
}
else
{
++cont; // 没找到,更新节点信息
begin = begin->next;
}
}
return -1; // 没找到
}
else
{
begin = list->tail->prev;
end = list->head;
cont = list->len - 1;
while(begin != end)
{
if (begin->data.data1 == data.data1 && begin->data.data2 == data.data2)
{
return cont;
}
else
{
--cont;
begin = begin->prev;
}
}
return -1; // 反之同上,只是顺序变化了
}
}