当前位置:   article > 正文

C语言二叉树详解

c语言二叉树

 

 

文章目录

 


概念

一棵二叉树是结点的一个有限集合,该集合:
1. 或者为空
2. 由一个根节点加上两棵别称为左子树和右子树的二叉树组成
注意:
1. 二叉树不存在度大于2的结点
2. 二叉树的子树有左右之分,次序不能颠倒,因此二叉树是有序树
任意的二叉树都由下面几种情况组合而成:
45d4a0f3f9db4a32b8447163aa03bcca.png

二叉树中有两个非常特别的存在:

1.满二叉树

一个二叉树,如果每一个层的结点数都达到最大值,则这个二叉树就是满二叉树。也就是
说,如果一个二叉树的层数为K,且结点总数是 ,则它就是满二叉树。

2.完全二叉树

完全二叉树是效率很高的数据结构,完全二叉树是由满二叉树而引出来的。对于深度为K
的,有n个结点的二叉树,当且仅当其每一个结点都与深度为K的满二叉树中编号从1至n的结点一一对 应时称之为完全二叉树。 要注意的是满二叉树是一种特殊的完全二叉树。
如图所示:
a7ac267975934f77a698d6f345332f83.png

 


 

一、二叉树的性质

1. 若规定根节点的层数为1,则一棵非空二叉树的 i 层上最多有2^(i-1) 个结点.
2. 若规定根节点的层数为1,则 深度为 h 的二叉树的最大结点数是2^h-1.
3. 对任何一棵二叉树, 如果度为 0 其叶结点个数为 n0 , 度为 2 的分支结点个数为n2  , 则有
n0 = n2+1
4. 若规定根节点的层数为1,具有 n 个结点的满二叉树的深度h= log(n+1)
5. 对于具有n个结点的完全二叉树,如果按照从上至下从左至右的数组顺序对所有节点从0开始编号,则对 于序号为i的结点有:
1. i>0 i 位置节点的双亲序号: (i-1)/2;i=0,i为根节点编号,无双亲节点
2. 2i+1<n ,左孩子序号: 2i+1 2i+1>=n 否则无左孩子
3. 2i+2<n ,右孩子序号: 2i+2 2i+2>=n 否则无右孩子

二、实现二叉树

二叉树我们是用链表实现的,下面是结构:

  1. #define _CRT_SECURE_NO_WARNINGS 1
  2. #include <stdio.h>
  3. #include <stdlib.h>
  4. #include <stdbool.h>
  5. #include <assert.h>
  6. typedef char BTDataType;
  7. typedef struct BTNode
  8. {
  9. BTDataType data;
  10. struct BTNode* left;
  11. struct BTNode* right;
  12. }BTNode;

我们可以看到一个节点中有三个变量,一个存放数据,一个指向左子树,一个指向右子树。将char类型重命名是为了后面更方便的更改类型,将结构体重命名为BTNode是为了更简单的写出指针类型。 

下面是实现的完整代码,然后我们依次进行讲解,在下面我们可以看到队列,这是因为二叉树的层序遍历和完全二叉树的判断用队列非常方便。

  1. #define _CRT_SECURE_NO_WARNINGS 1
  2. #include <stdio.h>
  3. #include <stdlib.h>
  4. #include <stdbool.h>
  5. #include <assert.h>
  6. typedef char BTDataType;
  7. typedef struct BTNode
  8. {
  9. BTDataType data;
  10. struct BTNode* left;
  11. struct BTNode* right;
  12. }BTNode;
  13. typedef BTNode* QueueDate;
  14. typedef struct QueueNode
  15. {
  16. struct QueueNode* next;
  17. QueueDate data;
  18. }QueueNode;
  19. typedef struct Queue
  20. {
  21. QueueNode* head;
  22. QueueNode* tail;
  23. int size;
  24. }Queue;
  25. QueueDate QueueFront(Queue* ps)
  26. {
  27. assert(ps);
  28. assert(ps->head);
  29. return ps->head->data;
  30. }
  31. bool QueueEmpty(Queue* ps)
  32. {
  33. assert(ps);
  34. return ps->size == 0;
  35. }
  36. void QueuePop(Queue* ps)
  37. {
  38. assert(ps);
  39. assert(ps->head);
  40. if (ps->head->next == NULL)
  41. {
  42. free(ps->head);
  43. ps->head = ps->tail = NULL;
  44. }
  45. else
  46. {
  47. QueueNode* tmp = ps->head->next;
  48. free(ps->head);
  49. ps->head = tmp;
  50. }
  51. ps->size--;
  52. }
  53. void QueuePush(Queue* ps, QueueDate x)
  54. {
  55. assert(ps);
  56. QueueNode* newnode = (QueueNode*)malloc(sizeof(QueueNode));
  57. if (newnode == NULL)
  58. {
  59. perror("malloc:");
  60. exit(-1);
  61. }
  62. newnode->data = x;
  63. newnode->next = NULL;
  64. if (ps->head == NULL)
  65. {
  66. ps->head = ps->tail = newnode;
  67. }
  68. else
  69. {
  70. ps->tail->next = newnode;
  71. ps->tail = newnode;
  72. }
  73. ps->size++;
  74. }
  75. void QueueDestroy(Queue* ps)
  76. {
  77. assert(ps);
  78. while (ps->head)
  79. {
  80. QueueNode* cur = ps->head->next;
  81. free(ps->head);
  82. ps->head = cur;
  83. }
  84. ps->head = ps->tail = NULL;
  85. ps->size = 0;
  86. }
  87. void QueueInit(Queue* ps)
  88. {
  89. assert(ps);
  90. ps->head = NULL;
  91. ps->tail = NULL;
  92. ps->size = 0;
  93. }
  94. //二叉树的销毁
  95. void BinaryTreeDestroy(BTNode* root)
  96. {
  97. if (root == NULL)
  98. {
  99. return;
  100. }
  101. BinaryTreeDestroy(root->left);
  102. BinaryTreeDestroy(root->right);
  103. free(root);
  104. }
  105. //二叉树的前序遍历
  106. void PrevOrder(BTNode* root)
  107. {
  108. if (root == NULL)
  109. {
  110. printf("NULL ");
  111. return;
  112. }
  113. printf("%c ", root->data);
  114. PrevOrder(root->left);
  115. PrevOrder(root->right);
  116. }
  117. //二叉树的中序遍历
  118. void middleOrder(BTNode* root)
  119. {
  120. if (root == NULL)
  121. {
  122. printf("NULL ");
  123. return;
  124. }
  125. middleOrder(root->left);
  126. printf("%c ", root->data);
  127. middleOrder(root->right);
  128. }
  129. //二叉树的后序遍历
  130. void AfterOrder(BTNode* root)
  131. {
  132. if (root == NULL)
  133. {
  134. printf("NULL ");
  135. return;
  136. }
  137. AfterOrder(root->left);
  138. AfterOrder(root->right);
  139. printf("%d ", root->data);
  140. }
  141. //二叉树的节点数
  142. int BinaryTreeSize(BTNode* root)
  143. {
  144. if (root == NULL)
  145. {
  146. return 0;
  147. }
  148. return BinaryTreeSize(root->left) + BinaryTreeSize(root->right) + 1;
  149. }
  150. //二叉树的叶子节点数
  151. int BinaryTreeLeafSize(BTNode* root)
  152. {
  153. if (root == NULL)
  154. {
  155. return 0;
  156. }
  157. if (root->left == NULL && root->right == NULL)
  158. {
  159. return 1;
  160. }
  161. return BinaryTreeLeafSize(root->left) + BinaryTreeLeafSize(root->right);
  162. }
  163. //二叉树的高度
  164. int BinaryTreeHeight(BTNode* root)
  165. {
  166. if (root == NULL)
  167. {
  168. return 0;
  169. }
  170. int Left = BinaryTreeHeight(root->left);
  171. int right = BinaryTreeHeight(root->right);
  172. return Left > right ? Left + 1 : right + 1;
  173. }
  174. //二叉树第K层节点数
  175. int BinaryTreeKnode(BTNode* root,int k)
  176. {
  177. if (root == NULL)
  178. {
  179. return 0;
  180. }
  181. if (k == 1)
  182. {
  183. return 1;
  184. }
  185. return BinaryTreeKnode(root->left, k - 1) + BinaryTreeKnode(root->right, k - 1);
  186. }
  187. //二叉树查找值为x的节点
  188. BTNode* BinaryTree(BTNode* root,BTDataType x)
  189. {
  190. if (root == NULL)
  191. {
  192. return NULL;
  193. }
  194. if (root->data == x)
  195. {
  196. return root;
  197. }
  198. BTNode* left = BinaryTree(root->left, x);
  199. if (left)
  200. {
  201. return left;
  202. }
  203. BTNode* right = BinaryTree(root->right, x);
  204. if (right)
  205. {
  206. return right;
  207. }
  208. return NULL;
  209. }
  210. // 通过前序遍历的数组"ABD##E#H##CF##G##"构建二叉树
  211. BTNode* BinaryTreeCreate(BTDataType* str, int n, int* i)
  212. {
  213. if (str[*i] == '#')
  214. {
  215. (*i)++;
  216. return NULL;
  217. }
  218. BTNode* newnode = (BTNode*)malloc(sizeof(BTNode));
  219. newnode->data = str[(*i)++];
  220. newnode->left = BinaryTreeCreate(str, n, i);
  221. newnode->right = BinaryTreeCreate(str, n, i);
  222. return newnode;
  223. }
  224. //层序遍历
  225. void BinaryTreeLevelOrder(BTNode* root)
  226. {
  227. Queue sl;
  228. QueueInit(&sl);
  229. if (root)
  230. {
  231. QueuePush(&sl, root);
  232. }
  233. while (!QueueEmpty(&sl))
  234. {
  235. BTNode* tmp = QueueFront(&sl);
  236. QueuePop(&sl);
  237. printf("%d ", tmp->data);
  238. if (tmp->left)
  239. {
  240. QueuePush(&sl, tmp->left);
  241. }
  242. if (tmp->right)
  243. {
  244. QueuePush(&sl, tmp->right);
  245. }
  246. }
  247. printf("\n");
  248. QueueDestroy(&sl);
  249. }
  250. //判断是否为完全二叉树
  251. int BinaryTreeComplete(BTNode* root)
  252. {
  253. Queue sl;
  254. QueueInit(&sl);
  255. if (root)
  256. {
  257. QueuePush(&sl, root);
  258. }
  259. while (!QueueEmpty(&sl))
  260. {
  261. BTNode* tmp = QueueFront(&sl);
  262. QueuePop(&sl);
  263. if (tmp==NULL)
  264. {
  265. break;
  266. }
  267. else
  268. {
  269. QueuePush(&sl, tmp->left);
  270. QueuePush(&sl, tmp->right);
  271. }
  272. }
  273. while (!QueueEmpty(&sl))
  274. {
  275. BTNode* tmp = QueueFront(&sl);
  276. QueuePop(&sl);
  277. if (tmp)
  278. {
  279. QueueDestroy(&sl);
  280. return 0;
  281. }
  282. }
  283. QueueDestroy(&sl);
  284. return 1;
  285. }
  286. int main()
  287. {
  288. /*BTNode* n1 = (BTNode*)malloc(sizeof(BTNode));
  289. BTNode* n2 = (BTNode*)malloc(sizeof(BTNode));
  290. BTNode* n3 = (BTNode*)malloc(sizeof(BTNode));
  291. BTNode* n4 = (BTNode*)malloc(sizeof(BTNode));
  292. BTNode* n5 = (BTNode*)malloc(sizeof(BTNode));
  293. BTNode* n6 = (BTNode*)malloc(sizeof(BTNode));
  294. n1->data = 1;
  295. n2->data = 2;
  296. n3->data = 3;
  297. n4->data = 4;
  298. n5->data = 5;
  299. n6->data = 6;
  300. n1->left = n2;
  301. n1->right = n4;
  302. n2->left = n3;
  303. n2->right = n5;
  304. n3->left = NULL;
  305. n3->right = NULL;
  306. n4->left = n6;
  307. n4->right = NULL;
  308. n5->right = NULL;
  309. n5->left = NULL;
  310. n6->left = NULL;
  311. n6->right = NULL;*/
  312. /*PrevOrder(n1);
  313. printf("\n");
  314. middleOrder(n1);
  315. printf("\n");
  316. AfterOrder(n1);
  317. printf("\n");
  318. printf("%d\n", BinaryTreeSize(n1));
  319. printf("%d\n", BinaryTreeLeafSize(n1));
  320. printf("%d\n", BinaryTreeHeight(n1));
  321. printf("%d\n", BinaryTreeKnode(n1,1));
  322. BTNode* tmp = BinaryTree(n1, 10);
  323. if (tmp == NULL)
  324. {
  325. printf("tmp=NULL\n");
  326. }
  327. else
  328. {
  329. printf("tmp=%d\n", tmp->data);
  330. }*/
  331. /*BinaryTreeLevelOrder(n1);
  332. printf("%d\n", BinaryTreeComplete(n1));*/
  333. /*BinaryTreeDestroy(n1);*/
  334. char str[100];
  335. scanf("%s", str);
  336. int i = 0;
  337. BTNode* tmp = BinaryTreeCreate(str, 100, &i);
  338. middleOrder(tmp);
  339. printf("\n");
  340. PrevOrder(tmp);
  341. BinaryTreeDestroy(tmp);
  342. return 0;
  343. }

 先看第一个:二叉树遍历

1.前序遍历:先遍历根 然后是左子树 然后是右子树

  1. void PrevOrder(BTNode* root)
  2. {
  3. if (root == NULL)
  4. {
  5. printf("NULL ");
  6. return;
  7. }
  8. printf("%c ", root->data);
  9. PrevOrder(root->left);
  10. PrevOrder(root->right);
  11. }

 6713c36ddd2e40f28a6518038966c990.png

如图所示,在递归的过程中,第一个节点为1,1不为空打印1然后递归1的左子树,1的左子树为2,不为空打印2,然后递归2的左子树,2的左子树为空打印NULL然后返回刚刚2的那个函数,2的左子树已经递归了所以该2的右子树,2的右子树为空打印NULL然后返回2的函数,2的函数执行完了返回1的函数,这时候1的左子树递归完成了,现在递归1的右子树,这里与1的左子树递归出来是一样的,然后成功用前序遍历出这棵树。后面的中序遍历就是先去遍历左子树不打印,然后左子树遍历完了在打印节点然后再去遍历右子树。

2.中序遍历:先遍历左子树,然后遍历根,再遍历右子树

  1. void middleOrder(BTNode* root)
  2. {
  3. if (root == NULL)
  4. {
  5. printf("NULL ");
  6. return;
  7. }
  8. middleOrder(root->left);
  9. printf("%c ", root->data);
  10. middleOrder(root->right);
  11. }

 d7b288fced8e485d9fb245c416f49c96.png

 与前序相同就不再讲述。

3.后序遍历:先遍历左子树,然后遍历右子树,最后遍历根节点。

  1. void AfterOrder(BTNode* root)
  2. {
  3. if (root == NULL)
  4. {
  5. printf("NULL ");
  6. return;
  7. }
  8. AfterOrder(root->left);
  9. AfterOrder(root->right);
  10. printf("%d ", root->data);
  11. }

 第二个:求二叉树的节点数

  1. int BinaryTreeSize(BTNode* root)
  2. {
  3. if (root == NULL)
  4. {
  5. return 0;
  6. }
  7. return BinaryTreeSize(root->left) + BinaryTreeSize(root->right) + 1;
  8. }

 求二叉树的节点很简单,当这个节点为空时就返回0否则就返回这个节点的左子树的节点加右子树的节点再加自己。单靠代码可能不好看出来,那么我们画个图看看。

e7188b80d53a4fd58565b393b96fd2ee.png

相信大家也看懂这个图了,本质就是计算一个节点的左子树的节点+右子树的节点再加自己。

第三个:求二叉树的叶子节点数

叶子节点我们在上篇已经讲过,就是度为0的节点。我们从这个角度出发,度为0就是左子树为空并且右子树为空,所以只需要将上面的代码稍微修改即可。

  1. int BinaryTreeLeafSize(BTNode* root)
  2. {
  3. if (root == NULL)
  4. {
  5. return 0;
  6. }
  7. if (root->left == NULL && root->right == NULL)
  8. {
  9. return 1;
  10. }
  11. return BinaryTreeLeafSize(root->left) + BinaryTreeLeafSize(root->right);
  12. }

 在这里一定要注意,当树为空要返回0否则就是死递归。下面是递归图:

5bb98628e8f34e1980227cd3556f40e7.png

 我们可以看到求叶子节点的递归非常简单,只需要记得当树为空返回0,如果没有这个条件当一棵树或者一个节点只有左边没有右边时就会死递归。

第四个:求树的高度

  1. int BinaryTreeHeight(BTNode* root)
  2. {
  3. if (root == NULL)
  4. {
  5. return 0;
  6. }
  7. int Left = BinaryTreeHeight(root->left);
  8. int right = BinaryTreeHeight(root->right);
  9. return Left > right ? Left + 1 : right + 1;
  10. }

 求树的高度只需要看一棵树中它的左子树高还是右子树高,我们返回高的那个然后再加上根的一层就是总高度,当一样高时随便返回一颗子树的高度即可。在这里需要注意的是一定要用两个值去分别记录左右子树的高度,否则效率非常差每一层递归都要重新计算高度。

b0e4390328fc4e93915d530baaea9f44.png

如递归图所示当有变量记录其左右高度时会比直接递归快很多。

第五个:求第K层节点数

  1. int BinaryTreeKnode(BTNode* root,int k)
  2. {
  3. if (root == NULL)
  4. {
  5. return 0;
  6. }
  7. if (k == 1)
  8. {
  9. return 1;
  10. }
  11. return BinaryTreeKnode(root->left, k - 1) + BinaryTreeKnode(root->right, k - 1);
  12. }

怎么求第K层节点数呢?我们发现当我们要求一棵树的第三层时,从根节点的角度看是第三层,从第二层的角度看是第二层,从第三层的角度看是第一层,也就是说每次都可以转化为这个节点的左子树的K-1层的节点+这个节点的右子树的K-1层节点,当K=1也就是自己就是第一层时那么只有自己一个节点,所以返回1即可。

c4192d8a1aff4636a910181e29737d70.png

第六个:二叉树查找值为x的节点

  1. BTNode* BinaryTree(BTNode* root,BTDataType x)
  2. {
  3. if (root == NULL)
  4. {
  5. return NULL;
  6. }
  7. if (root->data == x)
  8. {
  9. return root;
  10. }
  11. BTNode* left = BinaryTree(root->left, x);
  12. if (left)
  13. {
  14. return left;
  15. }
  16. BTNode* right = BinaryTree(root->right, x);
  17. if (right)
  18. {
  19. return right;
  20. }
  21. return NULL;
  22. }

 查找值为x的节点我们要考虑几个问题,第一个是如果找到了这个节点能否直接返回,第二个是当找不到的时候要返回空指针,当这个节点为空时也要返回空指针,我们在递归的过程中一定要保证每一个都有返回值。在这里当我们找到这个节点时为什么要用变量保存呢?因为当我们找到这个节点后我们应该要函数直接返回而不是继续去递归它的左右子树。

5e5c4498e732443f80841290c42465ce.png

通过递归图我们可以看到每次找到节点后返回这个节点必须要有变量去保存这个节点,否则就白找了,并且通过判断变量是否为空的情况就可以知道有没有找到,当任意一个变量不为空时直接返回了找到的这个节点就不会去递归其他的节点。 

第七个:通过前序遍历的数组"ABD##E#H##CF##G##"构建二叉树

这个其实是牛客网的一道题,就是通过前序遍历来让一个字符串被二叉树存储。

  1. BTNode* BinaryTreeCreate(BTDataType* str, int n, int* i)
  2. {
  3. if (str[*i] == '#')
  4. {
  5. (*i)++;
  6. return NULL;
  7. }
  8. BTNode* newnode = (BTNode*)malloc(sizeof(BTNode));
  9. newnode->data = str[(*i)++];
  10. newnode->left = BinaryTreeCreate(str, n, i);
  11. newnode->right = BinaryTreeCreate(str, n, i);
  12. return newnode;
  13. }

 如图当遇到字符#时,就是空指针,str是传来的字符串,i是记录字符串位置的下标,在这里一定要注意此函数要传下标的地址,因为我们在递归的过程中i会++如果不是传地址的话每次递归后的i++后对上一次函数栈帧不起作用,我们通过递归图来演示。

  1. int main()
  2. {
  3. char str[100];
  4. scanf("%s", str);
  5. int i = 0;
  6. BTNode* tmp = BinaryTreeCreate(str, 100, &i);
  7. return 0;
  8. }

 a36ae4fab0d54aea9d28f33a238500a8.png

如图所示当走完全部字符串后返回二叉树的根节点,这样就创建好了一颗二叉树。在这里要注意的是不管遇到空指针还是正常字符都需要让下标I++。在这里我们开辟的空间到最后结束程序都要记得销毁。

第八个:销毁二叉树

  1. void BinaryTreeDestroy(BTNode* root)
  2. {
  3. if (root == NULL)
  4. {
  5. return;
  6. }
  7. BinaryTreeDestroy(root->left);
  8. BinaryTreeDestroy(root->right);
  9. free(root);
  10. }

 销毁二叉树同样需要递归来实现,我们只需要先找到左子树最后一个节点然后依次销毁即可。在这里需要注意的是当这个节点为空时,一定要返回。

527b72974930474697fc6684372764d4.png

第九个:二叉树的层序遍历

  1. void BinaryTreeLevelOrder(BTNode* root)
  2. {
  3. Queue sl;
  4. QueueInit(&sl);
  5. if (root)
  6. {
  7. QueuePush(&sl, root);
  8. }
  9. while (!QueueEmpty(&sl))
  10. {
  11. BTNode* tmp = QueueFront(&sl);
  12. QueuePop(&sl);
  13. printf("%d ", tmp->data);
  14. if (tmp->left)
  15. {
  16. QueuePush(&sl, tmp->left);
  17. }
  18. if (tmp->right)
  19. {
  20. QueuePush(&sl, tmp->right);
  21. }
  22. }
  23. printf("\n");
  24. QueueDestroy(&sl);
  25. }

层序遍历我们是用队列实现的,先将根节点入队列,每次出队头元素前都需要将这个节点的左右子树入队列。当队列为空说明所有节点都遍历完了,直接销毁队列即可。

d4a463efcaeb43f9b14ccf5b3c9f93a9.png 

 当然每次入队列我们是不会入空树的因为层序遍历就是每层从左到右根的遍历。每次用一个变量去保存队列头元素,这样方便出队列和入队列。入节点的子树的时候每次左右子树不为空再入队列。在这里要说明一下,队列里保存的是二叉树结构体的指针而不是节点,如果保存的是节点就会出错。如下面代码要保存树的节点的地址。

  1. typedef BTNode* QueueDate;
  2. typedef struct QueueNode
  3. {
  4. struct QueueNode* next;
  5. QueueDate data;
  6. }QueueNode;

第十个:判断是不是完全二叉树

完全二叉树可以在最后一个节点只有左子树,但一定不会在已经有空树的后面还有节点如下图所示:

58d02ff98f57436d92b70669582400a5.png

如图所示,非完全二叉树有空节点后后面还会有节点,而完全二叉树在某一个节点为空时它的右边全是空节点,依据这个特性我们就很容易的判断一棵树是否为完全二叉树。

  1. int BinaryTreeComplete(BTNode* root)
  2. {
  3. Queue sl;
  4. QueueInit(&sl);
  5. if (root)
  6. {
  7. QueuePush(&sl, root);
  8. }
  9. while (!QueueEmpty(&sl))
  10. {
  11. BTNode* tmp = QueueFront(&sl);
  12. QueuePop(&sl);
  13. if (tmp==NULL)
  14. {
  15. break;
  16. }
  17. else
  18. {
  19. QueuePush(&sl, tmp->left);
  20. QueuePush(&sl, tmp->right);
  21. }
  22. }
  23. while (!QueueEmpty(&sl))
  24. {
  25. BTNode* tmp = QueueFront(&sl);
  26. QueuePop(&sl);
  27. if (tmp)
  28. {
  29. QueueDestroy(&sl);
  30. return 0;
  31. }
  32. }
  33. QueueDestroy(&sl);
  34. return 1;
  35. }

我们继续用队列实现,这次空节点也要入队列,当队列的头变成空节点时我们出队列并且退出循环,当队列不为空时我们继续拿队头元素,一旦这个元素不是空节点就说明不是完全二叉树,当队列全部出完也没有遇到非空的节点时,那么就说明这棵树为完全二叉树。


总结

二叉树最重要的就是递归,要深刻的理解递归过程就必须要多画图,只要理解了递归那么再去写二叉树就手到擒来了。

 

 

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/知新_RL/article/detail/531319
推荐阅读
相关标签
  

闽ICP备14008679号