概要
前面分別介紹紅黑樹的理論知識 和紅黑樹的C語言實現 。本章是紅黑樹的C++實現,若讀者對紅黑樹的理論知識不熟悉,建立先學習紅黑樹的理論知識,再來學習本章。
目錄 1. ?紅黑樹的介紹 2. 紅黑樹的C++實現(代碼說明) 3. 紅黑樹的C++實現(完整源碼) 4. 紅黑樹的C++測試程序
轉載請注明出處:http://www.cnblogs.com/skywang12345/p/3624291.html
更多內容: 數據結構與算法系列 目錄
(01)?紅黑樹(一)之 原理和算法詳細介紹 (02)?紅黑樹(二)之 C語言的實現 (03 )?紅黑樹(三)之 C++的實現?
?
紅黑樹的介紹
紅黑樹(Red-Black Tree,簡稱R-B Tree),它一種特殊的二叉查找樹。 紅黑樹是特殊的二叉查找樹,意味著它滿足二叉查找樹的特征:任意一個節點所包含的鍵值,大于等于左孩子的鍵值,小于等于右孩子的鍵值。 除了具備該特性之外,紅黑樹還包括許多額外的信息。
紅黑樹的每個節點上都有存儲位表示節點的顏色,顏色是紅(Red)或黑(Black)。 紅黑樹的特性: (1) 每個節點或者是黑色,或者是紅色。 (2) 根節點是黑色。 (3) 每個葉子節點是黑色。 [注意:這里葉子節點,是指為空的葉子節點!] (4) 如果一個節點是紅色的,則它的子節點必須是黑色的。 (5) 從一個節點到該節點的子孫節點的所有路徑上包含相同數目的黑節點。
關于它的特性,需要注意的是: 第一,特性(3)中的葉子節點,是只為空(NIL或null)的節點。 第二,特性(5),確保沒有一條路徑會比其他路徑長出倆倍。因而,紅黑樹是相對是接近平衡的二叉樹。
紅黑樹示意圖如下:
?
紅黑樹的C++實現(代碼說明)
紅黑樹的基本操作是添加 、刪除 和旋轉 。在對紅黑樹進行添加或刪除后,會用到旋轉方法。為什么呢?道理很簡單,添加或刪除紅黑樹中的節點之后,紅黑樹就發生了變化,可能不滿足紅黑樹的5條性質,也就不再是一顆紅黑樹了,而是一顆普通的樹。而通過旋轉,可以使這顆樹重新成為紅黑樹。簡單點說,旋轉的目的是讓樹保持紅黑樹的特性。 旋轉包括兩種:左旋 和 右旋。下面分別對紅黑樹的基本操作進行介紹。
?
1. 基本定義
enum RBTColor{RED, BLACK};template <
class T>
class RBTNode{ public :RBTColor color; // 顏色 T key;
// 關鍵字(鍵值) RBTNode *left;
// 左孩子 RBTNode *right;
// 右孩子 RBTNode *parent;
// 父結點
RBTNode(T value, RBTColor c, RBTNode *p, RBTNode *l, RBTNode *
r):key(value),color(c),parent(),left(l),right(r) {}
};template <
class T>
class RBTree { private :RBTNode <T> *mRoot;
// 根結點 public :RBTree(); ~
RBTree(); // 前序遍歷"紅黑樹" void preOrder(); // 中序遍歷"紅黑樹" void inOrder(); // 后序遍歷"紅黑樹" void postOrder(); // (遞歸實現)查找"紅黑樹"中鍵值為key的節點 RBTNode<T>*
search(T key); // (非遞歸實現)查找"紅黑樹"中鍵值為key的節點 RBTNode<T>*
iterativeSearch(T key); // 查找最小結點:返回最小結點的鍵值。
T minimum(); // 查找最大結點:返回最大結點的鍵值。
T maximum(); // 找結點(x)的后繼結點。即,查找"紅黑樹中數據值大于該結點"的"最小結點"。 RBTNode<T>* successor(RBTNode<T> *
x); // 找結點(x)的前驅結點。即,查找"紅黑樹中數據值小于該結點"的"最大結點"。 RBTNode<T>* predecessor(RBTNode<T> *
x); // 將結點(key為節點鍵值)插入到紅黑樹中 void insert(T key); // 刪除結點(key為節點鍵值) void remove(T key); // 銷毀紅黑樹 void destroy(); // 打印紅黑樹 void print(); private : // 前序遍歷"紅黑樹" void preOrder(RBTNode<T>* tree)
const ; // 中序遍歷"紅黑樹" void inOrder(RBTNode<T>* tree)
const ; // 后序遍歷"紅黑樹" void postOrder(RBTNode<T>* tree)
const ; // (遞歸實現)查找"紅黑樹x"中鍵值為key的節點 RBTNode<T>* search(RBTNode<T>* x, T key)
const ; // (非遞歸實現)查找"紅黑樹x"中鍵值為key的節點 RBTNode<T>* iterativeSearch(RBTNode<T>* x, T key)
const ; // 查找最小結點:返回tree為根結點的紅黑樹的最小結點。 RBTNode<T>* minimum(RBTNode<T>*
tree); // 查找最大結點:返回tree為根結點的紅黑樹的最大結點。 RBTNode<T>* maximum(RBTNode<T>*
tree); // 左旋 void leftRotate(RBTNode<T>* &root, RBTNode<T>*
x); // 右旋 void rightRotate(RBTNode<T>* &root, RBTNode<T>*
y); // 插入函數 void insert(RBTNode<T>* &root, RBTNode<T>*
node); // 插入修正函數 void insertFixUp(RBTNode<T>* &root, RBTNode<T>*
node); // 刪除函數 void remove(RBTNode<T>* &root, RBTNode<T> *
node); // 刪除修正函數 void removeFixUp(RBTNode<T>* &root, RBTNode<T> *node, RBTNode<T> *
parent); // 銷毀紅黑樹 void destroy(RBTNode<T>* &
tree); // 打印紅黑樹 void print(RBTNode<T>* tree, T key,
int direction); #define rb_parent(r) ((r)->parent)
#define rb_color(r) ((r)->color)
#define rb_is_red(r) ((r)->color==RED)
#define rb_is_black(r) ((r)->color==BLACK)
#define rb_set_black(r) do { (r)->color = BLACK; } while (0)
#define rb_set_red(r) do { (r)->color = RED; } while (0)
#define rb_set_parent(r,p) do { (r)->parent = (p); } while (0)
#define rb_set_color(r,c) do { (r)->color = (c); } while (0)
};
RBTNode是紅黑樹的節點類,而RBTree對應是紅黑樹的操作實現類。在RBTree中包含了根節點mRoot和紅黑樹的相關API。 注意:(01) 在實現紅黑樹API的過程中,我重載了許多函數。重載的原因,一是因為有的API是內部接口,有的是外部接口;二是為了讓結構更加清晰。 ? ? ? ? ? (02) 由于C++的實現是在上一篇介紹的"C語言"實現基礎上移植而來,在該代碼中,保留了一些C語言的特性;例如(宏定義)。
?
2. 左旋
對x進行左旋,意味著"將x變成一個左節點"。
?
左旋的實現代碼(C++語言)
/* * 對紅黑樹的節點(x)進行左旋轉** 左旋示意圖(對節點x進行左旋):* px px* / /* x y * / \ --(左旋)--> / \ #* lx y x ry * / \ / \* ly ry lx ly ** */
template <
class T>
void RBTree<T>::leftRotate(RBTNode<T>* &root, RBTNode<T>*
x)
{ // 設置x的右孩子為y RBTNode<T> *y = x->
right; // 將 “y的左孩子” 設為 “x的右孩子”; // 如果y的左孩子非空,將 “x” 設為 “y的左孩子的父親” x->right = y->
left; if (y->left !=
NULL)y ->left->parent =
x; // 將 “x的父親” 設為 “y的父親” y->parent = x->
parent; if (x->parent ==
NULL){root = y;
// 如果 “x的父親” 是空節點,則將y設為根節點
} else { if (x->parent->left ==
x)x ->parent->left = y;
// 如果 x是它父節點的左孩子,則將y設為“x的父節點的左孩子” else x ->parent->right = y;
// 如果 x是它父節點的左孩子,則將y設為“x的父節點的左孩子”
} // 將 “x” 設為 “y的左孩子” y->left =
x; // 將 “x的父節點” 設為 “y” x->parent =
y;
}
?
3. 右旋
對y進行左旋,意味著"將y變成一個右節點"。
?
右旋的實現代碼(C++語言)
/* * 對紅黑樹的節點(y)進行右旋轉** 右旋示意圖(對節點y進行左旋):* py py* / /* y x * / \ --(右旋)--> / \ #* x ry lx y * / \ / \ #* lx rx rx ry* */
template <
class T>
void RBTree<T>::rightRotate(RBTNode<T>* &root, RBTNode<T>*
y)
{ // 設置x是當前節點的左孩子。 RBTNode<T> *x = y->
left; // 將 “x的右孩子” 設為 “y的左孩子”; // 如果"x的右孩子"不為空的話,將 “y” 設為 “x的右孩子的父親” y->left = x->
right; if (x->right !=
NULL)x ->right->parent =
y; // 將 “y的父親” 設為 “x的父親” x->parent = y->
parent; if (y->parent ==
NULL) {root = x;
// 如果 “y的父親” 是空節點,則將x設為根節點
} else { if (y == y->parent->
right)y ->parent->right = x;
// 如果 y是它父節點的右孩子,則將x設為“y的父節點的右孩子” else y ->parent->left = x;
// (y是它父節點的左孩子) 將x設為“x的父節點的左孩子”
} // 將 “y” 設為 “x的右孩子” x->right =
y; // 將 “y的父節點” 設為 “x” y->parent =
x;
}
?
4. 添加
將一個節點插入到紅黑樹中,需要執行哪些步驟呢?首先,將紅黑樹當作一顆二叉查找樹,將節點插入;然后,將節點著色為紅色;最后,通過"旋轉和重新著色"等一系列操作來修正該樹,使之重新成為一顆紅黑樹。詳細描述如下: 第一步: 將紅黑樹當作一顆二叉查找樹,將節點插入。 ? ? ? ?紅黑樹本身就是一顆二叉查找樹,將節點插入后,該樹仍然是一顆二叉查找樹。也就意味著,樹的鍵值仍然是有序的。此外,無論是左旋還是右旋,若旋轉之前這棵樹是二叉查找樹,旋轉之后它一定還是二叉查找樹。這也就意味著,任何的旋轉和重新著色操作,都不會改變它仍然是一顆二叉查找樹的事實。 ? ? ? 好吧?那接下來,我們就來想方設法的旋轉以及重新著色,使這顆樹重新成為紅黑樹!
第二步:將插入的節點著色為"紅色"。 ? ? ? 為什么著色成紅色,而不是黑色呢?為什么呢?在回答之前,我們需要重新溫習一下紅黑樹的特性: (1) 每個節點或者是黑色,或者是紅色。 (2) 根節點是黑色。 (3) 每個葉子節點是黑色。 [注意:這里葉子節點,是指為空的葉子節點!] (4) 如果一個節點是紅色的,則它的子節點必須是黑色的。 (5) 從一個節點到該節點的子孫節點的所有路徑上包含相同數目的黑節點。 ? ? 將插入的節點著色為紅色,不會違背"特性(5)"!少違背一條特性,就意味著我們需要處理的情況越少。接下來,就要努力的讓這棵樹滿足其它性質即可;滿足了的話,它就又是一顆紅黑樹了。o(∩∩)o...哈哈
第三步: 通過一系列的旋轉或著色等操作,使之重新成為一顆紅黑樹。 ? ? ? ?第二步中,將插入節點著色為"紅色"之后,不會違背"特性(5)"。那它到底會違背哪些特性呢? ? ? ? ?對于"特性(1)",顯然不會違背了。因為我們已經將它涂成紅色了。 ? ? ? ?對于"特性(2)",顯然也不會違背。在第一步中,我們是將紅黑樹當作二叉查找樹,然后執行的插入操作。而根據二叉查找數的特點,插入操作不會改變根節點。所以,根節點仍然是黑色。 ? ? ? ?對于"特性(3)",顯然不會違背了。這里的葉子節點是指的空葉子節點,插入非空節點并不會對它們造成影響。 ? ? ? ?對于"特性(4)",是有可能違背的! ? ? ? 那接下來,想辦法使之"滿足特性(4)",就可以將樹重新構造成紅黑樹了。
添加操作的實現代碼(C++語言)
/* * 將結點插入到紅黑樹中** 參數說明:* root 紅黑樹的根結點* node 插入的結點 // 對應《算法導論》中的node */
template <
class T>
void RBTree<T>::insert(RBTNode<T>* &root, RBTNode<T>*
node)
{RBTNode <T> *y =
NULL;RBTNode <T> *x =
root; // 1. 將紅黑樹當作一顆二叉查找樹,將節點添加到二叉查找樹中。 while (x !=
NULL){y =
x; if (node->key < x->
key)x = x->
left; else x = x->
right;}node ->parent =
y; if (y!=
NULL){ if (node->key < y->
key)y ->left =
node; else y ->right =
node;} else root =
node; // 2. 設置節點的顏色為紅色 node->color =
RED; // 3. 將它重新修正為一顆二叉查找樹
insertFixUp(root, node);
} /* * 將結點(key為節點鍵值)插入到紅黑樹中** 參數說明:* tree 紅黑樹的根結點* key 插入結點的鍵值 */
template <
class T>
void RBTree<T>
::insert(T key)
{RBTNode <T> *z=
NULL; // 如果新建結點失敗,則返回。 if ((z=
new RBTNode<T>(key,BLACK,NULL,NULL,NULL)) ==
NULL) return ;insert(mRoot, z);
}
內部接口 ?-- insert(root, node)的作用是將"node"節點插入到紅黑樹中。其中,root是根,node是被插入節點。 外部接口 ?-- insert(key)的作用是將"key"添加到紅黑樹中。
添加修正操作的實現代碼(C++語言)
/* * 紅黑樹插入修正函數** 在向紅黑樹中插入節點之后(失去平衡),再調用該函數;* 目的是將它重新塑造成一顆紅黑樹。** 參數說明:* root 紅黑樹的根* node 插入的結點 // 對應《算法導論》中的z */
template <
class T>
void RBTree<T>::insertFixUp(RBTNode<T>* &root, RBTNode<T>*
node)
{RBTNode <T> *parent, *
gparent; // 若“父節點存在,并且父節點的顏色是紅色” while ((parent = rb_parent(node)) &&
rb_is_red(parent)){gparent =
rb_parent(parent); // 若“父節點”是“祖父節點的左孩子” if (parent == gparent->
left){ // Case 1條件:叔叔節點是紅色
{RBTNode <T> *uncle = gparent->
right; if (uncle &&
rb_is_red(uncle)){rb_set_black(uncle);rb_set_black(parent);rb_set_red(gparent);node =
gparent; continue ;}} // Case 2條件:叔叔是黑色,且當前節點是右孩子 if (parent->right ==
node){RBTNode <T> *
tmp;leftRotate(root, parent);tmp =
parent;parent =
node;node =
tmp;} // Case 3條件:叔叔是黑色,且當前節點是左孩子。
rb_set_black(parent);rb_set_red(gparent);rightRotate(root, gparent);} else // 若“z的父節點”是“z的祖父節點的右孩子”
{ // Case 1條件:叔叔節點是紅色
{RBTNode <T> *uncle = gparent->
left; if (uncle &&
rb_is_red(uncle)){rb_set_black(uncle);rb_set_black(parent);rb_set_red(gparent);node =
gparent; continue ;}} // Case 2條件:叔叔是黑色,且當前節點是左孩子 if (parent->left ==
node){RBTNode <T> *
tmp;rightRotate(root, parent);tmp =
parent;parent =
node;node =
tmp;} // Case 3條件:叔叔是黑色,且當前節點是右孩子。
rb_set_black(parent);rb_set_red(gparent);leftRotate(root, gparent);}} // 將根節點設為黑色
rb_set_black(root);
}
insertFixUp(root, node)的作用是對應"上面所講的第三步"。它是一個內部接口。
?
5. 刪除操作
將紅黑樹內的某一個節點刪除。需要執行的操作依次是:首先,將紅黑樹當作一顆二叉查找樹,將該節點從二叉查找樹中刪除;然后,通過"旋轉和重新著色"等一系列來修正該樹,使之重新成為一棵紅黑樹。詳細描述如下:
第一步:將紅黑樹當作一顆二叉查找樹,將節點刪除。 ? ? ? 這和"刪除常規二叉查找樹中刪除節點的方法是一樣的"。分3種情況: ① 被刪除節點沒有兒子,即為葉節點。那么,直接將該節點刪除就OK了。 ② 被刪除節點只有一個兒子。那么,直接刪除該節點,并用該節點的唯一子節點頂替它的位置。 ③ 被刪除節點有兩個兒子。那么,先找出它的后繼節點;然后把“它的后繼節點的內容”復制給“該節點的內容”;之后,刪除“它的后繼節點”。在這里,后繼節點相當于替身,在將后繼節點的內容復制給"被刪除節點"之后,再將后繼節點刪除。這樣就巧妙的將問題轉換為"刪除后繼節點"的情況了,下面就考慮后繼節點。 在"被刪除節點"有兩個非空子節點的情況下,它的后繼節點不可能是雙子非空。既然"的后繼節點"不可能雙子都非空,就意味著"該節點的后繼節點"要么沒有兒子,要么只有一個兒子。若沒有兒子,則按"情況① "進行處理;若只有一個兒子,則按"情況② "進行處理。
第二步:通過"旋轉和重新著色"等一系列來修正該樹,使之重新成為一棵紅黑樹。 ? ? ? ?因為"第一步"中刪除節點之后,可能會違背紅黑樹的特性。所以需要通過"旋轉和重新著色"來修正該樹,使之重新成為一棵紅黑樹。
刪除操作的實現代碼(C++語言)
/* * 刪除結點(node),并返回被刪除的結點** 參數說明:* root 紅黑樹的根結點* node 刪除的結點 */
template <
class T>
void RBTree<T>::remove(RBTNode<T>* &root, RBTNode<T> *
node)
{RBTNode <T> *child, *
parent;RBTColor color; // 被刪除節點的"左右孩子都不為空"的情況。 if ( (node->left!=NULL) && (node->right!=
NULL) ) { // 被刪節點的后繼節點。(稱為"取代節點") // 用它來取代"被刪節點"的位置,然后再將"被刪節點"去掉。 RBTNode<T> *replace =
node; // 獲取后繼節點 replace = replace->
right; while (replace->left !=
NULL)replace = replace->
left; // "node節點"不是根節點(只有根節點不存在父節點) if (rb_parent(node)){ if (rb_parent(node)->left ==
node)rb_parent(node) ->left =
replace; else rb_parent(node) ->right =
replace;} else // "node節點"是根節點,更新根節點。 root =
replace; // child是"取代節點"的右孩子,也是需要"調整的節點"。 // "取代節點"肯定不存在左孩子!因為它是一個后繼節點。 child = replace->
right;parent =
rb_parent(replace); // 保存"取代節點"的顏色 color =
rb_color(replace); // "被刪除節點"是"它的后繼節點的父節點" if (parent ==
node){parent =
replace;} else { // child不為空 if (child)rb_set_parent(child, parent);parent ->left =
child;replace ->right = node->
right;rb_set_parent(node ->
right, replace);}replace ->parent = node->
parent;replace ->color = node->
color;replace ->left = node->
left;node ->left->parent =
replace; if (color ==
BLACK)removeFixUp(root, child, parent);delete node; return ;} if (node->left !=
NULL)child = node->
left; else child = node->
right;parent = node->
parent; // 保存"取代節點"的顏色 color = node->
color; if (child)child ->parent =
parent; // "node節點"不是根節點 if (parent){ if (parent->left ==
node)parent ->left =
child; else parent ->right =
child;} else root =
child; if (color ==
BLACK)removeFixUp(root, child, parent);delete node;
} /* * 刪除紅黑樹中鍵值為key的節點** 參數說明:* tree 紅黑樹的根結點 */
template <
class T>
void RBTree<T>
::remove(T key)
{RBTNode <T> *
node; // 查找key對應的節點(node),找到的話就刪除該節點 if ((node = search(mRoot, key)) !=
NULL)remove(mRoot, node);
}
內部接口 ?-- remove(root, node)的作用是將"node"節點插入到紅黑樹中。其中,root是根,node是被插入節點。 外部接口 ?-- remove(key)刪除紅黑樹中鍵值為key的節點。
刪除修正操作的實現代碼(C++語言)
/* * 紅黑樹刪除修正函數** 在從紅黑樹中刪除插入節點之后(紅黑樹失去平衡),再調用該函數;* 目的是將它重新塑造成一顆紅黑樹。** 參數說明:* root 紅黑樹的根* node 待修正的節點 */
template <
class T>
void RBTree<T>::removeFixUp(RBTNode<T>* &root, RBTNode<T> *node, RBTNode<T> *
parent)
{RBTNode <T> *
other; while ((!node || rb_is_black(node)) && node !=
root){ if (parent->left ==
node){other = parent->
right; if (rb_is_red(other)){ // Case 1: x的兄弟w是紅色的
rb_set_black(other);rb_set_red(parent);leftRotate(root, parent);other = parent->
right;} if ((!other->left || rb_is_black(other->left)) &&
( !other->right || rb_is_black(other->
right))){ // Case 2: x的兄弟w是黑色,且w的倆個孩子也都是黑色的
rb_set_red(other);node =
parent;parent =
rb_parent(node);} else { if (!other->right || rb_is_black(other->
right)){ // Case 3: x的兄弟w是黑色的,并且w的左孩子是紅色,右孩子為黑色。 rb_set_black(other->
left);rb_set_red(other);rightRotate(root, other);other = parent->
right;} // Case 4: x的兄弟w是黑色的;并且w的右孩子是紅色的,左孩子任意顏色。
rb_set_color(other, rb_color(parent));rb_set_black(parent);rb_set_black(other ->
right);leftRotate(root, parent);node =
root; break ;}} else {other = parent->
left; if (rb_is_red(other)){ // Case 1: x的兄弟w是紅色的
rb_set_black(other);rb_set_red(parent);rightRotate(root, parent);other = parent->
left;} if ((!other->left || rb_is_black(other->left)) &&
( !other->right || rb_is_black(other->
right))){ // Case 2: x的兄弟w是黑色,且w的倆個孩子也都是黑色的
rb_set_red(other);node =
parent;parent =
rb_parent(node);} else { if (!other->left || rb_is_black(other->
left)){ // Case 3: x的兄弟w是黑色的,并且w的左孩子是紅色,右孩子為黑色。 rb_set_black(other->
right);rb_set_red(other);leftRotate(root, other);other = parent->
left;} // Case 4: x的兄弟w是黑色的;并且w的右孩子是紅色的,左孩子任意顏色。
rb_set_color(other, rb_color(parent));rb_set_black(parent);rb_set_black(other ->
left);rightRotate(root, parent);node =
root; break ;}}} if (node)rb_set_black(node);
}
removeFixup(root, node, parent)是對應"上面所講的第三步"。它是一個內部接口。
?
紅黑樹的C++實現(完整源碼)
下面是紅黑樹實現的完整代碼和相應的測試程序。 (1) 除了上面所說的"左旋"、"右旋"、"添加"、"刪除"等基本操作之后,還實現了"遍歷"、"查找"、"打印"、"最小值"、"最大值"、"創建"、"銷毀"等接口。 (2) 函數接口大多分為內部接口和外部接口。內部接口是private函數,外部接口則是public函數。 (3) 測試代碼中提供了"插入"和"刪除"動作的檢測開關。默認是關閉的,打開方法可以參考"代碼中的說明"。建議在打開開關后,在草稿上自己動手繪制一下紅黑樹。
紅黑樹的實現文件(RBTree.h)
?
View Code
紅黑樹的測試文件(RBTreeTest.cpp)
?
View Code
?
紅黑樹的C++測試程序
測試程序已經包含在相應的實現文件(MaxHeap.cpp)中了 ,這里就不再重復說明。下面是測試程序的運行結果:
== 原始數據:
10 40 30 60 90 70 20 50 80
== 前序遍歷:
30 10 20 60 40 50 80 70 90
== 中序遍歷:
10 20 30 40 50 60 70 80 90
== 后序遍歷:
20 10 50 40 70 90 80 60 30
== 最小值:
10
== 最大值:
90
==
樹的詳細信息:
30 (B)
is root
10 (B)
is 30 ' s left child
20 (R)
is 10 ' s right child
60 (R)
is 30 ' s right child
40 (B)
is 60 ' s left child
50 (R)
is 40 ' s right child
80 (B)
is 60 ' s right child
70 (R)
is 80 ' s left child
90 (R)
is 80 ' s right child
總結
以上是生活随笔 為你收集整理的红黑树(三)之 C++的实现 的全部內容,希望文章能夠幫你解決所遇到的問題。
如果覺得生活随笔 網站內容還不錯,歡迎將生活随笔 推薦給好友。