树的题目基本都是二叉树,但是面试官还没有说是不是二叉树的时候千万不要先把答案说出来,要是面试官说是多叉树,而你做的是二叉树就直接挂了!

  

  一. 树的三种遍历。前序、中序、后序,如果直接考遍历,就肯定是让你写非递归代码的(递归版太弱智了),具体写法,要不你记下来,要不参考“递归”部分的,怎么递归转非递归,另一个就是给个中序+前序(后序),让你还原二叉树,中序必须给,不然还原不了(解不唯一),一般递归解决;

  二.  BST(Binary Search Tree)。这个考法多一点,怎么判断是不是BST(或者平衡树,或者完全树),有序数组(有序链表)转换成BST,在BST中找某个upper_bound的最大值(这个可以给root让你找符合要求的节点,可以给一个等于upper_bound的节点,有parent指针,让你找),然后还        有其他其他

  三. LCA(Least Common Ancestor,最近公共祖先)。超高频题,主要考法是给两个指针和树的root,找LCA,如果节点有parent节点(这时候就不给root了),就相当于链表找第一个交点了,如果没有parent就要麻烦一点;

  四. 序列化与发序列化。这个考法比较简单,就是写一个序列化和发序列化的方法,有思考过的话直接就可以秒了,一样的问题还有字符串数组的序列化。一般思路是加一个记录分段信息的head或者加一个不会出现的字符作为一种分割。有时候会说任何字符都可能出现,这时候可以用转义字符(想想C的字符串怎么记录\的吧)。

  树的遍历:

    遍历分为三种:前序中序后序。在三种遍历中,前序和中序遍历的非递归算法都很容易实现,非递归后序遍历实现起来相对来说要难一点。

  前序遍历:按照“根结点-左孩子-右孩子”的顺序进行访问。

  

 #include<iostream>
using namespace std; void presearch1(BinTree *root){//递归前序
if(root!=NULL){
cout<<root->data<<" "<<endl;
presearch1(root->leftchild);
presearch1(root->rightchild);
}
}
void presearch2(BinTree *root){//非递归前序:相当于直接到最左边最底部的地方,先是遍历根节点,然后最左边然后右边,然后在向上遍历。
/*根据前序遍历访问的顺序,优先访问根结点,然后再分别访问左孩子和右孩子。即对于任一结点,其可看做是根结点,因此可以直接访问,访问完之后,若其左孩子不为空,按相同规则访问它的左子树;当访问其左子树时,再访问它的右子树。因此其处理过程如下:
对于任一结点P:
1)访问结点P,并将结点P入栈;
2)判断结点P的左孩子是否为空,若为空,则取栈顶结点并进行出栈操作,并将栈顶结点的右孩子置为当前的结点P,循环至1);若不为空,则将P的左孩子置为当前的结点P;
3)直到P为NULL并且栈为空,则遍历结束。
*/
stack<BinTree *>s;//创建一个栈用来存储树的节点
BinTree *p=root;
while(p!=NULL || !s.empty()){//树不为空或者栈不为空
while(p!=NULL){//当根节点不为空,将更节点入栈
cout<<p->data<<" ,";
s.push(root);
p=p->leftchild;//一直到最左边的孩子,跳出循环
}
if(!s.empty()){//看栈是否为空,不为空将当前的节点P出栈
p=s.top();//将P置为更节点
s.pop();
p=p->rightchild;//右移
}
}
}

  

  中序遍历:按照“左孩子-根结点-右孩子”的顺序进行访问。

  

 #include<iostream>
using namespace std; void insearch1(BinTree *root){//非递归中序遍历
/*2.非递归实现
根据中序遍历的顺序,对于任一结点,优先访问其左孩子,而左孩子结点又可以看做一根结点,然后继续访问其左孩子结点,直到遇到左孩子结点为空的结点才进行访问,然后按相同的规则访问其右子树。因此其处理过程如下:
对于任一结点P,
1)若其左孩子不为空,则将P入栈并将P的左孩子置为当前的P,然后对当前结点P再进行相同的处理;
2)若其左孩子为空,则取栈顶元素并进行出栈操作,访问该栈顶结点,然后将当前的P置为栈顶结点的右孩子;
3)直到P为NULL并且栈为空则遍历结束
*/
stack <BinTree *>s;
BinTree *p=root;
while(p!=NULL || !s.empty()){
while(p!=NULL){
s.push(p);
p=p->leftchild;
}
if(!s.empty()){
p=s.top();
cout<<p->data<<" ,"<<endl;
s.pop();
p=p->rightchild;
}
}
}

  

  后序遍历:按照“左孩子-右孩子-根结点”的顺序进行访问。

  

 #include<iostream>
using namespace std; void postsearch1(BinTree *root){//非递归后序遍历
/*要保证根结点在左孩子和右孩子访问之后才能访问,因此对于任一结点P,先将其入栈。如果P不存在左孩子和右孩子,则可以直接访问它;
或者P存在左孩子或者右孩子,但是其左孩子和右孩子都已被访问过了,则同样可以直接访问该结点。
若非上述两种情况,则将P的右孩子和左孩子依次入栈,这样就保证了每次取栈顶元素的时候,左孩子在右孩子前面被访问,左孩子和右孩子都在根结点前面被访问。
*/
stack <BinTree *>s;
BinTree *cur;//当前节点
BinTree *pre=NULL;//前一次访问的节点
s.push(root);
while(!s.empty()){
cur=s.top();
if((cur->lchild==NULL &&cur->rchild==NULL)||(pre!=NULL&&
(pre==cur->lchild||pre==cur->rchild))){
//P不存在左孩子右孩子,或者左孩子或者右孩子已经被访问了
cout<<cur->data<<" ,"<<endl;
s.pop();
pre=cur;
}
else{//否则的话就讲右孩子左孩子依次入站,这样就可以先访问左孩子在访问右孩子了
if(cur->rchild!=NULL){
s.push(cur->rchild);
}
if(cur->lchild!=NULL){
s.push(cur->lchild);
}
}
}
}

  

  二. BST(Binary Search Tree)。

      前面提到的BST题目感觉写起来都不算特别麻烦,大概说说其中一个高频题:有序链表转BST。一种做法是,遍历链表,找到中点,中点作为root,再递归处理左右两边,这样的话时空复杂度是O(nlogn)+O(1),另一种方法是把链表所有指针存到vector中,这就转化成了有序数组转BST的问题,有了随机下标访问,就可以O(1)时间找到中点了,然后还是递归处理左右部分,时空复杂度O(n)+O(n)。

    题目一:二叉搜索树转化为有序双链表

      实际上就是对二叉查找树进行中序遍历。可以用两个变量分别保存刚访问的结点、新链表的头结点,访问某一个结点A时,设置该节点时left成员指向刚访问过的结点B,再设置结点B的right成员指向结点A。经过这样处理,得到的新双链表,除了头结点的left成员、尾结点的right成员没有设置外,其它的结点成员都被正确设置。而中序遍历的特点决定了第一个访问的数据节点的left成员一定为空指针,最后一个访问的数据节点的right成员也一定为空指针。因而不需要再对这两个成员进行额外的设置操作。

      时间和空间复杂度都是:O(N)

 static void tree2list_inorder(Node* root, Node*& prev, Node*& list_head)
{
/*①:当找到最左节点时,root设为mostleft,prev为NULL,所以当前节点的left指针不用设置,prev的右指针指向当前节点,表示下一个访问的节点是当前节点
②:依次递归,访问prev右指针指向的那个节点,和上述步骤一样:当前节点的left指向上一个访问的节点,上一个访问的节点的右指针指向当前节点,
当前节点被访问,如果当前节点存在右子树,再依次进行右子树的递归操作。
③:这里面已经将树转换为链表,链表的头结点就是树的leftmost节点,指针也是全部转换完成,left和right指针都全部指向正确的值。
④:这个算法其实就是把头结点设置好,然后修改leftright指针的问题 */
if (root->left) //当左子树不为空,一直递归,直到找到最左节点
tree2list_inorder(root->left, prev, list_head);
root->left = prev;//当前节点的左指针指向上一个节点
if (prev)//当上一个访问节点不为空,上一个节点的右指针指向当前节点,这里就形成了一个链表结构
prev->right = root;
prev = root;//访问完当前节点,递归下一个节点
if (list_head == NULL)
list_head = root;
if (root->right)
tree2list_inorder(root->right, prev, list_head);
}
Node* tree2list(Node* root)
{
Node* list_head = NULL;
Node* prev = NULL;
if (root)
tree2list_inorder(root, prev, list_head);
return list_head;
}

      

    题目二:判断二叉树是否平衡二叉树

      根据平衡二叉树的定义:每个结点的左右子树的高度差小等于1,只须在计算二叉树高度时,同时判断左右子树的高度差即可。

      先定义函数计算每个节点的深度,再调用函数去判断该树的每个左右节点的差去判断该树是否是平衡二叉树。

  递归遍历一个节点多次的算法

 #include<iostream>
using namespace std; template
static int Depth(BSTreeNode* root){//这个函数是计算树的深度
//递归的计算每个节点的深度,方便判断
if(root==NULL)
return ;
else{
int leftdepth=Depth(root->left);
int rightdepth=Depth(root->right);
return +(leftdepth>rightdepth?leftdepth:rightdepth);
}
}
//下面是利用递归判断左右子树的深度是否相差1来判断是否是平衡二叉树的函数:
template
static bool isBalance(BSTreeNode* root){
if(root==NULL)
return true;
int dis=Depth(root->left)-Depth(root->right);
//当该节点的左右子树深度等于1或者0时继续递归的计算该节点的左右子树的平衡性,知道遍历完该树的每个节点为止 if(dis> ||<-)
return false;
else
//同时递归的遍历左右子树
return isBalance(root->left)&&isBalance(root->right);
}

 遍历一个节点一次的算法

    

 #include<iostream>
using namespace std;
/* 下面是每个节点只遍历一次的解法:
如果我们使用后序遍历的方法,这样每当遍历一个节点的时候,我们就已经
遍历了它的左右子树,只要在遍历每个节点的时候记录它的深度,我们就可以一边
遍历一边判断这个几点是不是平衡的了。
*/
bool isBalance(BinaryTreeNode *root,int *depeth){
if(root==NULL){
*depeth=;
return true;
}
int left,right;//左右深度
if(isBalance(root->left,&left) &&isBalance(root->right,&right)){
int diff=left-right;
if(diff<=||diff>=-){
*depeth=+(left>right?left:right);
return true;
}
}
return false;
}

    题目三:二叉树的一系列操作(创建,删除,插入,排序等)

      

 #include<iostream>
#include<stack>
#include<stdlib.h> using namespace std; template <class T>
class BinarySearchTreeNode{
public :
T element;
struct BinarySearchTreeNode<T>* left;
struct BinarySearchTreeNode<T>* right;
struct BinarySearchTreeNode<T>* parent;
};
template <class T>
class BinarySearchTree{
private:
BinarySearchTreeNode<T> * root;
public:
BinarySearchTree();
// ~BinarySearchTree();
void insert(const T &ele);
int remove(const T & ele);//返回移除的节点的值
BinarySearchTreeNode<T>* search(const T &ele)const;//查找节点,返回的是一个节点指针
T tree_minmum(BinarySearchTreeNode<T>* root)const;//找出最小的节点
T tree_maxmum(BinarySearchTreeNode<T>* root)const;//找出最大的节点
T tree_successor(const T& elem) const;//前驱
T tree_predecessor(const T& elem)const;//后继
int empty()const;
void inorder_tree_walk()const;//二叉树排序
BinarySearchTreeNode<T>* get_root()const {return root;}//得到根节点
};
template<class T>
BinarySearchTree<T> ::BinarySearchTree(){//构造函数
root=NULL;
}
template<class T>
void BinarySearchTree<T>::insert(const T&ele){//插入节点
/**1. 建立两个临时的节点指针,还有一个新节点的指针
2. 首先判断是不是空树,空树就先创建一个根节点,然后赋值就行了
3. 若果不是空树,一个指针先指向根节点,插入值小于节点值就接着进入左子树,大于就进入右子树,循环
直到指针下一个左右子树为空,此时用另一个指针Y指向当前的这个指针X
4. 从Y指针开始,比较当前节点值与插入值大小,如果大于就插入当前节点的左子树,小于就插入右子树,
Y为插入节点的双亲节点
*/
if(!empty()){
BinarySearchTreeNode <T>* x;
BinarySearchTreeNode <T>* y;
BinarySearchTreeNode <T>* newnode=new BinarySearchTreeNode<T>;//创建一个新插入的空的节点
x=root;
y=NULL;
newnode->element=ele;//初始化新插入的节点
newnode->left=NULL;
newnode->right=NULL;
newnode->parent=NULL;
while(x){//开始查找遍历
y=x;//用y记录x指针的位置,找到合适的位置就已经记录下来
if(ele<x->element)
x=x->left;
else
x=x->right;
} if(y->element > ele)//开始进行插入,最后一步的比较,确定是在当前节点的左子树还是右子树
y->left = newnode;
else
y->right = newnode;
newnode->parent = y;//设置新节点的双亲为Y
}
else{
root = new BinarySearchTreeNode<T>;
root->element = ele;
root->parent =NULL;
root->left = NULL;
root->right = NULL;
}
} template <class T>
int BinarySearchTree<T>::remove(const T& ele){
/** 1. 删除节点有三种情况
1)删除的节点没有左右子树:直接把当前节点的父节点的一个左或者右指针置为NULL
2)删除的节点只有一个子树(左或者右),则通过让其父节点与其子树建立一条链进行连接
3)删除的节点既有左又有右,删除当前节点的后继,再用后继的值来代替当前节点的值
2. 遍历查找要删除的节点
*/
BinarySearchTreeNode<T>* node=search(ele);//找到我们要查找的元素的节点
BinarySearchTreeNode<T>* parent_1;
if(node!=NULL){
parent_1=node->parent;
if(node->left==NULL ||node->right==NULL){//第一、二种情况
if(node->left!=NULL){//当前节点的左子树不为空
if(parent_1->left==node)
parent_1->left=node->left;
if(parent_1->right==node)
parent_1->right=node->left;
}
else if(node->right!=NULL){//当前节点的额右子树不为空
if(parent_1->left==node)
parent_1->left=node->right;
if(parent_1->right==node)
parent_1->right=node->right;
}
else{//当前节点左右子树都为空
if(parent_1->left==node)
parent_1->left=NULL;
if(parent_1->right==node)
parent_1->right=NULL;
}
delete node;
}
else {
BinarySearchTreeNode<T>*temp;//临时节点
temp=search(tree_successor(node->element));//找到当前节点的后继,temp
node->element=temp->element;
//根据后继分情况判断
if(temp->parent->left == temp)//一种情况
{
temp->parent->left = temp->right;
temp->right->parent = temp->parent->left;
}
if(temp->parent->right == temp)//另一种情况
{
temp->parent->right = temp->right;
temp->right->parent = temp->parent->right;
}
delete temp;
} return ;
}
return -;
}
template <class T >
BinarySearchTreeNode<T>* BinarySearchTree<T>::search(const T &ele)const{
//递归实现查找的策略
BinarySearchTreeNode <T>* node=root;
while(node){
if(node->element==ele)
break;
else if(node->element>ele)
node=node->left;
else
node=node->right;
}
return node;
}
template <class T>
T BinarySearchTree<T>:: tree_minmum(BinarySearchTreeNode<T>* root)const{
BinarySearchTreeNode <T>*node=root;
if(node->left){
while(node->left)
node=node->left;
}
return node->element;
}
template <class T>
T BinarySearchTree<T>:: tree_maxmum(BinarySearchTreeNode<T>* root)const{
BinarySearchTreeNode <T>*node=root;
if(node->right){
while(node->right)
node=node->right;
}
return node->element;
}
template <class T>
int BinarySearchTree<T>::empty()const{
return {NULL==root};
}
template <class T>
T BinarySearchTree<T>::tree_successor(const T& elem) const{//求前驱的
/** 1. 前驱即是当前节点的左子树中最大的那个关键字
2. 没有左子树:则返回上一层,查找
*/
BinarySearchTreeNode<T>* pnode = search(elem);
BinarySearchTreeNotemplate <class T>
int RedBlackTree<T>:: insert_key(const T& k){//插入函数 }de<T>* parentnode;
if(pnode != NULL)
{
if(pnode->right)
return tree_minmum(pnode->right);
parentnode = pnode->parent;
while(parentnode && pnode == parentnode->right)
{
pnode = parentnode;
parentnode = parentnode->parent;
}
if(parentnode)
return parentnode->element;
else
return T();
}
return T(); }
template <class T>
T BinarySearchTree<T>::tree_predecessor(const T& elem) const{//求后继的
BinarySearchTreeNode<T>* pnode = search(elem);
BinarySearchTreeNode<T>* parentnode;
if(pnode != NULL)
{
if(pnode->right)
return tree_maxmum(pnode->right);
parentnode = pnode->parent;
while(parentnode && pnode == parentnode->left)
{
pnode = parentnode;
parentnode = pnode->parent;
}
if(parentnode)
return parentnode->element;
else
return T();
}
return T(); }
template <class T>
void BinarySearchTree<T>::inorder_tree_walk()const{
if(NULL !=root){
stack <BinarySearchTreeNode<T>*>s;//构建一个存储节点的栈
BinarySearchTreeNode<T>* temp;//创建一个临时节点
temp=root;
while(temp!=NULL || !s.empty()){
if(temp!=NULL){
s.push(temp);
temp=temp->left;
}
else{
temp=s.top();
s.pop();
cout<<temp->element<<" ";
temp=temp->right;
}
} }
}

   

    题目四:求一个二叉树的深度(从根节点到叶节点依次经过的节点形成的一条路径,最长路径的长度为树的深度)

    

 #include<iostream>
using namespace std;
int treeDepth(BSTree *root){
if(root ==NULL)
return ;
int leftdepth=treeDepth(root->left);
int rightdepth=treeDepth(root->right); return (leftdepth>rightdepth)?(leftdepth+):(rightdepth+);
}

       

    题目五:二叉树中两个节点的最近公共祖先节点

      这个问题可以分为三种情况来考虑:
         情况一:root未知,但是每个节点都有parent指针
         此时可以分别从两个节点开始,沿着parent指针走向根节点,得到两个链表,然后求两个链表的第一个公共节点,这个方法很简单,不需要详细解释的。

         情况二:节点只有左、右指针,没有parent指针,root已知
         思路:有两种情况,一是要找的这两个节点(a, b),在要遍历的节点(root)的两侧,那么这个节点就是这两个节点的最近公共父节点;
         二是两个节点在同一侧,则 root->left 或者 root->right 为 NULL,另一边返回a或者b。那么另一边返回的就是他们的最小公共父节点。
         递归有两个出口,一是没有找到a或者b,则返回NULL;二是只要碰到a或者b,就立刻返回。

          

 #include<iostream>
using namespace std;
// 二叉树结点的描述
typedef struct BiTNode
{
char data;
struct BiTNode *lchild, *rchild; // 左右孩子
}BinaryTreeNode; // 节点只有左指针、右指针,没有parent指针,root已知
BinaryTreeNode* findLowestCommonAncestor(BinaryTreeNode* root , BinaryTreeNode* a , BinaryTreeNode* b)
{
if(root == NULL)
return NULL;
if(root == a || root == b)
return root;
//这里往左边和右边找a节点,直到找到a节点或者b节点
BinaryTreeNode* left = findLowestCommonAncestor(root->lchild , a , b);
BinaryTreeNode* right = findLowestCommonAncestor(root->rchild , a , b);
if(left && right)//如果a在当前根节点左边,b在当前根节点右边,那么返回当前根节点
return root;
//否则就是a,b在最近公共祖先的同一侧,返回其中的一个就行了。
return left ? left : right;
}

         情况三:二叉树是个二叉查找树,且root和两个节点的值(a, b)已知

 #include<iostream>
using namespace std;
// 二叉树是个二叉查找树,且root和两个节点的值(a, b)已知
BinaryTreeNode* findLowestCommonAncestor(BinaryTreeNode* root , BinaryTreeNode* a , BinaryTreeNode* b)
{
char min , max;
if(a->data < b->data)
min = a->data , max = b->data;
else
min = b->data , max = a->data;
while(root)
{
if(root->data >= min && root->data <= max)
return root;
else if(root->data < min && root->data < max)
root = root->rchild;
else
root = root->lchild;
}
return NULL;
}

C++面试笔记--树的更多相关文章

  1. Java高级开发工程师面试笔记

    最近在复习面试相关的知识点,然后做笔记,后期(大概在2018.02.01)会分享给大家,尽自己最大的努力做到最好,还希望到时候大家能给予建议和补充 ----------------2018.03.05 ...

  2. php面试笔记(5)-php基础知识-自定义函数及内部函数考点

    本文是根据慕课网Jason老师的课程进行的PHP面试知识点总结和升华,如有侵权请联系我进行删除,email:guoyugygy@163.com 在面试中,考官往往喜欢基础扎实的面试者,而函数相关的考点 ...

  3. php面试笔记(3)-php基础知识-运算符

    本文是根据慕课网Jason老师的课程进行的PHP面试知识点总结和升华,如有侵权请联系我进行删除,email:guoyugygy@163.com 在面试中,考官往往喜欢基础扎实的面试者,而运算符相关的考 ...

  4. php面试笔记(2)-php基础知识-常量和数据类型

    本文是根据慕课网Jason老师的课程进行的PHP面试知识点总结和升华,如有侵权请联系我进行删除,email:guoyugygy@163.com 面试是每一个PHP初学者到PHP程序员必不可少的一步,冷 ...

  5. GitHub标星125k!阿里技术官用3个月总结出的24万字Java面试笔记

    最近收到一位粉丝的回馈! 这位粉丝已经成功入职阿里了小编很是羡慕啊! 今天就把这份30w字Java面试笔记给大家分享出来,说来也巧这份资料也是由一位阿里技术官整理出来的这算不算是"搬起石头砸 ...

  6. Java-J2SE学习笔记-树状展现文件结构

    1.利用java.io相关类树状展现文件结构 2.判定给定路径是否为dir,是则递归,每一递归一层缩进一次 3.代码 package Test; import java.io.File; public ...

  7. hibernate面试笔记

    Hibernate使用Java 反射机制 而不是字节码增强程序来实现透明性 如果JDBC代码写的完美,优化做好,那么JDBC效率是最高的.但是,实际开发中非常不现实,对程序员要求太高.一般情况下,hi ...

  8. 手游2dx面试笔记一

    第一轮IQ测试:都来面试程序了,相信IQ再怎么也坑不到哪里去吧.要问什么样的题,几页纸呐, 如:1.找出不同类:羚羊.斑马.鲨鱼 2.在()里添一字使2边都能组词:木()料 3.中间值?:1,2,4, ...

  9. C语言数据结构基础学习笔记——树

    树是一种一对多的逻辑结构,树的子树之间没有关系. 度:结点拥有的子树数量. 树的度:树中所有结点的度的最大值. 结点的深度:从根开始,自顶向下计数. 结点的高度:从叶结点开始,自底向上计数. 树的性质 ...

随机推荐

  1. HDU - 5306: Gorgeous Sequence (势能线段树)

    There is a sequence aa of length nn. We use aiai to denote the ii-th element in this sequence. You s ...

  2. redis实战之事务与持久化

    1. 事务描述 (1)什么是事务 事务,就是把一堆事情绑在一起,按顺序的执行,都成功了才算完成,否则恢复之前的样子 事务必须服从ACID原则,ACID原则分别是原子性(atomicity).一致性(c ...

  3. LeetCode LFU Cache

    原题链接在这里:https://leetcode.com/problems/lfu-cache/?tab=Description 题目: Design and implement a data str ...

  4. [Unity3D]关于U3D贴图格式压缩

    http://blog.sina.com.cn/s/blog_5b6cb9500102vi6i.html 因为有不少人都问过我压缩格式的问题,今天飞哥又重新提醒了一次.整理一下发个贴,以供大家查阅和讨 ...

  5. python 集合和深浅copy

    #1数据类型的补充#2.集合set#3.深浅copy 补充:str --> bytes s.encode('gbk')bytes --> str s.decode('gbk') 1.数据类 ...

  6. BZOJ2342:[SHOI2011]双倍回文

    浅谈\(Manacher\):https://www.cnblogs.com/AKMer/p/10431603.html 题目传送门:https://www.lydsy.com/JudgeOnline ...

  7. 操作Oracle 一条龙

    1 引用Oracle.DataAccess.dll 2 App.Config中配置连接字符串: Data Source=(DESCRIPTION = (ADDRESS = (PROTOCOL = TC ...

  8. 2018年长沙理工大学第十三届程序设计竞赛 E小木乃伊到我家(spfa模版)

    链接:https://www.nowcoder.com/acm/contest/96/E来源:牛客网 小木乃伊到我家 时间限制:C/C++ 1秒,其他语言2秒 空间限制:C/C++ 32768K,其他 ...

  9. Canvas 与 SVG 的比较

    Canvas:<canvas> 标签定义图形(只是图形容器),比如图表和其他图像,您必须使用脚本 (通常是JavaScript)来绘制图形.默认情况下 <canvas> 元素没 ...

  10. springmvc 注解式开发 处理器方法的返回值

    1.返回void -Ajax请求 后台: 前台: 返回object中的数值型: 返回object中的字符串型: 返回object中的自定义类型对象: 返回object中的list: 返回object中 ...