HFUT Data Structure Experiment: SkipList

news2024/11/26 13:49:19

写给我的学弟

如果你看到了这个题,赶快跑,千万别选。 这个题的图形化会让你非常痛苦。并且这道题只有小小85分,为啥不换个85分的更简单的?或者换个90分以上的题不好吗。

如果你单单想学习一下这个数据结构,那挺好的,欢迎往下看。

题目介绍

本设计题目的基本内容是构造并实现Skip List 的ADT,并能对其维护动态数据集合的效率进行一定的实验验证。

(2)课程设计目的

认识并应用Skip List 数据结构,体会线性表结构的变形形式。

(3)基本要求

①ADT 中应包括初始化、查找、插入、删除等基本操作。

②分析各基本操作的时间复杂性。

③针对实现Skip List 上基本操作的动态演示(图形演示)。

④能对Skip List 维护动态数据集合的效率进行实验验证,获得一定量的实验数据,如给定随机产生1000 个数据并将其初始化为严格Skip List,在此基础上进行一些列插入、删除、查找操作(操作序列也可以随机生成),获得各种操作的平均时间(或统计其基本操作个数);获得各操作执行时间的变化情况,应该是越来越大,当大到一定程度后应该进行适当的整理,需设计相应的整理算法,并从数量上确定何时较为合适;能和其他简单线性数据结构,如排序数组上的折半查找进行各类操作效率上的数量对比。

SkipList数据结构介绍

推荐在这个网站上看,介绍的挺清楚的

https://www.baeldung.com/cs/skip-lists

你也可以看下面这个视频,讲解的也挺好的。(需要科学上网)
https://www.youtube.com/watch?v=1G8h3u6Thzs&t=434s

简单来说,SkipList就是在原始链表上添加多层索引链表,同时,每一层的结点都有指向下一层的指针,这些指针构成了层级结构,搜索时可以跳过一定数量的结点,从而快速接近目标结点。

这样的结构使得SkipList的搜索时间复杂度从O(n)减小到O(log n) ,大大减小的搜索时间,这是SkipList数据结构的优点。

下面给出SkipList数据结构定义

结点的结构

struct Node {
 public:
     Node* above;//结点指向上层结点的指针
     Node* below;//结点指向下层结点的指针
     Node* next;//结点指向后方结点的指针
     Node* prev;//结点指向前方结点的指针
     int key;//结点保存的值
     //结点的构造函数
     Node(int key) {
         this->key = key;
         this->above = nullptr;
         this->below = nullptr;
         this->next = nullptr;
         this->prev = nullptr;
     }
 };

SkipList的结构

const int NEG_INF=INT_MIN, POS_INF=INT_MAX;

class SkipList {
private:
    Node*head,*tail;//分别表示跳表表头和表尾
    int heightOfSkipList=0;//当前跳表的高度
    int maxLevel=4;//最大层级,默认为4
    float probability=0.5f;//生成随机层次的概率 默认为0.5

    //生成随机初始数据
    void efficiencyVerification(int operationNum, int MaxLevel, float Probability);
    // ** 搜索、插入、删除等所需函数**//
    bool addLevelOrNot(int currentLevel) const;//是否添加一层,概率
    void increaseLevel(int level);//检查当前高度是否足以向上一层添加元素,不够则先添加一层空层,再向上添加一层
    void addEmptyLevel();//添加空层,只有头结点和尾结点
    Node*insertAfterAbove(Node *&position, Node *&q, int key);//
    void setAboveAndBelowNewNode(Node *&position, int key, Node *&newNode, Node *&nodeBelowNewNode);
    void removeOneNode(Node*&nodeToRemove);
    //** 主要函数  ** //
    void initialize();//初始化
    void initialize(int maxLevel,float probability);//含参初始化
    Node*skipSearch(int key);//搜索
    Node*skipInsert(int key);//插入
    Node*skipRemove(int key);//删除
    void printSkipList();//打印

public:
     SkipList()=default;
    ~SkipList();
    void operate();//操作接口

};

SkipList操作实现

初始化

只需要为SkipList的头结点和尾结点分配内存,并设置头、尾指针指向即可

注意,分配内存时即调用了Node的构造函数

/*
 * @brief 初始化SkipList
 * @param 空
 * @return 空
 */
void SkipList::initialize() {
    this->heightOfSkipList=0;
    head = new Node(NEG_INF); // 表头初始化为负无穷大
    tail = new Node(POS_INF); // 表尾初始化为正无穷大
    head->next = tail;
    tail->prev = head;
}

另一种初始化是含参初始化,初始化头、尾结点的同时初始化新生成一层的概率和最大层级数。

/*
 * @brief 初始化SkipList的最大层次和随机生成一层的可能性
 * @param MaxLevel SkipList最大层次
 * @param Probability SkipList随机生成一层的概率
 */
void SkipList::initialize(int MaxLevel, float Probability) {
    initialize();
    this->maxLevel = MaxLevel;
    this->probability = Probability;
}

搜索

SkipList的搜索是自顶向下查找,一直查找到最下层值为key的结点

Node *SkipList::skipSearch(int key) {
    Node*current=head;//从头开始查找
    //从顶向下查找
    while(current->below!= nullptr){
        //如果一层查找完了,就向下一层查找
        current=current->below;
        //找到的位置的下一个位置值要比key大
        while(key>=current->next->key){
            current=current->next;
        }
    }
    //返回找到的位置
    //如果SkipList中不存在key,则返回最后一个小于key的位置 用于插入
    //如果SkipList中存在key,则返回key所在位置
    return current;
}

插入

每执行向某一层插入元素前,都会事先向上生成一层空层。


Node* SkipList::skipInsert(int key) {
    Node *position = skipSearch(key);//找到插入的位置之前的结点,或是已经存在的值为key的结点
    Node *q;
    int level = -1;
    if (position->key == key) {
        return position;
    }

    int currentLevel=0;//当前skipList结点的高度

    //用do while是因为默认插入结点至少要在最底层插入一个 
    do {
        level++;//增加一层层次+1
        currentLevel++;//增加一层高度+1

        //检查当前SkipList高度是否足以向上一层添加元素,不够先在上一层添加一层空层
        canIncreaseLevel(level);

        q = position;//暂时保存当前位置(要插入元素的位置)

        //这里是找到要插入新的一层的结点的位置

        while (position->above == nullptr) {
            position = position->prev;//向前查找插入的位置,也就是查找上一层最后一个结点位置
        }

        position=position->above;//position赋值为上面找到的位置

        //在position位置插入新结点,也就是在原position位置(p指向的位置)的上面一层插入结点
        q = insertAfterAbove(position, q, key);

    } while (addLevelOrNot(currentLevel));//当前层已经插入,通过抛硬币决定是否继续向上添加层
    //循环的逻辑是抛硬币(用概率)决定是否往上增加一层
    return q;
}

下面是一些辅助函数

“抛硬币”判断是否往上增长一层的函数

bool SkipList::addLevelOrNot(int currentLevel) const {
    //高度不能超过最大高度
    if(currentLevel ==maxLevel-1)
        return false;
    random_device rd;
    mt19937 gen(rd());
    uniform_real_distribution<float> dis(0.0f, 1.0f);
    //如果随机生成的数小于probability,则添加新的一层
    if(dis(gen)<probability)
        return true;
    else
        return false;
}
//检测当前层数是否足以增加一层,不足以增加一层,则添加一层空层
void SkipList::canIncreaseLevel(int level) {
    if(level >= heightOfSkipList){
        heightOfSkipList++;
        addEmptyLevel();
    }
}
//添加一层空层
void SkipList::addEmptyLevel() {
    //生成新的空层的头结点为尾结点
    Node*newHeadNode = new Node(NEG_INF);
    Node*newTailNode = new Node(POS_INF);

    //设置新结点的头、尾、上下层级
    newHeadNode->next = newTailNode;
    newHeadNode->below = head;
    newTailNode->prev = newHeadNode;
    newTailNode->below = tail;
    //将新节点加入到原头、尾结点的上一层
    head->above=newHeadNode;
    tail->above=newTailNode;
    //设置新的头尾结点为最上层结点
    head = newHeadNode;
    tail = newTailNode;
}
/*
 * @brief 在q位置后插入结点新结点 同时处理新结点的上下结点问题
 * @position SkipList中新结点上一层
 */
Node *SkipList::insertAfterAbove(Node *&position, Node *&q, int key) {
    Node*newNode=new Node(key);

    //将新结点插入到结点q之后
    newNode->next=q->next;
    newNode->prev=q;
    q->next->prev=newNode;
    q->next=newNode;

    //q是position的下面一层的元素,需要找到插入位置position
    Node*nodeBelowNewNode=position->below->below;
    setAboveAndBelowNewNode(position, key, newNode, nodeBelowNewNode);

    return newNode;
}
/*
 * @brief 设置新结点上下指针
 * @position 新结点的上层位置
 * @key 新结点值
 * @newNode 新结点指针
 * @nodeBelowNewNode 新结点的下层结点指针
 * @return void
 */

void SkipList::setAboveAndBelowNewNode(Node *&position, int key, Node *&newNode, Node *&nodeBelowNewNode) {
    if(nodeBelowNewNode != nullptr){
        //找到新结点下一层的对应结点
        while(true){
            if(nodeBelowNewNode->next->key != key){
                nodeBelowNewNode=nodeBelowNewNode->next;
            }
            else{
                break;
            }
        }
        //设置新结点的下指针(也就是设置重复结点的上下指针)
        newNode->below=nodeBelowNewNode->next;
        nodeBelowNewNode->next->above=newNode;
    }

    //链接重复Key结点的上下位置
    //例子: 第一次插入的时候在第二层未插入3,而第二次插入在第二层插入了3
    //这样设置了新结点的上下左右结点之后,旧的值Key结点也能链接到上一层的新结点。
    if(position!= nullptr){
        if(position->next->key==key){
            newNode->above=position->next;
        }
    }
}

删除

/*
 * @brief 删除值为key的结点
 * @key 要删除的结点值
 * @return Node* 要删除的结点指针
 */
Node*SkipList::skipRemove(int key) {
    Node *nodeToRemove = skipSearch(key);
    if (nodeToRemove->key != key) {
        return nullptr;
    }
    removeOneNode(nodeToRemove);
    while(nodeToRemove!= nullptr){
        removeOneNode(nodeToRemove);
        if(nodeToRemove->above!= nullptr){
            nodeToRemove=nodeToRemove->above;
        }
        else{
            break;
        }
    }
    return nodeToRemove;
}



/*
 * @brief 设置好要删除的结点的前后指针
 * @param 要删除的结点
 * @return void
 */
void SkipList::removeOneNode(Node *&nodeToRemove) {
    Node *afterNodeToRemove = nodeToRemove->next;
    Node *beforeNodeToRemove = nodeToRemove->prev;
    beforeNodeToRemove->next = afterNodeToRemove;
    afterNodeToRemove->prev = beforeNodeToRemove;
}

析构函数(释放内存)

/*
 * @brief 析构函数,删除操作过程中产生的结点
 * @param 无
 *
 */

SkipList::~SkipList() {
    queue<Node*> nodeQueue;
    nodeQueue.push(head);
    //将所有层的头结点都放入队列
    while(!head->above){
        nodeQueue.push(head->above);
    }
    while(!nodeQueue.empty()){
        Node*current=nodeQueue.front();
        Node*next=current->next;
        while(next){
            delete current;
            current=next;
            next=next->next;
        }
        delete current;
    }
}

打印显示SkipList

/*
 * @brief 打印显示SkipList
 * @param void
 * @return void
 */
void SkipList::printSkipList() {
    cout << "\n当前SkipList如下:";
    Node* starting = head;
    Node* highestLevel = starting;
    int level = heightOfSkipList;

    while (highestLevel != nullptr) {
        cout << "\nLevel: " << level << "\n";
        while (starting != nullptr) {
            cout << starting->key;
            if (starting->next != nullptr) {
                cout << " : ";
            }
            starting = starting->next;
        }
        highestLevel = highestLevel->below;
        starting = highestLevel;
        level--;
    }
    cout << endl;
}

操作接口

/*
 * @brief 显示操作菜单
 * @param void
 * @return void
 */
void menu(){
    cout<<"1、初始化SkipList"<<endl;
    cout<<"2、插入"<<endl;
    cout<<"3、搜索"<<endl;
    cout<<"4、删除"<<endl;
    cout<<"5、打印当前SkipList(非可视化用)"<<endl;
    cout<<"6、计算时间效率并与排序数组二分查找对比"<<endl;
    cout<<"输入你的选择"<<endl;
}

/*
 * @brief 非图形化操作接口
 * @param void
 * @return void
 */
void SkipList::operate() {
    menu();
    bool isInitialized=false;
    int select;
    while(true){
        cin>>select;
        switch (select) {
            case 1:{
                cout<<"输入最大层级和生成层级的概率:";
                int MaxLevel;
                float Probability;
                cin >> MaxLevel >> Probability;
                initialize(MaxLevel, Probability);
                printSkipList();
                isInitialized= true;
                cout<<"初始化成功!"<<endl;
                break;
            }
            case 2:{
                if(!isInitialized){
                    cout<<"请先初始化!"<<endl;
                    break;
                }
                cout<<"输入插入的值:";
                int value;
                cin>>value;
                skipInsert(value);
                printSkipList();
                cout<<"插入成功!"<<endl;
                break;
            }
            case 3:{
                if(!isInitialized){
                    cout<<"请先初始化!"<<endl;
                    break;
                }
                cout<<"输入搜索的值:"<<endl;
                int value;
                cin>>value;
                if(skipSearch(value)->key==value){
                    cout<<"该值存在"<<endl;
                }
                else{
                    cout<<"该值不存在"<<endl;
                }
                break;
            }
            case 4:{
                if(!isInitialized){
                    cout<<"请先初始化!"<<endl;
                    break;
                }
                cout<<"输入删除的值:";
                int value;
                cin>>value;
                skipRemove(value);
                printSkipList();
                break;
            }
            case 5:{
                if(!isInitialized){
                    cout<<"请先初始化!"<<endl;
                    break;
                }
                printSkipList();
                break;
            }
            case 6:{
                cout<<"输入最大层级、生成层级的概率和随机操作数量:";
                int opNum;
                int MaxLevel;
                float Probability;
                cin >> MaxLevel >> Probability>>opNum;
                efficiencyVerification(opNum,MaxLevel,Probability);
                break;
            }

            default:{
                cout<<"输入错误,请重新输入:";
                break;
            }
        }
    }
}

效率分析函数

这个函数通过执行operationNum次随机的插入、搜索或删除函数,统计操作时间总和,通过比较来分析效率。

void SkipList::efficiencyVerification(int operationNum, int MaxLevel, float Probability) {
    //以给定的最大层数和生成层次的可能性初始化
    initialize(MaxLevel,Probability);
    random_device rd;
    mt19937 gen(rd());
    uniform_int_distribution<int> dis(1, 10000);  // 随机生成1到10000的整数
    vector<int>*dataSeries=new vector<int>(operationNum);


    //生成1000个数据的SkipList
    for (int i = 0; i < 1000; ++i) {
        skipInsert(dis(gen));
    }

    //为各种操作提供operationNum个数据
    for(int i=0;i<operationNum;i++){
        dataSeries->emplace_back(dis(gen));
    }

    //生成3种随机操作
    vector<int>* operations=new vector<int>(operationNum);
    uniform_int_distribution<int>dis2(1,3);
    for(int i=0;i<operationNum;i++){
        operations->emplace_back(dis2(gen));
    }

    // 开始计时
    chrono::steady_clock::time_point start = std::chrono::steady_clock::now();
    //执行操作
    int k=0;
    for(int i=0;i<operationNum;i++){
        if((*operations)[i]==1){//插入
            skipInsert((*dataSeries)[i]);
        }
        else if((*operations)[i]==2){//搜索
            skipSearch((*dataSeries)[i]);
        }
        else if((*operations)[i]==3){
            skipRemove((*dataSeries)[i]);
        }
        k++;
    }

    // 结束计时
    chrono::steady_clock::time_point end = chrono::steady_clock::now();
    // 计算时间间隔
    chrono::duration<double> duration = chrono::duration_cast<chrono::duration<double>>(end - start);
    // 输出执行时间
    cout << "执行时间: " << duration.count() << " seconds" << endl;
    delete operations;
    delete dataSeries;
}

运行示例

初始化

插入

搜索

搜索就不展示了,插入和删除都依赖于搜索

删除

操作效率分析

 普通的链表执行随机10000次操作用时如下

相比之下,SkipList的操作效率提高了近100倍

 数据结构实验的全部代码(给HFUTer)

首先,你需要安装SFML库,这个请自己上网查询如何安装

同时,请学习SFML的简单使用。我自己写的也不是很好。

这个过程需要时间,所以不推荐写这个题

SkipList.h


#ifndef SKIPLIST_SFML_SKIPLIST_H
#define SKIPLIST_SFML_SKIPLIST_H

#include <iostream>
#include <SFML/Graphics.hpp>
#include <bits/stdc++.h>
#include<windows.h>

using namespace std;
using namespace sf;
struct Node {
public:
    Node* above;
    Node* below;
    Node* next;
    Node* prev;
    int key;
    Node(int key) {
        this->key = key;
        this->above = nullptr;
        this->below = nullptr;
        this->next = nullptr;
        this->prev = nullptr;
    }
};


class SkipList {
private:
    int speed=50;//演示速度默认为50
    sf::RenderWindow* window;
    Node*head,*tail;//分别表示跳表表头和表尾
    int heightOfSkipList=0;//当前跳表的高度
    float probability=0.5f;//生成随机层次的概率 默认为0.5
    const int NEG_INF=INT_MIN, POS_INF=INT_MAX;

    // ** 搜索、插入、删除等所需函数**//
    bool addLevelOrNot() const;
    void canIncreaseLevel(int level);
    void addEmptyLevel();
    Node*insertAfterAbove(Node*position,Node*q,int key);
    void setBeforeAndAfterReferences(Node*q,Node*newNode);
    void setAboveAndBelowReferences(Node*&position,int key,Node*&newNode,Node*&nodeBeforeNewNode);
    void removeOneNode(Node*&nodeToRemove);
    //** 主要函数  ** //

    void initialize(float probability);

    void printSkipList();//图形化打印SkipList
    void setSpeed(int speed);//设置演示速度
    void initialize();//初始化SkipList
    void initializeSFML();//图形化
    Node*skipSearch(int key);
    void printSkipSearch(int key);
    Node*skipInsert(int key);
    Node*remove(int key);

public:
    explicit SkipList(sf::RenderWindow*window);
    SkipList()=default;
    ~SkipList();
    void operate();

};


#endif //SKIPLIST_SFML_SKIPLIST_H

 SkipList.cpp

#include "SkipList.h"


Node *SkipList::skipSearch(int key) {
    Node*current=head;//从头开始查找
    //从顶向下查找
    while(current->below!= nullptr){

        //如果一层查找完了,就向下一层查找
        current=current->below;
        cout<<"go to the next level"<<endl;
        //如果当前值小于要查找的值,继续查找
        while(key>=current->next->key){
            cout<<"Now Comparing "<<key<<" and "<< current->next->key<<endl;
            printSkipSearch(current->next->key);
            //与下一个结点比较
            current=current->next;
        }

        cout<<endl;
    }
    cout<<"arrive at the bottom level"<<endl;
    cout<<key<<" < "<<current->next->key<<" , here"<<endl;
    return current;
}

void SkipList::printSkipSearch(int key) {
    //注意清空上一次界面的图画
    window->clear(sf::Color::White);
    const int nodeWidth = 40;
    const int nodeHeight = 40;
    const int spacingX = 10;
    const int spacingY = 10;

    Node* starting = head;
    Node* highestLevel = starting;
    int level = heightOfSkipList;
    int offsetY = 0;

    sf::Font font;//设置字体 注意一定要设置,否则没法显示数据
    font.loadFromFile("D:\\Documents\\codes in Clion\\skiplist_SFML\\typewriter\\TYPEWR__.TTF");

    //按层打印
    while (highestLevel != nullptr) {
        //显示Level的图形
        sf::Text levelText;
        levelText.setFont(font);
        levelText.setCharacterSize(20);
        levelText.setString("Level: " + std::to_string(level));
        levelText.setFillColor(sf::Color::Black);
        levelText.setPosition(10, offsetY+17);
        offsetY+=10;//图形添加后在Y轴添加偏移
        window->draw(levelText);
        //下面是显示出当前行的数据
        int offsetX = 0;
        sf::Color color;
        while (starting != nullptr) {
            if(starting->key==key){
                color=sf::Color::Red;//将当前搜索的元素标红,以显示出搜索的过程
            }
            else{
                color=sf::Color::Black;
            }
            sf::RectangleShape shape(sf::Vector2f(nodeWidth, nodeHeight));
            shape.setFillColor(sf::Color::White);
            shape.setOutlineThickness(2);
            shape.setOutlineColor(color);
            shape.setPosition(offsetX + 10, offsetY + 30);

            sf::Text text;
            text.setFont(font);
            text.setCharacterSize(18);
            text.setFillColor(color);
            text.setStyle(sf::Text::Bold);
            //用于head和tail显示无穷
            if (starting->key == INT_MIN) {
                text.setString("-INF");
            } else if (starting->key == INT_MAX) {
                text.setString("INF");
            } else {
                text.setString(std::to_string(starting->key));
            }
            Sleep(101-speed);
            text.setPosition(offsetX + 15, offsetY + 30 + 15);
            window->draw(shape);
            window->draw(text);

            offsetX += nodeWidth + spacingX;
            starting = starting->next;
        }

        highestLevel = highestLevel->below;
        starting = highestLevel;
        level--;
        offsetY += nodeHeight + spacingY;
    }

    window->display();
}

Node *SkipList::skipInsert(int key) {
    Node *position = skipSearch(key);
    Node *q;
    int level = -1;
    int numberOfHeads = -1;
    if (position->key == key) {
        return position;
    }

    //用do while是因为默认插入结点至少要在最底层插入一个
    do {
        numberOfHeads++;
        level++;
        canIncreaseLevel(level);

        q = position;
        //试图向上增长一层
        while (position->above == nullptr) {
            position = position->prev;//向前查找插入的位置
        }
        position=position->above;
        q = insertAfterAbove(position, q, key);
    } while (addLevelOrNot());

    printSkipList();
    return q;
}



bool SkipList::addLevelOrNot() const {
    random_device rd;
    mt19937 gen(rd());
    uniform_real_distribution<float> dis(0.0f, 1.0f);
    //如果随机生成的数小于probability,则添加新的一层
    if(dis(gen)<probability)
        return true;
    else
        return false;
}

void SkipList::canIncreaseLevel(int level) {
    if(level >= heightOfSkipList){
        heightOfSkipList++;
        addEmptyLevel();
    }
}

//添加一层空层
void SkipList::addEmptyLevel() {
    //生成新的空层的头结点为尾结点
    Node*newHeadNode = new Node(NEG_INF);
    Node*newTailNode = new Node(POS_INF);

    newHeadNode->next = newTailNode;
    newHeadNode->below = head;
    newTailNode->prev = newHeadNode;
    newTailNode->below = tail;

    head->above=newHeadNode;
    tail->above=newTailNode;

    head = newHeadNode;
    tail = newTailNode;
}

//在当前层的上面一层插入结点
Node *SkipList::insertAfterAbove(Node*position,Node*q,int key) {
    Node*newNode=new Node(key);
    Node*nodeBeforeNewNode=position->below->below;
    setBeforeAndAfterReferences(q,newNode);
    setAboveAndBelowReferences(position,key,newNode,nodeBeforeNewNode);

    return newNode;
}

//将新结点newNode插入到结点q之后 这样就将新结点插入到了最下面一层
//@
void SkipList::setBeforeAndAfterReferences(Node *q, Node *newNode) {
    newNode->next=q->next;
    newNode->prev=q;
    q->next->prev=newNode;
    q->next=newNode;
}

void SkipList::setAboveAndBelowReferences(Node *&position, int key, Node *&newNode, Node *&nodeBeforeNewNode) {
    if(nodeBeforeNewNode!= nullptr){
        while(true){
            if(nodeBeforeNewNode->next->key!=key){
                nodeBeforeNewNode=nodeBeforeNewNode->next;
            }
            else{
                break;
            }
        }

        newNode->below=nodeBeforeNewNode->next;
        nodeBeforeNewNode->next->above=newNode;
    }
    if(position!= nullptr){
        if(position->next->key==key){
            newNode->above=position->next;
        }
    }
}


Node *SkipList::remove(int key) {
    Node *nodeToRemove = skipSearch(key);
    if (nodeToRemove->key != key) {
        return nullptr;
    }
    removeOneNode(nodeToRemove);

    while(nodeToRemove!= nullptr){
        removeOneNode(nodeToRemove);
        if(nodeToRemove->above!= nullptr){
            nodeToRemove=nodeToRemove->above;
        }
        else{
            break;
        }
    }
    printSkipList();
    return nodeToRemove;
}

/*
 * @brief 删除一个结点
 * @param 要删除的结点
 * @return void
 */
void SkipList::removeOneNode(Node *&nodeToRemove) {
    Node *afterNodeToRemove = nodeToRemove->next;
    Node *beforeNodeToRemove = nodeToRemove->prev;
    beforeNodeToRemove->next = afterNodeToRemove;
    afterNodeToRemove->prev = beforeNodeToRemove;
}

/*
 * @brief 析构函数,删除操作过程中产生的结点
 * @param 无
 *
 */

SkipList::~SkipList() {
    queue<Node*> nodeQueue;
    nodeQueue.push(head);
    //将所有层的头结点都放入队列
    while(!head->above){
        nodeQueue.push(head->above);
    }
    while(!nodeQueue.empty()){
        Node*current=nodeQueue.front();
        Node*next=current->next;//删除这条链上所有的指针
        while(next){
            delete current;
            current=next;
            next=next->next;
        }
        delete current;
    }
    delete window;
}

/*
 * @brief 初始化SkipList
 * @param 空
 * @return 空
 */
void SkipList::initialize() {
    heightOfSkipList=0;
    head = new Node(NEG_INF); // 表头初始化为负无穷大
    tail = new Node(POS_INF); // 表尾初始化为正无穷大
    head->next = tail;
    tail->prev = head;
}
/*
 * @brief 图形化初始化
 * @param void
 * @return void
 */
void SkipList::initializeSFML() {
    heightOfSkipList=0;
    head = new Node(NEG_INF); // 表头初始化为负无穷大
    tail = new Node(POS_INF); // 表尾初始化为正无穷大
    head->next = tail;
    tail->prev = head;
    printSkipList();
}

/*
 * @brief 初始化SkipList的最大层次和随机生成一层的可能性
 * @param Probability SkipList随机生成一层的概率
 * @return void
 */
void SkipList::initialize( float Probability) {
    initialize();
    this->probability = Probability;
}

/*
 * @brief 显示操作菜单
 * @param void
 * @return void
 */
void menu(){
    cout<<"1、初始化SkipList"<<endl;
    cout<<"2、插入"<<endl;
    cout<<"3、搜索"<<endl;
    cout<<"4、删除"<<endl;
    cout<<"5、设置演示速度(1-100)"<<endl;
    cout<<"输入你的选择"<<endl;
}
/*
 * @brief 图形化操作接口
 * @param void
 * @return void
 */

void SkipList::operate() {
    menu();
    bool isInitialized=false;
    int select;
    while(true){
        cin>>select;
        switch (select) {
            case 1:{
                initializeSFML();
                isInitialized= true;
                break;
            }
            case 2:{
                if(!isInitialized){
                    cout<<"请先初始化!"<<endl;
                    break;
                }
                cout<<"输入插入的值:";
                int value;
                cin>>value;
                skipInsert(value);
                break;
            }
            case 3:{
                if(!isInitialized){
                    cout<<"请先初始化!"<<endl;
                    break;
                }
                cout<<"输入搜索的值:"<<endl;
                int value;
                cin>>value;
                skipSearch(value);
                break;
            }
            case 4:{
                if(!isInitialized){
                    cout<<"请先初始化!"<<endl;
                    break;
                }
                cout<<"输入删除的值:";
                int value;
                cin>>value;
                remove(value);
                break;
            }
            case 5:{
                cout<<"输入演示速度:";
                int s;
                while(cin>>s){
                    if(s<1||s>100){
                        cout<<"输入错误,请重新输入!"<<endl;
                    }
                    else{
                        break;
                    }
                }
                setSpeed(s);
                cout<<"设置成功!"<<endl;
                break;
            }
            default:{
                cout<<"输入错误,请重新输入:";
                break;
            }
        }
    }

}

/*
 * @brief 图形化操作构造函数
 * @param window 图形化主页面
 * @return void
 */
SkipList::SkipList(sf::RenderWindow *window) {
    this->window=window;
}

/*


/*
 * @brief 图形化显示SkipList
 * @param void
 * @return void
 */
void SkipList::printSkipList() {
    //注意清空上次的界面
    window->clear(sf::Color::White);
    //方块的宽、高以及在 X Y 方向上的间隔
    const int nodeWidth = 40;
    const int nodeHeight = 40;
    const int spacingX = 10;
    const int spacingY = 10;

    //从头还是打印
    Node* starting = head;
    Node* highestLevel = starting;
    int level = heightOfSkipList;
    int offsetY = 0;

    sf::Font font;
    font.loadFromFile("D:\\Documents\\codes in Clion\\skiplist1_1\\typewriter\\TYPEWR__.TTF");

    while (highestLevel != nullptr) {
        sf::Text levelText;
        levelText.setFont(font);
        levelText.setCharacterSize(20);
        levelText.setString("Level: " + std::to_string(level));
        levelText.setFillColor(sf::Color::Black);
        levelText.setPosition(10, offsetY+17);
        offsetY+=10;
        window->draw(levelText);

        int offsetX = 0;
        while (starting != nullptr) {
            sf::RectangleShape shape(sf::Vector2f(nodeWidth, nodeHeight));
            shape.setFillColor(sf::Color::White);
            shape.setOutlineThickness(2);
            shape.setOutlineColor(sf::Color::Black);
            shape.setPosition(offsetX + 10, offsetY + 30);

            sf::Text text;
            text.setFont(font);
            text.setCharacterSize(18);
            text.setFillColor(sf::Color::Black);
            text.setStyle(sf::Text::Bold);

            if (starting->key == INT_MIN) {
                text.setString("-INF");
            } else if (starting->key == INT_MAX) {
                text.setString("INF");
            } else {
                text.setString(std::to_string(starting->key));
            }

            text.setPosition(offsetX + 15, offsetY + 30 + 15);
            Sleep(101-speed);
            window->draw(shape);
            window->draw(text);

            offsetX += nodeWidth + spacingX;
            starting = starting->next;
        }

        highestLevel = highestLevel->below;
        starting = highestLevel;
        level--;
        offsetY += nodeHeight + spacingY;
    }

    window->display();
}

void SkipList::setSpeed(int speed) {
    this->speed=speed;
}











 main.cpp


#include "SkipList.h"

int main()
{

    sf::RenderWindow window(sf::VideoMode(1000, 800), "SkipList visualization");
    window.setPosition(sf::Vector2i(0, 0));
    SkipList skipList(&window);
    skipList.operate();

    return 0;
}

图形化测试

初始化                        

      插入

 

搜索

删除需要当面演示。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/701181.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

xftp下载安装及简单使用

一、xftp简介 Xftp是一个功能强大的SFTP、FTP 文件传输软件。使用了 Xftp 以后&#xff0c;MS Windows 用户能安全地在 UNIX/Linux 和 Windows PC 之间传输文件。Xftp 能同时适应初级用户和高级用户的需要。它采用了标准的 Windows 风格的向导&#xff0c;它简单的界面能与其他…

GEACC-5595交换机

GE反射内存交换机特性 l 1 到 8 口可配置SFP收发器 l 大可级联256个节点 l 自动旁路故障节点 l 自动旁路模式可选 l 配置可选节点传输距离达10公里 l 可插拔收发器支持单模或者多模模式 l 1x8 口或者2x4 口 l 可以通过串口了解状态进行设置 概述 GE反射内存交换机&#xff08;以…

Linux-- . 和 ..

一、含义&#xff1a; . :表示当前路径 .. :表示直接上级路径 二、验证&#xff1a; 三、..用来返回上级目录&#xff0c;.有上面作用&#xff1f; 我们在Linux下写代码后生成的可执行程序a.out,我们运行它时的指令是./a.out 故使用.可以限定我们要执行的可执行程序在什么…

C语言编程—内存管理

C语言中的动态内存管理。C语言为内存的分配和管理提供了几个函数。这些函数可以在 <stdlib.h> 头文件中找到。 在C语言中&#xff0c;内存是通过指针变量来管理的。指针是一个变量&#xff0c;它存储了一个内存地址&#xff0c;这个内存地址可以指向任何数据类型的变量&…

JavaScript 中的行继续符

这个简短的 JavaScript 文章涵盖了 JavaScript 中的词法语法。 此外&#xff0c;还将使用各种新的换行技术深入介绍字符串&#xff0c;以及在处理这些字符串时如何处理换行符。 JavaScript 中的词法语法 在计算机科学中&#xff0c;词法语法是一种描述标记句法的技术语法。 该…

Spring Boot中的Profile:原理、用法与示例

Spring Boot中的Profile&#xff1a;原理、用法与示例 前言 Spring Boot 是一个快速开发 Spring 应用程序的框架&#xff0c;它提供了很多有用的功能和特性。其中&#xff0c;Profile 是一个常用的功能&#xff0c;它可以根据不同的环境配置来加载不同的配置文件&#xff0c;…

轻量级网络CNN系列(二):GhostNetV2

欢迎关注公众号 – AICV与前沿 欢迎关注公众号 – AICV与前沿 回顾 &#xff08;1&#xff09;Depthwise与Pointwise卷积 DW卷积的一个卷积核负责一个通道&#xff0c;例如对一个355的图片&#xff0c;输出通道数要与输入通道数相同&#xff0c;则普通卷积操作需要3333的卷积…

记一次Smartbi登录绕过

FOFA&#xff1a;app"SMARTBI" 找到目标站点验证是否存在漏洞 域名后面拼接以下路径 /smartbi/vision/RMIServlet 有下面的回显说明有可能存在漏洞(有些站点不行) 用Hackbar发送post请求 失败的话更改用户:system,public,service三个内置用户都尝试一遍 发送请求…

AutoSAR系列讲解(入门篇)3.6-RTE与Interface接口

RTE与Interface接口 一、Interface接口总览 二、AutoSAR接口 三、标准接口 四、标准AutoSAR接口 一、Interface接口总览 少说废话&#xff0c;先上图 上图将所有的接口以及其分布的位置都详细的标识了出来&#xff0c;还是用的原来的那张ECU的图添加的&#xff0c;方便大家…

用友NC uapjs RCE漏洞复现(CNVD-C-2023-76801)

0x01 产品简介 用友NC是一款企业级ERP软件。作为一种信息化管理工具&#xff0c;用友NC提供了一系列业务管理模块&#xff0c;包括财务会计、采购管理、销售管理、物料管理、生产计划和人力资源管理等&#xff0c;帮助企业实现数字化转型和高效管理。 0x02 漏洞概述 用友NC及N…

基于Java+Vue前后端分离医学生在线学习交流平台设计实现(源码+lw+部署文档+讲解等)

博主介绍&#xff1a;✌全网粉丝30W,csdn特邀作者、博客专家、CSDN新星计划导师、Java领域优质创作者,博客之星、掘金/华为云/阿里云/InfoQ等平台优质作者、专注于Java技术领域和毕业项目实战✌ &#x1f345;文末获取源码联系&#x1f345; &#x1f447;&#x1f3fb; 精彩专…

条件变量wait的另一种形式

flag.wait()里面可以有两个参数&#xff0c;第一个是锁&#xff0c;第二份一般是lambda表达式之类的谓词&#xff0c;其返回值一般是bool类型。 此时由于lambda表达式返回不为真&#xff0c;所以此时仍在等待。 此时lambda表达式就算正常的。

java学习记录之struts2注解校验ognl自定义拦截器

Result: 在struts中,Reuslt用于处理Action返回的结果.将我们之前在Servlet中耦合的功能代码.解耦了.将结果处理的代码封装到了Result中.Struts2已经预置了一些处理类.Dispatcher 转发Redirect 重定向redirectAction 重定向到一个Action(与redirect配合)plaintext 纯文本stream …

帆软report10.0,从人工智能到人工+智能,day1

前言&#xff1a; 最近在全基地推进品质CTQ&#xff0c;这个功能说实话在一年前已经开发完成了&#xff0c;中间修修补补&#xff0c;跌跌代代不下10-20版。看起来好像有点夸张吧。其实说实话&#xff0c;真正的BI系统用好&#xff0c;是需要业务人员和IT部门反复碰撞的。一年前…

volume 、namespace

顺带说一下 volume 和 namespace &#xff0c;咱们就开始分享一下 service 是什么 volume 是什么 还记得 docker 的 volume 吗&#xff0c;是一个数据卷 在 K8S 中&#xff0c;volume 是 pod 中能够被多个容器访问的共享目录 &#xff0c;实际上和 docker 是一样的 volume 是…

分享成为一个优秀的测试工程师需要具备哪些知识和经验?

根据我的观察&#xff0c;优秀的测试人员可以做的事情可以包括如下3点&#xff1a; 由单纯的测试变成项目质量保证工作 持续集成探索和推动和自动化测试技术研究 测试相关工具的开发 1、我们先来讲第一点&#xff0c;由单纯的测试变成项目质量保证工作 测试&#xff0c;从狭义…

versionOS开发笔记 01,如何在现有项目中启用 versionOS ?

visionOS 开发笔记 01&#xff0c;如何在现有项目中启用 visionOS &#xff1f; 在 Targets 中的 Supportted Destinations 中增加 Apple Vision 然后在项目的 Run Destination 中选择 Apple Vision Pro 就可以了

python+selenium环境搭建(一)

pythonselenium自动化测试环境包括四个部分&#xff1a;python、selenium、chrome谷歌浏览器、chrome谷歌浏览器驱动。其中浏览器也可以是火狐&#xff0c;IE等。目前自动化的主流浏览器还是chrome谷歌浏览器。 1.python的安装 目前测试行业应用最广的编程语言当属Python为首…

【C++】—— 继承

序言&#xff1a; 在之前&#xff0c;我们已经完成了对 C 初阶的讲解。接下来&#xff0c;我将带领大家学习关于C 进阶的相关知识&#xff0c;而今天我给大家介绍的就是关于 C三大特性之一的——继承。 目录 &#xff08;一&#xff09;继承的概念及定义 1、继承的概念 2、…

git常用命令之log

10. log 10.1 查看log 命令作用延展阅读git log输出 commit hsitory with commit detailgit reflog输出 HEAD ref 的 reflog链接git log --oneline--oneline选项会把提交信息压缩输出在单行。默认情况下&#xff0c;只显示commit id和commit message的第一行内容。$ git log …