博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
Python与数据结构[3] -> 树/Tree[2] -> AVL 平衡树和树旋转的 Python 实现
阅读量:5208 次
发布时间:2019-06-14

本文共 28068 字,大约阅读时间需要 93 分钟。

AVL 平衡树和树旋转


目录

 

1 AVL平衡二叉树

AVL(Adelson-Velskii & Landis)树是一种带有平衡条件的,一棵AVL树其实是一棵左子树和右子树高度最多差1的二叉。一棵树的不平衡主要是由于插入和删除的过程中产生的,此时则需要使用旋转来对AVL树进行平衡。

AVL Tree:                0            _____|_____          |           |          0           0           |___     ___|___              |   |       |              0   0       0                           |__                             |                             0

插入引起不平衡主要有以下四种情况:

Insert unbalance:    1:             |  2:           |  3:            | 4:           A(2)    |        A(2)   |       A(2)     |    A(2)                      __|       |      __|      |       |__      |    |__                |          |     |         |          |     |       |                   B(1)       |     B(1)      |          B(1)  |       B(1)              __|          |     |__       |        __|     |       |__              |             |        |      |       |        |          |            ----           |       ----    |      ----      |         ----        |X(0)|          |      |X(0)|   |     |X(0)|     |        |X(0)|        ----           |       ----    |      ----      |         ----

删除引起不平衡主要有以下四种情况:

Delete unbalance:    1:             |  2:           |  3:            |  4:           A(2)    |      A(2)     |        A(2)    |       A(2)                 __|__     |    __|__      |      __|__     |     __|__                |     |    |   |     |     |     |     |    |    |     |                    B(1) ----  |   B(1)  ----  |  ----     B(1) |  ----    B(1)                __|   |X(0)| |   |__  |X(0)| | |X(0)|  __|    | |X(0)|   |__                 |       ----  |      |  ----  |  ----  |       |  ----       |               C(0)          |      C(0)     |        C(0)    |             C(0)

 

2. 树旋转

对于上面的不平衡情况,可以分别采用以下的树旋转方式解决,

2.1 向左单旋转

适用于处理上面插入/删除引起的不平衡情况1,

下图中K2两个子树节点相差高度等于2,产生了不平衡,因此需要使用单旋转处理,

在向左单旋转时,若K1有右子树,则转变为K2的左子树。

K2(2)                      K1(1)               __|__                      ____|_____              |     |                    |          |              K1(1) None(-1)  -------->  X(0)       K2(1)            __|__                                 __|__           |     |                               |     |           X(0)  Y(0)                            Y(0)  None(-1)

2.2 向右单旋转

适用于处理上面插入/删除引起的不平衡情况4,

在向右单旋转时,若K1有左子树,则转变为K2的右子树。

K2(2)                      K1(2)               __|__                      ____|_____              |     |                    |          |          (-1)None  K1(1)   -------->    K2(1)      Y(0)                  __|__                __|__                 |     |              |     |                 X(0)  Y(0)       (-1)None  X(0)

2.3 右左双旋转

适用于处理上面插入/删除引起的不平衡情况2,

首先对K1进行一次向右单旋转,再对K3进行一次向左单旋转,

K3(3)                     K3(3)                     K2(2)                 __|__                     __|__                  _____|_____               |     |      right        |     |      left      |           |                 K1(2) D(0)  -------->     K2(2) D(0) -------->   K1(1)       K3(1)            __|__                     __|__                  __|__       __|__            |     |                   |     |                |     |     |     |                   A(0)  K2(1)               K1(1) C(0)             A(0)  B(0) C(0)  D(0)                   __|__               __|__                                 |     |             |     |                                B(0)  C(0)          A(0)  B(0)

2.4 左右双旋转

适用于处理上面插入/删除引起的不平衡情况3,

首先对K1进行一次向左单旋转,再对K3进行一次向右单旋转,

K3(3)                     K3(3)                     K2(2)                 __|__                     __|__                  _____|_____               |     |         left      |     |       right    |           |                 D(0)  K1(2)   -------->   D(0)  K2(2) -------->  K3(1)       K1(1)                  __|__                     __|__            __|__       __|__                  |     |                   |     |          |     |     |     |                         K2(1) A(0)                C(0)  K1(1)      D(0)  C(0)  B(0)  A(0)                   __|__                           __|__                                 |     |                         |     |                                C(0)  B(0)                      B(0)  A(0)

 

 

3 Python代码实现

下面利用Python实现AVL树,以及树的旋转操作,

完整代码

1 from search_tree import SearchTree  2   3   4 class AvlNode:  5     def __init__(self, val=None, lef=None, rgt=None, hgt=0):  6         self.value = val  7         self.left = lef  8         self.right = rgt  9         self.height = hgt 10  11     def __str__(self): 12         return str(self.value) 13  14  15 class AvlTree(SearchTree): 16     """ 17     AVL Tree: 18                 0  19            _____|_____ 20           |           | 21           0           0  22           |___     ___|___ 23               |   |       | 24               0   0       0  25                           |__ 26                              | 27                              0  28     Insert unbalance: 29     1:             |  2:           |  3:            | 4: 30            A(2)    |        A(2)   |       A(2)     |    A(2)              31          __|       |      __|      |       |__      |    |__         32         |          |     |         |          |     |       |            33         B(1)       |     B(1)      |          B(1)  |       B(1)         34       __|          |     |__       |        __|     |       |__          35      |             |        |      |       |        |          |         36     ----           |       ----    |      ----      |         ----      37    |X(0)|          |      |X(0)|   |     |X(0)|     |        |X(0)|     38     ----           |       ----    |      ----      |         ----          39  40     Delete unbalance: 41     1:             |  2:           |  3:            |  4: 42            A(2)    |      A(2)     |        A(2)    |       A(2)         43          __|__     |    __|__      |      __|__     |     __|__         44         |     |    |   |     |     |     |     |    |    |     |             45         B(1) ----  |   B(1)  ----  |  ----     B(1) |  ----    B(1)           46       __|   |X(0)| |   |__  |X(0)| | |X(0)|  __|    | |X(0)|   |__             47      |       ----  |      |  ----  |  ----  |       |  ----       |           48      C(0)          |      C(0)     |        C(0)    |             C(0) 49     """                                                 50     def get_height(self, node): 51         if isinstance(node, AvlNode): 52             return node.height 53         return -1 54  55     def single_rotate_left(self, k2): 56         """ 57                  K2(2)                      K1(1) 58                __|__                      ____|_____ 59               |     |                    |          | 60               K1(1) None(-1)  -------->  X(0)       K2(1) 61             __|__                                 __|__ 62            |     |                               |     | 63            X(0)  Y(0)                            Y(0)  None(-1) 64         """ 65         # Left rotate 66         k1 = k2.left 67         k2.left = k1.right 68         k1.right = k2 69         # Update height 70         k2.height = max(self.get_height(k2.left), self.get_height(k2.right)) + 1 71         k1.height = max(self.get_height(k1.left), k2.height) + 1 72         return k1 73  74     def single_rotate_right(self, k2): 75         """ 76                  K2(2)                      K1(2) 77                __|__                      ____|_____ 78               |     |                    |          | 79           (-1)None  K1(1)   -------->    K2(1)      Y(0) 80                   __|__                __|__ 81                  |     |              |     | 82                  X(0)  Y(0)       (-1)None  X(0) 83         """ 84         # Right rotate 85         k1 = k2.right 86         k2.right = k1.left 87         k1.left = k2 88         # Update height 89         k2.height = max(self.get_height(k2.left), self.get_height(k2.right)) + 1 90         k1.height = max(k2.height, self.get_height(k1.right)) + 1 91         return k1 92  93     def double_rotate_left(self, k3):    94         """ 95                  K3(3)                     K3(3)                     K2(2)   96                __|__                     __|__                  _____|_____  97               |     |      right        |     |      left      |           |    98               K1(2) D(0)  -------->     K2(2) D(0) -------->   K1(1)       K3(1) 99             __|__                     __|__                  __|__       __|__ 100            |     |                   |     |                |     |     |     |        101            A(0)  K2(1)               K1(1) C(0)             A(0)  B(0) C(0)  D(0)    102                __|__               __|__                   103               |     |             |     |                  104               B(0)  C(0)          A(0)  B(0)               105         """106         # Rotate between k1 and k2107         k3.left = self.single_rotate_right(k3.left)108         # Rotate between k3 and k2109         return self.single_rotate_left(k3)110 111     def double_rotate_right(self, k3):112         """113                  K3(3)                     K3(3)                     K2(2)  114                __|__                     __|__                  _____|_____ 115               |     |         left      |     |       right    |           |   116               D(0)  K1(2)   -------->   D(0)  K2(2) -------->  K3(1)       K1(1)117                   __|__                     __|__            __|__       __|__ 118                  |     |                   |     |          |     |     |     |        119                  K2(1) A(0)                C(0)  K1(1)      D(0)  C(0)  B(0)  A(0)    120                __|__                           __|__                   121               |     |                         |     |                  122               C(0)  B(0)                      B(0)  A(0)               123         """124         # Rotate between k1 and k2125         k3.right = self.single_rotate_left(k3.right)126         # Rotate between k3 and k2127         return self.single_rotate_right(k3)128 129     def insert(self, item):130         if self._root is None:131             self._root = AvlNode(item)132             return133         134         def _insert(item, node):135             if not node:136                 return AvlNode(item)137             if item < node.value:138                 node.left = _insert(item, node.left)139                 if self.get_height(node.left) - self.get_height(node.right) == 2:140                     if item < node.left.value:      # Situation 1: left --> left141                         node = self.single_rotate_left(node)142                     else:                           # Situation 2: left --> right143                         node = self.double_rotate_left(node)144             elif item > node.value:145                 node.right = _insert(item, node.right)146                 if self.get_height(node.right) - self.get_height(node.left) == 2:147                     if item < node.right.value:     # Situation 3: right --> left148                         node = self.double_rotate_right(node)149                     else:                           # Situation 4: right --> right150                         node = self.single_rotate_right(node)151             else: pass152             # Update node height (if not rotated, update is necessary.)153             node.height = max(self.get_height(node.left), self.get_height(node.right)) + 1154             return node155         self._root = _insert(item, self._root)156 157     def delete(self, item):158         if self._root is None:159             return160 161         def _delete(item, node):162             if not node:    # Node no found163                 # return None164                 raise Exception('Element not in tree.')165 166             if item < node.value:167                 node.left = _delete(item, node.left)168                 # Delete left, rotate right169                 if self.get_height(node.right) - self.get_height(node.left) == 2:170                     if self.get_height(node.right.right) >= self.get_height(node.right.left):   # Situation 4171                         node = self.single_rotate_right(node)172                     else:                                                                       # Situation 3173                         node = self.double_rotate_right(node)174 175             elif item > node.value:176                 node.right = _delete(item, node.right)177                 # Delete right, rotate left178                 if self.get_height(node.left) - self.get_height(node.right) == 2:179                     if self.get_height(node.left.left) >= self.get_height(node.left.right):     # Situation 1180                         node = self.single_rotate_left(node)181                     else:                                                                       # Situation 3182                         node = self.double_rotate_left(node)183 184             else:   # Node found185                 if node.left and node.right:186                     # Minimum node in right sub-tree has no left sub-node, can be used to make replacement187                     # Find minimum node in right sub-tree188                     min_node = self.find_min(node.right)    189                     # Replace current node with min_node190                     node.value = min_node.value191                     # Delete min_node in right sub-tree192                     node.right = _delete(min_node.value, node.right)193                 else:194                     if node.left: 195                         node = node.left196                     elif node.right:197                         node = node.right198                     else:199                         node = None200             # Update node height (if not ratated, update is necessary.)201             # If node is None, height is -1 already.202             if node:203                 node.height = max(self.get_height(node.left), self.get_height(node.right)) + 1204             return node205         self._root = _delete(item, self._root)206 207                  208 def test(avl):209     print('\nInit an AVL tree:')210     for i in [1, 2, 3, 4, 5, 6, 7]:211         avl.insert(i)212     avl.show()213 214     print('\nLeft single rotate:')215     avl.delete(5)216     avl.delete(7)217     avl.delete(6)218     avl.show()219 220     avl.make_empty()221     print('\nInit an AVL tree:')222     for i in [1, 2, 3, 4, 5, 6, 7]:223         avl.insert(i)224     225     print('\nRight single rotate:')226     avl.delete(1)227     avl.delete(3)228     avl.delete(2)229     avl.show()230 231     avl.make_empty()232     print('\nInit an AVL tree:')233     for i in [6, 2, 8, 1, 4, 9, 3, 5]:234         avl.insert(i)235     avl.show()236 237     print('\nRight-Left double rotate:')238     avl.delete(9)239     avl.show()240 241     avl.make_empty()242     print('\nInit an AVL tree:')243     for i in [4, 2, 8, 1, 6, 9, 5, 7]:244         avl.insert(i)245     avl.show()246 247     print('\nLeft-Right double rotate:')248     avl.delete(1)249     avl.show()250 251 if __name__ == '__main__':252     test(AvlTree())
View Code

分段解释

首先,需要导入查找树,以及定义AVL树的节点,

1 from search_tree import SearchTree 2  3  4 class AvlNode: 5     def __init__(self, val=None, lef=None, rgt=None, hgt=0): 6         self.value = val 7         self.left = lef 8         self.right = rgt 9         self.height = hgt10 11     def __str__(self):12         return str(self.value)

接着构建一棵AVL树的类,构造方法可继承查找树,

1 class AvlTree(SearchTree): 2     """ 3     AVL Tree: 4                 0  5            _____|_____ 6           |           | 7           0           0  8           |___     ___|___ 9               |   |       |10               0   0       0 11                           |__12                              |13                              0 14     Insert unbalance:15     1:             |  2:           |  3:            | 4:16            A(2)    |        A(2)   |       A(2)     |    A(2)             17          __|       |      __|      |       |__      |    |__        18         |          |     |         |          |     |       |           19         B(1)       |     B(1)      |          B(1)  |       B(1)        20       __|          |     |__       |        __|     |       |__         21      |             |        |      |       |        |          |        22     ----           |       ----    |      ----      |         ----     23    |X(0)|          |      |X(0)|   |     |X(0)|     |        |X(0)|    24     ----           |       ----    |      ----      |         ----         25 26     Delete unbalance:27     1:             |  2:           |  3:            |  4:28            A(2)    |      A(2)     |        A(2)    |       A(2)        29          __|__     |    __|__      |      __|__     |     __|__        30         |     |    |   |     |     |     |     |    |    |     |            31         B(1) ----  |   B(1)  ----  |  ----     B(1) |  ----    B(1)          32       __|   |X(0)| |   |__  |X(0)| | |X(0)|  __|    | |X(0)|   |__            33      |       ----  |      |  ----  |  ----  |       |  ----       |          34      C(0)          |      C(0)     |        C(0)    |             C(0)35     """

定义获取节点子树高度的方法,当节点为None时,返回高度为-1,

1     def get_height(self, node):2         if isinstance(node, AvlNode):3             return node.height4         return -1

定义向左的单旋转方法,旋转完成后更新高度

1     def single_rotate_left(self, k2): 2         """ 3                  K2(2)                      K1(1) 4                __|__                      ____|_____ 5               |     |                    |          | 6               K1(1) None(-1)  -------->  X(0)       K2(1) 7             __|__                                 __|__ 8            |     |                               |     | 9            X(0)  Y(0)                            Y(0)  None(-1)10         """11         # Left rotate12         k1 = k2.left13         k2.left = k1.right14         k1.right = k215         # Update height16         k2.height = max(self.get_height(k2.left), self.get_height(k2.right)) + 117         k1.height = max(self.get_height(k1.left), k2.height) + 118         return k1

定义向右的单旋转方法,旋转完成后更新高度

1     def single_rotate_right(self, k2): 2         """ 3                  K2(2)                      K1(2) 4                __|__                      ____|_____ 5               |     |                    |          | 6           (-1)None  K1(1)   -------->    K2(1)      Y(0) 7                   __|__                __|__ 8                  |     |              |     | 9                  X(0)  Y(0)       (-1)None  X(0)10         """11         # Right rotate12         k1 = k2.right13         k2.right = k1.left14         k1.left = k215         # Update height16         k2.height = max(self.get_height(k2.left), self.get_height(k2.right)) + 117         k1.height = max(k2.height, self.get_height(k1.right)) + 118         return k1

定义先右后左的双旋转方法,旋转完成后更新高度

1     def double_rotate_left(self, k3):    2         """ 3                  K3(3)                     K3(3)                     K2(2)   4                __|__                     __|__                  _____|_____  5               |     |      right        |     |      left      |           |    6               K1(2) D(0)  -------->     K2(2) D(0) -------->   K1(1)       K3(1) 7             __|__                     __|__                  __|__       __|__  8            |     |                   |     |                |     |     |     |         9            A(0)  K2(1)               K1(1) C(0)             A(0)  B(0) C(0)  D(0)    10                __|__               __|__                   11               |     |             |     |                  12               B(0)  C(0)          A(0)  B(0)               13         """14         # Rotate between k1 and k215         k3.left = self.single_rotate_right(k3.left)16         # Rotate between k3 and k217         return self.single_rotate_left(k3)

定义先左后右的双旋转方法,旋转完成后更新高度

1     def double_rotate_right(self, k3): 2         """ 3                  K3(3)                     K3(3)                     K2(2)   4                __|__                     __|__                  _____|_____  5               |     |         left      |     |       right    |           |    6               D(0)  K1(2)   -------->   D(0)  K2(2) -------->  K3(1)       K1(1) 7                   __|__                     __|__            __|__       __|__  8                  |     |                   |     |          |     |     |     |         9                  K2(1) A(0)                C(0)  K1(1)      D(0)  C(0)  B(0)  A(0)    10                __|__                           __|__                   11               |     |                         |     |                  12               C(0)  B(0)                      B(0)  A(0)               13         """14         # Rotate between k1 and k215         k3.right = self.single_rotate_left(k3.right)16         # Rotate between k3 and k217         return self.single_rotate_right(k3)

定义插入方法,进行递归插入,每次插入后都需要检测子树高度来决定是否需要对树进行平衡旋转,最后进行高度更新,若经过旋转则此时高度已经被更新。

1     def insert(self, item): 2         if self._root is None: 3             self._root = AvlNode(item) 4             return 5          6         def _insert(item, node): 7             if not node: 8                 return AvlNode(item) 9             if item < node.value:10                 node.left = _insert(item, node.left)11                 if self.get_height(node.left) - self.get_height(node.right) == 2:12                     if item < node.left.value:      # Situation 1: left --> left13                         node = self.single_rotate_left(node)14                     else:                           # Situation 2: left --> right15                         node = self.double_rotate_left(node)16             elif item > node.value:17                 node.right = _insert(item, node.right)18                 if self.get_height(node.right) - self.get_height(node.left) == 2:19                     if item < node.right.value:     # Situation 3: right --> left20                         node = self.double_rotate_right(node)21                     else:                           # Situation 4: right --> right22                         node = self.single_rotate_right(node)23             else: pass24             # Update node height (if not rotated, update is necessary.)25             node.height = max(self.get_height(node.left), self.get_height(node.right)) + 126             return node27         self._root = _insert(item, self._root)

定义删除方法,进行递归删除,删除后检测是否需要平衡旋转,并选择旋转的方式,删除完成根据需要进行高度更新。

1     def delete(self, item): 2         if self._root is None: 3             return 4  5         def _delete(item, node): 6             if not node:    # Node no found 7                 # return None 8                 raise Exception('Element not in tree.') 9 10             if item < node.value:11                 node.left = _delete(item, node.left)12                 # Delete left, rotate right13                 if self.get_height(node.right) - self.get_height(node.left) == 2:14                     if self.get_height(node.right.right) >= self.get_height(node.right.left):   # Situation 415                         node = self.single_rotate_right(node)16                     else:                                                                       # Situation 317                         node = self.double_rotate_right(node)18 19             elif item > node.value:20                 node.right = _delete(item, node.right)21                 # Delete right, rotate left22                 if self.get_height(node.left) - self.get_height(node.right) == 2:23                     if self.get_height(node.left.left) >= self.get_height(node.left.right):     # Situation 124                         node = self.single_rotate_left(node)25                     else:                                                                       # Situation 326                         node = self.double_rotate_left(node)27 28             else:   # Node found29                 if node.left and node.right:30                     # Minimum node in right sub-tree has no left sub-node, can be used to make replacement31                     # Find minimum node in right sub-tree32                     min_node = self.find_min(node.right)    33                     # Replace current node with min_node34                     node.value = min_node.value35                     # Delete min_node in right sub-tree36                     node.right = _delete(min_node.value, node.right)37                 else:38                     if node.left: 39                         node = node.left40                     elif node.right:41                         node = node.right42                     else:43                         node = None44             # Update node height (if not ratated, update is necessary.)45             # If node is None, height is -1 already.46             if node:47                 node.height = max(self.get_height(node.left), self.get_height(node.right)) + 148             return node49         self._root = _delete(item, self._root)

最后定义一个测试函数,用于测试AVL树的功能。

首先初始化一棵树

1 def test(avl):2     print('\nInit an AVL tree:')3     for i in [1, 2, 3, 4, 5, 6, 7]:4         avl.insert(i)5     avl.show()

得到结果

Init an AVL tree:4            |          4      2        |        __|__             1    |       |     |            3    |       2     6    6        |      _|_   _|_                 5    |     |   | |   |                 7    |     1   3 5   7

接着尝试删除5, 6, 7三个节点,完成一个向左单旋转,

1     print('\nLeft single rotate:')2     avl.delete(5)3     avl.delete(7)4     avl.delete(6)5     avl.show()

得到结果,可以看到,此时AVL树已经完成了一个向左的平衡旋转

Left single rotate:2           |       2      1       |     __|__     4       |    |     |        3   |    1     4            |         _|              |        |             |        3

清空树并再次初始化树后

尝试删除1, 2, 3三个节点,完成一个向右单旋转,

 

1     print('\nDelete element from tree:')2     avl.delete(1)3     avl.delete(3)4     avl.delete(2)5     avl.show()

得到结果,可以看到,此时AVL树已经完成了一个向右的平衡旋转

Delete element from tree:6           |       6      4       |     __|__           5   |    |     |      7       |    4     7            |    |_              |      |             |      5

 再次清空树,并建立一棵新的树

1     avl.make_empty()2     print('\nInit an AVL tree:')3     for i in [6, 2, 8, 1, 4, 9, 3, 5]:4         avl.insert(i)5     avl.show()

得到结果

Init an AVL tree:6              |       6      2          |     __|__         1      |    |     |        4      |    2     8            3  |   _|_    |_            5  |  |   |     |    8          |  1   4     9        9      |     _|_                |    |   |               |    3   5

此时尝试删除节点9,完成一个右左双旋转

1     print('\nRight-Left double rotate:')2     avl.delete(9)3     avl.show()

得到结果

Right-Left double rotate:4           |       4      2       |     __|__         1   |    |     |        3   |    2     6    6       |   _|_   _|_        5   |  |   | |   |        8   |  1   3 5   8

最后再次清空树建立一棵新树,

1     avl.make_empty()2     print('\nInit an AVL tree:')3     for i in [4, 2, 8, 1, 6, 9, 5, 7]:4         avl.insert(i)5     avl.show()

显示结果如下

Init an AVL tree:4              |       4          2          |     __|__         1      |    |     |    8          |    2     8        6      |   _|    _|_            5  |  |     |   |            7  |  1     6   9        9      |       _|_                |      |   |               |      5   7

此时尝试删除节点1,造成不平衡完成一个左右双旋转

1     print('\nLeft-Right double rotate:')2     avl.delete(1)3     avl.show()

得到结果

Left-Right double rotate:6           |       6           4       |     __|__         2   |    |     |        5   |    4     8    8       |   _|_   _|_        7   |  |   | |   |        9   |  2   5 7   9

 

相关阅读


1.

2.

转载于:https://www.cnblogs.com/stacklike/p/8290070.html

你可能感兴趣的文章
ajax-原生写法步骤
查看>>
.Net语言 APP开发平台——Smobiler学习日志:如何在手机上实现饼图图表
查看>>
svn完整备份迁移
查看>>
Python字典实现分析
查看>>
jenkins+testNG
查看>>
Java自定义范型的应用技巧
查看>>
[洛谷1485] 火枪打怪
查看>>
白话经典算法系列之六 快速排序 快速搞定
查看>>
错了:用流量能够放肆,有wifi则要节制
查看>>
CSS渐变字体、镂空字体、input框提示信息颜色、给图片加上内阴影、3/4圆
查看>>
https://zhidao.baidu.com/question/362784520674844572.html
查看>>
第八周
查看>>
my.cnf_For5.7_注释版
查看>>
【MFC 学习笔记】CFile读写文件
查看>>
Java 的IO操作初步(一)
查看>>
关于VGA时序的相应计算方式
查看>>
电感和感抗
查看>>
PAT B1018.锤子剪刀布(20)
查看>>
Yii2.0 集成使用富头像上传编辑器
查看>>
Extjs控件之 grid打印功能
查看>>