360面试

面试准备:

1 常用排序算法及复杂度

360面试_第1张图片

2 快速排序的优点

时间复杂度为o(nlogn),空间复杂度反映在递归造成的栈空间的使用,空间复杂度为o(logn) - o(n)
优点: 时间复杂度最优(冒泡排序相比);时间复杂度相同情况下,空间复杂度最优(与归并排序相比)
缺点: 算法不稳定,其不稳定性发生在对应元素与基准元素发生交换时

3 STL中vector和list的底层实现

vector是动态数组,list是双向链表,deque是双端队列
vector分配连续内存,在随机访问时最方便,但是在中间插入或删除时麻烦
list内存空间不连续,在插入删除时最方便,但是随机访问很麻烦
deque内存空间不连续,更像是vector和list的结合,在随机访问和插入删除都比较方便

map是有序无重复的关联容器,set是集合
map在底层通常用二叉搜索树来实现

4 栈和队列的区别

  • 栈的插入和删除操作都是在一端进行的,而队列的操作却是在两端进行的。
  • 栈是先进后出,队列是先进先出。
  • 栈只允许在表尾一端进行插入和删除,队列只允许在表尾一端进行插入,在表头一端进行删除。

5 c++中的多态

借用两篇博客中的解释:
解释1:
 c++的多态性就是通过晚绑定技术来实现的。

 c++的多态性用一句话概括就是:在基类的函数前加上virtual关键字,在派生类中重写该函数,运行时将会根据对象的实际类型来调用相应的函数,如果对象类型是派生类,就调用派生类的函数,如果对象类型是基类,就调用基类的函数。

 虚函数是在基类中定义的,目的是不确定它的派生类的具体行为,例如:
  定义一个基类:class Animal //动物,它的函数为breathe()
  再定义一个类class Fish //鱼。它的函数也为breathe()
  再定义一个类class Sheep //羊,它的函数也为breathe()

将Fish,Sheep定义成Animal的派生类,然而Fish与Sheep的breathe不一样,一个是在水中通过水来呼吸,一个是直接呼吸,所以基类不能确定该如何定义breathe,所以在基类中只定义了一个virtual breathe,它是一个空的虚函数,具体的函数在子类中分别定义,程序一般运行时,找到类,如果它有基类,再找到它的基类,最后运行的是基类中的函数,这时,它在基类中找到的是virtual标识的函数,它就会再回到子类中找同名函数,派生类也叫子类,基类也叫父类,这就是虚函数的产生,和类的多态性的体现。
这里的多态性是指类的多态性。

  • 函数的多态性是指一个函数被定义成多个不同参数的函数。当你调用这个函数时,就会调用不同的同名函数。
  • 一般情况下(不涉及虚函数),当我们用一个指针/引用调用一个函数的时候,被调用的函数是取决于这个指针/引用的类型。
  • 当设计到多态性的时候,采用了虚函数和动态绑定,此时的调用就不会在编译时候确定而是在运行时确定。不在单独考虑指针/引用的类型而是看指针/引用的对象的类型来判断函数的调用,根据对象中虚指针指向的虚表中的函数的地址来确定调用哪个函数

解释2:
C++多态方式:

(1)静态多态(重载,模板)
是在编译的时候,就确定调用函数的类型。

(2)动态多态(覆盖,虚函数实现)
在运行的时候,才确定调用的是哪个函数,动态绑定。运行基类指针指向派生类的对象,并调用派生类的函数。

  • 虚函数实现原理:虚函数表和虚函数指针。
  • 纯虚函数: virtual int fun() = 0;

多态基础介绍:

首先,什么是多态(Polymorphisn)?按字面的意思就是”多种形状”。我手头的书上没有找到一个多态的理论性的概念的描述。

暂且引用一下 Charlie Calverts的对多态的描述吧——多态性是允许你将父对象设置成为和一个或更多的他的子对象相等的技术,赋值之后,父对象就可以根据当前赋值给它的子对象的特性以不同的方式运作(摘自”Delphi4 编程技术内幕”)。

简单的说,就是一句话:允许将子类类型的指针赋值给父类类型的指针。多态性在Object Pascal和C++中都是通过虚函数(Virtual Function) 实现的。

好,接着是”虚函数”(或者是”虚方法”)。虚函数就是允许被其子类重新定义的成员函数。而子类重新定义父类虚函数的做法,称为”覆盖”(override),或者称为”重写”。


这里有一个初学者经常混淆的概念。覆盖(override)和重载(overload)。

上面说了,覆盖是指子类重新定义父类的虚函数的做法。而重载,是指允许存在多个同名函数,而这些函数的参数表不同(或许参数个数不同,或许参数类型不同,或许两者都不同)。其实,重载的概念并不属于”面向对象编程”,重载的实现是:编译器根据函数不同的参数表,对同名函数的名称做修饰,然后这些同名函数就成了不同的函数(至少对于编译器来说是这样的)。如,有两个同名函数:function func(p:integer):integer;和function func(p:string):integer;。那么编译器做过修饰后的函数名称可能是这样的:int_func、str_func。对于这两个函数的调用,在编译器间就已经确定了,是静态的(记住:是静态)。也就是说,它们的地址在编译期就绑定了(早绑定),因此,重载和多态无关!

真正和多态相关的是 “覆盖”。当子类重新定义了父类的虚函数后,父类指针根据赋给它的不同的子类指针,动态(记住:是动态!)的调用属于子类的该函数,这样的函数调用在编译期间是无法确定的(调用的子类的虚函数的地址无法给出)。
因此,这样的函数地址是在运行期绑定的(晚邦定)。结论就是:重载只是一种语言特性,与多态无关,与面向对象也无关!

那么,多态的作用是什么呢?我们知道,封装可以隐藏实现细节,使得代码模块化;继承可以扩展已存在的代码模块(类);它们的目的都是为了——代码重用。

而多态则是为了实现另一个目的——接口重用!而且现实往往是,要有效重用代码很难,而真正最具有价值的重用是接口重用,因为”接口是公司最有价值的资源。设计接口比用一堆类来实现这个接口更费时间。而且接口需要耗费更昂贵的人力的时间。”


参考:
https://www.cnblogs.com/cxq0017/p/6074247.html
http://www.runoob.com/cplusplus/cpp-polymorphism.html
https://www.cnblogs.com/Allen-rg/p/6927129.html

6 构造和析构

  • 构造函数的作用:只要创建类类型的新对象,都会自动执行构造函数,构造函数保证每个对象的数据成员具有合适的初始值。
  • 构造函数的名字与类的名字相同,并且不能指定返回类型
  • 构造函数可以没有形参,也可以有多个形参
  • 构造函数可以被重载
  • 类的析构函数是类的一种特殊的成员函数,它会在每次删除所创建的对象时执行。
    析构函数的名称与类的名称是完全相同的,只是在前面加了个波浪号(~)作为前缀,它不会返回任何值,也不能带有任何参数。析构函数有助于在跳出程序(比如关闭文件、释放内存等)前释放资源。

参考:
http://www.runoob.com/cplusplus/cpp-constructor-destructor.html

面试题

1 求二值图的最大连通域

面试官要求我输出最大连通域中联通像素的个数,其实还是需要把所有连通域求出来,找了几篇博客,已经讲的很清楚了:
https://blog.csdn.net/icvpr/article/details/10259577
https://blog.csdn.net/xjt2015/article/details/51283387
https://www.cnblogs.com/ryuasuka/p/4932239.html
https://www.cnblogs.com/ronny/p/img_aly_01.html
总的来说,就是两种思路,一种是两遍扫描:第一遍扫描记录连通的lable对,第二次扫描把lableImage中的大lable全部改成lable对中最小的lable,在两次扫描中间,需要计算连通的lable,这里可以使用图的深度优先搜索方法;第二种思路就是深度优先搜索的方法,一遍扫描,利用栈,把连通的像素全部找出来,第二种方法更加简单粗暴一些。
我用python把第二种方法简单实现了一下:

'''求二值图连通域'''
def calcConnectedBySeedFill(originImage):
    rows, cols = len(originImage), len(originImage[0])
    stack = []
    lable = 0
    lableImage = np.zeros([rows, cols], dtype=np.int)
    print(originImage)
    print(lableImage)
    for i in range(0, rows):
        for j in range(0, cols):
            if originImage[i][j] == 0 or lableImage[i][j] != 0:
                continue
            else:
                stack.append([i, j])
                lable += 1
                lableImage[i][j] = lable
                while len(stack) > 0:
                    currentIndex = stack.pop()
                    x, y = currentIndex[0], currentIndex[1]
                    if y-1 >= 0 and lableImage[x][y-1] == 0 and originImage[x][y-1] == 255:
                        stack.append([x, y-1])
                        lableImage[x][y-1] = lable
                    if y+1 <= cols-1 and lableImage[x][y+1] == 0 and originImage[x][y+1] == 255:
                        stack.append([x, y+1])
                        lableImage[x][y+1] = lable
                    if x-1 >= 0 and lableImage[x-1][y] == 0 and originImage[x-1][y] == 255:
                        stack.append([x-1, y])
                        lableImage[x-1][y] = lable
                    if x+1 <= rows-1 and lableImage[x+1][y] == 0 and originImage[x+1][y] == 255:
                        stack.append([x+1, y])
                        lableImage[x+1][y] = lable
    print(lableImage)
    return lableImage

你可能感兴趣的:(笔记)