freebsd rb.h remove を読んでみよう5

今回はrb.hのremoveを読んでいきましょう

a_attr void								\
a_prefix##remove(a_rbt_type *rbtree, a_type *node) {			\
    struct {								\
	a_type *node;							\
	int cmp;							\
    } *pathp, *nodep, path[sizeof(void *) << 4];			\
    /* Wind. */								\
    nodep = NULL; /* Silence compiler warning. */			\
    path->node = rbtree->rbt_root;					\
    for (pathp = path; pathp->node != &rbtree->rbt_nil; pathp++) {	\
	int cmp = pathp->cmp = a_cmp(node, pathp->node);		\
	if (cmp < 0) {							\
	    pathp[1].node = rbtn_left_get(a_type, a_field,		\
	      pathp->node);						\
	} else {							\
	    pathp[1].node = rbtn_right_get(a_type, a_field,		\
	      pathp->node);						\
	    if (cmp == 0) {						\
	        /* Find node's successor, in preparation for swap. */	\
		pathp->cmp = 1;						\
		nodep = pathp;						\
		for (pathp++; pathp->node != &rbtree->rbt_nil;		\
		  pathp++) {						\
		    pathp->cmp = -1;					\
		    pathp[1].node = rbtn_left_get(a_type, a_field,	\
		      pathp->node);					\
		}							\
		break;							\
	    }								\
	}								\
    }									\
    assert(nodep->node == node);					\
    pathp--;								\
    if (pathp->node != node) {						\
	/* Swap node with its successor. */				\
	bool tred = rbtn_red_get(a_type, a_field, pathp->node);		\
	rbtn_color_set(a_type, a_field, pathp->node,			\
	  rbtn_red_get(a_type, a_field, node));				\
	rbtn_left_set(a_type, a_field, pathp->node,			\
	  rbtn_left_get(a_type, a_field, node));			\
	/* If node's successor is its right child, the following code */\
	/* will do the wrong thing for the right child pointer.       */\
	/* However, it doesn't matter, because the pointer will be    */\
	/* properly set when the successor is pruned.                 */\
	rbtn_right_set(a_type, a_field, pathp->node,			\
	  rbtn_right_get(a_type, a_field, node));			\
	rbtn_color_set(a_type, a_field, node, tred);			\
	/* The pruned leaf node's child pointers are never accessed   */\
	/* again, so don't bother setting them to nil.                */\
	nodep->node = pathp->node;					\
	pathp->node = node;						\
	if (nodep == path) {						\
	    rbtree->rbt_root = nodep->node;				\
	} else {							\
	    if (nodep[-1].cmp < 0) {					\
		rbtn_left_set(a_type, a_field, nodep[-1].node,		\
		  nodep->node);						\
	    } else {							\
		rbtn_right_set(a_type, a_field, nodep[-1].node,		\
		  nodep->node);						\
	    }								\
	}								\
    } else {								\
	a_type *left = rbtn_left_get(a_type, a_field, node);		\
	if (left != &rbtree->rbt_nil) {					\
	    /* node has no successor, but it has a left child.        */\
	    /* Splice node out, without losing the left child.        */\
	    assert(rbtn_red_get(a_type, a_field, node) == false);	\
	    assert(rbtn_red_get(a_type, a_field, left));		\
	    rbtn_black_set(a_type, a_field, left);			\
	    if (pathp == path) {					\
		rbtree->rbt_root = left;				\
	    } else {							\
		if (pathp[-1].cmp < 0) {				\
		    rbtn_left_set(a_type, a_field, pathp[-1].node,	\
		      left);						\
		} else {						\
		    rbtn_right_set(a_type, a_field, pathp[-1].node,	\
		      left);						\
		}							\
	    }								\
	    return;							\
	} else if (pathp == path) {					\
	    /* The tree only contained one node. */			\
	    rbtree->rbt_root = &rbtree->rbt_nil;			\
	    return;							\
	}								\
    }	
    

insertの時と同じようにまずはターゲットとなるノードまでの
ノードを配列に入れていきます。
pathpにその配列の先頭のアドレスがありそれをpathp++でループしていきます。

path->node = rbtree->rbt_root;
	 
for (pathp = path; pathp->node != &rbtree->rbt_nil; pathp++) {

ループ内ではa_cmpを呼び出しその戻り値がマイナスの時(小さい時)は
pathpのノードの左の子を逆の場合はpathpのノードの右の子をpathpの配列の
次のindexに入れます。

int cmp = pathp->cmp = a_cmp(node, pathp->node);
		 
if (cmp < 0) {
			 
	pathp[1].node = rbtn_left_get(a_type, a_field, pathp->node);
			 
} else {
			 
	pathp[1].node = rbtn_right_get(a_type, a_field, pathp->node);


cmpがゼロの時すなわちpathp->nodeがターゲットのノード(ここでは削除されるべきノード)の時cmpを1にし(右側にあるノードが次に来る)そのポインターをnodepに格納します。

pathp->cmp = 1;
				 
nodep = pathp;

 

そのあとpathpを一つ進めます。つまり削除されるべきノードの右の子供をpathpが指しています。
そこから左の子のノードをpathpの配列にnilになるまで入れていきます。
左の子がnilのノードに来たらbreakします。

for (pathp++; pathp->node != &rbtree->rbt_nil; pathp++) {
	pathp->cmp = -1;
	pathp[1].node = rbtn_left_get(a_type, a_field, pathp->node);
}
break;

ここでpathpはforを出る時にnilのノードのを指しているので
まずは一つ巻き戻します。

pathp--;

pathpは何を指しているのでしょうか?
もし削除されるべきノードの右の子がnilだった場合
このループには入りません。
for (pathp++; pathp->node != &rbtree->rbt_nil; pathp++) {
ただ最初のpathp++は実行されるので次のpathp--と合わせてpathpは
削除されるノードのpathpを指しています。
そこで次の条件で分岐されます。

if (pathp->node != node) {

削除されるノード(node)とpathpの指しているノードが違うなら
つまり削除されるノードの右の子がnilでないならと読みかえられます。

削除されるノードの右の子がある場合どうなるのでしょう。
コメントに
/* Swap node with its successor. */
とあります。何をするのでしょうか?

一行づつ読んでみましょう。

bool tred = rbtn_red_get(a_type, a_field, pathp->node);
//pathp->nodeの色が赤かどうかを保存します。
rbtn_color_set(a_type, a_field, pathp->node, rbtn_red_get(a_type, a_field, node));
//pathpのノードに削除されるノードの色をセットします。
rbtn_left_set(a_type, a_field, pathp->node, rbtn_left_get(a_type, a_field, node));
//削除されるノードの左の子をpathpのノードの左にセットします。
rbtn_right_set(a_type, a_field, pathp->node, rbtn_right_get(a_type, a_field, node));
//削除されるノードの右の子をpathpのノードの右にセットします。
rbtn_color_set(a_type, a_field, node, tred);
//保存していたpathpのノードの色を削除されるノードの色にセットします。
nodep->node = pathp->node;
//削除されるノードのあったアドレスのノードにpathpのノードを入れます。
pathp->node = node;
//pathpのノードに削除されるノードを入れます。

f:id:yamshing:20151020202214p:plain

これが

f:id:yamshing:20151020202314p:plain

こうなるということです。
色は省略していますが同じ色になるように色も交換しています。
そしてこのあとこのnodepがpathすなわちpath配列の先頭かどうかをチェックして
そうならnodepのノード(4番)をルートにします。
そうでないならnodepの一つ前のindex(親ノード)のcmpによってcmpがマイナスなら
左に逆なら右につけます。
これでスワップが完了です。

ところで

/* If node's successor is its right child, the following code */
/* will do the wrong thing for the right child pointer.       */
/* However, it doesn't matter, because the pointer will be    */
/* properly set when the successor is pruned.                 */

このコメントはどういう意味でしょうか?
もし削除されるノードの次のノードが右の子のノードならおかしなことになると言っています。
つまりこれは削除されるノードが右に子のノードがあってその子のノードに左の子が一つもない場合のことを言っています。
これはこういう場合のことでしょう

f:id:yamshing:20151020202338p:plain

確かに
rbtn_right_set(a_type, a_field, pathp->node, rbtn_right_get(a_type, a_field, node));
で自分自身を自分の右の子にしてしまうことになってしまいます。
しかしその後pathpのノードに削除されるノードが入るので問題ないと言っています。

それでは次回は削除されるノードの右がnilの場合を見ていきましょう。

お疲れ様でした。