【校招面經】計算機基礎
一、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. 同一個程序中可以包括多個執行緒,並且執行緒共享整個程序的資源(暫存器、堆疊、上下文),一個程序至少包括一個執行緒。
七、封裝繼承多型
面向物件的三個基本特徵是:封裝、繼承、多型。
我們知道,封裝可以隱藏實現細節,使得程式碼模組化;繼承可以擴充套件已存在的程式碼模組(類);它們的目的都是:程式碼重用。而多型則是為了實現另一個目的——介面重用!多型的作用,就是為了類在繼承和派生的時候,保證使用“家譜”中任一類的例項的某一屬性時的正確呼叫。
封裝:
封裝是實現面向物件程式設計的第一步,封裝就是將資料或函式等集合在一個個的單元中(我們稱之為類)。被封裝的物件通常被稱為抽象資料型別。
封裝的意義:
封裝的意義在於保護或者防止程式碼(資料)被我們無意中破壞。在面向物件程式設計中資料被看作是一箇中心的元素並且和使用它的函式結合的很密切,從而保護它不被其它的函式意外的修改。
1. 保護資料成員,不讓類以外的程式直接訪問或修改,只能通過提供的公共的介面訪問==>資料封裝。
2. 方法的細節對使用者是隱藏的,只要介面不變,內容的修改不會影響到外部的呼叫者==>方法封裝。
3. 當物件含有完整的屬性和與之對應的方法時稱為封裝。
4. 從物件外面不能直接訪問物件的屬性,只能通過和該屬性對應的方法訪問。
5. 物件的方法可以接收物件外面的訊息。
類成員的訪問修飾符:
即類的方法和成員變數的訪問控制符,一個類作為整體物件不可見,並不代表他的所有域和方法也對程式其他部分不可見,需要有他們的訪問修飾符判斷。
許可權如下:
訪問修飾符 |
同一個類 |
同包 |
不同包,子類 |
不同包,非子類 |
private |
√ |
|||
protected |
√ |
√ |
√ |
|
public |
√ |
√ |
√ |
√ |
預設 |
√ |
√ |
繼承:
繼承主要實現重用程式碼,節省開發時間。
1、C#中的繼承符合下列規則:
-
- 繼承是可傳遞的。如果C從B中派生,B又從A中派生,那麼C不僅繼承了B中宣告的成員,同樣也繼承了A中的成員。Object類作為所有類的基類。
- 派生類應當是對基類的擴充套件。派生類可以新增新的成員,但不能除去已經繼承的成員的定義。
- 建構函式和解構函式不能被繼承。除此之外的其它成員,不論對它們定義了怎樣的訪問方式,都能被繼承。基類中成員的訪問方式只能決定派生類能否訪問它們。
- 派生類如果定義了與繼承而來的成員同名的新成員,就可以覆蓋已繼承的成員。但這並不因為這派生類刪除了這些成員,只是不能再訪問這些成員。
- 類可以定義虛文法、虛屬性以及虛索引指示器,它的派生類能夠過載這些成員,從而實現類可以展示出多型性。
2、new關鍵字
如果父類中聲明瞭一個沒有friend修飾的protected或public方法,子類中也聲明瞭同名的方法。則用new可以隱藏父類中的方法。(不建議使用)
3、base關鍵字
base 關鍵字用於從派生類中訪問基類的成員:
-
- 呼叫基類上已被其他方法重寫的方法。
- 指定建立派生類例項時應呼叫的基類建構函式。
多型:
1、“一個介面,多種方法”
同一操作作用於不同的物件,可以有不同的解釋,產生不同的執行結果。 多型的三個條件: a. 繼承的存在(繼承是多型的基礎,沒有繼承就沒有多型). b. 子類重寫父類的方法(多型下呼叫子類重寫的方法). c. 父類引用變數指向子類物件(子類到父類的型別轉換).
過載(overload)和重寫(override)是實現多型的兩種主要方式。
2、實現多型:
-
- 介面多型性。
- 繼承多型性。
- 通過抽象類實現的多型性。
八、概率題 利用X等概率生成1-n的數
X是一個以p的概率產生1,1-p的概率產生0的隨機變數,利用X等概率生成1-n的數
生成兩次,生成01,10的概率是相同的,生成00或者11則重新生成,構造一個等概率生成器,然後用二進位制生成1-n
九、解決Hash衝突的方法
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.建立公共溢位區:
這種方法的基本思想是:將雜湊表分為基本表和溢位表兩部分,凡是和基本表發生衝突的元素,一律填入溢位表
十、快速排序
十一、桶排序
桶排序_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