跳表C语言实现详解

目录

一、跳表(skip list)简介

1、时间复杂度分析

2、跳表索引动态更新

二、跳表数据存储模型

三、跳表操作

1、初始化

2、插入

3、删除

4、修改

5、查询

5、销毁

参考链接


一、跳表(skip list)简介

跳表使用空间换时间的设计思路,通过构建多级索引来提高查询的效率,实现了基于链表的“二分查找”。跳表是一种动态数据结构,支持快速的插入、删除、查找操作,时间复杂度都是 O(logn)。跳表的空间复杂度是 O(n)。不过,跳表的实现非常灵活,可以通过改变索引构建策略,有效平衡执行效率和内存消耗

1、时间复杂度分析

在单链表中,一旦定好要插入的位置,时间复杂度是很低的为O(1),但是,为了保持原始链表中数据的有序性,通常需要遍历链表找到需要插入的位置,这个查找操作会比较耗时。但是在跳表中,时间复杂度为O(logn),如下图,插入数据 6;在删除操作时,如果删除的节点在索引中也有出现,除了要删除原始链表中的数据外,还要删除索引中的节点

 

2、跳表索引动态更新

当我们不停地往跳表中插入数据时,如果我们不更新索引,就有可能出现某 2 个索引结点之间数据非常多的情况。极端情况下,跳表还会退化成单链表

作为一种动态数据结构,我们需要某种手段来维护索引与原始链表大小之间的平衡,也就是说,如果链表中结点多了,索引结点就相应地增加一些,避免复杂度退化,以及查找、插入、删除操作性能下降

而跳表是通过随机函数来维护前面提到的“平衡性”。当我们往跳表中插入数据的时候,我们可以选择同时将这个数据插入到部分索引层中。如何选择加入哪些索引层呢?我们通过一个随机函数,来决定将这个结点插入到哪几级索引中,比如随机函数生成了值 K,那我们就将这个结点添加到第一级到第 K 级这 K 级索引中。

 

二、跳表数据存储模型

如果一个节点存在K个向前的指针的话,那么该节点就是K层的节点。跳表的最大层数为节点中所以节点中最大的层数

#define SKIP_LIST_MALLOC(size)   rt_malloc(size);
#define SKIP_LIST_CALLOC(n,size) rt_calloc(n,size);
#define SKIP_LIST_FREE(p)        rt_free(p);

struct skip_list_node
{
	/*key是唯一的*/
	int key;       
	/*存储的内容*/
	int value;     
	/*当前节点最大层数*/
	int max_level; 
	/*柔性数组,根据该节点层数的不同指向大小不同的数组
	 *next[0]表示该节点的第一层下一节点的索引地址
	 *next[1]表示该节点的第二层下一节点的索引地址
	 *next[n]表示该节点的第n层下一节点的索引地址
	 */
	struct skip_list_node *next[];
};

struct skip_list
{
	int level; /*跳表的索引层数*/
	int num;   /*节点数目*/
	struct skip_list_node *head;
};

extern struct skip_list* skip_list_creat(int max_level);
extern int skip_list_insert (struct skip_list *list, int key, int value);
extern int skip_list_delete (struct skip_list *list, int key);
extern int skip_list_modify (struct skip_list *list, int key, int value);
extern int skip_list_search (struct skip_list *list, int key, int *value);
extern int skip_list_destroy(struct skip_list *list);

 

三、跳表操作

1、初始化

初始化的过程很简单,仅仅是生成下图中红线区域内的部分,也就是跳表的基础结构:

/**
 * 动态申请跳表节点.
 * 
 * @return NULL:内存申请失败
 *        !NULL:节点创建成功
 */
static skip_list_node* skip_list_node_creat(int level, int key, int value)
{
	struct skip_list_node *node = NULL;
	
	/* 节点空间大小 为节点数据大小+ level层索引所占用的大小 */
	node = SKIP_LIST_MALLOC(sizeof(*node) + level * sizeof(node));
	if (node == NULL)
		return NULL;
	
	/* 清空申请空间 */
	memset(node, 0, sizeof(*node) + level * sizeof(node));
	node->key = key;
	node->value = value;
	node->max_level = level;
	
	return node;
}

/**
 * 创建跳表头节点.
 * 
 * @param max_level:跳表最大层数
 * @return NULL:创建失败
 *        !NULL:创建成功
 */
struct skip_list* skip_list_creat(int max_level)
{
	struct skip_list *list = NULL;
	
	list = SKIP_LIST_MALLOC(sizeof(*list));
	if (list == NULL)
		return NULL;
	
	list->level = 1;
	list->count = 0;
	list->head = skip_list_node_creat(max_level, 0, 0);
	if (list->head == NULL)
	{
		SKIP_LIST_FREE(list);
		return NULL;
	}
	
	return list;
}

2、插入

由于跳表数据结构整体上是有序的,所以在插入时,需要首先查找到合适的位置,然后就是修改指针(和链表中操作类似),然后更新跳表的level变量

/**
 * 随机产生插入元素的索引层数,随机产生的.
 * 
 * @param list:跳表
 * @return 节点索引层数
 *        
 */
static int skip_list_level(struct skip_list *list)
{
	int i = 0, level = 1; /*索引层数至少为1,所以从1开始*/
	
	for (i=1; i<list->head->max_level; i++)
	{
		if ((rand() % 2) == 1)
		{
			level++;
		}
	}
	return level;
}

/**
 * 插入跳表节点.
 * 
 * @param list:跳表
 * @param key:
 * @param value:
 * @return -1:跳表为空
 *         -2:空间分配失败
 *         -3:key已经存在
 *          0:插入成功
 */
int skip_list_insert(struct skip_list *list, int key, int value)
{
	struct skip_list_node **update = NULL; /*用来更新每层索引指针,存放插入位置的前驱各层节点索引*/ 
	struct skip_list_node *cur = NULL;
	struct skip_list_node *prev = NULL;
	struct skip_list_node *insert = NULL;
	int i = 0, level = 0;
	
	if (list == NULL)
		return -1;
	
	/*申请update空间用于保存每层的索引指针*/
	update = (struct skip_list_node **)SKIP_LIST_MALLOC(sizeof(list->head->max_level * sizeof(struct skip_list_node *)));
	if (update == NULL)
		return -2;
	
	/*逐层查询,查找插入位置的前驱各层节点索引
	 *update[0] 存放第一层的插入位置前驱节点,update[0]->next[0]表示插入位置的前驱节点的下一节点(update[0]->next[0])的第一层索引值
	 *update[1] 存放第二层的插入位置前驱节点,update[1]->next[1]表示插入位置的前驱节点的下一节点(update[1]->next[0])的第二层索引值
	 *update[n] 存放第一层的插入位置前驱节点,update[n]->next[n]表示插入位置的前驱节点的下一节点(update[n]->next[0])的第n层索引值
	 */
	prev = list->head; /*从第一个节点开始的最上层开始找*/
	i = list->level - 1;
	for(; i>=0; i--)
	{
		/* 各层每个节点的下一个节点不为空 && 下个节点的key小于要插入的key */
		while ( ((cur = prev->next[i]) != NULL) && (cur->key < key) )
		{
			prev = cur; /* 向后移动 */
		}
		update[i] = prev; /* 各层要插入节点的前驱节点 */
	}
	
	/* 当前key已经存在,返回错误 */
	if ((cur != NULL) && (cur->key == key))
	{
		return -3;
	}
	
	/*获取插入元素的随机层数,并更新跳表的最大层数*/
	level = skip_list_level(list);
	/*创建当前节点*/
	insert = skip_list_node_creat(level, key, value);
	/*根据最大索引层数,更新插入节点的前驱节点,前面已经更新到了[0] - [(list->level-1)]*/
	if (level > list->level)
	{
		for (i=list->level; i<level; i++)
		{
			update[i] = list->head;/*这部分为多新增的索引层,所以前驱节点默认为头结点*/
		}
		list->level = level;/*更新跳表的最大索引层数*/
	}
	
	/*逐层更新节点的指针*/
	for (i=0; i<level; i++)
	{
		insert->next[i] = update[i]->next[i];
		update[i]->next[i] = insert;
	}
	
	/*节点数目加1*/
	list->num++;
	
	return 0;
}

3、删除

和插入是相同的,首先查找需要删除的节点,如果找到了该节点的话,那么只需要更新指针域,如果跳表的level需要更新的话,进行更新。

/**
 * 删除跳表节点.
 * 
 * @param list:跳表
 * @param key:
 * @param value:
 * @return -1:跳表为空 或 跳表节点数量为0
 *         -2:空间分配失败
 *         -3:key不存在
 *          0:删除成功
 */
int skip_list_delete(struct skip_list *list, int key)
{
	struct skip_list_node **update = NULL; /*用来更新每层索引指针,存放插入位置的前驱各层节点索引*/ 
	struct skip_list_node *cur = NULL;
	struct skip_list_node *prev = NULL;
	int i = 0;
	
	if (list == NULL && list->num == 0)
		return -1;
	
	/*申请update空间用于保存每层的节点索引指针*/
	update = (struct skip_list_node **)SKIP_LIST_MALLOC(sizeof(list->level * sizeof(struct skip_list_node *)));
	if (update == NULL)
		return -2;
	
	/*逐层查询,查找删除位置的前驱各层节点索引
	 *update[0] 存放第一层的删除位置前驱节点,update[0]->next[0]表示删除位置的前驱节点的下一节点(update[0]->next[0])的第一层索引值
	 *update[1] 存放第二层的删除位置前驱节点,update[1]->next[1]表示删除位置的前驱节点的下一节点(update[1]->next[0])的第二层索引值
	 *update[n] 存放第一层的删除位置前驱节点,update[n]->next[n]表示删除位置的前驱节点的下一节点(update[n]->next[0])的第n层索引值
	 */
	prev = list->head; /*从第一个节点开始的最上层开始找*/
	i = list->level - 1;
	for(; i>=0; i--)
	{
		/* 各层每个节点的下一个节点不为空 && 下个节点的key小于要插入的key */
		while ( ((cur = prev->next[i]) != NULL) && (cur->key < key) )
		{
			prev = cur; /* 向后移动 */
		}
		update[i] = prev; /* 各层要删除节点的前驱节点 */
	}
	
	/* 当前key存在 */
	if ((cur != NULL) && (cur->key == key))
	{
		/*逐层删除*/
		for(i=0; i<list->level; i++)
		{
			if (update[i]->next[i] == cur)
			{
				update[i]->next[i] = cur->next[i];
			}
		}
		
		SKIP_LIST_FREE(cur);
		cur = NULL;
		
		/*更新索引的层数*/
		for(i=list->level-1; i>=0; i--)
		{
			/*如果删除节点后,某层的头结点后驱节点为空,则说明该层无索引指针,索引层数需要减1*/
			if (list->head->next[i] == NULL) 
			{
				list->level --;
			}
			else 
			{
				break;
			}
		}
		
		list->num --; /*节点数减1*/
	}
	else
	{
		return -3;
	}
	
	return 0;
}

4、修改

/**
 * 查询当前key是否在跳表中,存在修改key对于的value数值.
 * 
 * @param list:跳表
 * @param key:修改key
 * @param value:修改的数据
 * @return -1:跳表为空 或 跳表节点数量为0
 *         -3:key不存在
 *          0:修改成功
 */
int skip_list_modify(struct skip_list *list, int key, int value)
{
	struct skip_list_node *cur = NULL;
	struct skip_list_node *prev = NULL;
	int i = 0;
	
	if (list == NULL && list->num == 0)
		return -1;
	
	/*逐层查找,查找查询位置原始链表的节点*/
	prev = list->head; /*从第一个节点开始的最上层开始找*/
	i = list->level - 1;
	for(; i>=0; i--)
	{
		/* 各层每个节点的下一个节点不为空 && 下个节点的key小于要插入的key */
		while ( ((cur = prev->next[i]) != NULL) && (cur->key < key) )
		{
			prev = cur; /* 向后移动 */
		}
	}
	
	/* 当前key存在 */
	if ((cur != NULL) && (cur->key == key))
	{
		cur->value = value;
	}
	else
	{
		return -3;
	}
	
	return 0;
}

5、查询

/**
 * 查询当前key是否在跳表中,存在返回查询的value数值.
 * 
 * @param list:跳表
 * @param key:
 * @param value:查询的数据
 * @return -1:跳表为空 或 跳表节点数量为0
 *         -3:key不存在
 *          0:查找成功
 */
int skip_list_search(struct skip_list *list, int key, int *value)
{
	struct skip_list_node *cur = NULL;
	struct skip_list_node *prev = NULL;
	int i = 0;
	
	if (list == NULL && value == NULL && list->num == 0)
		return -1;
	
	/*逐层查找,查找查询位置原始链表的节点*/
	prev = list->head; /*从第一个节点开始的最上层开始找*/
	i = list->level - 1;
	for(; i>=0; i--)
	{
		/* 各层每个节点的下一个节点不为空 && 下个节点的key小于要插入的key */
		while ( ((cur = prev->next[i]) != NULL) && (cur->key < key) )
		{
			prev = cur; /* 向后移动 */
		}
	}
	
	/* 当前key存在 */
	if ((cur != NULL) && (cur->key == key))
	{
		*value = cur->value;
	}
	else
	{
		return -3;
	}
	
	return 0;
}

5、销毁

/**
 * 销毁跳表.
 * 
 * @param list:跳表
 * @return -1:跳表为空
 *          0:成功
 */
int skip_list_destroy(struct skip_list *list)
{
	struct skip_list_node *cur = NULL;
	
	if (list == NULL && list->head == NULL)
		return -1;
	
	while((cur = list->head->next[0]) != NULL)
	{
		list->head->next[0] = cur->next[0];
		SKIP_LIST_FREE(cur);
		cur = NULL;
	}
	
	SKIP_LIST_FREE(list->head);
	SKIP_LIST_FREE(list);
	
	return 0;
}

参考链接

https://www.cnblogs.com/xuqiang/archive/2011/05/22/2053516.html

https://time.geekbang.org/column/intro/126  《数据结构与算法之美--王争》

  • 11
    点赞
  • 37
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值