博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
【练习】2021下半年数据结构刷题笔记和总结 (二) 树、查找-- 不同的排序算法、二叉排序树 平衡二叉树、哈希表查找、线索二叉树、
阅读量:3904 次
发布时间:2019-05-23

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

记录自己下半年写题目的记录。题目来自书或者网站。

练习(一)的地址:
https://blog.csdn.net/qq_41358574/article/details/117098620?ops_request_misc=%257B%2522request%255Fid%2522%253A%2522162343250816780357289113%2522%252C%2522scm%2522%253A%252220140713.130102334.pc%255Fblog.%2522%257D&request_id=162343250816780357289113&biz_id=0&utm_medium=distribute.pc_search_result.none-task-blog-2blogfirst_rank_v2~rank_v29-2-117098620.nonecase&utm_term=%E5%88%B7%E9%A2%98&spm=1018.2226.3001.4450

文章目录


知识点整理

  • 平衡二叉树插入结点时速度比较慢
  • 平衡二叉树的各项操作的时间复杂度为o(logn)
  • 有序数组的查找性能高但删除性能低,有序链表的查找性能低但删除性能高,AVL和哈希表的查找和删除性能都高

1.一棵二叉排序树,设计算法查找两个结点之和等于给定值x的结点

1.思路一 中序遍历产生中序序列(左子树所有结点的关键字均小于根节点关键字)

typedef struct node {
char data; struct node* lchild; struct node* rchild;}btnode;void InOrder(btnode* bt, vector
& order) {
//order:中序遍历产生的序列 InOrder(bt->lchild, order); order.push_back(bt); InOrder(bt->rchild, order);}bool FindSum(vector
order, int x, btnode* &p1, btnode* &p2)//p1 p2传入的是引用 这样能得到和为x的结点{
//查找两个结点值之和等于x的p1和p2 int i = 0, j = order.size()-1; while (i < j) {
if (order[i]->data + order[j]->data == x) {
p1 = order[i]; p2 = order[j]; return true; } else if (order[i]->data + order[j]->data > x) {
j--; } else i++; } return false;}bool TwoNodeSum(btnode* bt, int x, btnode*& p1, btnode*& p2){
vector
order; InOrder(bt, order); if (FindSum(order,x, p1, p2)) return true; else return false;}

思路二 采用两个栈分别记录bt的左下结点和右下结点(初始状态保存根结点的左下结点和右下结点)

bool TwoNodeSum(btnode* bt, int x, btnode*& p1, btnode*& p2){
stack
left, right; btnode* p = bt, * q; while (p != NULL) {
if (p->lchild != nullptr) {
left.push(p->lchild); p = p->lchild; } p = bt;//注意这个步骤 此后根结点再次赋给p if (p->rchild != nullptr) {
right.push(bt->rchild); p = p->rchild; } while (!left.empty() && !right.empty()) {
p1 = left.top(); p2 = right.top(); if (p1->data + p2->data > x)//找次大结点 即p2左孩子的所有右下结点进栈 {
q = p2; right.pop(); if (q->lchild != NULL) {
p = q->lchild; while (p) {
right.push(p); p = p->rchild; } } } else if (p1->data + p2->data < x) {
//找次小结点 q = p1; left.pop(); if (q->rchild != NULL) {
p = q->rchild; while (p) {
left.push(p); p = p->lchild; } } } else return true; } }; return false;}

2.树的层次遍历

按从上到下从左到右的顺序输出各个结点的值,如果从根到某个叶节点的路径上有的结点没有在输入中给出,或者给出超过一次,应该输出-1

样例输入:
(11, LL) (7, LLL) (8, R) (5, ) (4, L) (13, RL) (2, LLR) (1, RRR) (4, RR) ()
输出:
5 4 8 11 13 4 7 2 1

#include
struct Node {
int val; bool ifHas;//是否被赋值过 Node* left, * right; Node() :ifHas(false), left(NULL), right(NULL) {
};};Node* root;char s[maxn];//保存读入结点void addNode(int v, char* s) {
int n = strlen(s); for (int i = 0; i < n; i++) {
if (s[i] == 'L') {
if (root->left == NULL) {
root->left = new Node();//结点不存在,建立新节点 root = root->left; } } else if (s[i] == 'R') {
if (root->right == NULL) {
root->right = new Node(); root = root->right; } } if (root->ifHas == true) continue; root->val = v;//赋值 root->ifHas = true;//置为已经访问 }}//插入结点的操作bool bfs(vector
& ans){
Node* temp; queue
q; q.push(root); while (!q.empty()) {
temp = q.front(); if (temp->ifHas == false) return false;//注意这个验错。如果没有被赋值过,表明输入有误 ans.push_back(q.front()->val); q.pop(); if (temp->left)q.push(temp->left); if (temp->right)q.push(temp->right); return true; } }bool read_input(){
Node node; int val; while (1) {
if (scanf("%s", s) != 1) return false; if (!strcmp("()", s)) break;//读到结尾标志,退出循环 sscanf(&s[1],"%d",&val);//s[1]是字符 &s[1]是字符串“11,LL)” addNode(val, strchr(s, ',') + 1); } return true;}

3.一二叉排序树中的关键字由整数构成,为了查找某关键字k,会得到一个查找序列,判断一个数组a中的序列是否为该二叉排序的查找序列

//假设序列a有n个关键字,如果查找成功则a[n-1]=k,用i扫描a数组,p用于在二叉树bt中查找(p的初值指向根节点) 注意:没查找一层都比较p->key与a[i]是否相等//如果不相等 返回falsebool FindSer(Node* bt, int k, int a[], int n){
if (bt == NULL) return false; if (a[n - 1] != k) return false;//先提前判断这个 Node* p = bt; int i = 0; while (i < n && p != NULL) {
if (p->val != a[i]) return false; if (k > p->val) p = p->right; else if (k < p->val) p = p->right; i++; } if (p != NULL) return true; return false;}

4.求关键字分别为x,y的结点的最近公共祖先

Node* LCA(Node*bt,int x,int y){
{
if (bt == nullptr) return NULL; if (x < bt->left->val && y < bt->left->val) return LCA(bt->left, x, y); else if (x > bt->right->val && y > bt->right->val) return LCA(bt->right, x, y); else return bt;//注意 如果以上两种情况都不是,则bt为最近公共结点 }

5.设计算法将二叉排序树变为有序链表,要求不能创建新结点,只修改指针

方法一:

//1.递归的方法 将二叉树bt中所有结点的左指针lchild转化为指向前驱结点,右指针转化为指向后继结点//if bt->lchild==null first = bt,if bt->rchild==null last =bt//注意需要销毁双链表void convert(Node* bt, Node*& first, Node*& last){
if (bt == nullptr) {
//空树 first = nullptr; last = nullptr; } Node* lfirst, * llast, * rfirst, * rlast;//递归调用时左子树和右子树的首位指针,一共四个 if (bt->left == nullptr) first = bt; else if (bt->left != nullptr) {
convert(bt->left, lfirst, llast); first = lfirst; bt->left = llast; llast->right = bt; } if (bt->right == nullptr)last = bt; else {
convert(bt->right, rfirst, rlast); last = rlast; bt->right = rfirst; rfirst->left = bt; }}void Display(Node* first, Node* last){
if (first != NULL) {
//输出转化后的双链表 while (first != last) {
cout << first->val << ","; first = first->right; } cout << first->val;//最后一个结点的值 } }void destroy(Node*& L){
//销毁双链表 Node* pre = L, *p = pre->right; //一个首结点 一个首结点的后继结点 while (pre->right) {
free(pre); pre = p; p = pre->right; } free(pre);//注意最后还有一个结点要释放}

方法二:

中序遍历 用pre全局变量指向中序遍历当前访问结点bt的前驱节点,初始为NULL,中序访问bt时,设置pre->rchild=bt bt->lchid = pre
两个方法的输出和销毁双链表函数是一样的

Node* pre = NULL;void InOrder(Node* bt){
if (bt == NULL) return; if (bt->left) {
InOrder(bt->left); bt->left = pre; pre->right = bt; } if (pre != NULL)pre->right = bt; bt->left = pre; pre = bt; InOrder(bt->right);//递归构造右子树}void convert(Node* bt, Node*& first){
//转化为首结点为first的双链表 Node* p = bt; if (p->left != NULL) {
p = p->left; } first = p; pre = NULL; InOrder(bt);}int main() {
Node* first,*bt; //用排序二叉树算法建树 convert(bt, first);//转化为有序单链表 //输出双链表的函数 //销毁双链表 }

6.设计算法交换二叉树b的所有左右子树要求空间复杂度为1

void swap(btnode*b){
btnode*temp;if(b!=NULL){
swap(b->lchild);swap(b->rchild);temp=b->lchild;b->lchild=b->rchild;b->rchild=temp;}}//基于后序遍历算法

7.设计算法交换二叉树b的所有左右子树要求不破坏原二叉树结构

btnode* swap(btnode*b){
btnode*t,*t1,*t2;if(b==NULL)t=NULL;else{
t=(btnode*)malloc(sizof(btnode));//复制根节点t->data=b->data;t1=swap(b->lchild);t2=swap(b->rchild);t->lchild=t2;t->rchild=t1;}return t;}

8.设计算法利用结点的右孩子指针将一棵二叉树的叶结点从左往右串成单链表

//先序遍历的方法,尾插法构建叶结点,head指向建立单链表的首结点,tail指向尾结点//head tail以引用的方式传入void link(btnode*b,btnode*&head,btnode*&tail){
if(b!=NULL){
if(b->lchild==NULL&&b->rchild==NULL)//叶结点{
if(head==NULL){
//此时为第一个叶结点head=b;tail=head;}else{
tail->rchild=b;tail=b;}}if(b->lchild!=NULL){
link(b->lchild,head,tail);}if(b->rchild!=NULL)link(b->rchild,head,tail);}//创建并输出叶结点构成的单链表void vreateLink(btnode*b){
btnode*head=NULL,*tail;link(b,head,tail)//注意head一开始指向nulltail->rchild=NULL;//注意尾结点的rchild置空btnode*p=head;while(p!=NULL){
cout<
data<<",";p=p->next;}}

9.设计算法输出所有根结点到叶结点的路径及其路径和

void dispapath(vector
path){
vector
::iterator it;int sum=0;for(it=path.begin();it!=path.end();it++){
sum+=*it;cout<<*it<<" ";}cout<
path){
if(b==NULL)return;path.push_back(b->data);if(b->lchild==NULL&&b->rchild==NULL){
dispath(path);//找到一个叶结点,输出一条路径}pathSum(b->lchild,path);pathSum(b->rchild,path);//如果不是叶结点,先序遍历的方式继续寻找后面的结点

10.【面试题9-11★★★】一个含有13个元素的数组为前半部分、后半部分是递增有序,但整个数组不是递增数组,且不知道前、后两个部分中的元素个数,怎么最快地找出其中的一个数

如[2,4,5,6,7,8][1,3,5,6,7]

解:由于不知道前、后两个有序部分中的元素个数,所以先要将其找出来。假设前、后两个部分分别为a[0.m]和a[m+.n-1],先采用顺序方法在a[0…m]中从前向后查找,若a[i]==target(目标元素),找到后算法结束,或者发现元素逆序后退出循环(即找到分界线
元素a[m]),然后在a[m+1.n-1]有序区间中采用二分查找
上述过程对应的时间复杂度为O(n)。对应的算法如下:

int binSearch(int a[],int low,int high,int target){
//二分查找的函数while(low<=high){
int mid=low+(high-low)>>1;//位运算更高效if(a[mid]==target) return mid;else if(a[mid]>target)high=mid-1;elselow=mid+1;}return -1;//没找到}int search(int a[],int n,int target){
int i;for(i = 0;i < n;i++){
if(a[i]==target)return i;if(a[i]>a[i+1])break;}int low=i+1,high=n-1;binSearch(a,low,high,target);//如果a[0,..m]中没有找到目标,则在第二个递增数组中查找

11.一个非降序数组,若target在数组中出现,返回位置,否则返回它将插入的位置

写法1:

int SearchInsert(int a[],int n,int target){
if(a[n-1] < target) return n;//a是一递增数组 int low=0,high = n-1; while(low <= high) {
int mid = (low+high)/2; if(a[mid] > target) high=mid-1; else if(a[mid] < target) low=mid+1; else return mid; } return low;}

写法2:

int SearchInsert(int a[],int n,int target){
if(a[n-1] < target) return n;//a是一递增数组 int low=0,high = n-1; while(low < high) {
int mid = (low+high)/2; if(a[mid] > target) high=mid; else low=mid+1; } return high;}

12.二分插入排序

void BinInsertSort(int a[],int n){
int i,j,low,high,mid; int tmp; for(i = 1;i < n;i++) {
if(a[i] < a[i-1]) {
tmp = a[i]; low=0;high=i-1; while(low<=high) {
mid = (low+high)/2; if(a[mid] > tmp) high=mid-1; else low=mid+1; } for(j=i-1;j>=high+1;j--) a[j+1]=a[j];//集中进行元素后移 a[high+1]=tmp; } }} int main() {
int a[5]={
1,74,3,15,6};BinInsertSort(a,5);for(int i = 0;i < 5;i++)cout<
<<" "; }

13.希尔排序

void shell(int a[],int n){
int i,j,d; int tmp; d = n/2; while(d > 0) {
for(i = d;i < n;i++){
j = i - d;tmp = a[i];while(j>=0 && a[j] > tmp){
a[j+d] = a[j]; j = j-d;}a[j+d] = tmp; } d = d/2; }}

是不稳定的排序算法。

每一趟不一定产生有序区,最后一趟产生全部的有序序列。

直接插入排序:每一趟产生的有序区不一定是全局有序的。是稳定的排序算法。平均时间复杂度为o(n²)

对于直接插入排序,初始数据越接近正序性能越好。

14.桶排序实战:用链表表示桶,从文件中读取数字

15.一个序列用带头结点的单链表存储,采用快速排序求递增排序

先找划分基准,再分成两半递归排序

void swap(int&x,int &y){
int temp = x;x = y;y = temp;}LinkNode* Paration(LinkNode* first,LinkNode*tail){
if(first == tail) return first;//只有一个结点LinkNode* pt = first,* p =pt->next;int temp = first->data;//基准值while(true){
if(p->data < temp){
pt = pt->next;swap(pt->data,p->data);//如果p结点小于temp}if(p == tail) break;p = p->next;}swap(pt->data,first->data);//找到划分位置,即pt指向的结点return pt;}void QuickSort(LinkNode*&first,LinkNode*&tail){
if(first!=NULL && tail != NULL && first != tail){
LinkNode*pt = Paration(first,tail);QuickSort(first,pt);QuickSort(pt->next,tail);}}

通过修改结点的值实现,没有改变结点地址。

16.二叉查找树的操作

遍历操作:

void inSort(btnode*node){
if(node!=NULL){
inSort(node->lchild);//递归遍历左子树std::cout<
data<
rchild);//递归遍历右子树}}

插入结点

步骤:如果二叉查找树为空,则插入结点就为根节点

否则将待插入结点与根节点进行比较,如果小于根节点就插入到左子树,大于根节点就插入到右子树中

void insert(btnode*node){
btnode*temp = NULL,*cnt;//中间指针cnt = root;while(cnt != NULL){
temp=cnt;//用于保存最后一个有效结点if(cnt->data > node->data){
cnt = cnt->lchild;}else{
cnt = cnt->rchild;}}if(temp==NULL)//根节点为空{
root = node;count++;}if(node->data
data){
temp->lchild = node;count++;}else{
temp->rchild = node;count++;}}

二叉查找树的删除操作

考虑几种情况:

如果没有子女结点,则修改父节点的指针,使其指向NULL,删除结点x
如果结点x只有一个孩子结点,修改其父节点和孩子结点的parent指针 建立联系,如果左右孩子都存在 用左子树中的最大值或者右子树的最小值代替x

二叉查找树的查找操作

btnode search(DataType k){
btnode*node = root;while(node!=NULL){
if(node->data>k)node= node->lchild;else if(node->data < k)node = node->rchild;elsebreak;//相等则退出}return node;}

判断一个数组是否是二叉查找树的后序遍历

//判断一个数组是否是一个二叉查找树的后序遍历bool postSequence(int sequence[],int len){
if(sequence == NULL || len <= 0) return false;//先验错int root = sequence[len-1];//找到根节点int i = 0;for(;i
root) break;//左子树均小于根节点 这里i是下标也是长度int j = i;for(;j < len-1;j++)//注意这里j是下标而不是长度if(sequence[j] < root) return false;//如果右子树有小于根节点的 返回falsebool left = true;//判断左子树是否是查找树if(i > 0){
//有可能没有左子树,故加上i>0的判断 left = postSequence(sequence,i);//i是左子树的长度}bool right = true;if(j < len-1)right = postSequence(sequence+i,len-i-1);//注意右子树开始的序列和右子树的长度return (right && left);} int main() {
int s[7]; for(int i = 0;i <7;i++) cin>>s[i]; bool res=postSequence(s,7); if(res) cout<<"是";}

5 7 6 9 11 10 8

在这里插入图片描述

17.线索二叉树

我们利用二叉树链式存储结构的空指针来存储结点的直接前驱指针和直接后继结点指针,这些指针称为线索,利用线索来保留前驱和后继结点信息的二叉树称为线索二叉树。

我们设定结点左指针指向前驱节点,右指针指向后继结点,且每个结点增加两个标志量lFlag和rFlag,当lchild rchild是指向孩子节点的指针时相应的标志量为1,是线索时为0
也就是说rchild lchild可能时指向孩子的指针也可能是线索

数据结构如下:

template
class ThreadBTNode{
public:friend class ThreadBT
;ThreadBTNode(DataType data){
lFlag = rFlag =0;this->data = data;lchild = rchild = NULL;}ThreadBTNode(){
lFlag = rFlag =0;lchild = rchild = NULL;//带数据结点的构造函数和空构造函数}private:ThreadBTNode
*lchild,*rchild;//左右指针域int lFlag,rFlag;//标志量DataType data;//数据域};template
class ThreadBT{
public:ThreadBT(){
root = NULL;}ThreadBT(DataType data){
root = new ThreadBTNode< DataType>(data);}private:}

寻找中序遍历线索二叉树的前驱和后继结点

线索二叉树的前驱和后继结点与使用的遍历方式有关,线索二叉树之间的区别就是根据不同的遍历方式其结点的前驱和后继不同

对二叉树进行一次中序遍历便明白结点node的前驱节点是左子树中最右边的结点,后继结点是右子树最左边的结点

template
ThreadBTNode
*ThreadBT
::prior(ThreadBTNode
*node){
ThreadBTNode
*s = node->lchild;if(node->lFlag==1){
//为1时代表node的左指针不是线索,不指向前驱,如果lFlag为0,可以直接返回swhile(s->Flag==1){
s = s->rchild;}//一直找结点的右子结点,直到找到左子树的最右子节点}return s;//返回前驱}

同理查找后继:

template
ThreadBTNode
*ThreadBT
::succ(ThreadBTNode
*node){
ThreadBTNode
*s;s = node->rchild;if(node->rFlag == 1){
while(s->lFlag == 1)//循环查找结点的右子节点,找到右子树的最左节点s = s->lchild;}return s;}

先序、后序遍历的前驱后继

标志ltag和rtag,并约定:

0 结点中的 lchild字段指向左孩子;
ltag=
1 结点中的 lchild字段指向前驱;
0 结点中的 rchild字段指向右孩子;
rtag=
1 结点中的 rchild字段指向后继;

typedef    struct   tbnode{
elementtype data; tbnode *lchild, *rchild; int ltag, rtag; }tbnode

(1) 先序线索二叉树中先序后继的求解——先序后继

对先序线索二叉树中任意结点P,求其先序后继。
讨论:
(a) 若p有左孩子 ——按照遍历的过程描述(PPLPR)可知,
其后继应为:左子树PL中的第一个结点,
p的左孩子结点,
因此, p->lchild为其后继;
(b) 否则,*p有右孩子 ——类似地,可知
p->rchild为其后继;
© 否则,
p->rchild为其后继线索;
由此得算法如下:

tbnode   *presuc(tbnode *p) {
if ( p -> ltag == 0 ) return ( p -> lchild ); else return ( p -> rchild );}

后序前驱的求解

分析:
(a) 若p有右孩子 —— 右孩子结点是其前驱;
(b) 否则,若
P有左孩子 —— 左孩子结点是其前驱;
© 否则 —— p -> lchild是其前驱线索 。
与先序后继的求解方式对称。
由此得算法如下:

tbnode   *postpre(tbnode *p) {
if ( p -> rtag == 0 ) return ( p -> rchild ); else return ( p -> lchild ); }

(6) 后序后继的求解

分析:
(a) 根 —— 无后继;
(b) 若*p是其父结点的右孩子 —— 父结点是其后继;
© 若是父结点的左孩子 ——
无右兄弟 —— 父结点是其后继;
有右兄弟
—— 右兄弟子树的后序序列的第一个结点是其后继
——右兄弟子树中最左下的叶子结点。

线索二叉树中插入结点

在结构中插入一个元素或结点是常见的运算。

在线索二叉树中插入一个结点时,不仅要实现指定结点的父子关系的运算,还需要在插入结点后,通过修改线索,以维护二叉树的线索关系。例如,在右图线索二叉树中,将某结点插入到作为结点F的左孩子。
如何实现相关的操作?
分析:对这类插入操作,通常从两个方面来考虑其实现:
(1)一个是父子关系的连接实现
按照指定关系连接即可
(2)线索关系的维护
这一关系的实现有一定的难度。

下面分别讨论。

(1)父子关系的连接实现
对前述问题,由问题描述可知,
假设指针P指示到了F结点,
连接操作如图所示,操作如下:
p->lchild=s;
p->ltag=0;

(2)线索关系的维护

这一操作主要由如下组成:
(某些操作可能因具体问题而不同)
设置新插入结点的前驱、后继线索
修改新结点的前驱结点的后继线索
修改新结点的后继结点的前驱线索
例如,在右图线索二叉树中,
将某结点插入到作为结点F的左孩子。
如何实现相关的操作?
在这里插入图片描述

下面先讨论线索操作的一般性方法,

然后给出本题的操作实现。假设当前结点为P,由于线索关系使得结点之间建立了线性关系,因此,插入结点时的线索维护类似于双链表中插入结点。由于新结点是作为P的后继,
因此,可有如下的逻辑图:

在这里插入图片描述

. 二叉树的线索化算法实现

——先序线索化为例

前驱结点的后继线索化;
当前结点的前驱线索化;
判断当前结点是否需要后继线索化

void prethread( bnode * t, bnode * pre){
if ( t != NULL ){
if (( pre != NULL ) && ( pre->rtag==1)) pre->rchild=t; if ( t->lchild == NULL ) {
t->lchild = pre; t->ltag=1;} else t->ltag=0; if ( t->rchild == NULL ) t->rtag=1; else t->rtag=0; pre = t; if ( t->ltag==0) prethread( t->lchild, pre); if ( t->rtag==0) prethread( t->rchild, pre); }}

遍历线索二叉树

//利用前面的寻找前驱和后继节点的算法 中序void inOrder(){
if(root !=NULL){
ThreadBTNode
*s = root;while(prior(s)!=NULL){
s = prior(s);//找到起始结点}while(succ(s)!=NULL){
cout<
data<<" ";s = succ(s);//从起始结点开始遍历节点}}

18.分块查找

基本思想:先插索引表,利用顺序查找或者二分查找确定待查关键字k,属于哪一个块

根据索引表中块首记录的地址找到主表中块的起始地址,利用顺序查找的方法查找k,如果找到则返回地址,否则返回null
在这里插入图片描述
在这里插入图片描述

你可能感兴趣的文章
linux listen
查看>>
linux内核网络监听哈希表介绍
查看>>
linux :内核调试神器SystemTap — 简介与使用(一)
查看>>
linux内核:systemtap内核调试 例子
查看>>
linux:cpu 每-CPU 的变量
查看>>
Linux系统调用之SYSCALL_DEFINE
查看>>
linux:如何指定进程运行的CPU
查看>>
linux内核的数据结构:3 每CPU变量
查看>>
linux内核的数据结构:2 散列表
查看>>
linux:socket 系统调用在linux内核中的实现流程图
查看>>
linux:查看内核锁
查看>>
linux内核:CPU私有变量(per-CPU变量)
查看>>
编程之外:使用Latex/Tex创建自己的简历。
查看>>
Linux内核哈希表分析与应用
查看>>
Linux内核分析 - 网络[十二]:UDP模块 - socket
查看>>
linux kernel中epoll的设计和实现
查看>>
linux kernel中epoll的设计和实现
查看>>
linux:网络栈内存不足引发进程挂起问题
查看>>
linux:用systemtap来修改下linux内核变量的值
查看>>
linux:systemtap观察page_cache的使用情况
查看>>