備戰秋招——算法與數據結構(4)

在這裏插入圖片描述

● 請你來手寫一下快排的代碼

參考回答:

int once_quick_sort(vector<int> &data, int left, int right)
{
int key = data[left];
while (left < right)
{
while (left < right && key <= data[right])
{
right--;
}
if (left < right)
{
data[left++] = data[right];
}
while (left < right && key > data[left])
{
left++;
}
if (left < right)
{
data[right--] = data[left];
}
}
data[left] = key;
return left;
}
int quick_sort(vector<int> & data, int left, int right)
{
if (left >= right )
{
return 1;
}
int middle = 0;
middle = once_quick_sort(data, left, right);
quick_sort(data, left, middle-1);
quick_sort(data, middle + 1, right);
}

● 請你手寫一下快排的代碼

參考回答:

nt once_quick_sort(vector<int> &data, int left, int right)
{
int key = data[left];
while (left < right)
{
while (left < right && key <= data[right])
{
right--;
}
if (left < right)
{
data[left++] = data[right];
}
while (left < right && key > data[left])
{
left++;
}
if (left < right)
{
data[right--] = data[left];
}
}
data[left] = key;
return left;
}
int quick_sort(vector<int> & data, int left, int right)
{
if (left >= right )
{
return 1;
}
int middle = 0;
middle = once_quick_sort(data, left, right);
quick_sort(data, left, middle-1);
quick_sort(data, middle + 1, right);
}

● 請問求第k大的數的方法以及各自的複雜度是怎樣的,另外追問一下,當有相同元素時,還可以使用什麼不同的方法求第k大的元素

參考回答:
首先使用快速排序算法將數組按照從大到小排序,然後取第k個,其時間複雜度最快爲O(nlogn)
使用堆排序,建立最大堆,然後調整堆,知道獲得第k個元素,其時間複雜度爲O(n+klogn)

首先利用哈希表統計數組中個元素出現的次數,然後利用計數排序的思想,線性從大到小掃描過程中,前面有k-1個數則爲第k大的數

利用快排思想,從數組中隨機選擇一個數i,然後將數組分成兩部分Dl,Dr,Dl的元素都小於i,Dr的元素都大於i。然後統計Dr元素個數,如果Dr元素個數等於k-1,那麼第k大的數即爲k,如果Dr元素個數小於k,那麼繼續求Dl中第k-Dr大的元素;如果Dr元素個數大於k,那麼繼續求Dr中第k大的元素。

當有相同元素的時候,

首先利用哈希表統計數組中個元素出現的次數,然後利用計數排序的思想,線性從大到小掃描過程中,前面有k-1個數則爲第k大的數,平均情況下時間複雜度爲O(n)

● 請你來介紹一下各種排序算法及時間複雜度

參考回答:
插入排序:對於一個帶排序數組來說,其初始有序數組元素個數爲1,然後從第二個元素,插入到有序數組中。對於每一次插入操作,從後往前遍歷當前有序數組,如果當前元素大於要插入的元素,則後移一位;如果當前元素小於或等於要插入的元素,則將要插入的元素插入到當前元素的下一位中。
希爾排序:先將整個待排序記錄分割成若干子序列,然後分別進行直接插入排序,待整個序列中的記錄基本有序時,在對全體記錄進行一次直接插入排序。其子序列的構成不是簡單的逐段分割,而是將每隔某個增量的記錄組成一個子序列。希爾排序時間複雜度與增量序列的選取有關,其最後一個值必須爲1.

歸併排序:該算法採用分治法;對於包含m個元素的待排序序列,將其看成m個長度爲1的子序列。然後兩兩合歸併,得到n/2個長度爲2或者1的有序子序列;然後再兩兩歸併,直到得到1個長度爲m的有序序列。

冒泡排序:對於包含n個元素的帶排序數組,重複遍歷數組,首先比較第一個和第二個元素,若爲逆序,則交換元素位置;然後比較第二個和第三個元素,重複上述過程。每次遍歷會把當前前n-i個元素中的最大的元素移到n-i位置。遍歷n次,完成排序。

快速排序:通過一趟排序將要排序的數據分割成獨立的兩部分,其中一部分的所有數據都比另外一部分的所有數據都要小,然後再按此方法對這兩部分數據分別進行快速排序,整個排序過程可以遞歸進行,以此達到整個數據變成有序序列。

選擇排序:每次循環,選擇當前無序數組中最小的那個元素,然後將其與無序數組的第一個元素交換位置,從而使有序數組元素加1,無序數組元素減1.初始時無序數組爲空。

堆排序:堆排序是一種選擇排序,利用堆這種數據結構來完成選擇。其算法思想是將帶排序數據構造一個最大堆(升序)/最小堆(降序),然後將堆頂元素與待排序數組的最後一個元素交換位置,此時末尾元素就是最大/最小的值。然後將剩餘n-1個元素重新構造成最大堆/最小堆。

各個排序的時間複雜度、空間複雜度及穩定性如下:
在這裏插入圖片描述

● 請你說一說你知道的排序算法及其複雜度

參考回答:
1、冒泡排序:
從數組中第一個數開始,依次遍歷數組中的每一個數,通過相鄰比較交換,每一輪循環下來找出剩餘未排序數的中的最大數並“冒泡”至數列的頂端。

穩定性:穩定

平均時間複雜度:O(n ^ 2)

2、插入排序:

從待排序的n個記錄中的第二個記錄開始,依次與前面的記錄比較並尋找插入的位置,每次外循環結束後,將當前的數插入到合適的位置。

穩定性:穩定

平均時間複雜度:O(n ^ 2)

3、希爾排序(縮小增量排序):

希爾排序法是對相鄰指定距離(稱爲增量)的元素進行比較,並不斷把增量縮小至1,完成排序。

希爾排序開始時增量較大,分組較多,每組的記錄數目較少,故在各組內採用直接插入排序較快,後來增量di逐漸縮小,分組數減少,各組的記錄數增多,但由於已經按di−1分組排序,文件叫接近於有序狀態,所以新的一趟排序過程較快。因此希爾 排序在效率上比直接插入排序有較大的改進。

在直接插入排序的基礎上,將直接插入排序中的1全部改變成增量d即可,因爲希爾排序最後一輪的增量d就爲1。

穩定性:不穩定

平均時間複雜度:希爾排序算法的時間複雜度分析比較複雜,實際所需的時間取決於各次排序時增量的個數和增量的取值。時間複雜度在O(n ^ 1.3)到O(n ^ 2)之間。

4、選擇排序:

從所有記錄中選出最小的一個數據元素與第一個位置的記錄交換;然後在剩下的記錄當中再找最小的與第二個位置的記錄交換,循環到只剩下最後一個數據元素爲止。

穩定性:不穩定

平均時間複雜度:O(n ^ 2)

5、快速排序

1)從待排序的n個記錄中任意選取一個記錄(通常選取第一個記錄)爲分區標準;

2)把所有小於該排序列的記錄移動到左邊,把所有大於該排序碼的記錄移動到右邊,中間放所選記錄,稱之爲第一趟排序;

3)然後對前後兩個子序列分別重複上述過程,直到所有記錄都排好序。

穩定性:不穩定

平均時間複雜度:O(nlogn)

6、堆排序:

堆:

1、完全二叉樹或者是近似完全二叉樹。

2、大頂堆:父節點不小於子節點鍵值,小頂堆:父節點不大於子節點鍵值。左右孩子沒有大小的順序。

堆排序在選擇排序的基礎上提出的,步驟:

1、建立堆

2、刪除堆頂元素,同時交換堆頂元素和最後一個元素,再重新調整堆結構,直至全部刪除堆中元素。

穩定性:不穩定

平均時間複雜度:O(nlogn)

7、歸併排序:

採用分治思想,現將序列分爲一個個子序列,對子序列進行排序合併,直至整個序列有序。

穩定性:穩定

平均時間複雜度:O(nlogn)

8、計數排序:

思想:如果比元素x小的元素個數有n個,則元素x排序後位置爲n+1。

步驟:

1)找出待排序的數組中最大的元素;

2)統計數組中每個值爲i的元素出現的次數,存入數組C的第i項;

3)對所有的計數累加(從C中的第一個元素開始,每一項和前一項相加);

4)反向填充目標數組:將每個元素i放在新數組的第C(i)項,每放一個元素就將C(i)減去1。

穩定性:穩定

時間複雜度:O(n+k),k是待排序數的範圍。

9、桶排序:

步驟:

1)設置一個定量的數組當作空桶子; 常見的排序算法及其複雜度:

2)尋訪序列,並且把記錄一個一個放到對應的桶子去;

3)對每個不是空的桶子進行排序。

4)從不是空的桶子裏把項目再放回原來的序列中。

時間複雜度:O(n+C) ,C爲桶內排序時間。

● 請問海量數據如何去取最大的k個

參考回答:
1.直接全部排序(只適用於內存夠的情況)
當數據量較小的情況下,內存中可以容納所有數據。則最簡單也是最容易想到的方法是將數據全部排序,然後取排序後的數據中的前K個。

這種方法對數據量比較敏感,當數據量較大的情況下,內存不能完全容納全部數據,這種方法便不適應了。即使內存能夠滿足要求,該方法將全部數據都排序了,而題目只要求找出top K個數據,所以該方法並不十分高效,不建議使用。

2.快速排序的變形 (只使用於內存夠的情況)

這是一個基於快速排序的變形,因爲第一種方法中說到將所有元素都排序並不十分高效,只需要找出前K個最大的就行。

這種方法類似於快速排序,首先選擇一個劃分元,將比這個劃分元大的元素放到它的前面,比劃分元小的元素放到它的後面,此時完成了一趟排序。如果此時這個劃分元的序號index剛好等於K,那麼這個劃分元以及它左邊的數,剛好就是前K個最大的元素;如果index > K,那麼前K大的數據在index的左邊,那麼就繼續遞歸的從index-1個數中進行一趟排序;如果index < K,那麼再從劃分元的右邊繼續進行排序,直到找到序號index剛好等於K爲止。再將前K個數進行排序後,返回Top K個元素。這種方法就避免了對除了Top K個元素以外的數據進行排序所帶來的不必要的開銷。

3.最小堆法

這是一種局部淘汰法。先讀取前K個數,建立一個最小堆。然後將剩餘的所有數字依次與最小堆的堆頂進行比較,如果小於或等於堆頂數據,則繼續比較下一個;否則,刪除堆頂元素,並將新數據插入堆中,重新調整最小堆。當遍歷完全部數據後,最小堆中的數據即爲最大的K個數。

4.分治法

將全部數據分成N份,前提是每份的數據都可以讀到內存中進行處理,找到每份數據中最大的K個數。此時剩下NK個數據,如果內存不能容納NK個數據,則再繼續分治處理,分成M份,找出每份數據中最大的K個數,如果M*K個數仍然不能讀到內存中,則繼續分治處理。直到剩餘的數可以讀入內存中,那麼可以對這些數使用快速排序的變形或者歸併排序進行處理。

5.Hash法

如果這些數據中有很多重複的數據,可以先通過hash法,把重複的數去掉。這樣如果重複率很高的話,會減少很大的內存用量,從而縮小運算空間。處理後的數據如果能夠讀入內存,則可以直接排序;否則可以使用分治法或者最小堆法來處理數據。

● 請問快排的時間複雜度最差是多少?什麼時候時間最差

參考回答:
O(N2),元素本來倒序排列用時最多
● 請問穩定排序哪幾種?
參考回答:
基數排序、冒泡排序、直接插入排序、折半插入排序、歸併排序

● 請你介紹一下快排算法;以及什麼是穩定性排序,快排是穩定性的嗎;快排算法最差情況推導公式

參考回答:
1、快排算法
根據哨兵元素,用兩個指針指向待排序數組的首尾,首指針從前往後移動找到比哨兵元素大的,尾指針從後往前移動找到比哨兵元素小的,交換兩個元素,直到兩個指針相遇,這是一趟排序,經常這趟排序後,比哨兵元素大的在右邊,小的在左邊。經過多趟排序後,整個數組有序。

穩定性:不穩定

平均時間複雜度:O(nlogn)

2、穩定排序

假定在待排序的記錄序列中,存在多個具有相同的關鍵字的記錄,若經過排序,這些記錄的相對次序保持不變,即在原序列中,r[i]=r[j],且r[i]在r[j]之前,而在排序後的序列中,r[i]仍在r[j]之前,則稱這種排序算法是穩定的;否則稱爲不穩定的。

快排算法是不穩定的排序算法。例如:

待排序數組:int a[] ={1, 2, 2, 3, 4, 5, 6};

若選擇a[2](即數組中的第二個2)爲樞軸,而把大於等於比較子的數均放置在大數數組中,則a[1](即數組中的第一個2)會到pivot的右邊, 那麼數組中的兩個2非原序。

若選擇a[1]爲比較子,而把小於等於比較子的數均放置在小數數組中,則數組中的兩個2順序也非原序。

3、快排最差情況推倒

在快速排序的早期版本中呢,最左面或者是最右面的那個元素被選爲樞軸,那最壞的情況就會在下面的情況下發生啦:

1)數組已經是正序排過序的。 (每次最右邊的那個元素被選爲樞軸)

2)數組已經是倒序排過序的。 (每次最左邊的那個元素被選爲樞軸)

3)所有的元素都相同(1、2的特殊情況)

因爲這些案例在用例中十分常見,所以這個問題可以通過要麼選擇一個隨機的樞軸,或者選擇一個分區中間的下標作爲樞軸,或者(特別是對於相比更長的分區)選擇分區的第一個、中間、最後一個元素的中值作爲樞軸。有了這些修改,那快排的最差的情況就不那麼容易出現了,但是如果輸入的數組最大(或者最小元素)被選爲樞軸,那最壞的情況就又來了。

快速排序,在最壞情況退化爲冒泡排序,需要比較O(n2)次(n(n - 1)/2次)。在這裏插入圖片描述

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章