红黑树详解实现 C++

红黑树详解&实现 C++

红黑树

红黑树是一种自平衡二叉查找树,可以保证在最坏情况下基本动态集合操作的时间复杂度为O(lgn)

性质

  • 红黑树是一棵满足红黑性质的二叉搜索树,它在每个节点上增加了一个存储位来表示节点的颜色,可以是REDBLACK
  • 通过对任何一条从根到叶子的简单路径上各个结点的颜色进行约束,红黑树确保没有一条路径会比其他路径长出两倍,因而近似于平衡,可以保证最坏情况下基本动态集合操作的时间复杂度为O(lgn)
  • 红黑性质:
  1. 每个节点是黑色或红色
  2. 根节点是黑色的
  3. 每个叶节点都是黑色的
  4. 若一个节点是红色,则其两个子结点都是黑色的
  5. 对每个节点,从该节点到其所有后代叶节点的简单路径上,均包含相同数目的黑色节点
  • 若一个结点没有子结点或父结点,则该结点相应指针属性的值为NIL,可以把这些NIL视为指向二叉树的叶节点(外部结点)的指针,而把带关键字的结点视为树的内部结点
  • 从某个结点x出发(不含该节点)到达一个也节点的任意一条简单路径上的黑节点个数成为该节点的黑高(black-height),即为bh(x),红黑树黑高为其根节点黑高

旋转

  • 左旋和右旋操作修改红黑树都在O(1)时间内完成,旋转只有指针改变,其他所有属性保持不变
  • 左旋右旋修改的树和未修改之前进行中序遍历产生的关键字值列表相同

左旋

C++代码

void RedBlackTree::leftRotate(Node* x) {Node *y = x->right; // 保存x右子树x->right = y->left;	// 将y的左子树作为x的右孩子if (y->left != NIL) {// 若y的左子树y->left->parent = x;}// y的双亲设为x的双亲y->parent = x->parent;if (x->parent == NIL) {// 若x为根结点,则旋转后的y作为根节点root = y;} else if (x == x->parent->left) {// 若x是x双亲的左子树,则旋转后y作为x原双亲的左子树x->parent->left = y;} else {// 反之亦然x->parent->right = y;}// 旋转后y作为x的双亲,x作为y的左子树y->left = x;x->parent = y;
}

伪代码

LEFT-ROTATE(T,x)
y = x.right	//set y
x.right = y.left	// turn y's subtree into x's right subtree
if y.left ≠ T.nily.left.p = x
y.p = x.p
if x.p == T.nilT.root = y
elseif x == x.p.leftx.p.left = y
elsex.p.right = y
y.left = x		// put x on y's left
x.p = y

右旋

C++代码

void RedBlackTree::rightRotate(Node* x) {// 与左旋相反Node *y = x->left;x->left = y->right;if (y->right != NIL) {y->right->parent = x;}y->parent = x->parent;if (x->parent == NIL) {root = y;} else if (x == x->parent->left) {x->parent->left = y;} else {x->parent->right = y;}y->right = x;x->parent = y;
}

伪代码

右旋操作的伪代码与左旋相反,交换rightleft即可

图解

插入

  • O(lgn)时间内完成向一个棵含有n个结点的红黑树插入一个新节点,并将新节点着色为红色
  • 因为新节点着色为红色可能违反其中的一跳红黑性质,故插入后对结点重新着色并旋转,以保持红黑性质

C++代码

void RedBlackTree::insert(Node* z) {Node *x = root;Node *y = NIL;while (x != NIL) {// 找到z应插入的位置// y为z应插入位置的双亲y = x;if (z->data < x->data) {x = x->left;} else {x = x->right;}}z->parent = y;if (y == NIL) {// 若z的双亲y为NIL,则z为rootroot = z;} else if (z->data < y->data) {// z结点关键字小于y,z作为y的左子树y->left = z;} else {// 反之为右子树y->right = z;}// 设置新插入结点的左右子树为NIL,着色为红色z->left = NIL;z->right = NIL;z->color = RED;// 进行插入调整以维护红黑性质insertFixup(z);
}

伪代码

RB-INSERT(T, z)
y = T.nil
x = T.root
while x ≠ T.nily = xif z.key < x.keyx = x.leftelse x = x.right
z.p = y
if y == T.nilT.root = z
elseif z.key < y.keyy.left = z
elsey.right = z
z.left = T.nil
z.right = T.nil
z.color = RED
RB-INSERT-FIXUP(T, z)

插入调整操作

  • 插入操作必然不会破坏性质1,插入的新结点的左右孩子节点必然是叶子结点,着色均为黑色,满足性质3,故不会破坏性质3,而插入的新节点着色为红色,其代替了一个黑色的叶子节点,同时它的叶子结点为黑色,故不会破坏性质5。
  • 故插入操作只可能会破坏性质2和性质4,即根结点为黑色且红色结点不能有黑色的孩子节点。
    需要考虑六中情况,其中三种情况另外三种情况左右对称,这取决于z的双亲结点是祖父结点的左孩子还是右孩子,在z的双亲是左孩子的情况下,区别于z的叔结点的颜色不同。

情况1

结点z和其双亲、叔结点都是红色的,因为结点z的祖父结点是黑色的,故将z的双亲结点和叔结点着色为黑色,解决z及其双亲结点都是红色的问题,同时将z的祖父结点着色为红色,保证经过当前子树的简单路径的黑色结点数量不变,维护性质5,无论z是左孩子还是右孩子都可以依此来处理。此时,将z的祖父结点作为新的z结点继续调整,现在性质4的破坏只可能发生在新的红色结点z和它的双亲结点之间,条件是如果其父亲结点也是红色的,因为当前新的红色结点z的后代子树已经被调整,保证其满足红黑性质,迭代到当前的z继续调整,若当前的z为根节点,则跳出循环并将当前结点着色为黑色即可,性质1、性质3和性质4不会被破坏,维护了性质2,由于根节点变为黑色,不影响性质5,故满足红黑性质

情况2

结点z的叔结点y是黑色的且z是一个右孩子,此时z变为z的双亲结点,z上升一层,通过左旋操作使得z再下降一层,z的祖父结点不变,由于zz的双亲结点都是红色,故能够维护性质5不变,经过上述操作,将情况2转变为情况3处理

情况3

结点z的叔结点y是黑色的且z是一个左孩子,此时改变结点z的双亲结点的颜色为黑色,再经过右旋操作维护性质5。在情况2和情况3中,因为唯一着色为红色的z的祖先节点会因为情况3的右旋操作称为一个黑色结点的子结点,必然不会破坏性质2,同时两种情况的操作调整红黑树以维持性质4,性质1和性质3必然不会被破坏,能够保证红黑树满足红黑性质

C++代码

void RedBlackTree::insertFixup(Node* z) {while (z->parent->color == RED) {if (z->parent == z->parent->parent->left) {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;leftRotate(z);}z->parent->color = BLACK;z->parent->parent->color = RED;rightRotate(z->parent->parent);}} else {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;rightRotate(z);}z->parent->color = BLACK;z->parent->parent->color = RED;leftRotate(z->parent->parent);}}}root->color = BLACK;
}

伪代码

RB-INSERT-FIXUP(T, z)
while z.p.color == REDif z.p == z.p.p.lefty = z.p.p.rightif y.color == REDz.p.color = BLACK		// case1y.color = BLACK		// case1z.p.p.color = RED		// case1z = z.p.p			// case1else if z == z.p.rightz = z.p			// case2LEFT-ROTATE(T,z)		// case2z.p.color = BLACK			// case3z.p.p.color = RED			// case3RIGHT-ROTATE(T, z.p.p)		// case3else(same as then caluse with "right" and "left" exchanged)y = z.p.p.leftif y.color == REDz.p.color = BLACKy.color = BLACKz.p.p.color = REDz = z.p.pelse if z == z.p.leftz = z.pRIGHT-ROTATE(T,z)z.p.color = BLACKz.p.p.color = REDLEFT-ROTATE(T, z.p.p)T.root.color = BLACK

图解

删除操作

  • 红黑树的删除需要考虑四种情况,在考虑四种情况的同时,在整个过程中维护结点y为从树中删除的结点或移至树内的结点,当待删除结点z子节点少于两个时,y指向待删除结点,否则y指向z的后继结点,之后将y代替z的位置并删除z
  • 由于y的颜色可能改变,用originalColor记录y改变前的颜色,同时赋予yz同样的颜色,再删除操作结束时,若原来的颜色是黑色的,则会引起红黑性质的破坏,因为若是前两种情况,y指向删除结点z,若z为黑色则会破坏红黑性质,若为红色则不会破坏红黑性质;若是后两种情况,y指向删除后代替z的结点,且为y着色为z的颜色,故z位置的颜色不变,不会引起红黑性质的破坏,但y原来的原色若为黑色,则会破坏红黑性质,需要进行调整,若为红色则不会破坏红黑性质
  • 维护指针x指向y原来的位置,前两种情况x指向y的原始位置,即z的左孩子或右孩子,后两种情况x也指向y的原始位置,此时为y的右孩子,因为右孩子之后将代替y

情况1

待删除结点z只有左子树,则让左子树代替待删除结点,再删除结点z

情况2

待删除结点z只有右子树,则让右子树代替待删除结点,再删除结点z

情况3

待删除结点有两个子树,选取待删除结点的直接后继结点y,若其直接后继结点y是待删除结点z的右孩子,则直接将其用右孩子结点代替,再删除结点z

情况4

待删除结点有两个子树,选取待删除结点的直接后继结点y,若其直接后继结点y不是待删除结点z的右孩子,则将直接后继结点yy的右孩子代替,再将y从原本以y为根结点的子树中删除,并用y代替z,再删除结点zyz右子树中关键字最小的结点,必然没有左子树,若也无右子树,即y为叶子结点,上述操作会直接删除y

C++代码

void RedBlackTree::remove(Node *z) {Node *y = z;Node *x = NIL;bool originalColor = y->color;if (z->left == NIL) {x = z->right;transplant(z, z->right);} else if (z->right == NIL) {x = z->left;transplant(z, z->left);} else {y = treeMinimum(z->right);originalColor = y->color;x = y->right;if (y->parent == z) {x->parent = y;} else {transplant(y, y->right);y->right = z->right;y->right->parent = y;}transplant(z, y);y->left = z->left;y->left->parent = y;y->color = z->color;}if (originalColor == BLACK) {deleteFixUp(x);}
}

伪代码

RB-DELETE(T, z)
y = z
y-original-color = y.color
if z.left == T.nilx = z.rightRB-TRANSPLANT(T, z, z.right)
elseif z.right == T.nilx = z.leftRB-TRANSPLANT(T, y, y.right)
elsey = TREE-MINIMUM(z.right)y-original-color = y.colorx = y.rightif y.p == zx.p = yelseRB-TRANSPLANT(T, y, y.right)y.right = z.righty.right.p = yRB-TRANSPLANT(T, z, y)y.left = z.lefty.left.p = yy.color = z.color
if y-original-color == BLACKRB-DELETE-FIXUP(T, x)

删除调整操作

  • 删除操作中,被删除结点或被移动来代替删除结点的颜色若为红色则能够保持红黑性质,原因如下
    1. 树中黑高不变
    2. 不存在相邻的两个红色结点,因为y在树中占据z的位置,在考虑到z的颜色,树中y的新位置不可能有两个相邻的红色结点,若y不是z的右孩子,则y原右孩子x代替y的位置,若y是红色,则x一定是黑色,代替后不可能出现两个红色结点相邻
    3. y为红色,则不可能是根结点,故根结点仍然是黑色的
  • 被删除结点或被移动来代替删除结点的颜色若为黑色就可能已经引入一个或多个红黑性质被破坏的情况,可能如下问题
    1. y是原来的根结点,y的一个红色孩子成为新的根结点,违反性质2
    2. xx的双亲都是红色的,违反性质4
    3. 在树中移动y导致先前包含y的任何简单路径上黑结点数量少1,故y的任何祖先都不满足性质5
  • 为改正上述问题,将占有y原来位置的结点x视为还有一重额外的黑色,在这种假设下,性质5成立,红黑树满足条件,当黑色结点y删除或移动时,将黑色“下推”给结点x,现在问题变为结点x可能既不是黑色也不是红色,而是红黑色或双重黑色,违反性质1,而结点的color属性仍然为BLACKRED,额外的黑色是针对x结点的,而不反映在其color属性上
  • 删除调整操作有为八种情况,以x是其双亲左孩子为例,x是双亲右孩子与其对称
  • 考虑调整完成的条件
    x指向红黑结点,即x结点的color属性为RED,将x着色为黑色,当x

情况1

x的兄弟结点w是红色的,因为w必须有黑色子结点,所以可以改变wx双亲结点的颜色,然后对x双亲做一次左旋而不违反红黑性质,现在w指向x新的兄弟结点,颜色为黑色,这样就从情况1转到情况2、情况3或情况4处理

情况2

x的兄弟结点w为黑色且w的两个子结点都是黑色的,因为w也是黑色的,所以从xw去掉一重黑色,使得x只有一重黑色而w为红色,为补偿从xw中去掉的黑色,在原来是红色或黑色的x双亲上新增一重黑色,将x指向x的双亲继续调整。若是由情况1进入情况2,则新的x是红黑色的,因为原来的x的双亲是红色的,现在x上升代替其双亲并上推一层黑色给双亲,此时红黑色的xcolor属性为RED,循环终止,将x着色为黑色后调整结束;若新x结点是根结点,也跳出循环,root着色为黑色,删除x多余的黑色,调整结束

情况3

x的兄弟结点w为黑色且w左孩子是红色的而w右孩子是黑色的,交换w和其左孩子的颜色,在对w进行右旋操作,而不违反红黑树的性质,此时x的新兄弟结点w是一个有红色结点右孩子的黑色结点,转变为情况4

情况4

x的兄弟结点w为黑色且w右孩子是红色的,将双亲结点的颜色赋予w并将双亲结点染色成黑色,w的右孩子结点也染色成黑色,将双亲结点左旋,此时对于当前以w为根结点的子树,根结点的颜色较之前x的双亲为根时没有改变,x将一重黑色上推给x的双亲,而w将自身原来的黑色推给其原来为红色的右孩子,此时右孩子占据原来w结点的位置,这样既不会违反红黑性质,也去除x多余的一重黑色,维护了性质1,满足红黑性质,将x指向为根结点跳出循环,调整结束。

C++代码

void RedBlackTree::deleteFixUp(Node *x) {while (x != root && x->color == BLACK) {if (x == x->parent->left) {Node *w = x->parent->right;if (w->color == RED) {w->color = BLACK;x->parent->color = RED;leftRotate(x->parent);w = x->parent->right;}if (w->left->color == BLACK && w->right->color == BLACK) {w->color = RED;x = x->parent;} else {if (w->right->color == BLACK) {w->left->color = BLACK;w->color = RED;rightRotate(w);w = x->parent->right;}w->color = x->parent->color;x->parent->color = BLACK;w->right->color = BLACK;leftRotate(x->parent);x = root;}} else {Node *w = x->parent->left;if (w->color == RED) {w->color = BLACK;x->parent->color = RED;rightRotate(x->parent);w = x->parent->left;}if (w->right->color == BLACK && w->left->color == BLACK) {w->color = RED;x = x->parent;} else {if (w->left->color == BLACK) {w->right->color = BLACK;w->color = RED;leftRotate(w);w = x->parent->left;}w->color = x->parent->color;x->parent->color = BLACK;w->left->color = BLACK;rightRotate(x->parent);x = root;}}}x->color = BLACK;
}

伪代码

RB-DELETE-FIXUP(T, x)while x != T.root and x.color == BLACKif x == x.p.leftw = x.p.rightif w.color == REDw.color = BLACK         // case1x.p.color = RED         // case1LEFT-ROTATE(T, x.p)     // case1w = x.p.right           // case1if w.left.color == BLACK and w.right.color == BLACKw.color = RED           // case2x = x.p                 // case2elseif w.right.color == BLACK   // case3w.left.color = BLACK    // case3w.color = RED           // case3RIGHT-ROTATE(T, w)      // case3w = x.p.right           // case3w.color = x.p.color         // case4x.p.color = BLACK           // case4w.right.color = BLACK       // case4LEFT-ROTATE(T, x.p)         // case4x = T.root                  // case4elsew = x.p.leftif w.color == REDw.color = BLACK         // case1x.p.color = RED         // case1LEFT-ROTATE(T, x.p)     // case1w = x.p.left           // case1if w.right.color == BLACK and w.left.color == BLACKw.color = RED           // case2x = x.p                 // case2elseif w.left.color == BLACK   // case3w.right.color = BLACK    // case3w.color = RED           // case3LEFT-ROTATE(T, w)      // case3w = x.p.left           // case3w.color = x.p.color         // case4x.p.color = BLACK           // case4w.left.color = BLACK       // case4RIGHT-ROTATE(T, x.p)         // case4x = T.root                  // case4
x.color = BLACK

图解

移动替换操作

u替换用v替换,考虑u为根结点、左孩子、右孩子的情况,以及v是否为NIL的情况

C++代码

void RedBlackTree::transplant(Node *u, Node *v) {if (u->parent == NIL) {root = v;} else if (u == u->parent->left) {u->parent->left = v;} else {u->parent->right = v;}v->parent = u->parent;
}

伪代码

RB-TRANSPLANT(T, u, v)
if u.p == T.nilT.root = v;
elseif u == u.p.leftu.p.left = v
else u.p.right = v
v.p = u.p

查找最小值操作

根据二叉搜索树定义,不断向左寻找,找到最左的顶点即可,操作与二叉搜索树相同

C++代码

Node* RedBlackTree::treeMinimum(Node *x) {while (x->left != NIL) {x = x->left;}return x;
}

伪代码

TREE-MINIMUM(x)
while x.left != NILx = x.left
return x

搜索操作

操作与二叉搜索树相同

C++代码

Node* RedBlackTree::search(int data) {Node *x = root;while (x != NIL) {if (data < x->data) {x = x->left;} else if (data > x->data) {x = x->right;} else {break;}}return x;
}

伪代码

ITERATIVE-TREE-SEARCH(x, k)while x != NIL and k != x.keyif k < x.keyx = x.leftelsex = rightreturn x

实现代码

/*
author : eclipse
email  : eclipsecs@qq.com
time   : Sun Jul 12 19:25:40 2020
*/
#include<bits/stdc++.h>
using namespace std;struct Node {int data;Node *left;Node *right;Node *parent;bool color;Node(int value, Node *leftChild, Node *rightChild, Node *p, bool colour) : data(value), left(leftChild), right(rightChild), parent(p), color(colour) {}
};class RedBlackTree {
private:static const bool RED = true;static const bool BLACK = false;Node *NIL;Node *root;void leftRotate(Node* x);void rightRotate(Node* x);void insertFixup(Node* z);void insert(Node *z);void transplant(Node *u, Node *v);void remove(Node *z);Node* treeMinimum(Node *x);void deleteFixUp(Node *x);void inOrderTraverse(Node *x);Node* search(int data);
public:RedBlackTree();void insert(int data);void inOrderTraverse();void remove(int data);
};RedBlackTree::RedBlackTree() {NIL = new Node(0, NULL, NULL, NULL, BLACK);root = NIL;
}void RedBlackTree::leftRotate(Node* x) {Node *y = x->right;x->right = y->left;if (y->left != NIL) {y->left->parent = x;}y->parent = x->parent;if (x->parent == NIL) {root = y;} else if (x == x->parent->left) {x->parent->left = y;} else {x->parent->right = y;}y->left = x;x->parent = y;
}void RedBlackTree::rightRotate(Node* x) {Node *y = x->left;x->left = y->right;if (y->right != NIL) {y->right->parent = x;}y->parent = x->parent;if (x->parent == NIL) {root = y;} else if (x == x->parent->left) {x->parent->left = y;} else {x->parent->right = y;}y->right = x;x->parent = y;
}void RedBlackTree::insert(int data) {Node *x = new Node(data, NIL, NIL, NIL, RED);insert(x);
}void RedBlackTree::insert(Node* z) {Node *x = root;Node *y = NIL;while (x != NIL) {y = x;if (z->data < x->data) {x = x->left;} else {x = x->right;}}z->parent = y;if (y == NIL) {root = z;} else if (z->data < y->data) {y->left = z;} else {y->right = z;}z->left = NIL;z->right = NIL;z->color = RED;insertFixup(z);
}void RedBlackTree::insertFixup(Node* z) {while (z->parent->color == RED) {if (z->parent == z->parent->parent->left) {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;leftRotate(z);}z->parent->color = BLACK;z->parent->parent->color = RED;rightRotate(z->parent->parent);}} else {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;rightRotate(z);}z->parent->color = BLACK;z->parent->parent->color = RED;leftRotate(z->parent->parent);}}}root->color = BLACK;
}void RedBlackTree::transplant(Node *u, Node *v) {if (u->parent == NIL) {root = v;} else if (u == u->parent->left) {u->parent->left = v;} else {u->parent->right = v;}v->parent = u->parent;
}Node* RedBlackTree::search(int data) {Node *x = root;while (x != NIL) {if (data < x->data) {x = x->left;} else if (data > x->data) {x = x->right;} else {break;}}return x;
}void RedBlackTree::remove(int data) {Node *x = search(data);if (x != NIL) {remove(x);printf("\nRemove element %d successfully!\n", data);} else {printf("\nElement %d not exist!\n", data);}
}void RedBlackTree::remove(Node *z) {Node *y = z;Node *x = NIL;bool originalColor = y->color;if (z->left == NIL) {x = z->right;transplant(z, z->right);} else if (z->right == NIL) {x = z->left;transplant(z, z->left);} else {y = treeMinimum(z->right);originalColor = y->color;x = y->right;if (y->parent == z) {x->parent = y;} else {transplant(y, y->right);y->right = z->right;y->right->parent = y;}transplant(z, y);y->left = z->left;y->left->parent = y;y->color = z->color;}if (originalColor == BLACK) {deleteFixUp(x);}
}Node* RedBlackTree::treeMinimum(Node *x) {while (x->left != NIL) {x = x->left;}return x;
}void RedBlackTree::deleteFixUp(Node *x) {while (x != root && x->color == BLACK) {if (x == x->parent->left) {Node *w = x->parent->right;if (w->color == RED) {w->color = BLACK;x->parent->color = RED;leftRotate(x->parent);w = x->parent->right;}if (w->left->color == BLACK && w->right->color == BLACK) {w->color = RED;x = x->parent;} else {if (w->right->color == BLACK) {w->left->color = BLACK;w->color = RED;rightRotate(w);w = x->parent->right;}w->color = x->parent->color;x->parent->color = BLACK;w->right->color = BLACK;leftRotate(x->parent);x = root;}} else {Node *w = x->parent->left;if (w->color == RED) {w->color = BLACK;x->parent->color = RED;rightRotate(x->parent);w = x->parent->left;}if (w->right->color == BLACK && w->left->color == BLACK) {w->color = RED;x = x->parent;} else {if (w->left->color == BLACK) {w->right->color = BLACK;w->color = RED;leftRotate(w);w = x->parent->left;}w->color = x->parent->color;x->parent->color = BLACK;w->left->color = BLACK;rightRotate(x->parent);x = root;}}}x->color = BLACK;
}void RedBlackTree::inOrderTraverse() {inOrderTraverse(root);
}void RedBlackTree::inOrderTraverse(Node *x) {if (x == NIL) {return;}inOrderTraverse(x->left);printf("%d ", x->data);inOrderTraverse(x->right);
}int main(int argc, char const *argv[]) {RedBlackTree *redBlackTree = new RedBlackTree();int N;scanf("%d", &N);for (int i = 0; i < N; i++) {int data;scanf("%d", &data);redBlackTree->insert(data);}redBlackTree->inOrderTraverse();redBlackTree->remove(1);redBlackTree->remove(2);redBlackTree->remove(3);redBlackTree->remove(4);redBlackTree->inOrderTraverse();return 0;
}

测试数据

10
1 2 3 12312 3213 4 23 45 90 89

测试结果

1 2 3 4 23 45 89 90 3213 12312
Remove element 1 successfully!Remove element 2 successfully!Remove element 3 successfully!Remove element 4 successfully!
23 45 89 90 3213 12312

鸣谢

算法导论

最后

  • 上述伪代码和图解来自算法导论
  • 由于博主水平有限,不免有疏漏之处,欢迎读者随时批评指正,以免造成不必要的误解!