数据库索引结构---T树的原理

来源:互联网 发布:网络基础知识有哪些 编辑:程序博客网 时间:2024/05/18 02:15

索引用于在查询时提高效率之用。可以为每张表的某个字段定义索引来提高在该字段上的查询效率。由于数据库要处理的数据量非常大,而内存因为价格昂贵,而容量有限,且必须满足一定的实时性,因而对其中的数据的存储及索引方式进行研究,找出有效的数据组织方式是非常有必要的。磁盘数据库系统的典型的索引技术是B-tree索引。B-tree结构的主要目的是减少完成数据文件的索引查找所需要的磁盘I/O的数量。B-tree通过控制节点内部的索引值达到这个目的,在节点中包含尽可能多的索引条目(增加一次磁盘I/O可以访问的索引条目)。
      另一方面,T-tree是针对主存访问优化的索引技术。T-tree是一种一个节点中包含多个索引条目的平衡二叉树,T-tree的索引项无论是从大小还是算法上都比B-tree精简得多。T-tree的搜索算法不分搜索的值在当前的节点还是在内存中的其他地方,每访问到一个新的索引节点,索引的范围减少一半。
      T树索引用来实现关键字的范围查询。T树是一棵特殊平衡的二叉树(AVL),它的每个节点存储了按键值排序的一组关键字。T树除了较高的节点空间占有率,遍历一棵树的查找算法在复杂程度和执行时间上也占有优势。现在T树己经成为内存数据库中最主要的一种索引方式。

1.T树相关概念
     T树具有以下特点:①左子树与右子树之差不超过1,②在一个存储节点可以保存多个键值,它的最左与最右键值分别为这个节点的最小与最大键值,它的左子树仅仅包含那些键值小于或等于最小键值的一记录,同理右子树只包括那些键值大于或等于最大键值的记录,③同时拥有左右子树的节点被称为内部节点,只拥有一个子树的节点被称为半叶一,没有子树的节点被称为叶子,④为了保持空间的利用率,每一个内部节点都需要包含一个最小数目的键值。由此可知T树是一个每个结点含有多个关键字的平衡二叉树,每个节点内的关键字有序排列,左子树都要比根节点关键字小,右子树都要比根节点关键字大。
在上述T树结点结构中,包含如下信息:
(1) balance(平衡因子),其绝对值不大于1,balance =右子树高度-左子树高度;
(2) Left_child_ptr和Right_child_ptr分别表示当前结点的左子树和右子树指针;
(3) Max_Item表示结点中所能容纳的键值的最大数;
(4)Key[0]至K[Max_Item-1]为结点内存放的关键字;
(5)nItem是当前节点实际存储的关键字个数。
对于T树有如下特征:
(1)与AVL树相似,T树中任何结点的左右子树的高度之差最大为1;
(2)与AVL树不同,T树的结点中可存储多个键值,并且这些键值排列有序;
(3)T树结点的左子树中容纳的键值不大于该结点中的最左键值;右子树中容纳的键值不小于该结点中的最右键值;
(4)为了保证每个结点具有较高的空间占用率,每个内部结点所包含的键值数目必须不小于某个指定的值,通常为(Max_Item-2)(Max_Item为结点中最大键值目)。

2.T树索引的操作
      用T树作为索引方式主要完成三个工作:查找,插入,删除。其中插入和删除都是以查找为基础。下面分别介绍三种操作的流程。
(1)T树的查找类似于二叉树,不同之处主要在于每一结点上的比较不是针对结点中的各个元素值,而是首先检查所要查找的目标键值是否包含在当前结点的最左键值和最右键值所确定的范围内,如果是的话,则在当前结点的键值列表中使用二分法进行查找;如果目标键值小于当前结点的最左键值,则类似地搜索当前结点的左孩子结点;如果目标键值大于当前结点的最右键值,则类似地搜索当前结点的右孩子结点。 
(2)T树的插入是以查找为基础,应用查找操作定位目标键值插入位置,并记下查找过程所遇到的最后结点。如果查找成功,判断此结点中是否有足够的存储空间。如果有,则将目标键值插入结点中;否则将目标键值插入此结点,然后将结点中的最左键值插入到它的左子树中(此时是递归插入操作),之后结束;否则分配新结点,并插入目标键值;然后根据目标键值与结点的最大最小键值之间的关系,将新分配的结点链接为结点的左孩子或右孩子;对树进行检查,判断T树的平衡因子是否满足条件,如果平衡因子不满足则执行旋转操作。

(3)T树的删除操作也是以查找为基础,应用查找操作定位目标键值。如果查找失败,则结束;否则令N为目标键值所在的结点,并从结点N中删除目标键值;删除节点后,如果结点N为空,则删除结点N,并对树的平衡因子进行检查,判断是否需要执行旋转操作;如果结点N中的键值数量少于最小值,则根据N的平衡因子决定从结点N的左子树中移出最大的键值或者右子树中移出最小值来填充。 

3.T树索引实现关键技术
     实现T树索引即要实现T树的查找,插入和删除。其中又以查找为基础,对T树的维护也就是T树的旋转为关键。当由于插入或删除键值导致树的失衡,则要进行T树的旋转。使之重新达到平衡。
     在插入情况下,需要依次对所有沿着从新创建结点到根结点路径中的结点进行检查,直到出现如下两种情况之一时中止:某个被检查结点的两个子树高度相等,此时不需要执行旋转操作;某个被检查结点的两个子树的高度之差大于1,此时对该结点仅需执行一次旋转操作即可。
     在删除情况下,类似地需要依次对所有沿着从待删除结点的父结点到根结点路径中的结点进行检查,在检查过程中当发现某个结点的左右子树高度之差越界时,需要执行一次旋转操作。与插入操作不同的是,执行完旋转操作之后,检查过程不能中止,而是必须一直执行到检查完根结点。
     由此可以看出,对于插入操作,最多只需要一次旋转操作即可使T树恢复到平衡状态;而对于删除操作则可能会引起向上的连锁反应,使高层结点发生旋转,因而可能需要进行多次旋转操作。
     为了对T树进行平衡,需要进行旋转操作,旋转是T树中最关键也是最难的的操作,下面介绍T树旋转的技术。旋转可分为四种情况:由左孩子的左子树的插入(或者删除)引起的旋转记为LL旋转,类似有LR,RR及RL旋转。插入时的情况与删除类似。

#pragma once  //  // by xiaomo  // 2011.06  //Ttree.h  #include<iostream>  using namespace std;    enum ENUM  {      MaxSize=64,      MinSize=MaxSize-2  };    template<typename T>  struct TtreeNode  {      int balance;             //平衡因子        TtreeNode<T>* left;        //左子树指针      TtreeNode<T>* right;   //右子树指针        unsigned short int nItems;    //当前实际元素数目        T item[MaxSize];            //节点键值数组      T data[MaxSize];           //数据数组  };        template<typename T>  class Ttree  {  public:      Ttree(void);      virtual ~Ttree(void);        void clear();     //清空树      void _erase(TtreeNode<T>* pNode);  //删除pNode以及其子节点      void freeNode(TtreeNode<T>* pNode);    //删除当前节点      bool isEmpty();    //判断树是否为空        void insert(const T key,const T data);       //插入操作      void remove(T key);        T find(T key);       //查找      virtual int keycompare(T key1,T key2);    //键值的比较      void traverseTree(int order);    protected:      void preOrderTraverse(TtreeNode<T>* pNode)const; //遍历,const说明该函数不能修改类中任何非const函数      void inOrderTraverse(TtreeNode<T>* pNode)const;      void postOrderTraverse(TtreeNode<T>* pNode)const;      TtreeNode<T>* root;      //当前root指针      int  m_nSize;         //当前已分配的节点数目    private:      TtreeNode<T>* mallocNode();        int balanceFactor(TtreeNode<T>* pNode)const;      int Depth(TtreeNode<T>* pNode)const;        bool _insert(TtreeNode<T>* &pNode,const T key,const T data);  //插入操作      int _remove(TtreeNode<T>* &pNode,T key);       //删除操作        //树的四种旋转情况,调整树以适应平衡环境      TtreeNode<T>* SingleRotateLeft(TtreeNode<T>* pNode);    //左左      TtreeNode<T>* SingleRotateRight(TtreeNode<T>* pNode);   //右右      TtreeNode<T>* DoubleRotateLeft(TtreeNode<T>* pNode);    //左右      TtreeNode<T>* DoubleRotateRight(TtreeNode<T>* pNode);   //右左        int balanceLeftBranch(TtreeNode<T>* &pNode);     //调整左子树      int balanceRightBranch(TtreeNode<T>* &pNode);   //调整右子树                                              };      template<typename T>  Ttree<T>::Ttree(void)         //构造函数  {      root=NULL;      m_nSize=0;  }    template<typename T>         //释构函数  Ttree<T>::~Ttree(void)  {      clear();      root=NULL;      m_nSize=0;  }    template<typename T>  void Ttree<T>::clear()          //清空树  {      _erase(root);  }    template<typename T>  void Ttree<T>::_erase(TtreeNode<T>* pNode)          //删除pnode节点及其子节点  {      if(pNode==NULL)      {            return;      }        _erase(pNode->left);      _erase(pNode->right);        freeNode(pNode);  }    template<typename T>  void Ttree<T>::freeNode(TtreeNode<T>* pNode)            //删除当前节点  {      if(pNode)      {          delete pNode;          pNode=NULL;      }  }    template<typename T>  bool Ttree<T>::isEmpty()              //判断是否为空  {      if(root==NULL)return true;      else return false;  }    template<typename T>  TtreeNode<T>* Ttree<T>::mallocNode()              //分配空间  {      TtreeNode<T>* pNode=new TtreeNode<T>;      memset(pNode, 0, sizeof(TtreeNode<T>));            pNode->balance=0;      pNode->left=NULL;      pNode->right=NULL;      pNode->nItems=0;      m_nSize+=1;        return pNode;  }    template<typename T>  int Ttree<T>::keycompare(T key1, T key2)  {      int p=key1;      int q=key2;        return p<q?-1:(p==q?0:1);  }    template<typename T>  void Ttree<T>::traverseTree(int order)  {      switch(order)      {      case 1:          preOrderTraverse(root);          break;      case 2:          inOrderTraverse(root);          break;      case 3:          postOrderTraverse(root);          break;      }  }    template<typename T>  void Ttree<T>::preOrderTraverse(TtreeNode<T> *pNode) const       //先序遍历  {        if(pNode)        {               for(int i=0;i<pNode->nItems;++i)            {                cout<<pNode->item[i]<<" ";                //cout<<pNode->data[i]<<" ";            }            cout<<endl;            preOrderTraverse(pNode->left);            preOrderTraverse(pNode->right);                  }  }    template<typename T>  void Ttree<T>::inOrderTraverse(TtreeNode<T> *pNode) const           //中序遍历  {                   if(pNode)        {              inOrderTraverse(pNode->left);            for(int i=0;i<pNode->nItems;++i)            {               cout<<pNode->item[i]<<" ";// cout<<pNode->data[i]<<" ";            }            cout<<endl;            inOrderTraverse(pNode->right);               }  }    template<typename T>  void Ttree<T>::postOrderTraverse(TtreeNode<T> *pNode) const          //后序遍历  {        if(pNode)        {               postOrderTraverse(pNode->left);            postOrderTraverse(pNode->right);            for(int i=0;i<pNode->nItems;++i)            {                cout<<pNode->item[i]<<" ";//cout<<pNode->data[i]<<" ";            }cout<<endl;                  }  }        template<typename T>                                    //平衡因子  int Ttree<T>::balanceFactor(TtreeNode<T> *pNode) const  {       return Depth(pNode->right)-Depth(pNode->left);  }    template<typename T>                              //求树的深度  int Ttree<T>::Depth(TtreeNode<T> *pNode) const  {       if(pNode==NULL)return 0;         int l=Depth(pNode->left);       int r=Depth(pNode->right);         return 1+(l>r?l:r);  }    template<typename T>  TtreeNode<T>* Ttree<T>::SingleRotateLeft(TtreeNode<T> *pNode)    //左左情况(右旋)  {      TtreeNode<T>* pl=pNode->left;      pNode->left=pl->right;      pl->right=pNode;        //调整平衡因子      pNode->balance=balanceFactor(pNode);      pl->balance=balanceFactor(pl);        return pl;  }    template<typename T>  TtreeNode<T>* Ttree<T>::SingleRotateRight(TtreeNode<T> *pNode)         //右右情况(左旋)  {      TtreeNode<T>* pr=pNode->right;      pNode->right=pr->left;      pr->left=pNode;        //调整平衡因子      pNode->balance=balanceFactor(pNode);      pr->balance=balanceFactor(pr);        return  pr;  }    template<typename T>  TtreeNode<T>* Ttree<T>::DoubleRotateLeft(TtreeNode<T> *pNode)   //左右情况(先左旋后右旋)  {      pNode->left=SingleRotateRight(pNode->left);          //left rotation      pNode->balance=balanceFactor(pNode);  //Adjust the balance factor        return  SingleRotateLeft(pNode);         //right rotation  }    template<typename T>    TtreeNode<T>* Ttree<T>::DoubleRotateRight(TtreeNode<T> *pNode)   //右左情况(先右旋后左旋)  {      pNode->right=SingleRotateLeft(pNode->right);          //right rotation      pNode->balance=balanceFactor(pNode);  //Adjust the balance factor        return  SingleRotateRight(pNode);        //left rotation  }    template<typename T>  T Ttree<T>::find(T key)                             //查找节点  {      TtreeNode<T>* pNode=root;        while(pNode)      {          int n=pNode->nItems;          T keymin=pNode->item[0];          T keymax=pNode->item[n>0?n-1:0];            int ncmp1=keycompare(key,keymin);          int ncmp2=keycompare(key,keymax);            if(ncmp1>=0&&ncmp2<=0)  //二分查找          {              int l=0,r=n-1;                while(l<=r)              {                  int i=(l+r)>>1;                  T itemkey=pNode->item[i];                  int ncmp=keycompare(key,itemkey);                  if(ncmp==0)                  {                      return pNode->data[i];                  }                  else if(ncmp>0) {l=i+1;}                  else {r=i-1;}              }              break;          }          else if(ncmp1<0)          {              pNode=pNode->left;          }          else if(ncmp2>0)          {              pNode=pNode->right;          }      }      return -1;  }    template<typename T>  void Ttree<T>::insert(const T key,const T data)                     //插入节点  {       if(root==NULL)       {             root=mallocNode();             root->item[0]=key;             root->data[0]=data;             root->nItems=1;             root->left=NULL;             root->right=NULL;       }       else       {           TtreeNode<T>* pNode=root;           //根据具体情况插入节点           _insert(pNode,key,data);                   if(pNode!=root)           {               root=pNode;           }       }  }      template<typename T>  bool Ttree<T>::_insert(TtreeNode<T> * &pNode,const T key,const T data)  {      int n=pNode->nItems;      T keymin=pNode->item[0];      T keymax=pNode->item[n>0?n-1:0];        ////////////////////////////////////////////      //compare to the minnimum key      int nDifMin=keycompare(key,keymin);      if(nDifMin<=0)       //如果key小于等于keymin      {          TtreeNode<T>* pl=pNode->left;            if((nDifMin==0||pl==NULL)&&pNode->nItems<MaxSize)      //在当前节点插入新数据          {                for(int i=n;i>0;--i)                {                    pNode->item[i]=pNode->item[i-1];                    pNode->data[i]=pNode->data[i-1];                }                pNode->item[0]=key;                pNode->data[0]=data;                pNode->nItems+=1;                  return false;          }          if(pl==NULL)    //当前键值数目已经达到最大值,而且左子树为空的情况下,新建左子树并插入数据          {              pl=mallocNode();              pl->item[0]=key;              pl->data[0]=data;              pl->nItems+=1;              pNode->left=pl;          }          else            //其他情况下,继续递归          {              TtreeNode<T>* plchild=pl;                bool isbfGrow=_insert(plchild,key,data);                if(plchild!=pl)              {                  pNode->left=pl=plchild;      //relocation              }                if(!isbfGrow)             //如果左子树深度不增加,则返回false              {                  return false;              }          }          //在左子树深度加1的情况下,程序才运行到此处,则需要更新balance factor          if(pNode->balance>0)               {              pNode->balance=0;              return false;          }          if(pNode->balance==0)          {              pNode->balance=-1;              return true;          }          else          {              if(pl->balance<0)      //遇到左左情况,执行右旋              {                    pNode=SingleRotateLeft(pNode);              }              else              {                    pNode=DoubleRotateLeft(pNode);   //遇到左右情况,先左旋再右旋              }              return false;          }      }      ////////////////////////////////////////////      //compare to the maxmum key value      int nDifMax=keycompare(key,keymax);      if(nDifMax>=0)           //如果key大于等于keymax      {          TtreeNode<T>* pr=pNode->right;          if((pr==NULL||nDifMax)&&pNode->nItems<MaxSize)          //在当前节点插入数据          {              pNode->item[n]=key;              pNode->data[n]=data;              pNode->nItems+=1;                return false;          }          else if(pr==NULL)                  //创建新的右子树          {              pr=mallocNode();              pr->item[0]=key;              pr->data[0]=data;              pr->nItems+=1;              pNode->right=pr;          }          else          {                                         //继续递归查找并插入              TtreeNode<T>* prchild=pr;                bool isbfGrow=_insert(prchild,key,data);                if(prchild!=pr)              {                  pNode->right=pr=prchild;              }              if(!isbfGrow)              {                  return false;              }          }          //在右子树深度加1的情况下,程序才运行到此处,则需要更新balance factor          if(pNode->balance<0)          {              pNode->balance=0;              return false;          }          else if(pNode->balance==0)          {              pNode->balance=1;              return true;          }          else          {              if(pr->balance>0)              {                  pNode=SingleRotateRight(pNode);              }              else              {                  pNode=DoubleRotateRight(pNode);              }              return false;          }      }      ////////////////////////////////////////////      //新的键值在数组的中间,用二分法查找     int l=0,r=n-1;     while(l<r)     {         int mid=(l+r)>>1;         T cur_key=pNode->item[mid];         int cmp=keycompare(key,cur_key);         if(cmp>0)         {             l=mid+1;         }         else if(cmp>0)         {             r=mid;         }         else{break;}     }       if(pNode->nItems<MaxSize)                 //数组长度没达到最大值     {         for(int i=n;i>r;--i)         {             pNode->item[i]=pNode->item[i-1];             pNode->data[i]=pNode->data[i-1];         }         pNode->item[r]=key;         pNode->data[r]=data;         pNode->nItems+=1;                  return false;     }     else     {         T keyID,dataID;         //考虑平平衡因子的大小做出不同的选择         if(pNode->balance>=0)                   { //由于数组长度达到最大,在当前节点中插入新数据,并将数组原第一个元素插入到左子树中             keyID=pNode->item[0];             dataID=pNode->data[0];               for(int i=1;i<r;++i)             {                  pNode->item[i-1]=pNode->item[i];                  pNode->data[i-1]=pNode->data[i];             }             pNode->item[r-1]=key;             pNode->data[r-1]=data;               return _insert(pNode,keyID,dataID);         }         else{ //在当前节点中插入新数据,并将数组原第一个元素插入到有子树中。当前是左子树深度较大,因此往右子树添加元素             keyID=pNode->item[n-1];             dataID=pNode->data[n-1];               for(int i=n-1;i>r;--i)             {                  pNode->item[i]=pNode->item[i-1];                  pNode->data[i]=pNode->data[i-1];             }             pNode->item[r]=key;             pNode->data[r]=data;               return _insert(pNode,keyID,dataID);         }     }     return 0;  }    template<typename T>  int Ttree<T>::balanceLeftBranch(TtreeNode<T>* &pNode)//平衡左子树,更新节点信息  {      if(pNode->balance<0){pNode->balance=0;return 1;}     //返回1表示当前调整的情况对当前节点的父节点以及祖先节点的平衡因子产生影响      else if(pNode->balance==0){pNode->balance=1;return 0;} //返回0则对父节点及祖先节点无影响      else {          TtreeNode<T>* pr=pNode->right;          if(pr->balance>=0)          {              pNode=SingleRotateRight(pNode);     //右旋              //return 0;          }          else {              pNode=DoubleRotateRight(pNode);    //先右旋后左旋              //return 1;          }          return 1;      }      return 0;  }    template<typename T>  int Ttree<T>::balanceRightBranch(TtreeNode<T>* &pNode)//平衡右子树,更新节点信息  {      if(pNode->balance>0){pNode->balance=0;return 1;}      else if(pNode->balance==0){pNode->balance=-1;return 0;}      else {          TtreeNode<T>* pl=pNode->left;          if(pl->balance<=0)          {              pNode=SingleRotateLeft(pNode);     //左旋          }          else {              pNode=DoubleRotateLeft(pNode);    //先左旋后右旋          }          return 1;      }  }    template<typename T>  void Ttree<T>::remove(T key)  {      int isOK=_remove(root,key);      if(isOK==-1){cout<<"fail to remove such data"<<endl;}  }    template<typename T>  int Ttree<T>::_remove(TtreeNode<T>* &pNode,T key)      //删除操作  {      if(pNode==NULL)return -1;        int n=pNode->nItems;      T keymin=pNode->item[0];      T keymax=pNode->item[n>0?n-1:0];        int nDifMin=keycompare(key,keymin);      if(nDifMin<0)    //继续搜索左子树      {          TtreeNode<T>* pl=pNode->left;                if(pl!=NULL)          {              TtreeNode<T>* plchild=pl;              int flag=_remove(plchild,key);                if(pl!=plchild)              {                  pNode->left=plchild;              }              if(flag>0)         //flag>0表明树的层次由于remove操作而改变,则重新调整              {                  return balanceLeftBranch(pNode);              }              else return flag;             }          else return -1;       //表示没有找到要删除的key      }      int nDifMax=keycompare(key,keymax);      if(nDifMax<=0)  //在当前节点删除数据      {          int i=0;          for(i=0;i<n;++i)          {              if(key==pNode->item[i])break;          }          if(pNode->item[i]!=key)return -1; //表示没有找到要删除的key          if(n<=1)          {              if(pNode->left==NULL)  //如果左子树为空,此种情况对于左右子树皆为空也适用              {                  TtreeNode<T>* pNewNode=pNode->right;                  freeNode(pNode);                  pNode=pNewNode;              }              else if(pNode->right==NULL)//如果右子树为空              {                  TtreeNode<T>* pNewNode=pNode->left;                  freeNode(pNode);                  pNode=pNewNode;              }              return 1;             //树层次改变,返回1          }          else {             //在当前节点内删除              if(pNode->nItems<=MinSize)  //当前节点键值数目小于或等于最低限制值              {                  TtreeNode<T>* plchild=pNode->left,*prchild=pNode->right;                  //如果结点N中的键值数量少于最小值,则根据N的平衡因子决定从结点N的左子树中移出最大的键值或者右子树中移出最小值来填充。                   if(plchild!=NULL&&pNode->balance<=0)                  {                      while(plchild->right!=NULL)                      {                          plchild=plchild->right;                      }                      while(i>0)                      {                          pNode->item[i]=pNode->item[i-1];                          pNode->data[i]=pNode->data[i-1];                          --i;                      }                      pNode->item[0]=plchild->item[plchild->nItems-1];                      pNode->data[0]=plchild->data[plchild->nItems-1];                      key=pNode->item[0];                      TtreeNode<T>* plTmp=plchild;                      int flag=_remove(plTmp,key);                        if(plTmp!=plchild)                      {pNode->left=plTmp;}                      if(flag>0){flag=balanceLeftBranch(pNode);}                        return flag;                  }                  else if(prchild!=NULL)                  {                       while(prchild->left!=NULL)                      {                          prchild=prchild->left;                      }                      while(i<n-1)                      {                          pNode->item[i]=pNode->item[i+1];                          pNode->data[i]=pNode->data[i+1];                          ++i;                      }                      pNode->item[n-1]=plchild->item[0];                      pNode->data[n-1]=plchild->data[0];                      key=pNode->item[n-1];                        TtreeNode<T>* prTmp=prchild;                      int flag=_remove(prTmp,key);                        if(prTmp!=prchild)                      {pNode->right=prTmp;}                      if(flag>0){flag=balanceRightBranch(pNode);}                        return flag;                  }              }              //当前节点键值数目大于最小限制值              int k=i;              while(k<n-1)              {                  pNode->item[k]=pNode->item[k+1];                  pNode->data[k]=pNode->data[k+1];                  k++;              }              pNode->nItems-=1;              return 0;   //树的层次不变,返回0                        }      }      else{    //搜索右子树          TtreeNode<T>* pr=pNode->right;          if(pr!=NULL)          {              TtreeNode<T>* prchild=pr;              int flag=_remove(prchild,key);                if(pr!=prchild)              {pNode->right=prchild;}                if(flag>0)                  //树层次改变              {                  return balanceRightBranch(pNode);              }              else return flag;       //树层次不变          }      }      return -1;  }  

0 0
原创粉丝点击