红黑树

定义:一棵二叉查找树如果满足下面的红黑性质,则为一棵红黑树:

1)每个结点不是红的就是黑的

2)根结点是黑的

3)每个叶结点是黑的

4)如果一个结点是红的,它的两个儿子都是黑的(即不可能有两个连续的红色结点)

5)对于每个结点,从该结点到其子孙结点的所有路径上包含相同数目的黑结点

性质:

这些约束确保了红黑树的关键特性: 从根到叶子的最长的可能路径不多于最短的可能路径的两倍长。结果是这个树大致上是平衡的。插入、删除和查找某个值的最坏情况下的时间复杂度与树的高度成比例。因为树的高度被控制,所以红黑树在最坏情况下都是高效的,而不同于普通的二叉查找树

要知道为什么这些性质确保了这个结果,注意到性质4导致了路径不能有两个毗连的红色节点就足够了。最短的路径可能都是黑色节点,最长的可能路径有交替的红色和黑色节点。因为根据性质5所有最长的路径都有相同数目的黑色节点,这就表明了没有路径能多于任何其他路径的两倍长。

来源与应用:

*1972年 由Rudolf Bayer发明的,他称之为“对称二叉B树”,它现代的名字是Leo J. Guibas和 Robert Sedgewick 于1978年写的一篇论文中获得的。它是复杂的,但它的操作有着良好的最坏情况运行时间,并且在实践中是高效的: 它可以在O(log n)时间内做查找,插入和删除,这里的n 是树中元素的数目。

*红黑树多用在内部排序,即全放在内存中的,微软STL的map和set的内部实现就是红黑树。B树多用在内存里放不下,大部分数据存储在外存上时。因为B树层数少,因此可以确保每次操作,读取磁盘的次数尽可能的少。

*在Linux中有很多地方用到了RD树。Linux内核在管理vm_area_struct时就是采用了红黑树来维护内存块的。anticipatory, deadline, 和CFQ I/O调度都使用的是RB树进行请求跟踪,还有CD/DVD驱动的包管理也是如此。高精度计时器(high-resolutiontimer)使用RB树组织定时请求。

复杂度:

一棵有n个结点的红黑树的高度最多为2log(n+1)

操作:

因为每一个红黑树也是一个特化的二叉查找树,因此红黑树上的只读操作与普通二叉查找树上的只读操作相同。然而,在红黑树上进行插入操作和删除操作会导致不再符合红黑树的性质。恢复红黑树的性质需要少量(O(log n))的颜色变更(实际是非常快速的)和不超过三次树旋转(对于插入操作是两次)。虽然插入和删除很复杂,但操作时间仍可以保持为 O(log n) 次。

插入(5种情况):

通过下列函数,可以找到一个结点的叔父和祖父结点:

 node* grandparent(node *n){
return n->parent->parent;
} node* uncle(node *n){
if(n->parent==grandparent(n)->left)
return grandparent(n)->right;
else
return grandparent(n)->left;
}

情形1:新节点N为根节点,我们把它绘成黑色,满足性质2和5。

 void insert_case1(node* n){
if(n->parent==NULL)
n->color=BLACK;
else
insert_case2(n);
}

情形2:新节点的父节点P是黑色的。所以性质4没有失效(新节点是红色的),性质5也未受到威胁,虽然新节点N有两个黑色的叶子子节点,但是新节点N是红色的并且它取代了原来的一个黑色的子节点,所以性质5仍然满足。

 void insert_case2(node* n){
if(n->parent->color==BLACK)
return; //插入此处后树仍是有效的
else
insert_case3(n);
}

注意:在下列情形下,我们假定新节点的父节点为红色,所以它有祖父结点;因为如果父节点是根节点,那么父节点应当是黑色。所以新节点总有一个叔父结点。

情形3:如果父节点P和叔父节点U二者都是红色,我们可以将它们两个重绘为黑色并重绘祖父节点为红色。因为通过父节点P或叔父节点U的任何路径都必定通过祖父节点G,在这些路径上的黑节点数目没有改变。但是,红色的祖父节点G的父节点也有可能是红色的,这就违反了性质4。为了解决这个问题,我们在祖父节点G上递归地进行情形1的整个过程。(把G当成是新加入的节点进行各种情形的检查)

 void insert_case3(node *n) {
if (uncle(n) != NULL && uncle(n)->color == RED) {
n->parent->color = BLACK;
uncle(n)->color = BLACK;
grandparent(n)->color = RED;
insert_case1(grandparent(n));
}
else
insert_case4(n);
}

注意:在余下的情况,我们假定父节点P是祖父节点G的左儿子。如果它是右儿子,情形4和情形5中所述的左和右对调。

情形4:父节点P是红色而叔父节点U是黑色或缺少,并且新节点N是父节点P的右儿子而父节点P是祖父节点G的左儿子。在这种情况下我们进行一次左旋,调换新节点N和父节点P的角色;接着,我们按情形5处理以前的父节点P以解决仍然失效的性质4。注意这个改变会导致某些路径通过它们以前不通过的新节点N(比如图中1号叶子节点)或不通过节点P(比如图中3号叶子节点),但由于这两个节点都是红色的,所以性质5仍有效。

 void insert_case4(node *n) {
if (n == n->parent->right && n->parent == grandparent(n)->left) {
rotate_left(n->parent);
n = n->left;
} else if (n == n->parent->left && n->parent == grandparent(n)->right) {
rotate_right(n->parent);
n = n->right;
}
insert_case5(n);
}

情形5: 父节点P是红色而叔父节点U是黑色或缺少,新节点N是其父节点的左子节点,而父节点P又是其父节点G的左子节点。在这种情形下,我们进行针对祖父节点G的一次右旋转; 在旋转产生的树中,以前的父节点P现在是新节点N和以前的祖父节点G的父节点。我们知道以前的祖父节点G是黑色,否则父节点P就不可能是红色(如果P和G都是红色就违反了性质4,所以G必须是黑色)。我们切换以前的父节点P和祖父节点G的颜色,结果的树满足性质4。性质5也仍然保持满足,因为通过这三个节点中任何一个的所有路径以前都通过祖父节点G,现在它们都通过以前的父节点P。在各自的情形下,这都是三个节点中唯一的黑色节点。

  void insert_case5(node *n) {
n->parent->color = BLACK;
grandparent(n)->color = RED;
if (n == n->parent->left && n->parent == grandparent(n)->left) {
rotate_right(grandparent(n));
} else {
/* Here, n == n->parent->right && n->parent == grandparent(n)->right */
rotate_left(grandparent(n));
}
}

注意插入实际上是原地算法,因为上述所有调用都使用了尾部递归

删除(6种情况)

如果需要删除的节点有两个儿子,那么问题可以被转化成删除另一个只有一个儿子的节点的问题(为了表述方便,这里所指的儿子,为非叶子节点的儿子)。对于二叉查找树,在删除带有两个非叶子儿子的节点的时候,我们找到要么在它的左子树中的最大元素、要么在它的右子树中的最小元素,并把它的值转移到要删除的节点中(如在这里所展示的那样)。我们接着删除我们从中复制出值的那个节点,它必定有少于两个非叶子的儿子。因为只是复制了一个值,不违反任何性质,这就把问题简化为如何删除最多有一个儿子的节点的问题。它不关心这个节点是最初要删除的节点还是我们从中复制出值的那个节点。

在本文余下的部分中,我们只需要讨论删除只有一个儿子的节点(如果它两个儿子都为空,即均为叶子,我们任意将其中一个看作它的儿子)。如果我们删除一个红色节点(此时该节点的儿子将都为叶子节点),它的父亲和儿子一定是黑色的。所以我们可以简单的用它的黑色儿子替换它,并不会破坏性质3和性质4。通过被删除节点的所有路径只是少了一个红色节点,这样可以继续保证性质5。另一种简单情况是在被删除节点是黑色而它的儿子是红色的时候。如果只是去除这个黑色节点,用它的红色儿子顶替上来的话,会破坏性质5,但是如果我们重绘它的儿子为黑色,则曾经通过它的所有路径将通过它的黑色儿子,这样可以继续保持性质5。

需要进一步讨论的是在要删除的节点和它的儿子二者都是黑色的时候,这是一种复杂的情况。我们首先把要删除的节点替换为它的儿子。出于方便,称呼这个儿子为N(在新的位置上),称呼它的兄弟(它父亲的另一个儿子)为S。在下面的示意图中,我们还是使用P称呼N的父亲,SL称呼S的左儿子,SR称呼S的右儿子。我们将使用下述函数找到兄弟节点:

 node *sibling(node *n)
{
if (n == n->parent->left)
return n->parent->right;
else
return n->parent->left;
}

我们可以使用下列代码进行上述的概要步骤,这里的函数 replace_node 替换 child 到 n 在树中的位置。出于方便,在本章节中的代码将假定空叶子被用不是 NULL 的实际节点对象来表示(在插入章节中的代码可以同任何一种表示一起工作)。

 void delete_one_child(node *n)
{
/*
* Precondition: n has at most one non-null child.
*/
node *child = is_leaf(n->right) ? n->left : n->right; replace_node(n, child);
if (n->color == BLACK) {
if (child->color == RED)
child->color = BLACK;
else
delete_case1(child);
}
free(n);
}

如果N和它初始的父亲是黑色,则删除它的父亲导致通过N的路径都比不通过它的路径少了一个黑色节点。因为这违反了性质5,树需要被重新平衡。有几种情形需要考虑:

情形1: N是新的根。在这种情形下,我们就做完了。我们从所有路径去除了一个黑色节点,而新根是黑色的,所以性质都保持着。

 void delete_case1(node *n)
{
if (n->parent != NULL)
delete_case2(n);
}

注意: 在情形2、5和6下,我们假定N是它父亲的左儿子。如果它是右儿子,则在这些情形下的应当对调。

情形2: S是红色。在这种情形下我们在N的父亲上做左旋转,把红色兄弟转换成N的祖父,我们接着对调N的父亲和祖父的颜色。完成这两个操作后,尽管所有路径上黑色节点的数目没有改变,但现在N有了一个黑色的兄弟和一个红色的父亲(它的新兄弟是黑色因为它是红色S的一个儿子),所以我们可以接下去按情形4情形5情形6来处理。

 void delete_case2(node *n)
{
struct node *s = sibling(n); if (s->color == RED) {
n->parent->color = RED;
s->color = BLACK;
if (n == n->parent->left)
rotate_left(n->parent);
else
rotate_right(n->parent);
}
delete_case3(n);
}

情形3: N的父亲、S和S的儿子都是黑色的。在这种情形下,我们简单的重绘S为红色。结果是通过S的所有路径,它们就是以前通过N的那些路径,都少了一个黑色节点。因为删除N的初始的父亲使通过N的所有路径少了一个黑色节点,这使事情都平衡了起来。但是,通过P的所有路径现在比不通过P的路径少了一个黑色节点,所以仍然违反性质4。要修正这个问题,我们要从情形1开始,在P上做重新平衡处理。

 void delete_case3(node *n)
{
node *s = sibling(n); if ((n->parent->color == BLACK) &&
(s->color == BLACK) &&
(s->left->color == BLACK) &&
(s->right->color == BLACK)) {
s->color = RED;
delete_case1(n->parent);
} else
delete_case4(n);
}

情形4: S和S的儿子都是黑色,但是N的父亲是红色。在这种情形下,我们简单的交换N的兄弟和父亲的颜色。这不影响不通过N的路径的黑色节点的数目,但是它在通过N的路径上对黑色节点数目增加了一,添补了在这些路径上删除的黑色节点。

 void delete_case4(node *n)
{
node *s = sibling(n); if ((n->parent->color == RED) &&
(s->color == BLACK) &&
(s->left->color == BLACK) &&
(s->right->color == BLACK)) {
s->color = RED;
n->parent->color = BLACK;
} else
delete_case5(n);
}

情形5: S是黑色,S的左儿子是红色,S的右儿子是黑色,而N是它父亲的左儿子。在这种情形下我们在S上做右旋转,这样S的左儿子成为S的父亲和N的新兄弟。我们接着交换S和它的新父亲的颜色。所有路径仍有同样数目的黑色节点,但是现在N有了一个右儿子是红色的黑色兄弟,所以我们进入了情形6。N和它的父亲都不受这个变换的影响。

 void
delete_case5(node *n)
{
node *s = sibling(n); if (s->color == BLACK) { /* this if statement is trivial,
due to Case 2 (even though Case two changed the sibling to a sibling's child,
the sibling's child can't be red, since no red parent can have a red child). */
// the following statements just force the red to be on the left of the left of the parent,
// or right of the right, so case six will rotate correctly.
if ((n == n->parent->left) &&
(s->right->color == BLACK) &&
(s->left->color == RED)) { // this last test is trivial too due to cases 2-4.
s->color = RED;
s->left->color = BLACK;
rotate_right(s);
} else if ((n == n->parent->right) &&
(s->left->color == BLACK) &&
(s->right->color == RED)) {// this last test is trivial too due to cases 2-4.
s->color = RED;
s->right->color = BLACK;
rotate_left(s);
}
}
delete_case6(n);
}

情形6: S是黑色,S的右儿子是红色,而N是它父亲的左儿子。在这种情形下我们在N的父亲上做左旋转,这样S成为N的父亲和S的右儿子的父亲。我们接着交换N的父亲和S的颜色,并使S的右儿子为黑色。子树在它的根上的仍是同样的颜色,所以性质3没有被违反。但是,N现在增加了一个黑色祖先: 要么N的父亲变成黑色,要么它是黑色而S被增加为一个黑色祖父。所以,通过N的路径都增加了一个黑色节点。

此时,如果一个路径不通过N,则有两种可能性:

  • 它通过N的新兄弟。那么它以前和现在都必定通过S和N的父亲,而它们只是交换了颜色。所以路径保持了同样数目的黑色节点。
  • 它通过N的新叔父,S的右儿子。那么它以前通过S、S的父亲和S的右儿子,但是现在只通过S,它被假定为它以前的父亲的颜色,和S的右儿子,它被从红色改变为黑色。合成效果是这个路径通过了同样数目的黑色节点。

在任何情况下,在这些路径上的黑色节点数目都没有改变。所以我们恢复了性质4。在示意图中的白色节点可以是红色或黑色,但是在变换前后都必须指定相同的颜色。

 void delete_case6(node *n)
{
node *s = sibling(n); s->color = n->parent->color;
n->parent->color = BLACK; if (n == n->parent->left) {
s->right->color = BLACK;
rotate_left(n->parent);
} else {
s->left->color = BLACK;
rotate_right(n->parent);
}
}

C++ 示例代码

 #define BLACK 1
#define RED 0 using namespace std; class bst {
private: struct Node {
int value;
bool color;
Node *leftTree, *rightTree, *parent; Node() {
color = RED;
leftTree = NULL;
rightTree = NULL;
parent = NULL;
value = ;
} Node* grandparent() {
if (parent == NULL) {
return NULL;
}
return parent->parent;
} Node* uncle() {
if (grandparent() == NULL) {
return NULL;
}
if (parent == grandparent()->rightTree)
return grandparent()->leftTree;
else
return grandparent()->rightTree;
} Node* sibling() {
if (parent->leftTree == this)
return parent->rightTree;
else
return parent->leftTree;
}
}; void rotate_right(Node *p) {
Node *gp = p->grandparent();
Node *fa = p->parent;
Node *y = p->rightTree; fa->leftTree = y; if (y != NIL)
y->parent = fa;
p->rightTree = fa;
fa->parent = p; if (root == fa)
root = p;
p->parent = gp; if (gp != NULL) {
if (gp->leftTree == fa)
gp->leftTree = p;
else
gp->rightTree = p;
} } void rotate_left(Node *p) {
if (p->parent == NULL) {
root = p;
return;
}
Node *gp = p->grandparent();
Node *fa = p->parent;
Node *y = p->leftTree; fa->rightTree = y; if (y != NIL)
y->parent = fa;
p->leftTree = fa;
fa->parent = p; if (root == fa)
root = p;
p->parent = gp; if (gp != NULL) {
if (gp->leftTree == fa)
gp->leftTree = p;
else
gp->rightTree = p;
}
} void inorder(Node *p) {
if (p == NIL)
return; if (p->leftTree)
inorder(p->leftTree); cout << p->value << " "; if (p->rightTree)
inorder(p->rightTree);
} string outputColor(bool color) {
return color ? "BLACK" : "RED";
} Node* getSmallestChild(Node *p) {
if (p->leftTree == NIL)
return p;
return getSmallestChild(p->leftTree);
} bool delete_child(Node *p, int data) {
if (p->value > data) {
if (p->leftTree == NIL) {
return false;
}
return delete_child(p->leftTree, data);
} else if (p->value < data) {
if (p->rightTree == NIL) {
return false;
}
return delete_child(p->rightTree, data);
} else if (p->value == data) {
if (p->rightTree == NIL) {
delete_one_child(p);
return true;
}
Node *smallest = getSmallestChild(p->rightTree);
swap(p->value, smallest->value);
delete_one_child(smallest); return true;
}
} void delete_one_child(Node *p) {
Node *child = p->leftTree == NIL ? p->rightTree : p->leftTree;
if (p->parent == NULL && p->leftTree == NIL && p->rightTree == NIL) {
p = NULL;
return;
} if (p->parent == NULL) {
delete p;
child->parent = NULL;
root = child;
return;
} if (p->parent->leftTree == p) {
p->parent->leftTree = child;
} else {
p->parent->rightTree = child;
}
child->parent = p->parent; if (p->color == BLACK) {
if (child->color == RED) {
child->color = BLACK;
} else
delete_case(child);
} delete p;
} void delete_case(Node *p) {
if (p->parent == NULL) {
p->color = BLACK;
return;
}
if (p->sibling()->color == RED) {
p->parent->color = RED;
p->sibling()->color = BLACK;
if (p == p->parent->leftTree)
rotate_left(p->sibling());
else
rotate_right(p->sibling());
}
if (p->parent->color == BLACK && p->sibling()->color == BLACK
&& p->sibling()->leftTree->color == BLACK && p->sibling()->rightTree->color == BLACK) {
p->sibling()->color = RED;
delete_case(p->parent);
} else if (p->parent->color == RED && p->sibling()->color == BLACK
&& p->sibling()->leftTree->color == BLACK && p->sibling()->rightTree->color == BLACK) {
p->sibling()->color = RED;
p->parent->color = BLACK;
} else {
if (p->sibling()->color == BLACK) {
if (p == p->parent->leftTree && p->sibling()->leftTree->color == RED
&& p->sibling()->rightTree->color == BLACK) {
p->sibling()->color = RED;
p->sibling()->leftTree->color = BLACK;
rotate_right(p->sibling()->leftTree);
} else if (p == p->parent->rightTree && p->sibling()->leftTree->color == BLACK
&& p->sibling()->rightTree->color == RED) {
p->sibling()->color = RED;
p->sibling()->rightTree->color = BLACK;
rotate_left(p->sibling()->rightTree);
}
}
p->sibling()->color = p->parent->color;
p->parent->color = BLACK;
if (p == p->parent->leftTree) {
p->sibling()->rightTree->color = BLACK;
rotate_left(p->sibling());
} else {
p->sibling()->leftTree->color = BLACK;
rotate_right(p->sibling());
}
}
} void insert(Node *p, int data) {
if (p->value >= data) {
if (p->leftTree != NIL)
insert(p->leftTree, data);
else {
Node *tmp = new Node();
tmp->value = data;
tmp->leftTree = tmp->rightTree = NIL;
tmp->parent = p;
p->leftTree = tmp;
insert_case(tmp);
}
} else {
if (p->rightTree != NIL)
insert(p->rightTree, data);
else {
Node *tmp = new Node();
tmp->value = data;
tmp->leftTree = tmp->rightTree = NIL;
tmp->parent = p;
p->rightTree = tmp;
insert_case(tmp);
}
}
} void insert_case(Node *p) {
if (p->parent == NULL) {
root = p;
p->color = BLACK;
return;
}
if (p->parent->color == RED) {
if (p->uncle()->color == RED) {
p->parent->color = p->uncle()->color = BLACK;
p->grandparent()->color = RED;
insert_case(p->grandparent());
} else {
if (p->parent->rightTree == p && p->grandparent()->leftTree == p->parent) {
rotate_left(p);
rotate_right(p);
p->color = BLACK;
p->leftTree->color = p->rightTree->color = RED;
} else if (p->parent->leftTree == p && p->grandparent()->rightTree == p->parent) {
rotate_right(p);
rotate_left(p);
p->color = BLACK;
p->leftTree->color = p->rightTree->color = RED;
} else if (p->parent->leftTree == p && p->grandparent()->leftTree == p->parent) {
p->parent->color = BLACK;
p->grandparent()->color = RED;
rotate_right(p->parent);
} else if (p->parent->rightTree == p && p->grandparent()->rightTree == p->parent) {
p->parent->color = BLACK;
p->grandparent()->color = RED;
rotate_left(p->parent);
}
}
}
} void DeleteTree(Node *p) {
if (!p || p == NIL) {
return;
}
DeleteTree(p->leftTree);
DeleteTree(p->rightTree);
delete p;
}
public: bst() {
NIL = new Node();
NIL->color = BLACK;
root = NULL;
} ~bst() {
if (root)
DeleteTree(root);
delete NIL;
} void inorder() {
if (root == NULL)
return;
inorder(root);
cout << endl;
} void insert(int x) {
if (root == NULL) {
root = new Node();
root->color = BLACK;
root->leftTree = root->rightTree = NIL;
root->value = x;
} else {
insert(root, x);
}
} bool delete_value(int data) {
return delete_child(root, data);
}
private:
Node *root, *NIL;
};

维基百科:红黑树

D&F学数据结构系列——红黑树的更多相关文章

  1. D&F学数据结构系列——二叉排序树

    二叉排序树(Binary Sort Tree) 定义:对于树中的每个结点X,它的左子树中所有关键字值小于X的关键字值,而它的右子树中所有关键字值大于X的关键字值. 二叉查找树声明: #ifndef _ ...

  2. D&F学数据结构系列——B树(B-树和B+树)介绍

    B树 定义:一棵B树T是具有如下性质的有根树: 1)每个节点X有以下域: a)n[x],当前存储在X节点中的关键字数, b)n[x]个关键字本身,以非降序存放,因此key1[x]<=key2[x ...

  3. D&F学数据结构系列——二叉堆

    二叉堆(binary heap) 二叉堆数据结构是一种数组对象,它可以被视为一棵完全二叉树.同二叉查找树一样,堆也有两个性质,即结构性和堆序性.对于数组中任意位置i上的元素,其左儿子在位置2i上,右儿 ...

  4. D&F学数据结构系列——AVL树(平衡二叉树)

    AVL树(带有平衡条件的二叉查找树) 定义:一棵AVL树是其每个节点的左子树和右子树的高度最多差1的二叉查找树. 为什么要使用AVL树(即为什么要给二叉查找树增加平衡条件),已经在我之前的博文中说到过 ...

  5. D&F学数据结构系列——前驱和后继

    前驱和后继 本文所述为二叉排序树的前驱和后继,如果想了解二叉排序树的概念,可以参考我的博文http://www.cnblogs.com/sage-blog/p/3864640.html 给定一个二叉查 ...

  6. D&F学数据结构系列——插入排序

    插入排序(insertion sort) 插入排序由P-1趟(pass)排序组成.对于P=1趟到P=N-1趟,插入排序保证从位置0到位置P-1上的元素为已排序状态.插入排序利用了这样的事实:位置0到位 ...

  7. Linux 内核里的数据结构:红黑树(rb-tree)

    转自:https://www.cnblogs.com/slgkaifa/p/6780299.html 作为一种数据结构.红黑树可谓不算朴素.由于各种宣传让它过于神奇,网上搜罗了一大堆的关于红黑树的文章 ...

  8. 物联网安全himqtt防火墙数据结构之红黑树源码分析

    物联网安全himqtt防火墙数据结构之红黑树源码分析 随着5G的发展,物联网安全显得特别重要,himqtt是首款完整源码的高性能MQTT物联网防火墙 - MQTT Application FireWa ...

  9. 【数据结构】红黑树与跳表-(SortSet)-(TreeMap)-(TreeSet)

    SortSet 有序的Set,其实在Java中TreeSet是SortSet的唯一实现类,内部通过TreeMap实现的:而TreeMap是通过红黑树实现的:而在Redis中是通过跳表实现的: Skip ...

随机推荐

  1. 济南学习 Day 3 T1 am

    NP(np)Time Limit:1000ms Memory Limit:64MB题目描述LYK 喜欢研究一些比较困难的问题,比如 np 问题.这次它又遇到一个棘手的 np 问题.问题是这个样子的:有 ...

  2. fluent nhibernate 初体验

    离开.net框架两年时间,发展的很快呀.原先自我感觉良好到以为只差一个MVP的考核什么的,现在觉得真的差好远了. 呵呵,废话就不多说了.这次花了两天时间才拿下fluent nhibernate的fir ...

  3. SQL语句执行顺寻

    SQL语句执行的时候是有一定顺序的.理解这个顺序对SQL的使用和学习有很大的帮助. 1.from 先选择一个表,或者说源头,构成一个结果集. 2.where 然后用where对结果集进行筛选.筛选出需 ...

  4. C#操作系统日志

    系统日志可以帮助我们分析操作系统的安全与否,也可以帮助我们将一些不好调试的信息显示出来. C#操作系统日志主要是通过EventLog类来实现的. 一 图解 打开事件查看器,其中与EventLog类对应 ...

  5. How to generate number Sequence[AX 2012]

    Suppose we want create number sequence for Test field on form in General  ledger module Consideratio ...

  6. C# 缓存学习总结

    昨天整理了一下缓存的基本用法,和缓存依赖类 CacheDependency类的使用,今天整理一下缓存的数据库依赖类SqlCacheDependency 1.数据库依赖类SqlCacheDependen ...

  7. 命令行参数的处理函数getopt

    命令参数 在linux下, shell命令的参数分两种情况: a.参数需要附加信息, 如"wget http://www.abc.com/1.zip -o 1.zip" b.参数不 ...

  8. VirtualBox虚拟机安装MSDOS和MINIX2.0.0双系统

    1. 在VirtualBox中新建一个MSDOS虚拟机. 2.下载一个MSDOS软盘镜像. 3.启动虚拟机,提示选择安装盘时,选择步骤2下载过来的MSDOS镜像. 4.正常启动进入DOS命令行,用FD ...

  9. Python之回调魔法

    Python中魔法(前后又下划线)会在对象的生命周期被回调. 借助这种回调, 可以实现AOP或者拦截器的思想. 在Python语言中提供了类似于C++的运算符重在功能:一下为Python运算符重在调用 ...

  10. Swift学习:闭包(Closures)

    /* 闭包(Closures)* 闭包是自包含的功能代码块,可以在代码中使用或者用来作为参数传值.* 在Swift中的闭包与C.OC中的blocks和其它编程语言(如Python)中的lambdas类 ...