链表之CIRCLEQ

Posted 车子 chezi

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了链表之CIRCLEQ相关的知识,希望对你有一定的参考价值。

文章目录

头文件

头文件来自我最近看的项目:apache-mynewt-core-1.9.0\\sys\\sys\\include\\sys\\queue.h

仅截取部分。

/*
 * Circular queue declarations.
 */
#define	CIRCLEQ_HEAD(name, type)					\\
struct name 								\\
	struct type *cqh_first;		/* first element */		\\
	struct type *cqh_last;		/* last element */		\\


#define	CIRCLEQ_HEAD_INITIALIZER(head)					\\
	 (void *)&(head), (void *)&(head) 

#define	CIRCLEQ_ENTRY(type)						\\
struct 								\\
	struct type *cqe_next;		/* next element */		\\
	struct type *cqe_prev;		/* previous element */		\\


/*
 * Circular queue functions.
 */
#define	CIRCLEQ_EMPTY(head)	((head)->cqh_first == (void *)(head))

#define	CIRCLEQ_FIRST(head)	((head)->cqh_first)

#define	CIRCLEQ_FOREACH(var, head, field)				\\
	for ((var) = CIRCLEQ_FIRST((head));				\\
	    (var) != (void *)(head) || ((var) = NULL);			\\
	    (var) = CIRCLEQ_NEXT((var), field))

#define	CIRCLEQ_FOREACH_REVERSE(var, head, field)			\\
	for ((var) = CIRCLEQ_LAST((head));				\\
	    (var) != (void *)(head) || ((var) = NULL);			\\
	    (var) = CIRCLEQ_PREV((var), field))

#define	CIRCLEQ_INIT(head) do 						\\
	CIRCLEQ_FIRST((head)) = (void *)(head);				\\
	CIRCLEQ_LAST((head)) = (void *)(head);				\\
 while (0)

#define	CIRCLEQ_INSERT_AFTER(head, listelm, elm, field) do 		\\
	CIRCLEQ_NEXT((elm), field) = CIRCLEQ_NEXT((listelm), field);	\\
	CIRCLEQ_PREV((elm), field) = (listelm);				\\
	if (CIRCLEQ_NEXT((listelm), field) == (void *)(head))		\\
		CIRCLEQ_LAST((head)) = (elm);				\\
	else								\\
		CIRCLEQ_PREV(CIRCLEQ_NEXT((listelm), field), field) = (elm);\\
	CIRCLEQ_NEXT((listelm), field) = (elm);				\\
 while (0)

#define	CIRCLEQ_INSERT_BEFORE(head, listelm, elm, field) do 		\\
	CIRCLEQ_NEXT((elm), field) = (listelm);				\\
	CIRCLEQ_PREV((elm), field) = CIRCLEQ_PREV((listelm), field);	\\
	if (CIRCLEQ_PREV((listelm), field) == (void *)(head))		\\
		CIRCLEQ_FIRST((head)) = (elm);				\\
	else								\\
		CIRCLEQ_NEXT(CIRCLEQ_PREV((listelm), field), field) = (elm);\\
	CIRCLEQ_PREV((listelm), field) = (elm);				\\
 while (0)

#define	CIRCLEQ_INSERT_HEAD(head, elm, field) do 			\\
	CIRCLEQ_NEXT((elm), field) = CIRCLEQ_FIRST((head));		\\
	CIRCLEQ_PREV((elm), field) = (void *)(head);			\\
	if (CIRCLEQ_LAST((head)) == (void *)(head))			\\
		CIRCLEQ_LAST((head)) = (elm);				\\
	else								\\
		CIRCLEQ_PREV(CIRCLEQ_FIRST((head)), field) = (elm);	\\
	CIRCLEQ_FIRST((head)) = (elm);					\\
 while (0)

#define	CIRCLEQ_INSERT_TAIL(head, elm, field) do 			\\
	CIRCLEQ_NEXT((elm), field) = (void *)(head);			\\
	CIRCLEQ_PREV((elm), field) = CIRCLEQ_LAST((head));		\\
	if (CIRCLEQ_FIRST((head)) == (void *)(head))			\\
		CIRCLEQ_FIRST((head)) = (elm);				\\
	else								\\
		CIRCLEQ_NEXT(CIRCLEQ_LAST((head)), field) = (elm);	\\
	CIRCLEQ_LAST((head)) = (elm);					\\
 while (0)

#define	CIRCLEQ_LAST(head)	((head)->cqh_last)

#define	CIRCLEQ_NEXT(elm,field)	((elm)->field.cqe_next)

#define	CIRCLEQ_PREV(elm,field)	((elm)->field.cqe_prev)

#define	CIRCLEQ_REMOVE(head, elm, field) do 				\\
	if (CIRCLEQ_NEXT((elm), field) == (void *)(head))		\\
		CIRCLEQ_LAST((head)) = CIRCLEQ_PREV((elm), field);	\\
	else								\\
		CIRCLEQ_PREV(CIRCLEQ_NEXT((elm), field), field) =	\\
		    CIRCLEQ_PREV((elm), field);				\\
	if (CIRCLEQ_PREV((elm), field) == (void *)(head))		\\
		CIRCLEQ_FIRST((head)) = CIRCLEQ_NEXT((elm), field);	\\
	else								\\
		CIRCLEQ_NEXT(CIRCLEQ_PREV((elm), field), field) =	\\
		    CIRCLEQ_NEXT((elm), field);				\\
 while (0)

例子

#include <stddef.h>
#include <stdio.h>
#include <stdlib.h>
#include "bsd_queue.h"  // 就是我上面贴的代码

struct entry 
    int data;
    CIRCLEQ_ENTRY(entry) entries;           /* Queue */
;

CIRCLEQ_HEAD(circlehead, entry);


int
main(void)

    struct entry *n1, *n2, *n3, *np;
    struct circlehead head;                 /* Queue head */
    int i;

    CIRCLEQ_INIT(&head);                    /* Initialize the queue */

    n1 = malloc(sizeof(struct entry));      /* Insert at the head */
    CIRCLEQ_INSERT_HEAD(&head, n1, entries);

    n1 = malloc(sizeof(struct entry));      /* Insert at the tail */
    CIRCLEQ_INSERT_TAIL(&head, n1, entries);

    n2 = malloc(sizeof(struct entry));      /* Insert after */
    CIRCLEQ_INSERT_AFTER(&head, n1, n2, entries);

    n3 = malloc(sizeof(struct entry));      /* Insert before */
    CIRCLEQ_INSERT_BEFORE(&head, n2, n3, entries);

    CIRCLEQ_REMOVE(&head, n2, entries);     /* Deletion */
    free(n2);
                                            /* Forward traversal */
    i = 0;
    CIRCLEQ_FOREACH(np, &head, entries)
        np->data = i++;
                                            /* Reverse traversal */
    CIRCLEQ_FOREACH_REVERSE(np, &head, entries)
        printf("%i\\n", np->data);
                                            /* Queue deletion */
    n1 = CIRCLEQ_FIRST(&head);
    while (n1 != (void *)&head) 
        n2 = CIRCLEQ_NEXT(n1, entries);
        free(n1);
        n1 = n2;
    
    CIRCLEQ_INIT(&head);

    exit(EXIT_SUCCESS);

代码分析

我们一点一点看上面的例子。

先来一张我自己绘制的示意图。红色表示我觉得不太“正常”的地方,指针的类型不匹配,需要强制转换。

CIRCLEQ_ENTRY 和 CIRCLEQ_HEAD

struct entry 
    int data;
    CIRCLEQ_ENTRY(entry) entries;           /* Queue */
;

CIRCLEQ_HEAD(circlehead, entry);

宏展开是:

struct entry 
    int data;
    struct  
        struct entry *cqe_next; 
        struct entry *cqe_prev; 
     entries;
;
struct circlehead  
    struct entry *cqh_first; 
    struct entry *cqh_last; 
;

和其他链表不同,这里没有用二级指针,cqe_prev 和 cqh_last 都是 struct entry * 类型。(为什么呢)

我前面写的文章都是研究宏展开后的代码,这次我们换一个思路,直接看宏的定义

CIRCLEQ_HEAD_INITIALIZER

#define	CIRCLEQ_HEAD_INITIALIZER(head)					\\
	 (void *)&(head), (void *)&(head) 

初始化一个链表后,表头的两个指针都指向自己。

CIRCLEQ_FIRST 和 CIRCLEQ_LAST

#define	CIRCLEQ_FIRST(head)	((head)->cqh_first)
#define	CIRCLEQ_LAST(head)	((head)->cqh_last)

这两个很好理解,不解释了。

CIRCLEQ_PREV 和 CIRCLEQ_NEXT

#define	CIRCLEQ_PREV(elm,field)	((elm)->field.cqe_prev)
#define	CIRCLEQ_NEXT(elm,field)	((elm)->field.cqe_next)

field 就是最开始定义的 entries

CIRCLEQ_INIT

#define	CIRCLEQ_INIT(head) do 						\\
	CIRCLEQ_FIRST((head)) = (void *)(head);				\\
	CIRCLEQ_LAST((head)) = (void *)(head);				\\
 while (0)

初始化一个链表后,表头的两个指针都指向自己。如下图

CIRCLEQ_EMPTY

#define	CIRCLEQ_EMPTY(head)	((head)->cqh_first == (void *)(head))

CIRCLEQ_INSERT_TAIL

#define	CIRCLEQ_INSERT_TAIL(head, elm, field) do 			\\
	CIRCLEQ_NEXT((elm), field) = (void *)(head);			\\
	CIRCLEQ_PREV((elm), field) = CIRCLEQ_LAST((head));		\\
	if (CIRCLEQ_FIRST((head)) == (void *)(head))			\\
		CIRCLEQ_FIRST((head)) = (elm);				\\
	else								\\
		CIRCLEQ_NEXT(CIRCLEQ_LAST((head)), field) = (elm);	\\
	CIRCLEQ_LAST((head)) = (elm);					\\
 while (0)

把 elm 插入到链表的尾部,都需要改变哪些指针呢?

  1. elm 的 field.cqe_next,因为它是最后一个,所以它应该等于 (void *)(head)
  2. elm 的 field.cqe_prev
  3. 插入后,elm 前面的那个节点的 field.cqe_next(如果前面的节点是表头,那就修改CIRCLEQ_FIRST(head)
  4. 修改 (head)->cqh_last 指向 elm

代码第 2、3 行分别解决了 1、2

代码第 4、5 行解决了 3 的括号中的情况

第 7 行解决了 3,elm 前面的那个节点就是原来最后的节点,CIRCLEQ_LAST(head),它的 cqe_next 就是 CIRCLEQ_NEXT(CIRCLEQ_LAST((head)), field)

第 8 行解决了 4

CIRCLEQ_INSERT_HEAD

#define	CIRCLEQ_INSERT_HEAD(head, elm, field) do 			\\
	CIRCLEQ_NEXT((elm), field) = CIRCLEQ_FIRST((head));		\\
	CIRCLEQ_PREV((elm), field) = (void *)(head);			\\
	if (CIRCLEQ_LAST((head)) == (void *)(head))			\\
		CIRCLEQ_LAST((head)) = (elm);				\\
	else								\\
		CIRCLEQ_PREV(CIRCLEQ_FIRST((head)), field) = (elm);	\\
	CIRCLEQ_FIRST((head)) = (elm);					\\
 while (0)

把 elm 插入到链表的头部,都需要改变哪些指针呢?

  1. elm 的 field.cqe_next
  2. elm 的 field.cqe_prev,它应该等于 (void *)(head)
  3. (head)->cqh_first
  4. 插入后,elm 后面的那个节点的 field.cqe_prev(如果插入 elm 前链表为空,那就修改CIRCLEQ_LAST(head)

第 2 行解决了 1

第 3 行解决了 2

第 4-5 行解决了 4 的括号中的情况

第 7 行解决了 4

第 8 行解决了 3

代码 4-7 行说明,因为没有用二级指针,所以头插的时候需要分情况考虑。但是不用二级指针也有好处,就是代码更好理解。

CIRCLEQ_INSERT_AFTER

#define	CIRCLEQ_INSERT_AFTER(head, listelm, elm, field) do 		\\
	CIRCLEQ_NEXT((elm), field) = CIRCLEQ_NEXT((listelm), field);	\\
	CIRCLEQ_PREV((elm), field) = (listelm);				\\
	if (CIRCLEQ_NEXT((listelm), field) == (void *)(head))		\\
		CIRCLEQ_LAST((head)) = (elm);				\\
	else								\\
		CIRCLEQ_PREV(CIRCLEQ_NEXT((listelm), field), field) = (elm);\\
	CIRCLEQ_NEXT((listelm), field) = (elm);				\\
 while (0)

要把 elm 插入到 listelm 元素的后面,要修改哪些指针呢?

  1. elm 的 field.cqe_next
  2. elm 的 field.cqe_prev
  3. listelm 的 field.cqe_next
  4. 插入之前,listelm 后面的那个节点的 field.cqe_prev(如果 listelm 后面没有节点了,就修改 CIRCLEQ_LAST(head)

2-3 行分别解决了 1、2

4-5 行解决了 4 括号里的情况

第 7 行解决了 4

第 8 行解决了 3

CIRCLEQ_INSERT_BEFORE

#define	CIRCLEQ_INSERT_BEFORE(head, listelm, elm, field) do 		\\
	CIRCLEQ_NEXT((elm), field) = (listelm);				\\
	CIRCLEQ_PREV((elm), field) = CIRCLEQ_PREV((listelm), field);	\\
	if (CIRCLEQ_PREV((listelm), field) == (void *)(head))		\\
		CIRCLEQ_FIRST((head)) = (elm);				\\
	else								\\
		CIRCLEQ_NEXT(CIRCLEQ_PREV((listelm), field), field) = (elm);\\
	CIRCLEQ_PREV((listelm), field) = (elm);				\\
 while (0)

要把 elm 插入到 listelm 元素的前面,要修改哪些指针呢?

  1. elm 的 field.cqe_next
  2. elm 的 field.cqe_prev
  3. listelm 的 field.cqe_prev
  4. 插入之前,listelm 前面的那个节点的 field.cqe_next(如果 listelm 前面是表头,就修改 CIRCLEQ_FIRST(head)

2-3 行分别解决了 1、2

4-5 行解决了 4 括号里的情况

第 7 行解决了 4

第 8 行解决了 3

CIRCLEQ_FOREACH

#define	CIRCLEQ_FOREACH(var, head, field)				\\
	for ((var) = CIRCLEQ_FIRST((head));				\\
	    (var) != (void *)(head) || ((var) = NULL);			\\
	    (var) = CIRCLEQ_NEXT((var), field))

顺序遍历,只要 (var) != (void *)(head) 就会往下进行,如果等于 head,根据 || 运算符的求值规则,会执行 (var) = NULL,这个表达式的结果为 false,于是终止 for 循环

CIRCLEQ_FOREACH_REVERSE

#define	CIRCLEQ_FOREACH_REVERSE(var, head, field)			\\
	for ((var) = CIRCLEQ_LAST((head));				\\
	    (var) != (void *)(head) || ((var) = NULL);			\\
	    (var) = CIRCLEQ_PREV((var), field))

逆序遍历,和顺序遍历类似,不解释了。

CIRCLEQ_REMOVE

#define	CIRCLEQ_REMOVE(head, elm, field) do 				\\
	if (CIRCLEQ_NEXT((elm), field) == (void *)(head))		\\
		CIRCLEQ_LAST((head)) = CIRCLEQ_PREV((elm), field);	\\
	else								\\
		CIRCLEQ_PREV(CIRCLEQ_NEXT((elm), field), field) =	\\
		    CIRCLEQ_PREV((elm), field);				\\
	if (CIRCLEQ_PREV((elm), field) == (void *)(head))		\\
		CIRCLEQ_FIRST((head)) = CIRCLEQ_NEXT((elm), field);	\\
	else								\\
		CIRCLEQ_NEXT(CIRCLEQ_PREV((elm), field), field) =	\\
		    CIRCLEQ_NEXT((elm), field);				\\
 while (0)

移除节点 elm,需要修改那些指针呢?

  1. elm 前面那个节点的 field.cqe_next

  2. elm 后面那个节点的 field.cqe_prev

  3. 对于 1,如果 elm 前面的节点是 head,那就需要修改 CIRCLEQ_FIRST(head)

  4. 对于 2, 如果 elm 后面没有节点了,那就需要修改 CIRCLEQ_LAST(head)

代码 2-3 行解决了 4

5-6 行解决了 2

代码 7-8 行解决了 3

10-11 解决了 1

【end】

以上是关于链表之CIRCLEQ的主要内容,如果未能解决你的问题,请参考以下文章

链表之CIRCLEQ

数据结构线性表之双向带头循环链表

数据结构与算法-线性表之循环链表

数据结构线性表之实现单循环链表

数据结构(线性表之单链表)

线性链表之顺序表