双向链表
- **概念:**双向链表也叫双链表,是链表的一种,是在操作系统中常用的数据结构,它的每个数据结点中都有两个指针,分别指向直接后继和直接前驱,其头指针
head 是唯一确定的。
————所以,从双向链表中的任意一个结点开始,都可以很方便地访问它的前驱结点和后继结点,这种数据结构形式使得双向链表在查找时更加方便,特别是大量数据的遍历。由于双向链表具有对称性,能方便地完成各种插入、删除等操作,但需要注意前后方向的操作。 - 出现意义: 循环单链表的出现,虽然能够实现从任一结点出发沿着链能找到其前驱结点,但时间耗费是O(n)。如果希望从表中快速确定某一个结点的前驱,另一个解决方法就是在单链表的每个结点里再增加一个指向其前驱的指针域prior。这样形成的链表中就有两条方向不同的链,我们可称之为双(向)链表(Double
Linked List)。
双向链表结构和单向链表的区别:最后一个结点的链接地址上,单向链表是null,而双向链表是表头的链接地址。
双链表的结构定义如下:
typedef struct DNode
{
ElemType data;
struct DNode *prior,*next;
}DNode,*DoubleList;
同时双向链表也可以有循环表,称为双向循环链表
————————————————
参考:
原文链接:https://blog.csdn.net/blacklord/article/details/1755007
定义链表节点数据类型 :
1 struct rt_list_node
2 {
3 struct rt_list_node *next; /* 指向后一个节点 */
4 struct rt_list_node *prev; /* 指向前一个节点 */
5 };
6 typedef struct rt_list_node rt_list_t;
rt_list_t 类型的节点里面有两个 rt_list_t 类型的节点指针 next 和 prev,分别用来指向链表中的下一个节点和上一个节点。
由 rt_list_t 类型的节点构成的双向链表示意图具体见图
此图让我瞬间明白,我之前思考的双向链表都是错的
(下图是一个双向链表的插入示意图),只要看上面的定义
一、 初始化链表节点
rt_list_t 类型的节点的初始化,就是将节点里面的 next 和 prev 这两个节点指针指向节点本身
1 rt_inline void rt_list_init(rt_list_t *l)
2 {
3 l->next = l->prev = l;
4 }
例子:
初始化完成后可检查下链表初始化是否成功?即判断链表是不是空的就行了,因为初始化完成的时候,链表肯定是空的,注意,在初始化链表的时候其实链表就是链表头,需要申请内存
1 head = rt_malloc(sizeof(rt_list_t));/* 申请动态内存 */
2 if (RT_NULL == head) /* 没有申请成功 */
3 rt_kprintf("动态内存申请失败!\n");
4 else
5 rt_kprintf("动态内存申请成功,头结点地址为%d!\n",head);
6
7 rt_kprintf("\n 双向链表初始化中......\n");
8 rt_list_init(head);
9 if (rt_list_isempty(head))
10 rt_kprintf("双向链表初始化成功!\n\n");
二、在双向链表表头后面插入一个节点 :
处理分为 4 步:
1 /* 在双向链表头部插入一个节点 */
2 rt_inline void rt_list_insert_after(rt_list_t *l, rt_list_t *n)
3 {
4 l->next->prev = n; /* 第 ① 步 */
5 n->next = l->next; /* 第 ② 步 */
6
7 l->next = n; /* 第 ③ 步 */
8 n->prev = l; /* 第 ④ 步 */
9 }
将要插入的节点设为N,插入位置之前的的地址设置为L,其原来L的后一个节点为L->next。
刚刚开始看这块有点懵,通过过程示意图,加上自己画图处理,清楚很多。详细介绍自己的理解:
第一步:先将L原来的后一个节点为L->next的prev 改为指向N的链表:l->next->prev=n;
第二步:将N的next变为原先的L指向->next。(先将旧的替换,才能换新的)。
第三步:将新的L的next指向n
第四步:将N的prev指向L
三、在双向链表表头前面插入一个节点 ||在双向链表尾部插入一个节点
分为四步
1 rt_inline void rt_list_insert_before(rt_list_t *l, rt_list_t *n)
2 {
3 l->prev->next = n; /* 第 ① 步 */
4 n->prev = l->prev; /* 第 ② 步 */
5
6 l->prev = n; /* 第 ③ 步 */
7 n->next = l; /* 第 ④ 步 */
8 }
设置:将列表头为L,L前一个节点为L->prev,新加入节点为N。
第一步:先将前一个节点为L->prev的next指向N
第二步:将N的prev 等于原先L->prev的被指向。(先将L原先由头指向尾的prev先替换给N)
第三步:原先指向L的prev已经给N了,所以更新L的prev指向——将L的prev指向N
第四步:将N的next指向L。
例子:
1 /* 插入节点:顺序插入与从末尾插入 */
2
3 rt_kprintf("添加节点和尾节点添加......\n");
4
5 /* 动态申请第一个结点的内存 */
6 node1 = rt_malloc(sizeof(rt_list_t));
7
8 /* 动态申请第二个结点的内存 */
9 node2 = rt_malloc(sizeof(rt_list_t));
10
11 rt_kprintf("添加第一个节点与第二个节点.....\n");
12
13 /* 因为这是在某个节点后面添加一个节点函数
14 为后面的 rt_list_insert_before(某个节点之前)
15 添加节点做铺垫,两个函数添加完之后的顺序是
16 head -> node1 -> node2 */
17
18 rt_list_insert_after(head,node2);
19
20 rt_list_insert_before(node2,node1);
21
22 if ((node1->prev == head) && (node2->prev == node1))
23 rt_kprintf("添加节点成功!\n\n");
24 else
25 rt_kprintf("添加节点失败!\n\n");
四、从双向链表删除一个节点
分为三步:
1 rt_inline void rt_list_remove(rt_list_t *n)
2 {
3 n->next->prev = n->prev; /* 第 ① 步 */
4 n->prev->next = n->next; /* 第 ② 步 */
5
6 n->next = n->prev = n; /* 第 ③ 步 */
7 }
要注意的是:将要删除的节点命名为N,那N的前一个结点为N->prev ,后一个节点为N->next
第一步:因为要删除N,所以要将N的prev和next更新,重新定向。将N下一个节点(N->next)的prev设为N原来指向的prev…
第二步:将N的前一个结点(N->prev)的next指向N的next
第三步:将删除的N的prev和next都指向自己N
例子:
1 rt_kprintf("删除节点......\n"); /* 删除已有节点 */
2 rt_list_remove(node1);
3 rt_free(node1);/* 释放第一个节点的内存 */
4 if (node2->prev == head)
5 rt_kprintf("删除节点成功\n\n");