最透彻的红黑树详解(图文并茂,一文全解)

前言

  刚开始接触红黑树的时候,感觉很难。其实不然,红黑树只是情况分的多了一点而已,相较于线段树,主席树等等,简单多了。对于红黑树3种插入维护4种删除维护没必要去记忆,稍作了解,对于红黑树的性质和特点,需要特别记忆。

注意,本文图中红黑树的叶子结点默认不画出来~

为什么要有红黑树

二叉搜索树

  二叉搜索树(又叫二叉排序树,BST):对于任意一个结点,其左子树的值必定小于该结点,其右子树的值必定大于该结点。那么中序遍历的时候,就是有序的了。理论上来说,增加,删除,修改的时间复杂度都是O(log(N))。但是它存在一个致命的问题。

  退化:向树中插入[1,2,3,4,5,6],此时树退化成了一个链表,增加,删除,修改的时间复杂度退化为O(N)

最透彻的红黑树详解(图文并茂,一文全解)

添加图片注释,不超过 140 字(可选)

平衡二叉搜索树

  平衡二叉搜索树(AVL Tree):它的左右两个子树的高度差的绝对值不超过1,并且左右两个子树都是一棵平衡二叉搜索树。如果向树中插入[1,2,3,4,5,6]

最透彻的红黑树详解(图文并茂,一文全解)

添加图片注释,不超过 140 字(可选)

可以看到AVLTree在最坏的情况下,依然保持了“绝对的平衡”:左右两个子树的高度差的绝对值不超过1。那么AVL Tree是如何保证平衡的呢,是通过旋转,可以看到,无论是插入还是删除元素,都要去通过旋转维护整个树的平衡。

  • AVL查询元素:O(log(N))
  • AVL插入元素:最多一次旋转O(1),加上查询的时间O(log(N)),插入的复杂度O(log(N))
  • AVL删除元素:必须检查从删除结点开始到根结点路径上的所有结点的平衡因子。因此删除的代价比较大,删除最多需要log(N)次旋转,加上查询的时间,删除的复杂度O(2log(N))

红黑树

  我们发现,AVL树未免太严格了一些,有没有一种数据结构,能让AVL树不那么严格平衡,降低维护平衡的开销,同时又不能像BST一样退化呢?

当然有,就是本文所写的红黑树(rbTree):

  • rbTree查询元素:O(log(N))
  • rbTree插入元素:插入最多2次旋转,加上查询的时间O(log(N)),插入的复杂度O(log(N))
  • rbTree删除元素:删除最多需要3次旋转,加上查询的时间,删除的复杂度O(log(N))

  虽然插入和删除元素后,需要旋转和变色(本文中统一为维护),但是这一时间复杂度可以估算为O(1)不计

  因为rbTree的第6条性质(见下文)

  • 所以红黑树的查询效率略低与AVL的查询
  • 红黑树和AVL插入的速度差不多
  • 红黑树删除的速度比AVL快,因为AVL删除最多需要og(N)次旋转

红黑树的应用场景

  1. c++ stl map,set(红黑树的封装)
  2. 进程调度cfs(用红黑树存储进程的集合,把调度的时间作为key,那么树的左下角时间就是最小的)
  3. 内存管理(每次使用malloc的时候都会分配一块小内存出来,那么这么块就是用红黑树来存,如何表述一段内存块呢,用开始地址+长度来表示,所以key->开始地址,val->大小)
  4. epoll中使用红黑树管理socketfd
  5. nginx中使用红黑树管理定时器,中序遍历第一个就是最小的定时器

红黑树的性质(重点)

  1. 每个结点是红的或者黑的
  2. 根结点是黑的
  3. 每个叶子结点是黑的(因为这条性质,一般用叶子结点在代码中被特殊表示)
  4. 如果一个结点是红的,则它的两个儿子都是黑的(不存在相邻红色)
  5. 从任一节点到叶子节点,所包含的黑色节点数目相同(即黑高度相同)
  6. 最长路径长度不超过最短路径长度的2倍(2n-1,一条黑红黑红…一条全黑)

红黑树的定义

#define RED 0
#define BlACK 1

typedef int KEY_TYPE;

typedef struct _rbtree_node {
    unsigned char color;//颜色
    struct _rbtree_node *left;//左子树
    struct _rbtree_node *right;//右子树
    struct _rbtree_node *parent;//父结点
    KEY_TYPE key;
    void *value;

} rbtree_node;//红黑树结点

typedef struct _rbtree {
    rbtree_node *root;//根结点
    rbtree_node *nil;//通用叶子结点
} rbtree;//红黑树

红黑树的左旋与右旋

动三个方向,改6个指针。 通过旋转,调整左右高度,使树达到平衡。

最透彻的红黑树详解(图文并茂,一文全解)

添加图片注释,不超过 140 字(可选)

左旋leftRotate(T,x)—中右->左中

降低X结点的高度,提高X结点右结点(即Y)的高度。

  1. x的右子树指向y的左子树
  2. 本来指向x结点的父指针,改成指向y
  3. y的左子树指向x结点
最透彻的红黑树详解(图文并茂,一文全解)

添加图片注释,不超过 140 字(可选)

右旋rightRotate(T,y)—中左->中右

降低Y结点的高度,提高Y结点左结点(即X)的高度。

  1. y的左子树指向x的右子树
  2. 本来指向y结点的父指针,改成指向x
  3. x的右子树指向y结点
最透彻的红黑树详解(图文并茂,一文全解)

添加图片注释,不超过 140 字(可选)

//左旋leftRotate(T,x)---中右->左中
//降低X结点的高度,提高X结点右结点(即Y)的高度。
void _left_rotate(rbtree *T, rbtree_node *x) {
    rbtree_node *y = x->right;
    //1
    x->right = y->left;//x的右子树指向y的左子树
    if (y->left != T->nil) {
        y->left->parent = x;//y的左子树的父节点指向x
    }
    //2
    y->parent = x->parent;//y的父结点指向x的父结点
    if (x->parent == T->nil) {//如果x是根结点
        T->root = y;
    } else if (x == x->parent->left) {
        x->parent->left = y;//本来指向x结点的父指针,改成指向y
    } else {
        x->parent->right = y;
    }
    //3
    y->left = x;//y的左子树指向x结点
    x->parent = y;//x的父节点指向y
}

//右旋
//copy左旋的代码
//left改成right,right改成left
//x改成y,y改成x
void _right_rotate(rbtree *T, rbtree_node *y) {
    rbtree_node *x = y->left;
    //1
    y->left = x->right;
    if (x->right != T->nil) {
        x->right->parent = y;
    }
    //2
    x->parent = y->parent;
    if (y->parent == T->nil) {
        T->root = x;
    } else if (y == y->parent->right) {
        y->parent->right = x;
    } else {
        y->parent->left = x;
    }
    //3
    x->right = y;
    y->parent = x;
}

红黑树插入结点与插入维护红黑树的三种情况

插入结点

  在插入结点时,我们始终认为“插入这个结点之前,原来的红黑树是满足红黑树性质的==”,那么插入的位置容易找,就是不断的对比key,最终找到位置,那么新增的结点是什么颜色呢?我们通过性质发现:

  • 如果新结点是黑色,违背了第5条性质
  • 如果新结点是红色,可能违背第4条性质

而第四条性质,我们可以通过旋转与上色的方式修复,所以在我们插入结点的时候,我们始终认为新结点是红色

//因为传入的key可能是字符,可能是整形,所以要提取出来
//这里可以看出,其实可以封装成一个模板
int key_compare(KEY_TYPE a, KEY_TYPE b) {
    //这里假设是int
    if (a > b) {
        return 1;
    } else if (a < b) {
        return -1;
    } else {
        return 0;
    }
}
void rbtree_insert(rbtree *T, rbtree_node *z) {
    //找位置
    rbtree_node *x = T->root;
    rbtree_node *y = T->nil;//y是x的父节点
    while (x != T->nil) {//二分找位置
        y = x;
        if (key_compare(z->key, x->key) < 0) {
            x = x->left;
        } else if (key_compare(z->key, x->key) > 0) {
            x = x->right;
        } else {
            //如果key相等,看自己的业务情景
            //重复插入可以不修改直接退出,可以修改val
            return;
        }
    }
    //插入
    z->parent = y;
    if (y == T->nil) {
        T->root = z;
    } else if (key_compare(z->key, y->key) < 0) {
        y->left = z;
    } else {
        y->right = z;
    }

    z->left = T->nil;
    z->right = T->nil;
    z->color = RED;
    //维护红黑树
    rbtree_insert_fixup(T, z);
}

插入结点后维护红黑树

  我们知道新增结点是红色,如果新结点是父节点也是红色,那么就需要维护红黑树了。

  如果父结点是爷结点是左子树,可以归纳出来三种情况。同理如果父结点是爷结点是右子树,我们也可以归纳出来三种情况。但是这三种情况的差异就是旋转方向的区别而已。一共是6种情况,但是归纳出来其实是3种,读者不要搞错了。

在下面的图中:z表示新增结点,y表示叔结点

父结点是爷结点是左子树

1. 叔结点是红色的

  • 将叔结点和父结点变黑,爷结点变红
  • 将当前结点变成爷结点(因为爷结点是红,爷结点的父节点也可能是红,所以要递归维护)
最透彻的红黑树详解(图文并茂,一文全解)

添加图片注释,不超过 140 字(可选)

2. 叔结点是黑色的且新结点是左子树

  • 将父结点变成黑色,爷结点变成红色
  • 以爷结点为中心右旋
最透彻的红黑树详解(图文并茂,一文全解)

添加图片注释,不超过 140 字(可选)

3. 叔结点是黑色的且新结点是右子树

  • 以父结点为中心左旋
  • 将父结点变黑色,爷结点变红色
  • 以爷结点为中心右旋
最透彻的红黑树详解(图文并茂,一文全解)

添加图片注释,不超过 140 字(可选)

父结点是爷结点是右子树

1. 叔结点是红色的

  • 将叔结点和父结点变黑,爷结点变红
  • 将当前结点变成爷结点(因为爷结点是红,爷结点的父节点也可能是红,所以要递归维护)
最透彻的红黑树详解(图文并茂,一文全解)

添加图片注释,不超过 140 字(可选)

2. 叔结点是黑色的且新结点是左子树

  • 以父结点为中心右旋
  • 将父结点变黑色,爷结点变红色
  • 以爷结点为中心左旋
最透彻的红黑树详解(图文并茂,一文全解)

添加图片注释,不超过 140 字(可选)

3. 叔结点是黑色的且新结点是右子树

  • 将父结点变成黑色,爷结点变成红色
  • 以爷结点为中心左旋
最透彻的红黑树详解(图文并茂,一文全解)

添加图片注释,不超过 140 字(可选)

维护代码

//修复第4条性质
void rbtree_insert_fixup(rbtree *T, rbtree_node *z) {
    while (z->parent->color == RED) {//父结点是红色的,需要调整
        if (z->parent == z->parent->parent->left) {//如果父结点是爷结点是左子树
            rbtree_node *y = z->parent->parent->right;//叔结点
            if (y->color == RED) {//叔结点是红色的
                //先变色,叔,父变黑
                z->parent->color = BLACK;
                y->color = BLACK;
                //爷结点变红
                z->parent->parent->color = RED;
                //下面的调整完了,调整上面
                z = z->parent->parent;
            } else {//叔父结点是黑色
                if (z == z->parent->right) {//新节点是在右边
                    z = z->parent;
                    rbtree_left_rotate(T, z);
                }
                z->parent->color = BLACK;
                z->parent->parent->color = RED;
                rbtree_right_rotate(T, z->parent->parent);
            }
        } else {
            // 如果父结点是爷结点是右子树
            rbtree_node *y = z->parent->parent->left;//叔父结点
            if (y->color == RED) {//叔父结点是红色的
                //先变色,叔,父变黑
                z->parent->color = BLACK;
                y->color = BLACK;
                //爷结点变红
                z->parent->parent->color = RED;
                //下面的调整完了,调整上面
                z = z->parent->parent;
            } else {//叔父结点是黑色
                if (z == z->parent->left) {//新节点是在左边
                    z = z->parent;
                    rbtree_right_rotate(T, z);
                }
                z->parent->color = BLACK;
                z->parent->parent->color = RED;
                rbtree_left_rotate(T, z->parent->parent);
            }
        }
    }
    //最后别忘了根节点始终是黑色
    T->root->color = BLACK;
}

红黑树删除结点与删除维护红黑树的四种情况

删除结点

我们定义:

  • 覆盖结点:z(被指定删除的结点,实际上被覆盖)
  • 删除结点:y(实际上被删除的结点,一般是后继结点)
  • 轴心结点:x(维护红黑树的结点)

红黑树删除结点根据改结点的左右子树分为三种情况:

  1. 没有左右子树
  2. 有且仅有一个子树
  3. 左右子树都有

对不同情况的处理:

  • 情况1:直接删除该结点
  • 情况2:将该结点的唯一子树挂到父结点上,然后删除该结点
  • 情况3:找一个删除结点y(后继结点)覆盖 指定结点z,然后删除 删除结点y,对于这个删除结点y来说,它的情况一定是情况1或情况2

删除代码

rbtree_node *rbtree_mini(rbtree *T, rbtree_node *x) {
    while (x->left != T->nil) {
        x = x->left;
    }
    return x;
}

//找后继结点
rbtree_node *rbtree_successor(rbtree *T, rbtree_node *x) {
    rbtree_node *y = x->parent;
    //右子树不为空,则找右子树中最左的元素
    if (x->right != T->nil) {
        return rbtree_mini(T, x->right);
    }
    //找到结点第一个父结点
    while ((y != T->nil) && (x == y->right)) {
        x = y;
        y = y->parent;
    }
    return y;
}

//覆盖结点z
//删除结点y
//轴心结点x
rbtree_node *rbtree_delete(rbtree *T, rbtree_node *z) {
    rbtree_node *y = T->nil;
    rbtree_node *x = T->nil;

    if ((z->left == T->nil) || (z->right == T->nil)) {
        y = z;//如果没有孩子或只有一个
    } else {//如果有两个子树则找后继
        y = rbtree_successor(T, z);
    }
    //一般x是y的右子树,找到轴心结点
    if (y->left != T->nil) {
        x = y->left;
    } else if (y->right != T->nil) {
        x = y->right;
    }
    //将该结点的唯一子树挂到父结点上,然后删除该结点
    x->parent = y->parent;
    if (y->parent == T->nil) {//根结点
        T->root = x;
    } else if (y == y->parent->left) {
        y->parent->left = x;
    } else {
        y->parent->right = x;
    }
    //进行覆盖操作
    if (y != z) {
        z->key = y->key;
        z->value = y->value;
    }
    //黑色才需要调整
    if (y->color == BLACK) {
        rbtree_delete_fixup(T, x);
    }
    return y;
}

删除结点后维护红黑树

  想一想,删除一个结点,该结点是什么颜色的时候才需要维护红黑树呢?

  • 如果是红色,没有违反任何性质。所以如果是红色直接删除即可,无需维护
  • 如果是黑色,黑色被删除,那么必定违反第5条性质,破坏了黑高,所以我们需要针对这一情况进行维护

  如果当前结点是父结点的左子树的情况,可以归纳出来四种情况。同理如果当前结点是父结点的右子树,我们也可以归纳出来四种情况。但是这四种情况的差异就是旋转方向的区别而已(镜像的)。一共是8种情况,但是归纳出来其实是4种,读者不要搞错了。

当前结点是父结点的左子树的情况

1.当前结点的兄弟结点是红色的

  • 兄弟节点变成黑色
  • 父节点变成红色
  • 父节点做左旋
  • 将兄弟结点调整为父节点的右子树
最透彻的红黑树详解(图文并茂,一文全解)

添加图片注释,不超过 140 字(可选)

2. 当前结点的兄弟结点是黑色的,而且兄弟结点的两个孩子结点都是黑色的

  • 兄弟节点变成红色
  • 轴心结点变为父节点
最透彻的红黑树详解(图文并茂,一文全解)

添加图片注释,不超过 140 字(可选)

3. 当前结点的兄弟结点是黑色的,而且兄弟结点的左孩子是红色的,右孩子是黑色的

  • 将左孩子涂黑
  • 将兄弟节点变红
  • 对兄弟节点右旋
  • 将兄弟结点调整为父节点的右子树
  • 现在情况3就会变成情况4,接着做情况4的步骤
最透彻的红黑树详解(图文并茂,一文全解)

添加图片注释,不超过 140 字(可选)

4. 当前结点的兄弟结点是黑色的,而且兄弟结点的左孩子是黑色的,右孩子是红色的

  • 将兄弟节点换成父节点的颜色
  • 把父节点和兄弟节点的右孩子涂黑
  • 对父节点做左旋
  • 设置x指针,指向根节点
最透彻的红黑树详解(图文并茂,一文全解)

添加图片注释,不超过 140 字(可选)

当前结点是父结点的右子树的情况

1. 当前结点的兄弟结点是红色的

  • 兄弟节点变成黑色
  • 父节点变成红色
  • 父节点做右旋
  • 将兄弟结点调整为父节点的左子树

2. 当前结点的兄弟结点是黑色的,而且兄弟结点的两个孩子结点都是黑色的

  • 兄弟节点变成红色
  • 轴心结点变为父节点

3. 当前结点的兄弟结点是黑色的,而且兄弟结点的左孩子是黑色的,右孩子是红色的

  • 将右孩子变黑
  • 将兄弟节点变红
  • 对兄弟结点左旋
  • 将兄弟结点调整为父节点的左子树
  • 现在情况3就会变成情况4,接着做情况4的步骤

4. 当前结点的兄弟结点是黑色的,而且兄弟结点的左孩子是红色的,右孩子是黑色的

  • 将兄弟节点换成父节点的颜色
  • 把父节点和兄弟节点的左孩子变黑
  • 对父节点做右旋
  • 将轴心结点调整为根结点

维护代码

void rbtree_delete_fixup(rbtree *T, rbtree_node *x) {
    //如果x是红色,变成黑色,如果x是黑色,需要调整
    while ((x != T->root) && (x->color == BLACK)) {
        //当前结点是父结点的左子树
        if (x == x->parent->left) {
            //兄弟节点
            rbtree_node *w = x->parent->right;
            // 情况1:兄弟节点为红色
            if (w->color == RED) {
                // 兄弟节点变成黑色
                w->color = BLACK;
                // 父节点变成红色
                x->parent->color = RED;
                // 父节点做左旋
                rbtree_left_rotate(T, x->parent);
                //将兄弟结点调整为父节点的右子树
                w = x->parent->right;
            }
            // 情况2:兄弟节点是黑色的,且兄弟的左孩子和右孩子都是黑色
            if ((w->left->color == BLACK) && (w->right->color == BLACK)) {
                // 兄弟节点变成红色
                w->color = RED;
                // 轴心结点变为父节点
                x = x->parent;
            } else {
                // 情况3:x的兄弟节点是黑色的,兄弟的左孩子是红色,右孩子是黑色
                if (w->right->color == BLACK) {
                    // 将左孩子涂黑
                    w->left->color = BLACK;
                    // 将兄弟节点变红
                    w->color = RED;
                    // 对兄弟节点右旋
                    rbtree_right_rotate(T, w);
                    // 重新设置x的兄弟节点
                    w = x->parent->right;
                }
                // 情况4:x的兄弟节点是黑色;x的兄弟节点的右孩子是红色的
                // 将兄弟节点换成父节点的颜色
                w->color = x->parent->color;
                // 把父节点和兄弟节点的右孩子涂黑
                x->parent->color = BLACK;
                w->right->color = BLACK;
                // 对父节点做左旋
                rbtree_left_rotate(T, x->parent);
                // 设置x指针,指向根节点
                x = T->root;
            }

        } else {//当前结点是父结点的右子树
            //兄弟节点
            rbtree_node *w = x->parent->left;
            //情况1:兄弟结点为红色
            if (w->color == RED) {
                // 兄弟节点变成黑色
                w->color = BLACK;
                // 父节点变成红色
                x->parent->color = RED;
                // 父节点做右旋
                rbtree_right_rotate(T, x->parent);
                //将兄弟结点调整为父节点的左子树
                w = x->parent->left;
            }
            // 情况2:兄弟节点是黑色的,且兄弟的左孩子和右孩子都是黑色
            if ((w->left->color == BLACK) && (w->right->color == BLACK)) {
                // 兄弟节点变成红色
                w->color = RED;
                // 轴心结点变为父节点
                x = x->parent;
            } else {
                // 情况3:x的兄弟结点是黑色的,兄弟的左孩子是黑色,右孩子是红色
                if (w->left->color == BLACK) {
                    //将右孩子变黑
                    w->right->color = BLACK;
                    //将兄弟节点变红
                    w->color = RED;
                    //对兄弟结点左旋
                    rbtree_left_rotate(T, w);
                    //将兄弟结点调整为父节点的左子树
                    w = x->parent->left;
                }
                // 情况4:x的兄弟结点是黑色的,兄弟的左孩子是红色,右孩子是黑色
                // 将兄弟节点换成父节点的颜色
                w->color = x->parent->color;
                // 把父节点和兄弟节点的左孩子变黑
                x->parent->color = BLACK;
                w->left->color = BLACK;
                // 对父节点做右旋
                rbtree_right_rotate(T, x->parent);
                //将轴心结点调整为根结点
                x = T->root;
            }
        }
    }
    // 继承节点变为黑色,为了弥补失去的黑高
    x->color = BLACK;
}

红黑树的遍历、查询、测试

rbtree_node *rbtree_search(rbtree *T, KEY_TYPE key) {
    rbtree_node *node = T->root;
    while (node != T->nil) {
        if (key < node->key) {
            node = node->left;
        } else if (key > node->key) {
            node = node->right;
        } else {
            return node;
        }
    }
    return T->nil;
}


void rbtree_traversal(rbtree *T, rbtree_node *node) {
    if (node != T->nil) {
        rbtree_traversal(T, node->left);
        printf("key:%d, color:%d\n", node->key, node->color);
        rbtree_traversal(T, node->right);
    }
}


int main() {
    int keyArray[20] = {24, 25, 13, 35, 23, 26, 67, 47, 38, 98, 20, 19, 17, 49, 12, 21, 9, 18, 14, 15};

    rbtree *T = (rbtree *) malloc(sizeof(rbtree));
    if (T == NULL) {
        printf("malloc failed\n");
        return -1;
    }

    T->nil = (rbtree_node *) malloc(sizeof(rbtree_node));
    T->nil->color = BLACK;
    T->root = T->nil;

    rbtree_node *node = T->nil;
    int i = 0;
    for (i = 0; i < 20; i++) {
        node = (rbtree_node *) malloc(sizeof(rbtree_node));
        node->key = keyArray[i];
        node->value = NULL;
        rbtree_insert(T, node);

    }
    rbtree_traversal(T, T->root);
    printf("----------------------------------------\n");

    for (i = 0; i < 20; i++) {
        rbtree_node *node = rbtree_search(T, keyArray[i]);
        rbtree_node *cur = rbtree_delete(T, node);
        free(cur);
        rbtree_traversal(T, T->root);
        printf("----------------------------------------\n");
    }
}
本文链接:https://www.dzdvip.com/34554.html 版权声明:本文内容均来源于互联网。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌抄袭侵权/违法违规的内容, 请发送邮件至 395045033@qq.com,一经查实,本站将立刻删除。
(0)
打赏 微信扫一扫 微信扫一扫 支付宝扫一扫 支付宝扫一扫
上一篇 2022年7月10日 12:31
下一篇 2022年7月11日 23:16

相关推荐

  • 铁山靠事件起因

    网红“铁山靠”被封号。看到这个消息后,网上一片拍手叫好。     在此之前,已经接连有多个网红因为践踏了底线的原因被封号。包括网红郭老师,还有人类高质量男性徐勤根等人,都曾经凭借着审丑文化红极一时,结果最后还是得到了应有的惩罚。     这次“铁山靠”,根据平台官方的通报,主要的原因是因为低俗,曾经因为多次践踏底线,被中断或者封禁直播间高达一百多次。结果他依旧我行我素,从来不纠正自己的问题,最终等来了封禁的结果,完全是自讨苦吃。     可能有些人并不熟悉铁山靠这一号网红,到底是谁。 网红之所以能够走红,都有自己的拿手“好戏”。有的人靠扮丑,有的人靠娘化形象,有的人则是靠骂人博眼球,从而达到走红赚钱的目的。     简单来说,铁山靠就是一个靠在直播间骂街成名的网红。 在铁山靠的直播间里,充斥着各种粗言烂语,每次和别人连麦的时候,他都会用他当地的地方方言,和别的主播斗嘴,甚至骂人。     此前我也曾对这个网红有所耳闻,他似乎就是一夜之间突然蹿红起来,在各大平台都是被强推的网红。结果打开他的视频看了十几秒,内容尴尬得让我差点抠出三室一厅。 别的网红起码能够唱点歌说个段子,连谢孟伟嘎子多少也有点演戏的本领,铁山靠到底有什么过人的本领?恕我实在看不出来。     到现在为止,我都想不通他为何可以走红网络。以前马路上骂街的人是要被唾弃的,结果这铁山靠在网上骂街,懂不懂就把“我嫩爹”挂在嘴边,这样的人反倒成为了网红,实在是令人匪夷所思。 此前为了吸引眼球,铁山靠先是和王思聪的绯闻前女友孙一宁对骂。两人在网上连麦,互骂不可开交,外人看着很热闹,似乎要打了起来一样,实际上目的只有当事的2个人知道,一切都只是演戏,一切都只不过为了流量和打赏而已。     后来尝到甜头后,铁山靠又和多个网红背地里合作,在直播间大骂一通,人气和话题都有了,赚得盆满锅满。 而影响恶劣的是,不仅仅是铁山靠的骂街行为让人难以忍受,他的一举一动还因此影响到了自己的粉丝群体,变得非常暴戾,甚至存在互踩互撕、控评等畸形“饭圈文化”乱象。     大概的情况就是,铁山靠的粉丝不仅在直播间为铁山靠刷人气,而且还经常跑到连麦的主播房间去控评,甚至骂人网暴。 毫不夸张地说,铁山靠这种一千多万粉丝的网红造成的恶劣影响,一点不比某些当红小鲜肉差。     铁山靠靠着骂街的套路,看似获得了很多的关注和名利,实际上却把他的无脑无…

    2021年9月18日
    69
  • 笔记本电脑排名前十(2022笔记本电脑十大品牌排行榜)

    2022年第一季度,随着各大企业的员工开始正常回公司上班,美国台式电脑出货量出现回温。 疫情之下,眼前的世界全部都在发生变化,太多太多随机的因素,让大家的消费更倾向务实而不是娱乐。而任何事物的发展都离不开它所处的环境,疫情以前,人们通常是公司一部办公电脑,家中一部娱乐用的电脑。但现在随时要面临随机的状况,最佳的选择明显是让二者合为一体,重要的数据带在身上。可见,疫情之下,笔记本电脑已经从曾经的单一需求,变成了多方向复合的需求。比如,职场人需要同时满足日常工作,和“突如其来”的居家办公,而二者在需求上就有着许多的不同点。   而且,伴随85后、90后逐渐变成现在职场的主力军,他们对笔记本的使用性能不断攀升,并且对于设计的需求逐渐转变为注重稳重、品质感和科技感,进而解决更多的使用场景下逐渐融合、日益增加的需求。例如,正襟危坐的商务洽谈、咖啡馆里的头脑风暴、接二连三的远程连线,随时都可能在下一刻出现,有一台性能优异、外观简约大方的笔记本电脑在身边保驾护航,可以使职场人更为从容的面对这些使用场景。   下面是笔者整理出来的笔记本电脑十大品牌,根据电商平台综合数据得出,排名无先后之分,仅供参考。   联想Lenovo   成立于1984年,是一家成立于中国、业务遍及180个市场的全球化科技公司,联想服务全球超过10亿用户,每年为全球用户提供数以亿计的智能终端设备,包括电脑、平板、智能手机等。   驰为HUWI   驰为创新科技(深圳)有限公司于2004年在中国深圳成立,是一家致力于打造互联网跨境品牌(CHUWI)的多元化高新技术企业,主营笔记本电脑、平板电脑、Mini PC、移动电源等数码产品。   惠普HP   惠普成立于1939年美国,全球知名计算机及办公设备制造商,专注于打印机、数码影像、软件、计算机与资讯服务的大型跨国IT公司。   华为   华为创立于1987年,是全球领先的ICT(信息与通信)基础设施和智能终端提供商。目前华为约有19.5万员工,业务遍及170多个国家和地区,服务全球30多亿人口。   小米   小米公司正式成立于2010年4月,是一家以智能手机、智能硬件和 IoT 平台为核心的消费电子及智能制造公司。   华硕   华硕电脑股份有限公司创立于1989年,为全球知名的主板制造商,并跻身全球前三大消费性笔记本电脑品牌。华硕始终对质量与创新全力以赴,不…

    2022年9月27日
    74
  • 一台苹果笔记本到底能用几年?

    一台苹果笔记本到底可以用多久?久到可以传承。 刚工作两年的费昂纳在2015年继承了叔父06版的Macbook。 “它曾经见证了我叔父从设计总监到米其林大厨的跌宕起伏,又陪伴我走过了4年大学时光,在19年用他写完毕业论文之后,我把它传给了我的小外甥。” “如果不是因为系统升不了级了,这台笔记本我可以用到死。” 虽然程序已经无法更新,外甥用来看小猪佩奇还是很流畅的。 关于苹果电脑的使用寿命,苹果官网告诉我们:“保守预计全新苹果笔记本使用时间大约4年”。 真正用了你才知道,那只是苹果公司对用户复购频率的期望。 实际上只要你坚持,你的苹果笔记本绝不会放弃,它会不断刷新你对笔记本使用寿命的认知。 “我还在用我2011版的macbook pro!它陪我上大学,研究生。现在我是一名教师,我用photoshop这些功能来娱乐,它仍然表现的很棒!” “我用的是2009年的Macbook,它已经停产了,但性能依旧很好。” “这段视频是用我2009年买的macbook上用720分辨率看的,记得我买它的时候听过“长寿”的传说,果然没有在开玩笑。” “2001年女儿出生时丈夫送了我这台苹果ibook,今天它和我女儿一起过了18岁生日,它就是我的外置大脑,我相信它可以陪我到最后。” 许多人用了十几年都没有换新款不是因为经济原因,而是因为真的没有必要。 除非故意损坏 自从1992年被苹果PowerBook 100开了光,瑞驰.凡纳特再也没有机会用其他品牌笔记本。 就是这台9英寸,单色背光分辨率只有640×400带轨迹球的家伙,让瑞驰欲罢不能了13年。 直到2006年乔帮主推出了的Macbook pro,他终于忍不住下了手。 苹果最后一款聚碳酸酯材质的黑色笔记本,成为他的另一个挚爱陪伴至今。 瑞驰用行动证明了:虽然苹果很贵,但是放在时间的维度,它更划算。 “苹果笔记本的品质绝对化石级别的,十几年来我对它唯一做维修就是更换电池。” 我现在还留着1992年的Mac,它依然可以工作。当然,我也爱我2007年的Macbook pro。 因为自己的苹果电脑太过耐用而导致无法购入最新款MacBook,31岁的程序员马特维尔在备份了所有工程文件后,烧掉了陪伴自己7年MacBook。 “如果我不这样做,我想它能一直用到我死。” 高级计算机安全顾问罗伯托·桑托乔在QUARE上回答了苹果笔记本为什么更长寿这个问题。 …

    2021年7月15日
    52
  • 产品经理产品需求文档(PRD)的撰写方法

    产品经理主要有两项职责:①评估产品机会 ② 定义要开发的产品;前者我们在上篇的《如何获得产品立项》文章中已经大致介绍过;而定义开发的产品则需要通过产品需求文档(PRD)来描述产品的特征和功能。本篇主要分享下博主平常工作中是如何撰写移动应用的PRD文档的。

    2021年4月27日
    65
  • 摩拜单车胡玮炜如何从月薪3000到36岁套现15亿

    故事从2014年说起,胡玮炜在杭州想租一辆共享单车却租不到,于是就发现了商机,并决定做共享单车,他从在以前汽车圈里的朋友聚在一起拉起了一支团队。 2015年1月摩拜在北京成立,希望通过技术创新结合新型的商业模式,解决最后一公里的出行难题。2016年4月摩拜单车正式上线,并在上海投入运营,9月1日进入北京。2018年5月胡玮炜入选《福布斯》25位亚洲新锐女性榜。2018年美团王兴收购摩拜,胡玮炜套现15亿完美离场,成为人生赢家,实现其财务自由 对于胡玮炜的成功背后离不开一个人,就是蔚来汽车创始人李斌,可谓是其人生导师。俗话说每一个成功女人的背后除了心酸,离不开一个强大的男人支持!在胡玮炜创办摩拜之初得到了李斌的大力支持并扩充其人脉,并且在每一个关键决策的时候都有李斌的帮助,并且指导其完美套现离场! 另一个成功因素就是在ofo戴威拒绝腾讯融资后,腾讯转投摩拜。致使摩拜在2017年单车大战中完美胜出!外界的议论胡玮炜照抄戴威,可是这个时代谁又在乎这个,都是以成败论英雄! 纵观胡玮炜的发家史,虽然短短3年除了运气,离不开个人的努力和做记者时攒下的人脉。还有就是能够把握其大势审时度势的能力!可谓是“女中豪杰”!

    2022年7月14日
    14
  • CPS,CPS产品如何推广跟单?

    接产品,要先拿主包或者其它包的链接,我们去下载玩玩产品。还有他们产品对应最近15-30天的数据(新增、注册、留存、arpu值、流水等数据截图)。总之要是不错的产品,这样ROI会高。

    2021年4月25日
    53