redis 源码学习笔记--跳跃表简单编码实现

要理解SkipList,得先从LinkedList说起。LinkedList 增删查改的时间复杂度都是O(N),它最大的问题就是通过一个节点只能reach到下一个节点(Double LinkedList 是一种改进方案),那么改进的思路就是通过一个节点reach到多个节点,例如下图:


这种情况下便可将复杂度减小为O(N/2)。这是一种典型的空间换时间的优化思路。

SkipList则 更进一步,采用了分治算法和随机算法设计。将每个节点所能reach到的最远的节点,两个节点之间看成是一个组,整个SkipList被分成了许多个组,而这些组的形成是随机的,如下图:


一般单向链表节点有两个字段,数据字段data和下一个指针节点字段next。可以看出跳跃表的每个节点下一个指针节点的个数是不定的,我们可以用指针数组表示:

typedef struct node
{
	valueType value;        // value值
	struct node * next[1];  // 后继指针数组,柔性数组 可实现结构体的变长
} Node;

注意,虽然next指针也可以是node**类型的,但是数组指针比这个好操作。

而且链表中的元素必须是有序的,这样才能达到类似于二分法查找元素的效果。所以在插入数据时不像一般链表一样,在头或尾插入,他必须要先找到插入的正确位置。怎么找到插入的位置呢?

链表只能通过遍历方式来逐一元素比较,只不过这里跳跃表的每个节点可能有多个上层节点,必须从上层开始遍历,因为上层节点稀少,而且能够指引下层节点。就好比国家行政单位,从上到下有省,市,县,镇,村行政单位一样。要找到一个村,可以从省开始,逐一锁定范围。

插入节点说到底也是个构造跳跃表的过程,所插入的节点也要有多层拷贝才行。如何确定所插入节点要砌多少层呢?

查找资料,一般给出的是由随机数,得到偶数的次数来决定。为什么要这么做就不深入探究了,反正可以到达一个很好的平衡点,不像红黑树,要经过那么复杂的左旋右旋来保证节点平衡。

跳跃表也有头节点的概念,初始时自己定义一个常量,用来生成头结点有多少层。如果在插入的过程中,新生成的节点层数是最高的,那么也要让头结点指向新增的层,如图所示:

查找节点的思想在插入节点的过程中已得到体现,这里就不再赘述。

完整代码如下:

https://github.com/shonm520/test_redis

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>

#define MAX_L 16 //最大层数  

//new_node生成一个Node结构体,同时生成包含n个Node *元素的数组
#define new_node(n) ((Node*)malloc(sizeof(Node)+n*sizeof(Node*)))
//定义key和value的类型 
typedef int keyType;
typedef int valueType;

//每个节点的数据结构
typedef struct node
{
	//keyType key;// key值
	valueType value;// value值
	struct node * next[1];// 后继指针数组,柔性数组 可实现结构体的变长
} Node;
//跳表结构
typedef struct skip_list
{
	int level;// 最大层数
	Node *head;//指向头结点
} skip_list;


Node *create_node(int level, valueType val);

skip_list *create_sl();
/*
插入元素的时候元素所占有的层数完全是随机算法,返回随机层数
*/
int randomLevel();

bool insert(skip_list *sl, valueType val);

bool erase(skip_list *sl, keyType key);

valueType *search(skip_list *sl, keyType key);
/*
从最高层开始逐层打印
sl 跳表指针
*/
void print_sl(skip_list *sl);

void sl_free(skip_list *sl);


// 创建节点
Node *create_node(int level, valueType val)
{
	Node *p = new_node(level);
	if (!p)
		return NULL;
	p->value = val;
	return p;
}

//创建跳跃表
skip_list *create_sl()
{
	skip_list *sl = (skip_list*)malloc(sizeof(skip_list));//申请跳表结构内存
	if (NULL == sl)
		return NULL;

	sl->level = 0;// 设置跳表的层level,初始的层为0层(数组从0开始)

	Node *h = create_node(MAX_L - 1, 0);//创建头结点
	if (h == NULL)  {
		free(sl);
		return NULL;
	}
	sl->head = h;
	int i;
	// 将header的next数组清空
	for (i = 0; i < MAX_L; ++i)  {
		h->next[i] = NULL;
	}
	srand(time(0));
	return sl;
}
//插入元素的时候元素所占有的层数完全是随机算法
int randomLevel()
{
	int level = 1;
	while (rand() % 2)
		level++;
	level = (MAX_L > level) ? level : MAX_L;
	return level;
}
/*
step1:查找到在每层待插入位置,跟新update数组
step2:需要随机产生一个层数
step3:从高层至下插入,与普通链表的插入完全相同。
*/
bool insert(skip_list *sl, valueType val)
{
	Node *update[MAX_L];
	Node *q = NULL, *p = sl->head;//q,p初始化
	int i = sl->level - 1;
	/******************step1*******************/
	//从最高层往下查找需要插入的位置,并更新update
	//即把降层节点指针保存到update数组
	for (; i >= 0; --i)  {
		while ((q = p->next[i]) && q->value < val)
			p = q;
		update[i] = p;
	}
	if (q && q->value == val)  {  //key已经存在的情况下
		q->value = val;
		return true;
	}
	/******************step2*******************/
	//产生一个随机层数level
	int level = randomLevel();
	//如果新生成的层数比跳表的层数大
	if (level > sl->level)  {
		//在update数组中将新添加的层指向header;
		for (i = sl->level; i < level; ++i)  {
			update[i] = sl->head;
		}
		sl->level = level;
	}
	//printf("%d\n", sizeof(Node)+level*sizeof(Node*));
	/******************step3*******************/
	//新建一个待插入节点,一层一层插入
	q = create_node(level, val);
	if (!q)
		return false;

	//逐层更新节点的指针,和普通链表插入一样
	for (i = level - 1; i >= 0; --i)  {
		q->next[i] = update[i]->next[i];
		update[i]->next[i] = q;
	}
	return true;
}
// 删除节点
bool erase(skip_list *sl, valueType val)
{
	Node *update[MAX_L];
	Node *q = NULL, *p = sl->head;
	int i = sl->level - 1;
	for (; i >= 0; --i)  {
		while ((q = p->next[i]) && q->value < val)  {
			p = q;
		}
		update[i] = p;
	}
	//判断是否为待删除的key
	if (!q || (q&&q->value != val))
		return false;

	//逐层删除与普通链表删除一样
	for (i = sl->level - 1; i >= 0; --i)  {
		if (update[i]->next[i] == q)  {  //删除节点
			update[i]->next[i] = q->next[i];
			//如果删除的是最高层的节点,则level--
			if (sl->head->next[i] == NULL)
				sl->level--;
		}
	}
	free(q);
	q = NULL;
	return true;
}
// 查找
valueType *search(skip_list *sl, keyType key)
{
	Node *q, *p = sl->head;
	q = NULL;
	int i = sl->level - 1;
	for (; i >= 0; --i)  {
		while ((q = p->next[i]) && q->value < key)  {
			p = q;
		}
		if (q && key == q->value)
			return &(q->value);
	}
	return NULL;
}

//从最高层开始逐层打印
void print_sl(skip_list *sl)
{
	Node *q;
	int i = sl->level - 1;
	for (; i >= 0; --i)  {
		q = sl->head->next[i];
		printf("level %d:\n", i + 1);
		while (q)  {
			printf("  val:%d\t", q->value);
			q = q->next[i];
		}
		printf("\n");
	}
}

// 释放跳跃表
void sl_free(skip_list *sl)
{
	if (!sl)
		return;

	Node *q = sl->head;
	Node *next;
	while (q)  {
		next = q->next[0];
		free(q);
		q = next;
	}
	free(sl);
}



int main()
{
	int a[] = { 5, 8, 1, 6, 3, 2, 7, 4, 9 };
	skip_list *sl = create_sl();
	int i = 0;
	for (; i < 9; ++i)
	{
		insert(sl, a[i]);
	}
	erase(sl, 5);
	print_sl(sl);
	int *p = search(sl, 5);
	if (p)
		printf("value %d found!!!\n", *p);
	sl_free(sl);
	return 0;
}

参看:

https://blog.csdn.net/daniel_ustc/article/details/20218489

https://blog.csdn.net/u014427196/article/details/52454462

https://www.cnblogs.com/charlesblc/p/5987812.html

https://blog.csdn.net/sunmenggmail/article/details/12648465

https://blog.csdn.net/dog250/article/details/46997155

https://blog.csdn.net/kisimple/article/details/38706729

猜你喜欢

转载自blog.csdn.net/zxm342698145/article/details/80902473