今天去参加360的面试,怎么说呢,可能没有太过重视他了。之前被各种虐,觉得虐的不够安心,不心服,今天去360 ,一面就挂了,想想,后来和同学交流,发现自己学的和思维就像一坨屎。总结下来,就是自己在碰到一个之前没有遇见的问题的时候,就容易慌张,加上的是 基础知识不够 深刻吧。失败了,男儿何曾需要泪水,谁要不是从不会学的呢。慢慢总结一下吧。
面试官第一个问题,就是在纸上写了一段代码。
#include
#include
using namespace std;
class A{
public :
A():data(0){
cout << "constructor"< av;
A a; //1
av.push_back(a); // 2
vector av2;
av2 = av; // 3
vector av3;
av3.assign(av.begin(),av.end()); //4
}
他问的是 每句话都 调用什么构造函数。当时因为没有考究过这些东西,有点慌了,还是基础不牢吧。
第一句话,肯定是构造函数了,没有什么好说的, 第二句话,假如push_back是指针的话,不会调用构造函数,但是假如是对象的话,调用的拷贝构造函数。我也算是答对了。不过这里有一点要注意的是,vector的内存扩展原理,假如在push一下的话,会拷贝两次,然后析构一次。还有一点需要注意的是vector有一个resize函数,这个函数是用来调整vector的大小,可以用来删除或者增加vector的元素。 增加的时候,有个特点,就是他会根据要增加的大小,一次性的扩展内存,而不是二倍二倍的扩展。
第三句,虽然是赋值语句,但是其实还是调用每个元素的拷贝构造函数。 第四句, 虽然是assign 但是调用的还是拷贝构造函数。这一个我回答错了,当时看primer也是不用心,因为c++11有一个emplace,讲的是调用每个元素的构造函数,其实应该讲的是拷贝构造函数把,我的理解有错的话,希望大家指正,而我记成了构造函数,所以说assign是构造函数。可恶的面试官竟然点头嗯嗯。哎。。。。
第二个题,由第一个题引出,讲的是 vector的内存增长方式,我讲了二倍增长,然后他问了一个问题,真的是蒙了,不会思考了,问我push_back的时间复杂度,我第一反应是o(1)但是,想到内存的扩展,会不会不是o(1)了。后来想想,按着等比公式算一下还是o(1),哎,我只能说这个面试官太聪明了,把我虎住了,自己太渣了。
第三个问题,估计看我对复杂度薄弱,就开始死问复杂度的问题,问我知不知道map是用什么实现的,我说红黑树,问我查找插入的复杂度是多少,我说是log(n),然后机智的面试官又问,删除呢,当时我犯嘀咕了,不一样么,难道不一样?额。。。好吧,又蒙了。。。我这性格啊,我这个基本功,感觉被面试官刷了一个。
至此 面试失败,在这一路,面试官一直在嗯,默许的点头,我不知道为什么会是这样,360的面试官还奇怪,别人即使在渣,大老远跑来,你就算不要,为啥当时就说不对呢,感觉自己被戏耍了一样啊。哎,,,也怪自己,这些基础都没掌握就敢说熟悉c++
同时面试的还有我们班的一个女同学,她问了一个指针和引用的区别。
我回忆了一下。
语法上讲: 指针不用必须初始化,而引用必须初始化。
指针可以指向空,而引用不可以指向空。
一旦申明,指针不可以指向其他的地址,也就是说指针是变量,而引用不可以,引用从一而终,是别名
实质上 : 引用在内部也是用指针实现的。编译器做的。
然后写了一个笔试题,就是 max subarray的,就最大子数组的和最大。这里我手写出程序,回忆一下吧,虐成狗自己被,假如我碰到的是这个面试官,会怎么样。
int maxSubArray(vector nums){
int maxv = INT_MIN;
int cur = 0;
for(int i = 0; i < nums.size(); i++){
cur += nums[i];
if(cur > maxv){
maxv = cur;
}
if(cur <= 0){
cur = 0;
}
}
return maxv;
}
然后ac了。这一题算是完了。
然后 又出了一道stirng 类的 拷贝构造函数和赋值构造函数。
class string {
private:
char *str;
int len;
public :
string(char * data):str(data){
if(data == NULL){
str = new char[1];
str[0] = '\0';
len = 0;
}
len = strlen(data);
str = new char[len+1];
strcpy(str,data);
}
string(const string& data){
len = data.len;
str = new char[len+1];
strcpy(str,data.str);
}
string& operator = (const string& data){
if(&data == this){
return *this;
}
delete[] this;
len = data.len;
str = new char[len+1];
strcpy(str,data.str);
return *this;
}
};
int partition(int *nums, int p, int r){
int i = p-1;
int val = nums[r];
for(int j = p; j < r; j++){
if(nums[j] < val){
swap(nums[++i],nums[j]);
}
}
std::swap(num[r],nums[++i]);
return i;
}
void quickSort(int *nums, int p, int r){
if(p < r){
int q = partition(nums,p,r);
quickSort(num,p,q-1);
quickSort(num,q+1,r);
}
}
第二题是 最小堆调整的,就是往最小堆里插入一个数字,然后调整一下。算法导论上有例子,我试着回忆了一下,手写一下在:
int insert(int *num, int val, int &n){
n++;
num[n] = val;
int i = n;
while(i > 1 && num[i] < num[i/2]){
swap(num[i] < num[i/2]);
i /= 2;
}
}
总体来说,这两面质量还是挺高的, 虽然自己对这些也比较熟,但是运气使然,没有赶上这样的面试官 也就无缘360了。
写到这里,360面试 就算完了,欠缺呢? 对数据结构不熟吧。 下面是一些资料。
AVL树是最先发明的自平衡二叉查找树。在AVL树中任何节点的两个子树的高度最大差别为一,所以它也被称为高度平衡树。查找、插入和删除在平均和最坏情况下都是O(log n)。增加和删除可能需要通过一次或多次树旋转来重新平衡这个树。
节点的平衡因子是它的左子树的高度减去它的右子树的高度(有时相反)。带有平衡因子1、0或 -1的节点被认为是平衡的。带有平衡因子 -2或2的节点被认为是不平衡的,并需要重新平衡这个树。平衡因子可以直接存储在每个节点中,或从可能存储在节点中的子树高度计算出来。
一般我们所看见的都是排序平衡二叉树
二、一般性质
AVL树具有以下性质:它是一棵空树或它的左右两个子树的高度差的绝对值不超过1,并且左右两个子树都是一棵平衡二叉树。在平衡二叉搜索树中,我们可以看到,其高度一般都良好地维持在O(log2n),其各操作的时间复杂度(O(log2n))同时也由此而决定,大大降低了操作的时间复杂度。另外,最小二叉平衡树的节点的公式如下 F(n)=F(n-1)+F(n-2)+1 这个类似于一个递归的数列。
三、一般操作
AVL树的基本操作一般涉及运作同在不平衡的二叉查找树所运作的同样的算法。但是要进行预先或随后做一次或多次所谓的"AVL旋转"。
1.插入
向AVL树插入可以通过如同它是未平衡的二叉查找树一样把给定的值插入树中,接着自底向上向根节点折回,于在插入期间成为不平衡的所有节点上进行旋转来完成。因为折回到根节点的路途上最多有1.5乘log n个节点,而每次AVL旋转都耗费恒定的时间,插入处理在整体上耗费O(log n) 时间
2.删除
从AVL树中删除可以通过把要删除的节点向下旋转成一个叶子节点,接着直接剪除这个叶子节点来完成。因为在旋转成叶子节点期间最多有log n个节点被旋转,而每次AVL旋转耗费恒定的时间,删除处理在整体上耗费O(log n) 时间。
3.查找
可以像普通二叉查找树一样的进行,所以耗费O(log n)时间,因为AVL树总是保持平衡的。不需要特殊的准备,树的结构不会由于查找而改变。(这是与伸展树查找相对立的,它会因为查找而变更树结构。)
http://www.cnblogs.com/biyeymyhjob/archive/2012/07/24/2606718.html
上面的性质可以看出 平衡二叉树的所有操作都是log(n)而
AVL是严格平衡树,因此在增加或者删除节点的时候,根据不同情况,旋转的次数比红黑树要多;
红黑是弱平衡的,用非严格的平衡来换取增删节点时候旋转次数的降低;
所以简单说,搜索的次数远远大于插入和删除,那么选择AVL树,如果搜索,插入删除次数几乎差不多,应该选择RB树。
红黑树上每个结点内含五个域,color,key,left,right,p。如果相应的指针域没有,则设为NIL。
一般的,红黑树,满足以下性质,即只有满足以下全部性质的树,我们才称之为红黑树:
1)每个结点要么是红的,要么是黑的。
2)根结点是黑的。
3)每个叶结点,即空结点(NIL)是黑的。
4)如果一个结点是红的,那么它的俩个儿子都是黑的。
5)对每个结点,从该结点到其子孙结点的所有路径上包含相同数目的黑结点。
http://blog.csdn.net/sup_heaven/article/details/39313731
所以c++中 map set搜索插入删除 的复杂度为log(n);
我应该会在后续的文章中总结 红黑数和 B+数的知识点,毕竟还是会被问的。
然后关于构造函数那一块也有必要在写个文章总结下