IT数码 购物 网址 头条 软件 日历 阅读 图书馆
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
图片批量下载器
↓批量下载图片,美女图库↓
图片自动播放器
↓图片自动播放器↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁
 
   -> 数据结构与算法 -> 求解顺序统计量的7种方法 -> 正文阅读

[数据结构与算法]求解顺序统计量的7种方法

七.动态顺序统计树

? ? ? ? 借助红黑树数据结构的扩张,每个节点都存储自己含有的字节点的个数,即size,nil节点的size为0。

? ? ? ? 其中size = left.size + right.size +1,利用节点的该信息,可以完成和前面三、四中递归查找的工作,一个元素在以该元素为根的子树中的Rank就是left.size + 1。这是本算法的关键所在。

? ? ? ? 如果查询第K个元素,则和root比较,计算root的Rank,如果K<Rank,说明在该树的左子树中存在第K个元素;如果K>Rank,则在右子树中查找第K-Rank个元素;如果相等则返回即可。这和前面利用快速排序主元划分思想的求解过程是十分类似的,只是该算法无需查找主元,只需要动态维护一棵扩张了的红黑树,维护该树的代价,和在该树中进行的操作(Insert、Delete、Find)等操作都和高度直接相关,即上述所有操作的时间复杂度均为O(lgn),是不是快到超乎你的想象呢?

? ? ? ? 不过红黑树性质虽好,但难在维护,在插入和删除过程中都需要动态维护该棵红黑树,红黑树的插入和删除过程都存在一个操作节点和着色两部分,第一部分沿着路径增加或减少节点的size,第二部分操作则涉及到旋转,在旋转中可以使用线性时间则可以维护该size。

1.Insert过程中的维护

第一阶段: 插入的节点从root向下寻找空的合适的位置,经过该路径的节点的size均+1;

第二阶段:插入的节点在着色的时候涉及到左旋右旋,如果右旋,则直接x = y.size;(旋转前)? ? ? ????????????????y.size = y.left + y.right + 1;( 旋转后)

比较简单吧!只涉及到两个过程。删除就要复杂一点了。

?2.?Delete过程中的维护

第一阶段:

????????从根到删除的节点,沿着该路径上的节点size均-1,同时,如果拼接的节点不是删除节点的直接后继,则要从拼接的节点到x路径上size-1,因为从x前方抽走了y,沿着该抽走路径的节点size-1,然后将y拼接上原来要删除node的位置,将其size置为node->size-1,完成维护工作。

????????是不是比较复杂?

第二阶段:

????????插入的节点在着色的时候涉及到左旋右旋,如果右旋,则直接?x = y.size;(旋转前)? ? ?

????????y.size = y.left + y.right + 1;( 旋转后)

? ? ? ? 聪明的你,一定发现了插入和删除的第二阶段的相似性。算法的复杂点在于删除操作对节点路径的影响不止一条,涉及到元素跨路径的拼接。

? ? ? ? 原文中的部分内容如下:?

? ? ? ? 代码如下:分为头文件Order_Statistic_Tree.h 和源文件Order_Statistic_Tree.c 以及main.c函数用于测试和演示。

? ? ? ? 1.Order_Statistic_Tree.h

//
// Created by long on 2021/11/22.
//

#ifndef ORDER_STATISTIC_TREE_H
#define ORDER_STATISTIC_TREE_H

#include <stdlib.h>
#include <stdio.h>
#include <time.h>
/*
 * 基于红黑树的扩张
 * 对每个节点添加size作为维护内容
 * 通过size可以很方便求出RANK
 * 在其上进行按顺序查找、求出给定元素的RANK、求给定节点的RANK等
 * 在红黑树的插入删除的两个阶段维护各个节点size的大小
 * 借助红黑树的优质特性可以使上述操作在Log(n)的时间复杂度下完成
 * 相较于线性的查找便捷许多
 */


typedef _Bool bool;
typedef int Item;
//定义枚举常量表示红黑
enum Color{
    Red,Black
};
typedef struct RBTree{
    int size;//节点所含的节点数 size.left+size.right+1
    Item data;
    enum Color color;//表示红黑的颜色标记
    struct RBTree*P,*Lchild,*Rchild;
}RBTree,*RBTreePointer;

//Search the node of item
RBTreePointer Search(RBTreePointer Tree, Item data);


//Insert the node into the binary tree
RBTreePointer Insert(RBTreePointer Tree, Item data);

RBTreePointer Delete(RBTreePointer Tree,Item data);//删除节点

//Fixup the color of the node which insert
RBTreePointer RB_Insert_Fixup(RBTreePointer Tree,RBTreePointer Node);

RBTreePointer RB_Delete_Fixup(RBTreePointer Tree,RBTreePointer Node);

//如果不使用带返回值的函数 对root的操作是很复杂的
//左旋
RBTreePointer LeftRotate(RBTreePointer Tree,RBTreePointer Node);

//右旋
RBTreePointer RightRotate(RBTreePointer Tree,RBTreePointer Node);

//The help function of delete
void TransPlant(RBTreePointer *Tree, RBTreePointer u, RBTreePointer v);

//Delete the Item from RBTree
bool RBDelete(RBTreePointer *Tree,Item data);

//The help function
RBTreePointer Minimize(RBTreePointer Tree,RBTreePointer node);

//查找第i大的元素
RBTreePointer OS_SELECT(RBTreePointer Tree,int i);

//辅助函数(核心函数
RBTreePointer OS_SELECT_Help(RBTreePointer Tree,int i);

//查找Node在Tree中的位置
int OS_RANK(RBTreePointer Tree,RBTreePointer Node);

int OS_RANK_ELEMENT(RBTreePointer Tree,int data);


RBTreePointer Successor(RBTreePointer Tree, Item data);

//Traverse the binary search tree
void InorderTraverse(RBTreePointer Tree,RBTreePointer nil);



#endif //ORDER_STATISTIC_TREE_H

????????2.Order_Statistic_Tree.c

//
// Created by 周龙 on 2021/11/22.
//

#include "Order_Statistic_Tree.h"

RBTreePointer Insert(RBTreePointer Tree, Item data) {
    //节点不存在
    if (!Tree) {
        Tree = (RBTreePointer) malloc(sizeof(RBTree));//Allocate the memory
        Tree->P = Tree->Lchild = Tree->Rchild = (RBTreePointer) malloc(sizeof(RBTree));//The parent of root is null
        Tree->data = data;
        Tree->size=1;
        Tree->color=Black;
        Tree->P->color=Black;//Set the color of nil black
        Tree->P->size=0;//nil不计节点数
        return Tree;
    }
    //在查找插入位置的时候沿着该路径使size++
    else {
        RBTreePointer Reserve = Tree;//Reserve the node of the tree
        //Find the site to insert
        RBTreePointer nil=Reserve->P;//The node of nil
        RBTreePointer Pre = Tree;//To record the predecessor of node
        while (Tree!=nil) {
            Pre = Tree;//记录前驱 用于更新
            Tree->size++;
            if (data == Tree->data) {
                //存在相同的内容无需插入 这个判断对size来说有点bug 不支持重复元素
                return Reserve;
            }
            if (data > Tree->data) {

                //大于则在右边
                Tree = Tree->Rchild;
            } else {

                //小于则在左边
                Tree = Tree->Lchild;
            }
        }
        //找到待插入的空节点 Pre不可能不存在
        RBTreePointer New_node = (RBTreePointer) malloc(sizeof(RBTree));
        New_node->data = data;
        New_node->Rchild = New_node->Lchild = nil;
        New_node->color=Red;//The new insert node must be red
        New_node->P = Pre;
        New_node->size=1;
        //在右侧插入
        if (data > Pre->data)
            Pre->Rchild = New_node;
            //在左侧插入
        else
            Pre->Lchild = New_node;
        Tree= RB_Insert_Fixup(Reserve,New_node);//在着色时调整fixup对size的操作
        return Tree;//Fixup the color of red black tree
    }
}

//6种情况 3种对称
RBTreePointer RB_Insert_Fixup(RBTreePointer Tree,RBTreePointer Node){
    RBTreePointer Reserve=Tree;
    RBTreePointer Nil=Tree->P;
    //The conflict of node
    while(Node->P->color==Red){
        //左侧
        if(Node->P==Node->P->P->Lchild){
            RBTreePointer Temp=Node->P->P->Rchild;
            //The uncle of the node is red
            if(Temp->color==Red){
                Node->P->color=Black;
                Temp->color=Black;
                Node=Node->P->P;
            }
            else{
                //判断是否需要左旋
                if(Node==Node->P->Rchild){
                    Node=Node->P;
                    //此处的根节点不会发生旋转
                    LeftRotate(Tree,Node);//Node left rotate
                }
                //处理右旋 判断根节点是否需要参加右旋
                //Node 为red 令node->p 为 black 保证不冲突 而后旋转 使节点node->p->p为中间节点
                Node->P->color=Black;
                Node->P->P->color=Red;
                //为根节点
                if(Node->P->P==Tree){
                    //修改根节点
                    RBTreePointer temp = RightRotate(Tree,Node->P->P);
                    Reserve=temp;
                }
                else{
                    RightRotate(Tree,Node->P->P);//Just right rotate
                }
            }
        }
        else{
            RBTreePointer Temp=Node->P->P->Lchild;
            //The uncle of the node is red
            if(Temp->color==Red){
                Node->P->color=Black;
                Temp->color=Black;
                Node=Node->P->P;
            }
                //右侧
            else{
                //判断是否需要右旋
                if(Node==Node->P->Lchild){
                    Node=Node->P;
                    //此处的根节点不会发生旋转
                    RightRotate(Tree,Node);//Node left rotate
                }
                //处理左旋 判断根节点是否需要参加左旋
                //Node 为red 令node->p 为 black 保证不冲突 而后旋转 使节点node->p->p为中间节点
                Node->P->color=Black;
                Node->P->P->color=Red;
                RBTreePointer temp=(RBTreePointer)malloc(sizeof(RBTree));
                //为根节点
                if(Node->P->P==Tree){
                    //修改根节点
                    temp = LeftRotate(Tree,Node->P->P);
                    Reserve=temp;
                }
                else{
                    LeftRotate(Tree,Node->P->P);//Just left rotate
                }
            }
        }
        //Fix the color of root
        Reserve->color=Black;
        Reserve->P=Nil;
        Reserve->P->color=Black;
    }
    return Reserve;
}



//左旋
RBTreePointer LeftRotate(RBTreePointer Tree,RBTreePointer Node){
    RBTreePointer nil = Tree->P;
    RBTreePointer temp=Node->Rchild;
    Node->Rchild=temp->Lchild;//连上child
    if(temp->Lchild!=nil){
        temp->Lchild->P=Node;//修改parent指针
    }
    //修改Node的亲节点
    temp->P=Node->P;
    //为ROOT
    if(Node->P!=nil){
        //左侧
        if(Node->P->Lchild==Node){
            Node->P->Lchild=temp;
        }
            //右侧
        else{
            Node->P->Rchild=temp;
        }
    }
    //链接node和temp
    temp->Lchild=Node;
    Node->P=temp;
    //同样的更新方式 维护size
    temp->size=Node->size;
    Node->size=Node->Rchild->size+1+Node->Lchild->size;
    return temp;//返回旋转后的节点
}



//右旋
RBTreePointer RightRotate(RBTreePointer Tree,RBTreePointer Node){
    RBTreePointer nil = Tree->P;
    RBTreePointer temp=Node->Lchild;//新的root node被旋转至right
    Node->Lchild=temp->Rchild;//连上child
    //是否是nil节点
    if(temp->Rchild!=nil){
        temp->Rchild->P=Node;//修改parent指针
    }
    //修改Node的亲节点
    //为ROOT
    temp->P=Node->P;
    if(Node->P!=nil){
        //左侧
        if(Node->P->Lchild==Node){
            Node->P->Lchild=temp;
        }
            //右侧
        else{
            Node->P->Rchild=temp;
        }
    }
    //链接node和temp
    temp->Rchild=Node;
    Node->P=temp;
    //更新size
    temp->size=Node->size;
    Node->size=Node->Rchild->size+1+Node->Lchild->size;
    return temp;//返回旋转后的节点
}


//Traverse the binary search tree
void InorderTraverse(RBTreePointer Tree,RBTreePointer nil) {
    //Traverse the data of tree
    if (Tree == nil ) {
        return;
    } else {
        //The Inorder Traverse
        InorderTraverse(Tree->Lchild,nil);
        printf("Data=%d Color=%d Size=%d\n", Tree->data,Tree->color,Tree->size);
        InorderTraverse(Tree->Rchild,nil);
    }
}


//修改删除的方式
bool RBDelete(RBTreePointer *Tree,Item data){
    /*
     * 沿用z的删除方式 使用y作为带插入的节点 而使用x作为新拼接上的节点
     * y插入z并不会导致节点内容的丢失 节点内容的丢失在x点处
     * 因而需要记录y源的颜色 如果为黑
     * 则表明在x进行替代的过程中使黑色节点的个数减少了
     */
    //使用指针传入的方式以便能修改指针
    RBTreePointer *Restore=Tree;//直接保存二级指针的值
    int flag;
    RBTreePointer Temp=*Tree;
    RBTreePointer nil=Temp->P;
    //Find the node which data equal to data
    while (Temp != nil) {
        //沿着路径减少size的值
        if (data == Temp->data) {
            break;
        }
        Temp->size--;
        if (Temp->data < data) {
            Temp = Temp->Rchild;
        } else {
            Temp = Temp->Lchild;
        }
    }
    if(Temp==nil){
        printf("The node is not exists\n");
        return 0;//The node is not exists
    }
    RBTreePointer z=Temp;
    RBTreePointer y=z;
    RBTreePointer x;
    int y_original_color=y->color;//记录y的原始颜色
    //开始delete
    if (z->Lchild ==(*Tree)->P ) {
        //connect the Rchild
        x=z->Rchild;//记录拼接的位置
        TransPlant(Restore, z, z->Rchild);
    }
    else if (z->Rchild == (*Tree)->P) {
        //connect the Lchild
        x=z->Lchild;//同上
        TransPlant(Restore,x, x->Lchild);
    }
    else{
        RBTreePointer pre = (*Tree)->P;
        RBTreePointer node=z->Rchild;
        while (node != (*Tree)->P) {
            node->size--;
            pre = node;
            node = node->Lchild;//Minimize data is always in the left
        }
        y= pre;
        y_original_color=y->color;//找到y的位置 即后继节点
        x=y->Rchild;//即将拼接的节点
        /*
         * 这一部分是十分关键的!!!!!!
         * 并不是多余的操作
         * 本身x如果为nil那么p是随意指向的
         * 那么如果在后面进行调整的时候是无法完成的
         * 因而需要修改x的p的指向 令p就是指向的删除的节点
        */
        x->P=y;
        if (y!= z->Rchild) {
            x->P=y;//??
            TransPlant(Tree,y,x);//concat the node
            y->Rchild = z->Rchild;
            y->Rchild->P = y;
        }
        //The successor must in the right of the node
        TransPlant(Restore,z,y);//拼接y到z的位置
        y->color=z->color;//修改y的颜色
        y->size=z->size-1;//修改z的节点大小
        //处理z的左节点
        y->Lchild=z->Lchild;
        y->Lchild->P = y;
    }
    if(y_original_color==Black){
        *Tree= RB_Delete_Fixup(*Tree,x);
    }
    return 1;
}




RBTreePointer RB_Delete_Fixup(RBTreePointer Tree,RBTreePointer x){
    RBTreePointer Restore = Tree;
    RBTreePointer w;
    //为了分担这个黑色的节点
    while(x!=Tree && x->color==Black){
        //在左侧
        if(x==x->P->Lchild){
            w=x->P->Rchild;//sibling node
            //为红则以w节点作为黑节点的消解节点
            if(w->color==Red){
                //旋转时一定要随时注意根节点的位置
                w->color=Black;//消解黑色节点
                x->P->color=Red;
                //若为根节点
                if(x==Tree)
                    //修改Tree的值然后用于返回
                    Tree= LeftRotate(Restore,x->P);
                else
                    LeftRotate(Restore,x->P);
            }
                //节点颜色为黑色
            else{
                //两个都是黑色
                if(w->Lchild->color==Black&&w->Rchild->color==Black){
                    w->color=Red;
                    x=x->P;//把Black向上推 直接结束循环 在循环体结束后为x的颜色赋值为Black
                    //不管剩下的红节点了吗
                }
                    //右侧为Black
                else {
                    //右侧
                    if (w->Rchild->color == Black) {
                        w->Lchild->color = Red;
                        w->color = Red;
                        //右旋w不会是根节点 则直接旋转就行
                        RightRotate(Restore, w);
                        w = x->P->Rchild;//重回原位置
                    }
                    //左侧
                    w->color=x->P->color;
                    x->P->color=Black;//旋转后不破坏prenode
                    w->Rchild->color=Black;//借助w的Rchild消解掉多余的黑色节点
                    //观察是否处理到了root
                    if(x->P==Tree){
                        Tree= LeftRotate(Restore,x->P);
                    }
                    else{
                        LeftRotate(Restore,x->P);
                    }
                    x=Tree;//x为根节点
                }
            }
        }
            //X在右侧
        else{
//            return Tree;
            //在右侧
            if(x==x->P->Rchild){
                w=x->P->Lchild;//sibling node
                //为红则以w节点作为黑节点的消解节点
                if(w->color==Red){
                    //旋转时一定要随时注意根节点的位置
                    w->color=Black;//消解黑色节点
                    x->P->color=Red;
                    //若为根节点
                    if(x==Tree)
                        //修改Tree的值然后用于返回
                        Tree= RightRotate(Restore,x->P);
                    else
                        RightRotate(Restore,x->P);
                }
                    //节点颜色为黑色
                else{
                    //两个都是黑色
                    if(w->Lchild->color==Black&&w->Rchild->color==Black){
                        w->color=Red;
                        x=x->P;//把Black向上推 直接结束循环 在循环体结束后为x的颜色赋值为Black
                        //不管剩下的红节点了吗 在节点结束后抹去颜色
                    }
                        //右侧为Black
                    else {
                        //左侧
                        if (w->Lchild->color == Black) {
                            w->Rchild->color = Red;
                            w->color = Red;
                            //左旋w不会是根节点 则直接旋转就行
                            LeftRotate(Restore, w);
                            w = x->P->Lchild;//重回原位置
                        }
                        //左侧
                        w->color=x->P->color;
                        x->P->color=Black;//旋转后不破坏prenode
                        w->Lchild->color=Black;//借助w的Lchild消解掉多余的黑色节点
                        //观察是否处理到了root
                        if(x->P==Tree){
                            Tree= RightRotate(Restore,x->P);
                        }
                        else{
                            RightRotate(Restore,x->P);
                        }
                        x=Tree;//x为根节点
                    }
                }
            }
        }
    }
    x->color=Black;
    return Tree;
}




//Let v be u->parent->child
void TransPlant(RBTreePointer *Tree, RBTreePointer u, RBTreePointer v) {
    //Reverse the root
    if (u->P ==(*Tree)->P) {
        *Tree = v;//The v is the root of BST
    }
        //The left child
    else if (u == u->P->Lchild) {
        u->P->Lchild = v;
    } else {
        u->P->Rchild = v;
    }
    //由于NULL代表一个nil节点 则无需判断 直接全指 为空也指
    v->P = u->P;//尽管是nil 它的parent已然需要往前指 改动节点为v
}



RBTreePointer Minimize(RBTreePointer Tree,RBTreePointer node) {
    RBTreePointer pre = Tree->P;
    while (node != Tree->P) {
        pre = node;
        node = node->Lchild;//Minimize data is always in the left
    }
    return pre;
}

RBTreePointer Maximum(RBTreePointer Tree,RBTreePointer node) {
    RBTreePointer pre = Tree->P;
    while (node != Tree->P) {
        pre = node;
        node = node->Rchild;//Minimize data is always in the left
    }
    return pre;
}

//Visit the successor of the assign node from binary search tree
RBTreePointer Successor(RBTreePointer Tree, Item data) {
    RBTreePointer Temp=Tree;
    RBTreePointer KeyNode = Search(Temp, data);//Find the node which will  delete
    if (KeyNode == Temp->P) {
        return Temp->P;
    }
    if (KeyNode->Rchild) {
        return Minimize(Tree,KeyNode->Rchild);
    } else {
        //Find the pre data
        RBTreePointer pre = KeyNode->P;
        while (pre && pre->Rchild == KeyNode) {
            KeyNode = KeyNode->P;
            pre = KeyNode->P;
        }
        return pre;
        //直到找到root或者node 如果是root表明没有节点为前驱 返回NULL
    }
}


//Search the item from binary search tree
RBTreePointer Search(RBTreePointer Tree, Item data) {
    RBTreePointer Temp=Tree;
    //Find the node which data equal to data
    while (Temp != Tree->P) {
        if (data == Temp->data) {
            return Temp;
        } else if (Temp->data < data) {
            Temp = Temp->Rchild;
        } else {
            Temp = Temp->Lchild;
        }
    }
    return Tree->P;//If the search is fail
}

RBTreePointer OS_SELECT(RBTreePointer Tree,int i){
    if(i>Tree->size){
        printf("The rank is more than size of array\n");
        return NULL;
    }
    else{
        return OS_SELECT_Help(Tree,i);
    }
}

RBTreePointer OS_SELECT_Help(RBTreePointer Tree,int i){
    int r=Tree->Lchild->size+1;//Rank Of Root
    if(r==i){
        return Tree;
    }
    else if(r>i){
       return OS_SELECT(Tree->Lchild,i);//Tree的Rank过大 则向左
    }
    else{
       return  OS_SELECT(Tree->Rchild,i-r);
    }
}


//查找Node在Tree中的位置
int OS_RANK(RBTreePointer Tree,RBTreePointer Node){
    if(Node==NULL){
        return 0;
    }
    int r=Node->Lchild->size+1;
    while(Node!=Tree){
        //如果是Rchild 则表明P的Lchild以及P都是Node的前驱
        if(Node->P->Rchild==Node){
            r+=Node->P->Lchild->size+1;
        }
        Node=Node->P;
    }
    return r;
}


int OS_RANK_ELEMENT(RBTreePointer Tree,int data){
    //向左加则向右减
    int r=Tree->Lchild->size+1;
    RBTreePointer Temp=Tree;
    while(Temp!=Tree->P&&Temp->data!=data){
        //如果大则向右
        if(data>Temp->data){
            Temp=Temp->Rchild;
            r+=Temp->Lchild->size+1;
        }
        //向左
        else{
            Temp=Temp->Lchild;
            r-=Temp->Rchild->size+1;
        }
    }
    if(Temp==Tree->P){
        return -1;//表示未查找到
    }
    return r;
}

????????3.main.c

#include "Order_Statistic_Tree.h"

int main(void) {
    RBTreePointer Tree=NULL;
   for(int i=0;i<10;i++){
       Tree= Insert(Tree,i);
   }
    InorderTraverse(Tree,Tree->P);
    printf("The rank i:\n");
    RBTreePointer Res=OS_SELECT(Tree,9);
    printf("按元素9的rank查找到元素:%d\n", OS_RANK(Tree,Res));
    int res= OS_RANK_ELEMENT(Tree,1);
    printf("元素1的位置为%d\n",res);
    res= OS_RANK_ELEMENT(Tree,5);
    printf("元素5的位置为%d\n",res);
    return 0;
}

? ? ? ? ?运行main.c就可以了,输出内容是我自己的测试内容,大家可以随意更改,只是这红黑树确实过于庞大,但一旦写出来,效果非常好。

? ? ? ? 算法运行结果:

? ? ? ?恳请大家指正!?

????????

  数据结构与算法 最新文章
【力扣106】 从中序与后续遍历序列构造二叉
leetcode 322 零钱兑换
哈希的应用:海量数据处理
动态规划|最短Hamilton路径
华为机试_HJ41 称砝码【中等】【menset】【
【C与数据结构】——寒假提高每日练习Day1
基础算法——堆排序
2023王道数据结构线性表--单链表课后习题部
LeetCode 之 反转链表的一部分
【题解】lintcode必刷50题<有效的括号序列
上一篇文章      下一篇文章      查看所有文章
加:2021-12-03 13:17:15  更:2021-12-03 13:18:10 
 
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁

360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年11日历 -2024/11/26 16:31:03-

图片自动播放器
↓图片自动播放器↓
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
图片批量下载器
↓批量下载图片,美女图库↓
  网站联系: qq:121756557 email:121756557@qq.com  IT数码