ICode9

精准搜索请尝试: 精确搜索
首页 > 其他分享> 文章详细

数据结构之AVL树

2021-12-05 13:58:02  阅读:175  来源: 互联网

标签:node tmp right tree height AVL 数据结构 left


//

在计算机科学中,AVL树是最先发明的自平衡二叉查找树。在AVL树中任何节点的两个子树的高度最大差别为1,所以它也被称为高度平衡树。增加和删除可能需要通过一次或多次  树旋转  来重新平衡这个树。AVL树得名于它的发明者G. M. Adelson-Velsky和E. M. Landis,他们在1962年的论文《An algorithm for the organization of information》中发表了它。

AVL树本质上还是一棵二叉搜索树,它的特点是:

1.本身首先是一棵二叉搜索树。

2.带有平衡条件:每个结点的左右子树的高度之差的绝对值(平衡因子)最多为1。 也就是说,AVL树,本质上是带了平衡功能的二叉查找树(二叉排序树,二叉搜索树)。

操作

编辑 语音

旋转

AVL树的基本操作一般涉及运做同在不平衡的二叉查找树所运做的同样的算法。但是要进行预先或随后做一次或多次所谓的"AVL 旋转"。 [2]

假设由于在二叉排序树上插入结点而失去平衡的最小子树根结点的指针为a(即a是离插入点最近,且平衡因子绝对值超过1的祖先结点),则失去平衡后进行进行的规律可归纳为下列四种情况:

单向右旋平衡处理LL:由于在*a的左子树根结点的左子树上插入结点,*a的平衡因子由1增至2,致使以*a为根的子树失去平衡,则需进行一次右旋转操作;

单向左旋平衡处理RR:由于在*a的右子树根结点的右子树上插入结点,*a的平衡因子由-1变为-2,致使以*a为根的子树失去平衡,则需进行一次左旋转操作;

双向旋转(先左后右)平衡处理LR:由于在*a的左子树根结点的右子树上插入结点,*a的平衡因子由1增至2,致使以*a为根的子树失去平衡,则需进行两次旋转(先左旋后右旋)操作。

双向旋转(先右后左)平衡处理RL:由于在*a的右子树根结点的左子树上插入结点,*a的平衡因子由-1变为-2,致使以*a为根的子树失去平衡,则需进行两次旋转(先右旋后左旋)操作。

插入

向AVL树插入可以通过如同它是未平衡的二叉查找树一样把给定的值插入树中,接着自底向上向根节点折回,于在插入期间成为不平衡的所有节点上进行旋转来完成。因为折回到根节点的路途上最多有 1.5 乘 log n 个节点,而每次 AVL 旋转都耗费恒定的时间,插入处理在整体上耗费 O(log n) 时间。 在平衡的的二叉排序树Balanced BST上插入一个新的数据元素e的递归算法可描述如下: 若BBST为空树,则插入一个数据元素为e的新结点作为BBST的根结点,树的深度增1; 若e的关键字和BBST的根结点的关键字相等,则不进行; 若e的关键字小于BBST的根结点的关键字,而且在BBST的左子树中不存在和e有相同关键字的结点,则将e插入在BBST的左子树上,并且当插入之后的左子树深度增加(+1)时,分别就下列不同情况处理之:BBST的根结点的平衡因子为-1(右子树的深度大于左子树的深度,则将根结点的平衡因子更改为0,BBST的深度不变; BBST的根结点的平衡因子为0(左、右子树的深度相等):则将根结点的平衡因子更改为1,BBST的深度增1; BBST的根结点的平衡因子为1(左子树的深度大于右子树的深度):则若BBST的左子树根结点的平衡因子为1:则需进行单向右旋平衡处理,并且在右旋处理之后,将根结点和其右子树根结点的平衡因子更改为0,树的深度不变; 若e的关键字大于BBST的根结点的关键字,而且在BBST的右子树中不存在和e有相同关键字的结点,则将e插入在BBST的右子树上,并且当插入之后的右子树深度增加(+1)时,分别就不同情况处理之。

删除

从AVL树中删除可以通过把要删除的节点向下旋转成一个叶子节点,接着直接剪除这个叶子节点来完成。因为在旋转成叶子节点期间最多有 log n个节点被旋转,而每次 AVL 旋转耗费恒定的时间,删除处理在整体上耗费 O(log n) 时间。

查找

在AVL树中查找同在一般BST完全一样的进行,所以耗费 O(log n) 时间,因为AVL树总是保持平衡的。不需要特殊的准备,树的结构不会由于查询而改变。(这是与伸展树查找相对立的,它会因为查找而变更树结构。)

#include <iostream.h> 

#include <math.h>; 

#include <stdlib.h>; //建立一个整数类型 

#define MAXSIZE 512

 

typedef struct obj_n_t { int obj_key; } obj_node_t; //建立树结点的基本机构 

typedef struct tree_n_t { int key; struct tree_n_t *left,*right; int height;

} tree_node_t; //建立堆栈 

 

tree_node_t *stack[MAXSIZE]; //warning!the tree can contain 256 leaves at most! 

int i=0; //堆栈计数器 //堆栈清空 

void stack_clear() 

{ 

    while(i!=0)

    {

        stack[i-1]=NULL;

        i--;

    }

}

//堆栈为空

int stack_empty()

{

    return(i==0);

}

//入栈函数

int push(tree_node_t *node)

{

    if(i<MAXSIZE)

    {

        stack[i++]=node;

        return(0);

    }

    else

    return(-1);

}

//出栈函数

tree_node_t *pop()

{

    if(i>0)

    return(stack[--i]);

    else

    return(0);

}

//建立get_node函数,用于动态分配内存空间

tree_node_t *get_node()

{

    tree_node_t *tmp;

    tmp=(tree_node_t *)malloc(sizeof(tree_node_t));

    return(tmp);

}

//建立return_node函数,用于释放内存

void return_node(tree_node_t *free_node)

{

    free(free_node);

}

//建立左旋转函数

void left_rotation(tree_node_t *node)

{

    tree_node_t *tmp;

    int tmp_key;

    tmp=node->left;

    tmp_key=node->key;

    node->left=node->right;

    node->key=node->right->key;

    node->right=node->left->right;

    node->left->right=node->left->left;

    node->left->left=tmp;

    node->left->key=tmp_key;

}

//建立右旋转函数

void right_rotation(tree_node_t *node)

{

    tree_node_t *tmp;

    int tmp_key;

    tmp=node->right;

    tmp_key=node->key;

    node->right=node->left;

    node->key=node->left->key;

    node->left=node->right->left;

    node->right->left=node->right->right;

    node->right->right=tmp;

    node->right->key=tmp_key;

}

int rebalance(tree_node_t *node)

{

    int finished=0;

    while(!stack_empty()&&!finished)

    {

        int tmp_height,old_height;

        node=pop(); //back to the root along the search path

        old_height=node->height;

        if(node->left->height-node->right->height==2)

        {

            if(node->left->left->height-node->right->height==1)

            {

right_rotation(node);

node->right->height=node->right->left->height+1;

node->height=node->right->height+1;

}

else

{

left_rotation(node->left);

right_rotation(node);

tmp_height=node->left->left->height;

node->left->height=tmp_height+1;

node->right->height=tmp_height+1;

node->height=tmp_height+2;

}

}

else if(node->left->height-node->right->height==-2)

{

if(node->right->right->height-node->left->height==1)

{

left_rotation(node);

node->left->height=node->left->right->height+1;

node->height=node->left->height+1;

}

else

{

right_rotation(node->right);

left_rotation(node);

tmp_height=node->right->right->height;

node->left->height=tmp_height+1;

node->right->height=tmp_height+1;

node->height=tmp_height+2;

}

}

else

{

if(node->left->height>node->right->height)

node->height=node->left->height+1;

else

node->height=node->right->height+1;

}

if(node->height==old_height)

finished=1;

}

stack_clear();

return(0);

}

//建立creat_tree函数,用于建立一颗空树

tree_node_t *creat_tree()

{

tree_node_t *root;

root=get_node();

root->left=root->right=NULL;

root->height=0;

return(root); //build up an empty tree.the first insert bases on the empty tree.

}

//建立find函数,用于查找一个对象

obj_node_t *find(tree_node_t *tree,int query_key)

{

tree_node_t *tmp;

if(tree->left==NULL)

return(NULL);

else

{

tmp=tree;

while(tmp->right!=NULL)

{

if(query_key<tmp->key)

tmp=tmp->left;

else

tmp=tmp->right;

}

if(tmp->key==query_key)

return((obj_node_t*)tmp->left);

else

return(NULL);

}

}

//建立插入函数

int insert(tree_node_t *tree,obj_node_t *new_obj)

{

tree_node_t *tmp;

int query_key,new_key;

query_key=new_key=new_obj->obj_key;

if(tree->left==NULL)

{

tree->left=(tree_node_t *)new_obj;

tree->key=new_key;

tree->height=0;

tree->right=NULL;

}

else

{

stack_clear();

tmp=tree;

while(tmp->right!=NULL)

{

//use stack to remember the path from root to the position at which the new object should be inserted.

//then after inserting,we can rebalance from the parrent node of the leaf which pointing to new object to the root node.

push(tmp);

if(query_key<tmp->key)

tmp=tmp->left;

else

tmp=tmp->right;

}

if(tmp->key==query_key)

return(-1);

else

{

tree_node_t *old_leaf,*new_leaf;

//It must allocate 2 node space in memory.

//One for the new one,another for the old one.

//the previous node becomes the parrent of the new node.

//when we delete a leaf,it will free two node memory spaces as well.

old_leaf=get_node();

old_leaf->left=tmp->left;

old_leaf->key=tmp->key;

old_leaf->right=NULL;

old_leaf->height=0;

new_leaf=get_node();

new_leaf->left=(tree_node_t *)new_obj;

new_leaf->key=new_key;

new_leaf->right=NULL;

new_leaf->height=0;

if(tmp->key<new_key)

{

tmp->left=old_leaf;

tmp->right=new_leaf;

tmp->key=new_key;

}

else

{

tmp->left=new_leaf;

tmp->right=old_leaf;

}

tmp->height=1;

}

}

rebalance(tmp);

return(0);

}

//建立删除函数

int del(tree_node_t *tree,int key)

{

tree_node_t *tmp,*upper,*other;

if(tree->left==NULL)

return(-1);

else if(tree->right==NULL)

{

if(tree->key==key)

{

tree->left=NULL;

return(0);

}

else

return(-1);

}

else

{

tmp=tree;

stack_clear();

while(tmp->right!=NULL)

{

upper=tmp;

push(upper);

if(key<tmp->key)

{

tmp=upper->left;

other=upper->right;

}

else

{

tmp=upper->right;

other=upper->left;

}

}

if(tmp->key!=key)

return(-1);

else

{

upper->key=other->key;

upper->left=other->left;

upper->right=other->right;

upper->height=upper->height-1;

return_node(tmp);

return_node(other);

rebalance(pop());

//here must pop,then rebalance can run from the parrent of upper,because upper has become a leaf.

return(0);

}

}

}

//建立测试遍历函数

int travel(tree_node_t *tree)

{

stack_clear();

if(tree->left==NULL)

push(tree);

else if(tree->left!=NULL)

{

int m=0;

push(tree);

while(i!=m)

{

if(stack[m]->left!=NULL && stack[m]->right!=NULL)

{

push(stack[m]->left);

push(stack[m]->right);

}

m++;

}

}

return(0);

}

//建立测试函数

int test_structure(tree_node_t *tree)

{

travel(tree);

int state=-1;

while(!stack_empty())

{

--i;

if(stack->right==NULL && stack->height==0) //this statement is leaf,but also contains an empty tree

state=0;

else if(stack->left!=NULL && stack->right!=NULL)

{

if(abs(stack->height-stack->height)<=1)

state=0;

else

{

state=-1;

stack_clear();

}

}

}

stack_clear();

return(state);

}

//建立remove_tree函数

int remove_tree(tree_node_t *tree)

{

travel(tree);

if(stack_empty())

return(-1);

else

{

while(!stack_empty())

{

return_node(pop());

}

return(0);

}

}

void main()

{

tree_node_t *atree=NULL;

obj_node_t obj[256],*f; //MAXSIZE=n(number of leaf)+(n-1) number of node

int n,j=0;

cout<<"Now Let's start this program! There is no tree in memory.\n";

int item;

while(item!=0)

{

cout<<"\nRoot address = "<<atree<<"\n";

cout<<"\n1.Create a tree\n";

cout<<"\n2.Insert a int type object\n";

cout<<"\n3.Test the structure of the tree\n";

cout<<"\n4.Find a object\n";

cout<<"\n6.Delete a object\n";

cout<<"\n7.Remove the Tree\n";

cout<<"\n0.Exit\n";

cout<<"\nPlease select:";

cin>>item;

cout<<"\n\n\n";

switch(item)

{

case 1:

{

atree=creat_tree();

cout<<"\nA new empty tree has been built up!\n";

break;

}

case 2:

{

if(atree!=NULL)

while(n!=3458)

{

cout<<"Please insert a new object.\nOnly one object every time(3458 is an end code) : ";

cin>>n;

if(n!=3458)

{

obj[j].obj_key=n;

if(insert(atree,&obj[j])==0)

{

j++;

cout<<"Integer "<<n<<" has been input!\n\n";

}

else

cout<<"\n\nInsert failed!\n\n";

}

}

else

cout<<"\n\nNo tree in memory,insert Fail!\n\n";

break;

}

case 3:

{

if(atree!=NULL)

{

n=test_structure(atree);

if(n==-1)

cout<<"\n\nIt's not a correct AVL tree.\n\n";

if(n==0)

cout<<"\n\nIt's a AVL tree\n\n";

}

else

cout<<"\n\nNo tree in memory,Test Fail!\n\n";

break;

}

case 4:

{

if(atree!=NULL)

{

cout<<"\n\nWhat do you want to find? : ";

cin>>n;

f=find(atree,n);

if(f==NULL)

{

cout<<"\n\nSorry,"<<n<<" can't be found!\n\n";

}

else

{

cout<<"\n\nObject "<<f->obj_key<<" has been found!\n\n";

}

}

else

cout<<"\n\nNo tree in memory,Find Fail!\n\n";

break;

}

case 5:

{

if(atree!=NULL)

{

travel(atree);

for(int count=0;count<i;count++)

{

cout<<" "<<stack[count]->key<<",";

}

}

else

cout<<"\n\nNo tree in memory,Travel Fail!\n\n";

break;

}

case 6:

{

if(atree!=NULL)

{

cout<<"\n\nWhich object do you want to delete?\n\n";

cin>>n;

if(del(atree,n)==0)

{

cout<<"\n\n"<<n<<" has been deleted!\n\n";

}

else

cout<<"\n\nNo this object\n\n";

}

else

cout<<"\n\nNo tree in memory,Delete Fail!\n\n";

break;

}

case 7:

{

if(atree!=NULL)

{

remove_tree(atree);

cout<<"\n\nThe Tree has been removed!\n\n";

atree=NULL;

}

else

cout<<"\n\nNo tree in memory,Removing Fail!\n\n";

}

default:

cout<<"\n\nNo this operation!\n\n";

}

n=0;

}

}

AVL树本质上是一颗二叉查找树,但是它又具有以下特点:它是一棵空树或它的左右两个子树的高度差的绝对值不超过1,并且左右两个子树都是一棵平衡二叉树。在AVL树中任何节点的两个子树的高度最大差别为一,所以它也被称为平衡二叉树。下面是平衡二叉树和非平衡二叉树对比的例图:

  平衡因子(bf):结点的左子树的深度减去右子树的深度,那么显然-1<=bf<=1;

AVL树的作用:

  我们知道,对于一般的二叉搜索树(Binary Search Tree),其期望高度(即为一棵平衡树时)为log2n,其各操作的时间复杂度(O(log2n))同时也由此而决定。但是,在某些极端的情况下(如在插入的序列是有序的时),二叉搜索树将退化成近似链或链,此时,其操作的时间复杂度将退化成线性的,即O(n)。我们可以通过随机化建立二叉搜索树来尽量的避免这种情况,但是在进行了多次的操作之后,由于在删除时,我们总是选择将待删除节点的后继代替它本身,这样就会造成总是右边的节点数目减少,以至于树向左偏沉。这同时也会造成树的平衡性受到破坏,提高它的操作的时间复杂度。

  例如:我们按顺序将一组数据1,2,3,4,5,6分别插入到一颗空二叉查找树和AVL树中,插入的结果如下图:

        

  由上图可知,同样的结点,由于插入方式不同导致树的高度也有所不同。特别是在带插入结点个数很多且正序的情况下,会导致二叉树的高度是O(N),而AVL树就不会出现这种情况,树的高度始终是O(lgN).高度越小,对树的一些基本操作的时间复杂度就会越小。这也就是我们引入AVL树的原因

AVL树的基本操作:

  AVL树的操作基本和二叉查找树一样,这里我们关注的是两个变化很大的操作:插入和删除!

  我们知道,AVL树不仅是一颗二叉查找树,它还有其他的性质。如果我们按照一般的二叉查找树的插入方式可能会破坏AVL树的平衡性。同理,在删除的时候也有可能会破坏树的平衡性,所以我们要做一些特殊的处理,包括:单旋转和双旋转!

  AVL树的插入,单旋转的第一种情况---右旋:

  由上图可知:在插入之前树是一颗AVL树,而插入之后结点T的左右子树高度差的绝对值不再 < 1,此时AVL树的平衡性被破坏,我们要对其进行旋转。由上图可知我们是在结点T的左结点的左子树上做了插入元素的操作,我们称这种情况为左左情况,我们应该进行右旋转(只需旋转一次,故是单旋转)。具体旋转步骤是:

  T向右旋转成为L的右结点,同时,Y放到T的左孩子上。这样即可得到一颗新的AVL树,旋转过程图如下:

  左左情况的右旋举例:

  AVL树的插入,单旋转的第一种情况---左旋:

 


 

   由上图可知:在插入之前树是一颗AVL树,而插入之后结点T的左右子树高度差的绝对值不再 < 1,此时AVL树的平衡性被破坏,我们要对其进行旋转。由上图可知我们是在结点T的右结点的右子树上做了插入元素的操作,我们称这种情况为右右情况,我们应该进行左旋转(只需旋转一次,故事单旋转)。具体旋转步骤是:

   T向右旋转成为R的左结点,同时,Y放到T的左孩子上。这样即可得到一颗新的AVL树,旋转过程图如下:

 

  右右情况的左旋举例:

  以上就是插入操作时的单旋转情况!我们要注意的是:谁是T谁是L,谁是R还有谁是X,Y,Z!T始终是开始不平衡的左右子树的根节点。显然L是T的左结点,R是T的右节点。X、Y、Y是子树当然也可以为NULL.NULL归NULL,但不能破坏插入时我上面所说的左左情况或者右右情况。

  AVL树的插入,双旋转的第一种情况---左右(先左后右)旋:

由  上图可知,我们在T结点的左结点的右子树上插入一个元素时,会使得根为T的树的左右子树高度差的绝对值不再 < 1,如果只是进行简单的右旋,得到的树仍然是不平衡的。我们应该按照如下图所示进行二次旋转:

  

  左右情况的左右旋转实例:

  AVL树的插入,双旋转的第二种情况---右左(先右后左)旋:

  由上图可知,我们在T结点的右结点的左子树上插入一个元素时,会使得根为T的树的左右子树高度差的绝对值不再 < 1,如果只是进行简单的左旋,得到的树仍然是不平衡的。我们应该按照如下图所示进行二次旋转:

  右左情况的右左旋转实例:

AVL树的插入代码实现:(仅供参考)

#include <iostream>

using namespace std;

#define DataType int

/*
    定义AVL树的结构体,链式
*/
typedef struct AvlNode{
    DataType    data;
    AvlNode    * m_pLeft;
    AvlNode    * m_pRight;
    int height;
}*AvlTree,*Position,AvlNode;

//求两个数的最大值
int Max(int a,int b)
{
    return a>b?a:b;
}
//求树的高度
int Height( AvlTree T)
{
    if(NULL == T)
        return -1;
    else
        return T->height;
}

//单旋转右旋
AvlTree singleRotateWithRight(AvlTree T)
{
    AvlTree L = T->m_pLeft;
    T->m_pLeft = L->m_pRight;
    L->m_pRight = T;
    T->height = Max( Height(T->m_pLeft),Height(T->m_pRight) ) + 1;
    L->height = Max( Height(L->m_pLeft),Height(L->m_pRight) ) + 1;
    return L;    //此时L成为根节点了(可参考AVL的插入的左左情况的右旋图)
}
//单旋转左旋
AvlTree singleRotateWithLeft(AvlTree T)
{
    AvlTree R = T->m_pRight;
    T->m_pRight = R->m_pLeft;
    R->m_pLeft = T;
    T->height = Max( Height(T->m_pLeft),Height(T->m_pRight) ) + 1;
    R->height = Max( Height(R->m_pLeft),Height(R->m_pRight) ) + 1;
    return R;    //此时R成为根节点了(可参考AVL的插入的左左情况的左旋图)
}
//双旋转,先左后右
AvlTree doubleRotateWithLeft(AvlTree T)        //先左后右
{
    T->m_pLeft = singleRotateWithLeft(T->m_pLeft);
    return singleRotateWithRight(T);
}
//双旋转,先右后左
AvlTree doubleRotateWithRight(AvlTree T)    //先右后左
{
    T->m_pRight = singleRotateWithRight(T->m_pRight);
    return singleRotateWithLeft(T);
}
AvlTree AvlTreeInsert(AvlTree T, DataType x)
{
    if(T == NULL)    //如果树为空
    {
        T = (AvlNode *)malloc(sizeof(struct AvlNode));
        if(T)
        {
            T->data = x;
            T->m_pLeft    = NULL;
            T->m_pRight = NULL;
            T->height = 0;
        }
        else
        {
            cout << "空间不够" << endl;
            exit(0);
        }
    }
    else if( x < T->data)        //如果插入到T结点的左子树上
    {
        T->m_pLeft = AvlTreeInsert(T->m_pLeft,x);    //先插入,后旋转
        if(Height(T->m_pLeft) - Height(T->m_pRight) == 2) //只有可能是这个
        {
            if(x < T->m_pLeft->data)        //左左情况,只需要右旋转
            {
                T = singleRotateWithRight( T );
            }
            else                            //左右情况,双旋转,先左
            {
                T = doubleRotateWithLeft( T );
            }
        }
    }
    else if( x > T->data )
    {
        T->m_pRight = AvlTreeInsert(T->m_pRight,x);
        if(Height(T->m_pRight) - Height(T->m_pLeft) == 2)
        {
            if(x > T->m_pRight->data)        //右右情况,进行左旋
            {
                T = singleRotateWithLeft( T );
            }
            else                            //左右情况,双旋转,先右
            {
                T = doubleRotateWithRight( T );
            }
        }
    }
    //如果这个数已经存在,那么不进行插入
    T->height = Max(Height(T->m_pLeft),Height(T->m_pRight)) + 1;
    return T;
}
//递归实现中序遍历
void inOrderVisitUseRecur(const AvlTree pCurrent)
{
    if(pCurrent)
    {
        inOrderVisitUseRecur(pCurrent->m_pLeft);
        cout << pCurrent->data << " ";
        if(pCurrent->m_pLeft)
            cout << " leftChild: "<<pCurrent->m_pLeft->data;
        else
            cout << " leftChild: "<<"NULL" ;
        if(pCurrent->m_pRight)
            cout << " rightChild: "<<pCurrent->m_pRight->data;
        else
            cout << " rightChild: "<< "NULL";
        cout << endl;
        inOrderVisitUseRecur(pCurrent->m_pRight);
    }
}
int main()
{
    AvlTree root = NULL;
    root = AvlTreeInsert(root,1);
    root = AvlTreeInsert(root,2);
    root = AvlTreeInsert(root,3);
    root = AvlTreeInsert(root,4);
    root = AvlTreeInsert(root,5);
    root = AvlTreeInsert(root,6);
    root = AvlTreeInsert(root,7);
    root = AvlTreeInsert(root,8);
    root = AvlTreeInsert(root,9);
    root = AvlTreeInsert(root,10);
    root = AvlTreeInsert(root,11);
    root = AvlTreeInsert(root,12);
    root = AvlTreeInsert(root,13);
    root = AvlTreeInsert(root,14);
    root = AvlTreeInsert(root,15);
    inOrderVisitUseRecur(root);
    return 0;
}

//

标签:node,tmp,right,tree,height,AVL,数据结构,left
来源: https://blog.csdn.net/Game_jqd/article/details/121728097

本站声明: 1. iCode9 技术分享网(下文简称本站)提供的所有内容,仅供技术学习、探讨和分享;
2. 关于本站的所有留言、评论、转载及引用,纯属内容发起人的个人观点,与本站观点和立场无关;
3. 关于本站的所有言论和文字,纯属内容发起人的个人观点,与本站观点和立场无关;
4. 本站文章均是网友提供,不完全保证技术分享内容的完整性、准确性、时效性、风险性和版权归属;如您发现该文章侵犯了您的权益,可联系我们第一时间进行删除;
5. 本站为非盈利性的个人网站,所有内容不会用来进行牟利,也不会利用任何形式的广告来间接获益,纯粹是为了广大技术爱好者提供技术内容和技术思想的分享性交流网站。

专注分享技术,共同学习,共同进步。侵权联系[81616952@qq.com]

Copyright (C)ICode9.com, All Rights Reserved.

ICode9版权所有