Doubly linked list
1. Brief introduction
The meaning of a doubly linked list is that in addition to the address of the next node, a node must additionally store the address of the previous node.
In this way, assuming you know the address of one of the nodes, we can easily traverse "forward" and "backward" instead of only "backward" traversal for singly linked lists.
Therefore, a double-linked list is more flexible than a singly-linked list. It will take slightly less time to traverse, but it will cost extra storage space. Some questions may be outrageous and will "advance and retreat", so the singly linked list cannot complete these functions. Double-linked list is a very typical "space for time".
Two. Idea diagram
2.1 Node
For creating a node, we must still be similar to a singly linked list, because it should contain prev
data
next
three parts. prev
Point to the address of the previous node, next
point to the address of the next node.
2.2 Linked list structure
Because of the structure of the doubly linked list, I can't just store one 头地址
, we have to declare one 尾地址
. In the case of forward traversal, the boundary at which the traversal ends is *next = tail
; in the case of reverse traversal, the boundary at which the traversal ends is *prev = head
. Moreover, in order to save space, we can add a count (to count the length of the linked list). If we index
insert it as an identifier, then we can index
get the traversal direction through the length and comparison of the linked list .
Of course, if you find it troublesome, you don't need to create a linked list, and you can directly use node
the data of the first node to record the length of the linked list. This article uses the first method.
2.3 Initialization of the linked list
It is best to create a structure separately, which contains head
*tail
len = 0
three parts, and assign initial values.
2.4 traversal of linked list
We can output in positive order or in reverse order, but the "beginning" and "process direction" of the traversal are different.
We'd better write a traversal function that can automatically determine the direction, so that for the following operations, we can take out the address of any node, and this code can be reused.
2.5 Insertion of linked list
The insertion of a doubly linked list is very simple, we only need to:
- The node to be inserted
NewNode
is*prev
changedNextNode
to*prev
;*next
changeLastNode
of*next
. - Then change
LastNode
the*next
sum of the previous nodeprev
toNewNode
the address of the inserted node , and you are done!
2.6 Deletion of linked list
We need to modify the two nodes around it:
- To
LastNode
the*next
change of its*next
- To
NextNode
the*prev
change it*prev
so that you can not traverse it, then the node has been deleted
2.7 Modification/Query of Linked List
This part is equivalent to traversal, you only need to modify the value.
Three. Code implementation
#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; // 反之同上,只是顺序变化了
}
}