Redis list实现原理 - 双向循环链表
双向链表
双向表示每个节点知道自己的直接前驱和直接后继,每个节点需要三个域
查找方向可以是从左往右也可以是从右往左,但是要实现从右往左还需要终端节点的地址,所以通常会设计成双向的循环链表;
双向的循环链表
循环链表指得是终端节点的next指向head节点,head的prior指向终端节点
若链表为空 则head的next和prior都是head自己
与普通链表不同之处就在于可以根据要查找的位置来决定遍历方向从而降低遍历次数,当要查找的数据在两端时效率更优
也可以实现redis中list类型可以从两端插入或取值
c语言实现:
#include <stdio.h>
#include <stdlib.h>
//定义节点结构
typedef struct Node {
struct Node *next, *prior;
int data, length;
} Node, *RDLinkList;
//初始化链表
RDLinkList initialLink() {
Node *head = malloc(sizeof(Node));
head->next = head; //next和prior都指向自身
head->prior = head;
head->length = 0;
return head;
}
//获取指定位置的节点
Node *get(RDLinkList list, int position) {
if (position<1 || position > list->length){
return NULL;
}
Node *current;
int index,reverse;
//判断要获取的位置在左边还是右边,从而确定遍历方向,以减少遍历次数
if (position <= (list->length / 2)){
//目标位置小于等于中心位置 从左边开始
index = 1;
current = list->next;//指向首节点
reverse = 0;
}else{
//目标位置大于中心位置 从右边开始
index = list->length;
current = list->prior;//指向终端节点
reverse = 1;
}
//如果下面还有值并且还没有到达指定的位置就继续遍历
while (current != list && position != index){
printf("loop\n");//查看当前循环次数
if (reverse == 1){
current = current->prior;
index -= 1;
}else{
current = current->next;
index += 1;
}
}
if (index == position && current!=list) {
return current;
}
return NULL;
}
//插入一个新节点到指定位置
void insert(RDLinkList list, int data, int position) {
Node *newNode, *pre;
if (position == 1) {
pre = list;
} else {
pre = get(list, position - 1);
}
//判断其位置是否可插入
if (pre == NULL) {
printf("位置非法");
exit(-1);
}
newNode = malloc(sizeof(Node));
newNode->data = data;
newNode->next = pre->next;
newNode->prior = pre;
pre->next = newNode;
newNode->next->prior = newNode;
list->length += 1;
}
//删除某个位置节点
void delete(RDLinkList list,int position){
Node * target = get(list,position);
if (target != NULL){
target->prior->next = target->next;
target->next->prior = target->prior;
free(target);
}
list->length-=1;
}
//插入到左边
void lpush(RDLinkList list,int data){
insert(list,data,1);
}
//插入到右边
void rpush(RDLinkList list,int data){
insert(list,data,list->length+1);
}
Node * pop(RDLinkList list,int left){
Node *target;
if (left == 1){
target = get(list,1);
} else{
target = get(list,list->length);
}
if (target != NULL){
target->prior->next = target->next;
target->next->prior = target->prior;
free(target);
}
return target;
}
//弹出最左边一个元素
Node *lpop(RDLinkList list){
return pop(list,1);
}
//弹出最右边一个元素
Node *rpop(RDLinkList list){
return pop(list,0);
}
int main() {
printf("Hello, World!\n");
RDLinkList linkList = initialLink();
insert(linkList,100,1);
insert(linkList,200,2);
insert(linkList,300,3);
insert(linkList,400,4);
insert(linkList,500,5);
insert(linkList,600,6);
insert(linkList,700,7);
insert(linkList,800,8);
insert(linkList,900,9);
insert(linkList,1000,10);
insert(linkList,1100,11);
//查找测试 从右边遍历 只需要遍历两个节点就能找到第9个
Node *res = get(linkList,9);
if (res != NULL){
printf("%d\n",res->data);
}
// pop push测试
RDLinkList linkList2 = initialLink();
lpush(linkList2,100);
lpush(linkList2,200);
rpush(linkList2,300);
rpush(linkList2,400);
printf("%d\n",lpop(linkList2)->data);
printf("%d\n",lpop(linkList2)->data);
printf("%d\n",rpop(linkList2)->data);
printf("%d\n",rpop(linkList2)->data);
return 0;
}
从同一端推入和弹出 如:lpush和lpop 能实现栈
从相反方向推入和弹出 如:lpush和rpop能实现队列