跳跃表(SkipList)的原理和实现

1、什么是跳跃表

2、跳跃表的实现


1、什么是跳跃表

    对于单链表来说,即便链表中存储的数据是有序的,如果我们要想在其中查找某个数据,也只能从头到尾遍历链表。这样查找效率就会很低,时间复杂度会很高,是O(n)。

    

    如果我们想要提高其查找效率,可以考虑在链表上建索引的方式。每两个结点提取一个结点到上一级,我们把抽出来的那一级叫作索引。

    

    这个时候,我们假设要查找节点8,我们可以先在索引层遍历,当遍历到索引层中值为 7 的结点时,发现下一个节点是9,那么要查找的节点8肯定就在这两个节点之间。我们下降到链表层继续遍历就找到了8这个节点。原先我们在单链表中找到8这个节点要遍历8个节点,而现在有了一级索引后只需要遍历五个节点。从这个例子里,我们看出,加来一层索引之后,查找一个结点需要遍的结点个数减少了,也就是说查找效率提高了,同理再加一级索引。

    

   从图中我们可以看出,查找效率又有提升。在例子中我们的数据很少,当有大量的数据时,我们可以增加多级索引,其查找效率可以得到明显提升。

    

    这种链表加多级索引的结构,就是跳跃表,每一个结点不单单只包含指向下一个结点的指针,可能包含很多个指向后续结点的指针,这样就可以跳过一些不必要的结点,从而加快查找、删除等操作。对于一个链表内每一个结点包含多少个指向后续元素的指针,这个过程是通过一个随机函数生成器得到,这样子就构成了一个跳跃表。这就是为什么论文“Skip Lists : A Probabilistic Alternative to Balanced Trees ”中有“概率”的原因了,就是通过随机生成一个结点中指向后续结点的指针数目。跳跃表使用概率均衡技术而不是使用强制性均衡,因此,对于插入和删除结点比传统上的平衡树算法更为简洁高效。     

2、跳跃表的实现

2.1 定义数据结构

struct skipNode {
	int key;
	int value;
	int level;	       // size表示该节点存在的最高层数
	skipNode* *next;   //skipNode* 的数组
	skipNode(int k, int v, int size) : key(k), value(v), level(size) 
    {
		next = new skipNode*[size];
	}
};

class skipList {
public:
	skipList(int, int, float prob = 0.5, int maxNum = 10000);
	~skipList();

	skipNode* find(const int) const;
	void erase(const int);
	void insert(const int k, const int v);

protected:
	// 获得新的level
	int getNewLevel()const;
	// 搜索指定key的附近节点
	skipNode* search(const int) const;	

private:
	float cutOff;	// 用于生成新的层级
	int levels;		// 当前已经分到了多少层
	int maxLevel;	// 层数上限
	int maxKey;		// key值上限
	int dataSize;   // 节点个数

	skipNode* headerNode;
	skipNode* tailNode;
	skipNode** last;	// 因为是单链结构,所以保存查找的节点的前一个节点
};

2.2 初始化跳跃表

/* prob = 每隔 1/prob 个节点分一层
   headKey = 头节点的key值,所有节点key值均不大于此key值
   tailKey = 为节点的key值,所有节点key值均不大于此key值
   maxNum = 跳表最大接受的节点个数
*/
skipList::skipList(int headKey, int tailKey, float prob, int maxNum)
{
	cutOff = prob * RAND_MAX;
	maxLevel = (int)ceil(logf((float)maxNum) / logf(1 / prob));
	levels = 0;
	maxKey = headKey;
	dataSize = 0;

	//初始化头尾节点
	headerNode = new skipNode(headKey, 0, maxLevel + 1);
	tailNode = new skipNode(tailKey, 0, 0);

	//上次对比的节点
	last = new skipNode*[maxLevel + 1];

	//初始化头尾节点指向
	for (int i = 0; i <= maxLevel; i++)
		headerNode->next[i] = tailNode;
}

skipList::~skipList()
{
	skipNode* node;
	// 第0层包含了所有的节点
	while (headerNode != tailNode) 
    {
		node = headerNode->next[0];
		delete headerNode;
		headerNode = node;
	}
	delete tailNode;
	delete[] last;
}

/* 利用随机数是否大于cutoff值获得新的level值,但新的level值不超过maxLevel,防止浪费 */
int skipList::getNewLevel() const
{
	int lev = 0;
	while (rand() <= cutOff)
		lev++;
	return lev <= maxLevel? lev: maxLevel;
}

skipNode* skipList::search(const int theKey) const
{
	if (theKey > maxKey)
		return NULL;
	skipNode* node = headerNode;
	for (int i = levels; i >= 0; i--)
	{
		while (node->next[i]->key < theKey)
			node = node->next[i];
		/* 因为节点处在单链上,所以,这里保存该节点的前一个节点,方便插入 */
		last[i] = node;
	}
	return node->next[0];
}

2.3 查找

    查找就是给定一个key,查找这个key是否出现在跳跃表中,如果出现,则返回其值,如果不存在,则返回不存在。

skipNode * skipList::find(const int theKey) const
{
	if (theKey >= maxKey)
		return NULL;

	skipNode* node = headerNode;
	for (int i = levels; i >= 0; i--)
		while (node->next[i]->key < theKey)
			node = node->next[i];

	if (node->next[0]->key == theKey)
		return node->next[0];

	return NULL;
}

2.4 插入

    插入包含如下几个操作:

  • 查找到需要插入的位置;
  • 申请新的结点;
  • 调整指针;
void skipList::insert(const int k, const int v)
{
	if (k > maxKey) {
		cout << "key = " << k << "不能比maxKey = " << maxKey << "大" << endl;
		return;
	}

	skipNode* node = search(k);

	/* 节点已存在 */
	if (node->key == k)
	{
		node->value = v;
		return;
	}

	/* 节点不存在, 此时node在新加的节点右侧,生成新的层次 */
	int newLevel = getNewLevel();
	if (newLevel > levels)
	{
		newLevel = ++levels;
		last[newLevel] = headerNode;
	}

	skipNode* newNode = new skipNode(k, v, newLevel);
	/* last节点数组保存了该节点的前一个节点, 每一层都插入 */
	for (int i = 0; i <= newLevel; i++)
	{
		newNode->next[i] = last[i]->next[i];
		last[i]->next[i] = newNode;
	}

	dataSize++;
	return;
}

2.5 删除

    删除操作类似于插入操作,包含如下3步:

  • 查找到需要删除的结点
  • 删除结点
  • 调整指针
void skipList::erase(const int theKey)
{
	if (theKey > maxKey)
		return;

	skipNode* node = search(theKey);
	if (node->key != theKey)
		return;

	for (int i = 0; i <= levels && last[i]->next[i] == node; i++)
		last[i]->next[i] = node->next[i];

	/* 删除没有数据的层 */
	while (levels > 0 && headerNode->next[levels] == tailNode)
		levels--;

	delete node;
	dataSize--;
}

 2.6 主函数

int main()
{
	skipList list(999, 1000);
	list.insert(1, 100);
	list.insert(3, 300);
	list.insert(2, 200);
	list.insert(7, 700);
	list.insert(6, 600);
	list.insert(4, 400);
	list.insert(5, 500);

	skipNode* node = list.find(3);
	list.erase(3);
	node = list.find(3);
}

 

  • 0
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 5
    评论
好的,以下是一个简单的跳跃表的 JAVA 实现,供您参考: ```java import java.util.Random; public class SkipList { private static final int MAX_LEVEL = 16; // 最大层数 private int levelCount = 1; // 当前层数 private Node head = new Node(); // 头节点 private Random random = new Random(); // 随机数生成器 // 获取指定层的前驱节点 private Node findPrev(int value, int level) { Node node = head; while (node.forwards[level] != null && node.forwards[level].data < value) { node = node.forwards[level]; } return node; } // 插入节点 public void insert(int value) { int level = randomLevel(); Node newNode = new Node(); newNode.data = value; newNode.maxLevel = level; Node[] update = new Node[level]; for (int i = 0; i < level; i++) { update[i] = head; } Node node = head; for (int i = level - 1; i >= 0; i--) { while (node.forwards[i] != null && node.forwards[i].data < value) { node = node.forwards[i]; } update[i] = node; } for (int i = 0; i < level; i++) { newNode.forwards[i] = update[i].forwards[i]; update[i].forwards[i] = newNode; } if (levelCount < level) { levelCount = level; } } // 删除节点 public void delete(int value) { Node[] update = new Node[levelCount]; Node node = head; for (int i = levelCount - 1; i >= 0; i--) { while (node.forwards[i] != null && node.forwards[i].data < value) { node = node.forwards[i]; } update[i] = node; } if (node.forwards[0] != null && node.forwards[0].data == value) { for (int i = levelCount - 1; i >= 0; i--) { if (update[i].forwards[i] != null && update[i].forwards[i].data == value) { update[i].forwards[i] = update[i].forwards[i].forwards[i]; } } } } // 查找节点 public Node find(int value) { Node node = head; for (int i = levelCount - 1; i >= 0; i--) { while (node.forwards[i] != null && node.forwards[i].data < value) { node = node.forwards[i]; } } if (node.forwards[0] != null && node.forwards[0].data == value) { return node.forwards[0]; } else { return null; } } // 随机生成层数 private int randomLevel() { int level = 1; for (int i = 1; i < MAX_LEVEL; i++) { if (random.nextInt() % 2 == 1) { level++; } } return level; } // 节点类 class Node { private int data = -1; // 数据 private Node[] forwards = new Node[MAX_LEVEL]; // 向前指针数组 private int maxLevel = 0; // 当前节点最大层数 } } ``` 上述实现中,跳跃表的节点类 `Node` 包含了节点的数据 `data`、向前指针数组 `forwards` 和当前节点最大层数 `maxLevel`。跳跃表类 `SkipList` 包含了跳跃表的层数 `levelCount` 和头节点 `head`,以及插入节点、删除节点、查找节点和随机生成层数的方法。其中,插入节点方法 `insert` 采用了更新数组 `update` 来记录每一层需要更新的节点,其余方法均采用类似的方式进行实现。 需要注意的是,跳跃表的插入、删除和查找操作的时间复杂度均为 $O(\log n)$,其中 $n$ 为跳跃表中元素的个数。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值