一.简述
双向链表的含义就是,一个节点除了下一个节点的地址,还要额外储存上一个节点的地址。
这样,假设你知道其中一个节点的地址的话,我们可以很方便的“向前”和“向后”遍历,而不是单链表的只能“向后”遍历。
所以,双链表比单链表更灵活,遍历的话,会花稍微更少的时间,但是会额外花费的储存空间。有些题可能比较离谱,会“进进退退”,所以单链表也无法完成这些功能。双链表是很典型的“空间换时间”。
二.思路图解
2.1 节点
对于创建一个节点,我们肯定还是类似于单链表,它因该包含prev
data
next
三个部分。prev
指向上一个节点的地址,next
指向下一个节点的地址。
2.2 链表结构
因为双向链表的结构,所以我不能只存放一个头地址
,我们还得声明一个尾地址
。正向遍历的话,遍历结束的边界是*next = tail
;反向遍历的话,遍历结束的边界是*prev = head
。而且,为了节约空间,我们可以加一个计数(统计链表长度),假设我们通过index
作为标示符插入,那么我们就可以通过链表长度和index
对比,得出遍历方向。
当然,你嫌麻烦,可以不创建链表这个类,直接用首节点的node
的数据来记录链表长也行,本文采用第一种方法。
2.3 链表的初始化
最好是单独创建一个结构体,包含head
*tail
len = 0
三个部分,并赋上初值。
2.4 链表的遍历
我们可以正序输出,也可以倒序输出,就只是遍历的"开头"和"过程方向"不同。
我们最好写出一个能够自动判断方向的遍历函数,这样对后面的操作,我们可以取出任意一个节点的地址,这段代码可以被复用。
2.5 链表的插入
双向链表的插入很简单,我们只要:
- 把待插入节点
NewNode
的*prev
改成NextNode
的*prev
;*next
改成LastNode
的*next
。 - 然后把前节点
LastNode
的*next
和改成插入节点的prev
改成NewNode
的地址,就完成了!
2.6 链表的删除
我们需要对它周围的两个节点都进行修改:
- 把
LastNode
的*next
改成它的*next
- 把
NextNode
的*prev
改成它的*prev
这样就遍历不到它了,然后节点就被删除了
2.7 链表的修改/查询
这一部分等同于遍历,只需要修改值就可以了。
三.代码实现
#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; // 反之同上,只是顺序变化了
}
}