redis-数据类型-链表

66 阅读3分钟

redis-数据类型-链表

参考链接

redis的链表的是一个 无环双端链表

数据结构

链表节点结构

节点之前通过的,前后指针关联起来,构成双端链表,即可从头到尾遍历,也可从尾到头遍历。

typedef struct listNode {
    struct listNode *prev;
    struct listNode *next;
    void *value;
} listNode;

image.png

链表整体结构

typedef struct list {
    listNode *head; // 指向链表的头部节点
    listNode *tail; // 指向链表的尾部节点
    unsigned long len; // 链表的长度
  
    void *(*dup)(void *ptr); // 复制链表节点保证的值
    void (*free)(void *ptr); // 释放链表节点保存的值 
    int (*match)(void *ptr, void *key); // 对比链表节点所保存的值 和 另一个输入值是否相等。

} list;

image.png

重点函数

学习一下,双链表的操作

  • 头插法 listAddNodeHead
list *listAddNodeHead(list *list, void *value)
{
    listNode *node;

    if ((node = zmalloc(sizeof(*node))) == NULL)
        return NULL;
    node->value = value;
  
  	// 如果是首次插入的节点,则头尾都指向该节点
    // 设置 node prev 和 next都为NULL 
    if (list->len == 0) {
        list->head = list->tail = node;
        node->prev = node->next = NULL;
    } else {
      	
        node->prev = NULL; // 设置 新头结点的node->prev=NULL
        node->next = list->head; // 新增node->next 指向 链表的头节点
        list->head->prev = node; // 链表头结点list->head->prev = node(新增节点)
        list->head = node; // 重新设置 list->head = node  头部节点
    }
    list->len++;
    return list;
}
  • 尾插法 listAddNodeTail
list *listAddNodeTail(list *list, void *value)
{
    listNode *node;

    if ((node = zmalloc(sizeof(*node))) == NULL)
        return NULL;
    node->value = value;
    if (list->len == 0) {
        list->head = list->tail = node;
        node->prev = node->next = NULL;
    } else {
        node->prev = list->tail; // 新node尾部节点->prev = list->tail 链表的尾部
        node->next = NULL; // 新node尾部节点next =NULL
        list->tail->next = node; // 链表尾部节点 next = 新node尾部节点
        list->tail = node; // 重新设置 list->tail = node 尾部节点
    }
    list->len++;
    return list;
}
  • 插入到某个节点后面
list *listInsertNode(list *list, listNode *old_node, void *value, int after) {
    listNode *node;

    if ((node = zmalloc(sizeof(*node))) == NULL)
        return NULL;
    node->value = value;
    if (after) { // 是否插入到 old_node的后面
        node->prev = old_node; // 设置新 插入节点的指向关系
        node->next = old_node->next;
        if (list->tail == old_node) { // 如果 old_node是刚好是为尾部节点,则重新设置尾部节点
            list->tail = node;
        }
    } else { // 插入到 old_node的 前面。
        node->next = old_node; // 设置新插入节点指向关系
        node->prev = old_node->prev; 
        if (list->head == old_node) { // 如果 old_node刚好头部节点,则重新设置 头部节点
            list->head = node;
        }
    }
  
    if (node->prev != NULL) { // 非头节点 则设置指向新的 新插入的节点
        node->prev->next = node;
    } 
    if (node->next != NULL) { // 非尾节点
        node->next->prev = node;
    }
    list->len++;
    return list;
}
  • 删除 listDelNode
void listDelNode(list *list, listNode *node)
{
  	
  	// 可能是头结点
    if (node->prev) // 删除的前一个节点!=NULL
        node->prev->next = node->next; // 删除节点的前一个节点的next  = 要删除节点的下一个节点 
    else
        list->head = node->next; // 删除的节点是头节点,则重新设置头节点,list->head = 删除节点的下一个节点
  	
  	// 可能是尾节点
    if (node->next) // 删除的下一个节点!=NULL
        node->next->prev = node->prev; // 删除节点下一个节点的prev = 删除节点的prev
    else
        list->tail = node->prev; // 删除的节点是尾节点,则重新设置为尾节点,list->tail = 删除节点的前一个节点。
    if (list->free) list->free(node->value);
    zfree(node);
    list->len--;
}
  • 获取指定小标的元素 listIndex
listNode *listIndex(list *list, long index) {
    listNode *n;

    if (index < 0) {
        index = (-index)-1; // 这样设计真的是 666,佩服
        n = list->tail;
        while(index-- && n) n = n->prev; // 当节点为NULL 或者 index为0 则返回
    } else {
        n = list->head;
        while(index-- && n) n = n->next;
    }
    return n;
}

总结

  • 无环双端链表:

链表节点有 前后指针,获取模个节点的前置节点和后置节点复杂度O(1)

链表 头节点prev 和 尾节点tail 都指向NULL,对链表的访问一NULL为终点。

  • 链表长度

list结构内部维护一个 len的字段,所以获取长度为 O(1)复杂度

  • 表头指针和表尾指针

list内部 有head 和tail 指针,分别指向链表的 头节点和 尾节点,则获取头尾节点的复杂度为O(1)