【校招面经】计算机基础

一、java中Array和ArrayList区别

1)精辟阐述:

可以将 ArrayList想象成一种“会自动扩增容量的Array”。

2)Array([]):最高效;但是其容量固定且无法动态改变;

     ArrayList:  容量可动态增长;但牺牲效率;

3)建议:

基于效率和类型检验,应尽可能使用Array,无法确定数组大小时才使用ArrayList!

不过当你试着解决更一般化的问题时,Array的功能就可能过于受限。

二、java中hashmap和hashtable的差别

HashMap是Hashtable的轻量级实现(非线程安全的实现),他们都完成了Map接口。主要的区别有:线程安全性,同步(synchronization),以及速度。

1.Hashtable继承自Dictionary类,而HashMap是Java1.2引进的Map interface的一个实现。

2.HashMap允许将null作为一个entry的key或者value,而Hashtable不允许。

3.HashMap是非synchronized,而Hashtable是synchronized,这意味着Hashtable是线程安全的,多个线程可以共享一个Hashtable;而如果没有正确的同步的话,多个线程是不能共享HashMap的。Java 5提供了ConcurrentHashMap,它是HashTable的替代,比HashTable的扩展性更好。(在多个线程访问Hashtable时,不需要自己为它的方法实现同步,而HashMap 就必须为之提供外同步(Collections.synchronizedMap))

4.另一个区别是HashMap的迭代器(Iterator)是fail-fast迭代器,而Hashtable的enumerator迭代器不是fail-fast的。所以当有其它线程改变了HashMap的结构(增加或者移除元素),将会抛出ConcurrentModificationException,但迭代器本身的remove()方法移除元素则不会抛出ConcurrentModificationException异常。但这并不是一个一定发生的行为,要看JVM。这条同样也是Enumeration和Iterator的区别。fail-fast机制如果不理解原理,可以查看这篇文章:http://www.cnblogs.com/alexlo/archive/2013/03/14/2959233.html

5.由于HashMap非线程安全,在只有一个线程访问的情况下,效率要高于HashTable。

6.HashMap把Hashtable的contains方法去掉了,改成containsvalue和containsKey。因为contains方法容易让人引起误解。 

7.Hashtable中hash数组默认大小是11,增加的方式是 old*2+1。HashMap中hash数组的默认大小是16,而且一定是2的指数。

8..两者通过hash值散列到hash表的算法不一样

三、Python中的赋值(复制)、浅拷贝、深拷贝之间的区别

 1.赋值: 只是复制了新对象的引用,不会开辟新的内存空间。

  2.浅拷贝: 创建新对象,其内容是原对象的引用。

    浅拷贝有三种形式:切片操作,工厂函数,copy模块中的copy函数。

    如: lst = [1,2,3,[4,5]] 

    切片操作:lst1 = lst[:] 或者 lst1 = [each for each in lst]

    工厂函数:lst1 = list(lst)

    copy函数:lst1 = copy.copy(lst)

    浅拷贝之所以称为浅拷贝,是它仅仅只拷贝了一层,在lst中有一个嵌套的list[4,5],如果我们修改了它,情况就不一样了。

  3.深拷贝:只有一种形式,copy模块中的deepcopy函数。

     和浅拷贝对应,深拷贝拷贝了对象的所有元素,包括多层嵌套的元素。

     深拷贝出来的对象是一个全新的对象,不再与原来的对象有任何关联。

复制代码

>>> import copy >>> a = [1,2,3,4,['a','b']] #定义一个列表a >>> b = a #赋值 >>> c = copy.copy(a) #浅拷贝 >>> d = copy.deepcopy(a) #深拷贝 >>> a.append(5) >>> print(a) [1, 2, 3, 4, ['a', 'b'], 5] #a添加一个元素5 >>> print(b) [1, 2, 3, 4, ['a', 'b'], 5] #b跟着添加一个元素5 >>> print(c) [1, 2, 3, 4, ['a', 'b']] #c保持不变 >>> print(d) [1, 2, 3, 4, ['a', 'b']] #d保持不变 >>> a[4].append('c') >>> print(a) [1, 2, 3, 4, ['a', 'b', 'c'], 5] #a中的list(即a[4])添加一个元素c >>> print(b) [1, 2, 3, 4, ['a', 'b', 'c'], 5] #b跟着添加一个元素c >>> print(c) [1, 2, 3, 4, ['a', 'b', 'c']] #c跟着添加一个元素c >>> print(d) [1, 2, 3, 4, ['a', 'b']] #d保持不变 #说明如下: #1.外层添加元素时, 浅拷贝c不会随原列表a变化而变化;内层list添加元素时,浅拷贝c才会变化。 #2.无论原列表a如何变化,深拷贝d都保持不变。 #3.赋值对象随着原列表一起变化

四、python类中的new与init

__new__ 用来创建实例,在返回的实例上执行__init__,如果不返回实例那么__init__将不会执行,__init__ 用来初始化实例,设置类及实例属性,调用方法等操作。

五、python 星号

一个星(*):表示接收的参数作为元组来处理

两个星(**):表示接收的参数作为字典来处理

可以看到,这两个是python中的可变参数。*args表示任何多个无名参数,它是一个tuple;**kwargs表示关键字参数,它是一个dict。并且同时使用*args和**kwargs时,必须*args参数列要在**kwargs前,像foo(a=1, b='2', c=3, a', 1, None, )这样调用的话,会提示语法错误“SyntaxError: non-keyword arg after keyword arg”

六、进程和线程的区别

1. 进程是资源的分配和调度的一个独立单元,而线程是CPU调度的基本单元

2. 同一个进程中可以包括多个线程,并且线程共享整个进程的资源(寄存器、堆栈、上下文),一个进程至少包括一个线程。

七、封装继承多态

from:https://blog.csdn.net/fanyun_01/article/details/50985333

面向对象的三个基本特征是:封装、继承、多态。

我们知道,封装可以隐藏实现细节,使得代码模块化;继承可以扩展已存在的代码模块(类);它们的目的都是:代码重用。而多态则是为了实现另一个目的——接口重用!多态的作用,就是为了类在继承和派生的时候,保证使用“家谱”中任一类的实例的某一属性时的正确调用。

封装:

 封装是实现面向对象程序设计的第一步,封装就是将数据或函数等集合在一个个的单元中(我们称之为类)。被封装的对象通常被称为抽象数据类型。 

 封装的意义:

  封装的意义在于保护或者防止代码(数据)被我们无意中破坏。在面向对象程序设计中数据被看作是一个中心的元素并且和使用它的函数结合的很密切,从而保护它不被其它的函数意外的修改。

1. 保护数据成员,不让类以外的程序直接访问或修改,只能通过提供的公共的接口访问==>数据封装。

2. 方法的细节对用户是隐藏的,只要接口不变,内容的修改不会影响到外部的调用者==>方法封装。 

3. 当对象含有完整的属性和与之对应的方法时称为封装。

4. 从对象外面不能直接访问对象的属性,只能通过和该属性对应的方法访问。

5. 对象的方法可以接收对象外面的消息。

    类成员的访问修饰符:

即类的方法和成员变量的访问控制符,一个类作为整体对象不可见,并不代表他的所有域和方法也对程序其他部分不可见,需要有他们的访问修饰符判断。

权限如下: 

访问修饰符

同一个类

同包

不同包,子类

不同包,非子类

private

protected

public

默认

继承:

继承主要实现重用代码,节省开发时间。

1、C#中的继承符合下列规则:

    1. 继承是可传递的。如果C从B中派生,B又从A中派生,那么C不仅继承了B中声明的成员,同样也继承了A中的成员。Object类作为所有类的基类。
    2. 派生类应当是对基类的扩展。派生类可以添加新的成员,但不能除去已经继承的成员的定义。
    3. 构造函数和析构函数不能被继承。除此之外的其它成员,不论对它们定义了怎样的访问方式,都能被继承。基类中成员的访问方式只能决定派生类能否访问它们。
    4. 派生类如果定义了与继承而来的成员同名的新成员,就可以覆盖已继承的成员。但这并不因为这派生类删除了这些成员,只是不能再访问这些成员。
    5. 类可以定义虚文法、虚属性以及虚索引指示器,它的派生类能够重载这些成员,从而实现类可以展示出多态性。

  2、new关键字

   如果父类中声明了一个没有friend修饰的protected或public方法,子类中也声明了同名的方法。则用new可以隐藏父类中的方法。(不建议使用)

  3、base关键字

   base 关键字用于从派生类中访问基类的成员:

    1. 调用基类上已被其他方法重写的方法。
    2. 指定创建派生类实例时应调用的基类构造函数。

多态:

1、“一个接口,多种方法”

同一操作作用于不同的对象,可以有不同的解释,产生不同的执行结果。  多态的三个条件: a.    继承的存在(继承是多态的基础,没有继承就没有多态). b.    子类重写父类的方法(多态下调用子类重写的方法). c.    父类引用变量指向子类对象(子类到父类的类型转换).

重载(overload)和重写(override)是实现多态的两种主要方式。

2、实现多态:

    1. 接口多态性。
    2. 继承多态性。
    3. 通过抽象类实现的多态性。

八、概率题 利用X等概率生成1-n的数

X是一个以p的概率产生1,1-p的概率产生0的随机变量,利用X等概率生成1-n的数

生成两次,生成01,10的概率是相同的,生成00或者11则重新生成,构造一个等概率生成器,然后用二进制生成1-n

九、解决Hash冲突的方法

from:https://blog.csdn.net/zhangdaisylove/article/details/47862753

 1.开放定址法(再散列法):

     基本思想:当关键字key的哈希地址p=H(key)出现冲突时,以p为基础,产生另一个哈希地址p1,如果p1仍然冲突,再以p为基础,产生另一个哈希地址p2,…,                            直到找出一个不冲突的哈希地址pi ,将相应元素存入其中。

     这种方法有一个通用的再散列函数形式:

               Hi=(H(key)+di)% m   i=1,2,…,n

     其中H(key)为哈希函数,m 为表长,di称为增量序列。增量序列的取值方式不同,相应的再散列方式也不同。 

     1.线性探测再散列: 

  dii=1,2,3,…,m-1

  冲突发生时,顺序查看表中下一单元,直到找出一个空单元或查遍全表。

     2.二次探测再散列:

  di=12,-12,22,-22,…,k2,-k2    ( k<=m/2 )

  冲突发生时,在表的左右进行跳跃式探测,比较灵活。

     3.伪随机探测再散列:

  di=伪随机数序列。

  具体实现时,应建立一个伪随机数发生器,(如i=(i+p) % m),并给定一个随机数做起点。

     4.  示例:

          已知哈希表长度m=11,哈希函数为:H(key)= key  %  11,则H(47)=3,H(26)=4,H(60)=5,假设下一个关键字为69,则H(69)=3,与47冲突。

  a): 如果用线性探测再散列处理冲突,下一个哈希地址为H1=(3 + 1)% 11 = 4,仍然冲突,再找下一个哈希地址为H2=(3 + 2)% 11 = 5,还是冲突,

               继续找下一个哈希地址为H3=(3 + 3)% 11 = 6,此时不再冲突,将69填入5号单元。

    0     1     2     3     4     5     6     7     8     9     10

                        47   26   60   69

  b): 如果用二次探测再散列处理冲突,下一个哈希地址为H1=(3 + 12)% 11 = 4,仍然冲突,再找下一个哈希地址为H2=(3 - 12)% 11 = 2,此时不再冲突,

               将69填入2号单元。

     0     1     2     3     4     5     6     7     8     9     10

                  69   47   26  60      

  c): 如果用伪随机探测再散列处理冲突,且伪随机数序列为:2,5,9,……..,则下一个哈希地址为H1=(3 + 2)% 11 = 5,仍然冲突,再找下一个哈希地址

               为H2=(3 + 5)% 11 = 8,此时不再冲突,将69填入8号单元。

     0     1     2     3     4     5     6     7     8     9     10

                         47   26  60                  69

   2.再哈希法:

         这种方法是同时构造多个不同的哈希函数:

                   Hi=RH1(key)  i=1,2,…,k

         当哈希地址Hi=RH1(key)发生冲突时,再计算Hi=RH2(key)……,直到冲突不再产生。这种方法不易产生聚集,但增加了计算时间。

   3.拉链法(HashMap的冲突处理方式):

          基本思想: 将所有哈希地址为i的元素构成一个称为同义词链的单链表,并将单链表的头指针存在哈希表的第i个单元中,因而查找、插入和删除主要

                            在同义词链中进行。链地址法适用于经常进行插入和删除的情况。

         例如:  已知一组关键字(32,40,36,53,16,46,71,27,42,24,49,64),哈希表长度为13,哈希函数为:H(key)= key % 13,

                     则用链地址法处理冲突的结果如图8.27所示:

       位置    Entry 

        0

        1  -->  40 --> 27 --> 53

        2

        3  -->  16 --> 42

        4

        5

        6  -->  32 --> 71

        7

        8

        9

        10 -->  36 --> 49

        11 -->  24

        12 -->  64

                本例的平均查找长度 ASL=(1*7+2*4+3*1)/13=1.38

   4.建立公共溢出区:

          这种方法的基本思想是:将哈希表分为基本表和溢出表两部分,凡是和基本表发生冲突的元素,一律填入溢出表

十、快速排序

from:https://www.cnblogs.com/ahalei/p/3568434.html

十一、桶排序

from:https://blog.csdn.net/ii1245712564/article/details/45869623

桶排序_BUCKETSORT

假设你有五百万份试卷,每份试卷的满分都是100分,如果要你对这些试卷按照分数进行排序,天噜啦,五百万份试卷啊,快速排序?堆排序?归并排序?面对这么多的数据,平均下来上面的每一种一种算法至少都要花费nlogn=5000000log5000000=111267433单位时间啊,将近一亿多,太慢了。

要是我们这样来做呢,首先买101只桶回来,分别为每一只桶编上0-100标号,我们就只管遍历一边所有的试卷,将分数为n的试卷丢入到编号为n的桶里面,当所有的试卷都放入到相应的桶里面时,我们就已经将所有的试卷按照分数进行排序了。遍历一遍所有数据的时间也就是五百万次,相比与一亿多次,那可是省了不少时间。这里所用到的就是桶排序的思想。

桶排序的思想

桶排序,顾名思义就是运用桶的思想来将数据放到相应的桶内,再将每一个桶内的数据进行排序,最后把所有桶内数据按照顺序取出来,得到的就是我们需要的有序数据

比如我们有下面的一些数据

49 43 11 61 31 71 53 51 71 84

下面我们按照这些数的十位将他们放入桶内

将数据放入桶内

bucket#.0 +++ 

bucket#.1 +++ 11 

bucket#.2 +++ 

bucket#.3 +++ 31 

bucket#.4 +++ 49 43 

bucket#.5 +++ 53 51 

bucket#.6 +++ 61 

猜你喜欢

转载自blog.csdn.net/u013382288/article/details/81412540