数据结构学习笔记(九)跳表、字典的跳表描述

一、跳表

        在使用有序链表描述的n个数对的字典中进行查找,至多需要n词比较。如果使用折半查找的思想,使用跳表来描述,可以大大加快搜索的速度。在下图10-1中,a表示的有序链表有7个数对,该链表增加一个头结点和一个尾节点。对该链表进行搜索最多需要7次关键字的比较。如果像b那样,在中间的节点中加入一个指针,那么比较时先和这个中间节点比较,在决定在前半部分还是后半部分继续搜索,因此搜索最多需要4次关键字的比较。

       也可以像c那样,分别在左半部分和后半部分的中间节点增加一个指针,以进一步减少最坏情况下的比较次数。该图有三级链表,0级链表是初始链表,包含7个数对,1级链表包含字典的二、四、六数对,2级链表只包含第四个数对。查找时,先根据2级链表,判断接下在左半部分还是右半部分继续查找,然后根据1级链表,最后根据0级链表查找,这个过程搜索时最快情况只需要3次比较。

        举个例子,查找关键字为77的数对。首先和2级链表的节点40比较,因为77大,所以接下来在1级链表中和节点75比较。因为还是77大,所以在0级链表中与75后面的节点80比较,最后得知77不在字典中。

      数据结构学习笔记(九)跳表、字典的跳表描述_第1张图片

      诸如c的结构称之为跳表。对于n个数对而言,0级链表包含所有数对,1级链表每2个数对取一个,2级链表每4个数对取一个,i级链表每2^i个数对取一个。一个数对属于i级链表,当且仅当它属于0~i级链表,但不属于i+1级链表。在图c中,关键字是40的数对属于2级链表,关键字为24和75 的数对属于1级链表,关键字为20、30、60、80的数对属于0级链表。

二、跳表的实现:


       插入和删除 

       在插入和删除时,要保持像图中c所示跳表的规则结构,需要耗时O(n)。在规则的跳表结构中,i级链表有n/2^i个记录,在插入要尽量逼近这种结构。插入的新数对属于i级链表的概率为 1 / 2^i。在实际确定新树所属的链表级别时,应考虑各种可能的情况。把新数对插入i级链表的可能性为p^i。在图c中,p=0.5。在一个规则的跳表中,i级链表包含1/p个i-1级链表的节点。

       假设需要插入关键字为77的数对。首先通过搜索确定链表中没有该元素。在搜索时,我们用到了关键字为40 的节点(2级链表指针)、关键字为75 的节点(1级链表指针)和一个0级链表指针。图d中。这几个指针被一条虚线切割。新数对插入的位置在75和80之间。    确定位置后,还要为新数对分配一个级(即确定他属于哪一级链表)。若新数对属于i级链表,则插入结果仅影响有虚线切割的0~i级链表的指针。图e是插入结果。

       删除操作,我们无法控制结构。要删除图e的节点77,首先要找到77。因为77为1级链表中数对的关键字,所以只需要改变0级和1级链表指针即可。当这些指针指向77后面的节点时,就得到图d的结构。


        级数的分配

       我们知道在规则的跳表结构中,i-1级链表的数对个数与i级链表的数对个数之比是一个分数p。因此属于i-1级链表的数对同时属于i级链表的概率是p。假设用一个统一的随机数生成器产生0~1之间的实数,产生的随机数<=p的概率是p。若下一个随机数<=p,则新数对应在1级链表上。要确定该数对是否在2级链表上,要由下一个随机数确定。若新的随机数<=p,则该元素也属于2级链表,重复该过程知道随机数>p为止。

        这种方式分配的级数可能特别大,为此设定一个级数的上限maxLevel,最大值为(见下图)。此外,这种方式还有一个缺点:当插入一个新数对之前有3个链表,而在插入之后就有可能有10个链表,也就是说3~8级链表为空时,新数对却被分配到9级链表。这种情况我们可以把级数设置为3。

数据结构学习笔记(九)跳表、字典的跳表描述_第2张图片

       举个例子。使用跳表描述一个最多1024个数对的字典。设p=0.5,则 maxLevel=log(1024)-1=9。假定从一个空字典开始,用具有一个头节点和尾节点的跳表结构描述,头结点有10个指针,每一个指针对应一个链表,且从头结点指向尾节点。当插入第一个数对时,为其在0~9之间分配一个级数。若分配的级数为9,则因为跳表还没有0~8级的记录,所以可以把等级改为0,只是只需修改一个指针即可。


       具体的实现过程如下:

#pragma once

//跳表的实现
#include 
#include 
#include 
#include 
#include "dictionary.h"

using namespace std;

//跳表的节点结构,跳表的头结点必须有足够的指针域,以满足最大链表级数的构建需要,而尾节点不需要指针域。
//每一个存有数对的节点都有一个存储数对的element域和一个大于自身级数的指针域,
//指针域用数组next表示,其中next[i]表示i级链表的指针,next[0]是0级上的下一个节点,next[i]是i级上的下一个节点
template 
struct skipNode
{
	typedef pair pairType;
	pairType element;//数据域
	skipNode **next;   // 指针数组,大小可变

	skipNode(const pairType& thePair, int size):element(thePair){
		next = new skipNode*[size];//类比new int[size]
	}
};


//跳表
template
class skipList : public dictionary
{
public:
	skipList(K, int maxPairs = 10000, float prob = 0.5);
	~skipList();

	bool empty() const { return dSize == 0; }
	int size() const { return dSize; }
	pair* find(const K&) const;
	void erase(const K&);
	void insert(const pair&);
	void output(ostream& out) const;

protected:
	float cutOff;          // 用来确定层数
	int level() const;     // generate a random level number
	int levels;            // 当前最大的非空链表
	int dSize;             // 字典的数对个数
	int maxLevel;          // 允许最大的链表层数
	K tailKey;             // 最大关键字
	skipNode* search(const K&) const;
	// search saving last nodes seen
	skipNode* headerNode;  // 头结点指针
	skipNode* tailNode;    // 尾节点指针
	skipNode** last;       // last[i] 表示 i层的最后一个节点
};

//构造函数。初始化类的成员,并为头结点、尾节点和数组last分配空间
//参数largeKey的值大于字典中任何数对的关键字,它存储在尾节点
//参数maxPairs的值是字典数对个数的最大值,一般字典不要超过该值为了程序性能
//参数prob是i-1级链表数对同时也是i级链表数对的概率
//在插入和删除操作之前的搜索操作所遇到的每一级链表的最后一个数对都存放在数组last中
//跳表初始布局为空,头结点有maxLevel+1个指针指向尾节点。
template
skipList::skipList(K largeKey, int maxPairs, float prob)
{//构造关键字小于largeKey且数对个数size小于maxPairs的跳表,0 tailPair;
	tailPair.first=tailKey;
	headerNode=new skipNode(tailPair,maxLevel+1);
	tailNode=new skipNode(tailPair,0);
	last=new skipNode *[maxLevel+1];

	//链表为空时,任意级链表中的头结点指向尾节点
	for(int i=0;i<=maxLevel+1;i++)
		headerNode->next[i]=tailNode;
}

//析构函数。删除所有数对,以及每一级的所有数对
template
skipList::~skipList()
{
	skipNode *nextNode;
	//删除所有数对,
	while(headerNode!=tailNode)
	{
		nextNode=headerNode->next[0];
		delete headerNode;
		headerNode=nextNode;
	}
	delete tailNode;
	//删除数组last
	delete [] last;
}

//查找操作,查找失败返回NULL,成功返回该数对的指针
//find从最高级链表开始搜索(即level级链表,他只有一个数对),直到0级链表。
//虽然在i级链表上,就可能查找到theKey元素,但是用来检验是否相等的额外操作是不必要的,因为大部分这样的数对都只出现在0级链表中。
//当从for循环退出时,指针正好处于要查找的数对的左边,与0级链表的下一个数对比较,即可确定要数对是否在跳表中
template
pair* skipList::find(const K& theKey) const
{
   if (theKey >= tailKey)
      return NULL;  

   // 位置 beforeNode 是关键字节点之前的一个节点
   skipNode* beforeNode = headerNode;
   for (int i = levels; i >= 0; i--)          // 从上一级链表到下一级链表
      while (beforeNode->next[i]->element.first < theKey)//跟踪i级链表指针
         beforeNode = beforeNode->next[i];

   // 检查下一个节点的关键字是否是theKey
   if (beforeNode->next[0]->element.first == theKey)
      return &beforeNode->next[0]->element; 

   return NULL;  //如果没找到,返回NULL
}

//插入操作应该为新的节点分配级数,在编写insert插入操作之前,需要level和search函数
//level函数是级的分配函数,为新数分配它所属的级链表,
//同时search函数,是搜索函数,目的以存储每一级链表搜索时所遇到的最后一节点的指针
template
int skipList::level() const
{// 返回一个表示链表级的随机数 <= maxLevel.
   int lev = 0;
   while (rand() <= cutOff)
      lev++;
   return (lev <= maxLevel) ? lev : maxLevel;
}
template
skipNode* skipList::search(const K& theKey) const
{// 搜索关键字 theKey ,把每一级链表中要查看的最后一个节点存储在数组 last 中
 // 返回关键字theKey的节点

   //  beforeNode 是关键字theKey的节点的前一个节点
   skipNode* beforeNode = headerNode;
   for (int i = levels; i >= 0; i--)
   {
      while (beforeNode->next[i]->element.first < theKey)
         beforeNode = beforeNode->next[i];
      last[i] = beforeNode;  // 最后一级链表i的节点
   }
   return beforeNode->next[0];
}

//插入操作,如果存在该节点进行更新,否则执行插入
template
void skipList::insert(const pair& thePair)
{
   if (thePair.first >= tailKey) // 关键字太大
   {
    cerr << "Key = " << thePair.first << " Must be < " << tailKey;
	exit(0);
   }
   
   // 查看关键字是否存在
   skipNode* theNode = search(thePair.first);
   //存在的话,进行更新
   if (theNode->element.first == thePair.first)
   {
      theNode->element.second = thePair.second;
      return;
   }

   // 不存在的话,执行插入
   int theLevel = level(); // 新节点的级
   if (theLevel > levels)//使得级数theLevel为<=levels+1
   {
      theLevel = ++levels;
      last[theLevel] = headerNode;
   }
   skipNode* newNode = new skipNode(thePair, theLevel + 1);
   for (int i = 0; i <= theLevel; i++)
   {// 插入i级链表
      newNode->next[i] = last[i]->next[i];
      last[i]->next[i] = newNode;
   }

   dSize++;
   return;
}

//删除关键字为theKey的数对
template
void skipList::erase(const K& theKey)
{
   if (theKey >= tailKey) 
      return;

   // 查看是否有匹配的数对
   skipNode* theNode = search(theKey);
   //如果没有直接返回
   if (theNode->element.first != theKey) 
      return;
   // 如果有删除节点
   for (int i = 0; i <= levels &&last[i]->next[i] == theNode; i++)
      last[i]->next[i] = theNode->next[i];
   
   // 更新链表级
   while (levels > 0 && headerNode->next[levels] == tailNode)
      levels--;
 
   delete theNode;
   dSize--;
}

//输出
template
void skipList::output(ostream& out) const
{
   // follow level 0 chain
   for (skipNode* currentNode = headerNode->next[0]; currentNode != tailNode; currentNode = currentNode->next[0])
      out << currentNode->element.first << " " << currentNode->element.second << "  ";
}
template 
ostream& operator<<(ostream& out, const skipList& x)
{
	x.output(out); return out;
}


        测试代码:

// test skip list class
#include 
#include "skipList.h"

using namespace std;

void main()
{
   skipList z(1000);
   pair p;

   // test insert
   p.first = 2; p.second = 10;
   z.insert(p);
   p.first = 10; p.second = 50;
   z.insert(p);
   p.first = 6; p.second = 30;
   z.insert(p);
   p.first = 8; p.second = 40;
   z.insert(p);
   p.first = 1; p.second = 5;
   z.insert(p);
   p.first = 12; p.second = 60;
   z.insert(p);
   cout << "The dictionary is " << z << endl;
   cout << "Its size is " << z.size() << endl;

   // test find
   cout << "Element associated with 1 is " << z.find(1)->second << endl;
   cout << "Element associated with 6 is " << z.find(6)->second << endl;
   cout << "Element associated with 12 is " << z.find(12)->second << endl;

   // test erase
   z.erase(1);
   z.erase(2);
   z.erase(6);
   z.erase(12);
   cout << "Deleted 1, 2, 6, 12" << endl;
   cout << "The dictionary is " << z << endl;
   cout << "Its size is " << z.size() << endl;
}


你可能感兴趣的:(数据结构)