先简单聊聊
上次学习了单向链表的相关内容。这次主要研究一下双向链表。
在单向链表中,每次找到一个结点,都要经历一个循环。如果在找到当前结点后,想要找到前面的结点,还要重新进行一次循环。
而双向链表中,是在单向链表的基础上,增加了一个指向前一个结点的指针。这样,通过空间换时间的方式,在很多操作中,会大幅的提高操作链表的效率。
下面我们开始具体聊聊双向链表。
1.相关概念
上篇文章单向链表,没有介绍相关链表的概念。个人感觉,单向的比较简单。但在双向链表上,前面已经提到了,新增了指向钱一个结点的指针,操作起来要比单向链表复杂了一些,尤其在双向循环链表中,如果头脑中没有一个相对来说的图形概念,很容易把自己绕晕。我在这篇文章把相关的概念补充补充。
1.1链表结点
1.1.1单向链表结点
单向量表结点包含两个域:数据域和指针域
- 数据域:顾名思义,用来存放数据的区域。具体类型,根据需求来定,下面的代码中,使用的是int类型。
- 指针域:用来指向下一个结点。里面存放的是地址,所以是指针类型。
1.1.2双向链表结点
与单向链表结点相比,多了一个指针域。也就是说,它有一个数据域和两个指针域。下面图片中data是数据域,指针域next和prior,分别指向后面结点的地址前面结点的地址。
1.2 链表
1.2.1 单向链表
单向链表是按照前一个结点指向后一个结点的方式,依次将结点连接起来的一种表现形式。
- 首元结点:链表的第一个结点,该结点没有其他结点的指针域指向。
- 尾结点:链表的最后一个结点,该结点的指针域指向NULL。
- 中间(普通)结点:该结点即有其它结点的指针域指向自由,又有自己的指针域指向其他结点。
如此细分的原因:当进行链表操作时(插入、删除、改、查),不同类型的结点处理方式不同。比如插入一个结点到单向量表中,插入的结点用node表示:
- 插入首元结点位置:node的next指向原首元结点,链表的首指针指向新结点即新的首元结点。
- 插入尾结点位置:原尾结点指向node,node的next指向NULL。
- 插入中间结点位置:
- 先找到要插入位置的前一个结点pre
- node的next指向pre的next
- pre的next指向node
文字有点苍白,还是图来的直观点
1.2.2 单向循环链表
1.2.3 双向链表
和单向链表相比,双向链表的结点中多了一个指向前结点的指针域。直接看图,比较直观一些
1.2.4 双向循环链表
双向循环链表通过前面的概念,看看图也是很好理解的。
1.3 头结点
- 什么是头结点?
在首元结点之前增加一个头结点,用来指向链表起始点。
- 头结点的好处
- 便于首元结点处理,有了头结点,首元结点就可以看做是普通结点,代码逻辑来说,少了一些逻辑判断。
- 便于空表和非空表的统一处理
- 头结点的数据域可以记录一些链表相关数据,比如记录链表的长度等等。
- 当你自己尝试的写一些链表操作代码时,你会爱上头结点的,哈哈。所以一下代码都会带有头结点。
p.s. 不信?你可以自己写写不带头结点的链表增删改查,你就知道有多恶心了!!!
2.相关代码
2.1单向链表
单向链表相关代码实现,请参考上篇文章20200401-数据结构-单向链表
2.2双向链表
我多说了,开始上代码。以下代码的主要目录流程
初始化-打印-增-删-改-查
准备代码
#define OK 1
#define ERROR 0
#define TRUE 1
#define FALSE 0
typedef int ElemType;
typedef int Status;
typedef struct Node * LinkList;
typedef struct Node{
ElemType data;
LinkList prior;
LinkList next;
}Node;
双向链表-非循环
初始化
Status createList(LinkList *list, int length) {
*list = (LinkList)malloc(sizeof(Node));
if (!*list) {
return ERROR;
}
LinkList p = *list;
p->data = -1;
p->prior = NULL;
p->next = NULL;
for (int i = 1; i <= length; i++) {
LinkList node = (LinkList)malloc(sizeof(Node));
if (!node) {
return ERROR;
}
node->data = i;
node->prior = NULL;
node->next = NULL;
p->next = node;
node->prior = p;
p = p->next;
}
return OK;
}
打印链表
Status displayList(LinkList list) {
if (list == NULL) {
printf("空链表不打印");
return ERROR;
}
LinkList p = list->next;
while (p) {
printf("%5d", p->data);
p = p->next;
}
printf("\n");
return OK;
}
增加元素
Status insertNode(LinkList *list, int pos, ElemType value) {
if (pos < 1) {
return ERROR;
}
if ((*list)->next == NULL) {
return ERROR;
}
LinkList p = (*list);
int i;
for (i = 1; i < pos && p; i++) {
p = p->next;
}
if (p == NULL) {
printf("插入位置大于链表长度\n");
return ERROR;
}
LinkList node = (LinkList)malloc(sizeof(Node));
if (!node) {
return ERROR;
}
node->data = value;
node->prior = NULL;
node->next = NULL;
if (p->next == NULL) {
p->next = node;
node->prior = p;
} else {
p->next->prior = node;
node->next = p->next;
node->prior = p;
p->next = node;
}
return OK;
}
删除元素
//根据指定位置 删除结点
Status listDelPos(LinkList *list, int pos, ElemType *delData) {
if (pos < 1) {
return ERROR;
}
LinkList p = *list;
if (p->next == NULL) {
return ERROR;
}
int i;
for (i = 1; i <= pos && p; i++) {
p = p->next;
}
if (!p) {
return ERROR;
}
*delData = p->data;
if (p->next == NULL) {
p->prior->next = NULL;
} else {
p->prior->next = p->next;
p->next->prior = p->prior;
}
free(p);
return OK;
}
//根据value 删除结点
Status listDelVal(LinkList *list, ElemType value) {
LinkList p = *list;
int ret = ERROR;
while (p->next) {
p = p->next;
if (value == p->data) {
p->prior->next = p->next;
if (p->next) {
p->next->prior = p->prior;
}
ret = OK;
break;
}
}
return ret;
}
修改元素
Status updateListIndexNode(LinkList *list, int index, ElemType elem) {
LinkList p = (*list)->next;
for (int i = 1; i < index && p; i++) {
p = p->next;
}
if (p) {
p->data = elem;
} else {
return ERROR;
}
return OK;
}
查找元素
int listSelectElem(LinkList list, ElemType elem) {
int index = 1;
LinkList p = list->next;
while (p) {
if (p->data == elem) {
return index;
}
p = p->next;
index++;
}
return -1;
}
双向循环链表链表
初始化
Status createList(LinkList *list) {
*list = (LinkList)malloc(sizeof(Node));
if (!*list) {
return ERROR;
}
LinkList p = *list;
p->data = -1;
p->prior = NULL;
p->next = NULL;
for (int i = 1; i <= 5; i++) {
LinkList node = (LinkList)malloc(sizeof(Node));
if (!node) {
return ERROR;
}
node->data = i;
node->next = *list;
node->prior = p;
p->next = node;
(*list)->prior = node;
p = p->next;
}
return OK;
}
打印链表
Status displayList(LinkList list) {
if (list == NULL) {
return ERROR;
}
LinkList p = list->next;
while (p != list) {
printf("%5d", p->data);
p = p->next;
}
printf("\n");
return OK;
}
增加元素
Status insertValue(LinkList *list, int pos, ElemType value) {
LinkList p = *list;
if (p == NULL) {
return ERROR;
}
int i = 1;
while (i < pos && p->next != *list) {
p = p->next;
i++;
}
if (i > pos) {
return ERROR;
}
LinkList node = (LinkList)malloc(sizeof(Node));
if (node == NULL) {
return ERROR;
}
node->data = value;
node->prior = p;
node->next = p->next;
node->next->prior = node;
p->next = node;
return OK;
}
删除元素
Status delValue(LinkList *list, int pos, ElemType *e) {
if (*list == NULL) {
return ERROR;
}
LinkList p = (*list)->next;
int i = 1;
while (i < pos && p->next != *list) {
p = p->next;
i++;
}
if (i != pos && p->next == *list) {
printf("要删除的位置大于链表的长度");
return ERROR;
}
*e = p->data;
p->next->prior = p->prior;
p->prior->next = p->next;
free(p);
if ((*list)->next == NULL) {
free(*list);
*list = NULL;
return OK;
}
return OK;
}
3.总结
以上代码可以有些边界值考虑不周到的地方,大家根据具体需求进行修改吧。链表的操作不要去背代码,而要去理解。想不通的地方,可以在纸上多画画。本人博客新手,有不好的地方,还请大家指出,我会学着去修改。
谢谢!!!
还是用那句话收尾:沿途的风景要比目的地更弯的否!!!