002--算法之"高手過招"[最小K個數(shù)]

綠色(1).jpg

序言

今天分享的這道題,也是在分治策略上非常經(jīng)典的題目. 而且這個題目多次出現(xiàn)在互聯(lián)網(wǎng)頭部企業(yè)作為面試的算法題. 比如字節(jié),騰訊.這道題目,實際上有多種解決方案. 今天分享的是其中一個解決方案. 后續(xù)也會更新它的其他解法.

在求解樞軸上,為了讓讀者更加快速的理解它的求解過程和變換. 特地畫了圖.以及在文章末尾貼上的完整代碼.以及代碼中加上了比較詳細的注釋. 給大家輔助理解. 希望能加速你的對這道的理解與實現(xiàn)~

嗯. 我要開始寫了~~~

1.1 最小K個數(shù)

  • **難度系數(shù): ☆☆☆☆

  • 題目來源: LeetCode 下分治策略專題

  • 題目描述: 設(shè)計一個算法, 找出數(shù)組中最小的k個數(shù). 以任意順序返回這k個數(shù)均可;

  • 輸入:arr = [1,3,5,7,2,4,6,8] , k = 4;

  • 輸出: [1,2,3,4]

  • 提示:

  • 0 <= len(arr) <= 100000

  • 0 <= k <= min(100000, len(arr))

  • 題目解讀:

  • 這個問題就是想要你從10w 個數(shù)字找出 最小的k 個數(shù);

  • 關(guān)鍵詞: 數(shù)量級10w, 最小的k個數(shù) , 返回順序任意;

  • 出現(xiàn)過企業(yè)面試題: 字節(jié)跳動,騰訊

1.2 快排實現(xiàn)最小K個數(shù)

LeetCode 執(zhí)行結(jié)果

image.png

問題分析:

其實這個問題就是一個非常經(jīng)典的快排問題. 但是大多數(shù)人遇到這個問題時 總是被 前面的數(shù)量級 總認為這樣的問題無法通過排序算法完成. 問題的表現(xiàn)形式,常常用以下方式描述: "如何從10萬個數(shù)中找到最大的100個數(shù)". 實際上這個問題就是今天我們要探討的 算法題. ** 設(shè)計一個算法, 找出數(shù)組中最小的k個數(shù). 以任意順序返回這k個數(shù)均可; **

這個問題在LeetCode 上"分治策略"題庫標(biāo)簽下, 實際上使用快速排序就是一種非常典型且明顯的分治策略了. 快速排序(Quick Sort)的基本思想: 通過一趟排序?qū)⒋判蛴涗浄指畛瑟毩⒌膬刹糠? 其中一部分記錄的關(guān)鍵字均為另一部分記錄的關(guān)鍵字小. 則可分別對兩部分記錄繼續(xù)進行排序, 以達到整個排序有序的目的;

值得注意的地方是, 使用快速排序后會讓源數(shù)據(jù)的數(shù)據(jù)位置發(fā)生變化. 但是在這樣的改變題目中明確指出是可以被允許的. 這個細節(jié)也是面試官會和你討論的一個小細節(jié);

image.png

1.3 快速排序思想

快速排序其實就是冒泡排序的升級, 它們都屬于交換排序;

快速排序也是通過不斷的比較和移動交換來實現(xiàn)排序的.只不過它的實現(xiàn),增大了記錄的比較和移動的距離; 將關(guān)鍵字較大的記錄從前面直接移動到后面; 關(guān)鍵字較小的記錄從后面直接移動到前面,從而減少了總的比較次數(shù)和移動交換次數(shù);

image.png

從快速排序的思想字母以上看, 好像這樣的計算是非常復(fù)雜且繁瑣的. 但是并非如此. 接下來我們就跟著 我的文字, 來快速理解, 快速排序的思路.

                              ![image.png](https://upload-images.jianshu.io/upload_images/4624551-400a3f48012a55b1.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240 "image.png") 

設(shè)計一個smallestK 函數(shù)思路:

  • 判斷當(dāng)前的數(shù)組是否為空/數(shù)組長度是否小于0,以及查找的k數(shù)是否小于0,返回的size是否賦予了對應(yīng)的地址空間;
  • low = 0, hight = arrSize -1;
  • 求得樞軸,并且將數(shù)組樞軸左邊的關(guān)鍵字都比它小, 右邊的關(guān)鍵字都比樞軸對應(yīng)的關(guān)鍵字大;
  • 將數(shù)組一分為二,對低子表進行排序,對高子表進行排序;
  • 排序結(jié)束后,將數(shù)組arr 中前k個數(shù)據(jù)存儲到 ans 數(shù)組中并返回;

那么我們來看看 ****smallestK 的代碼實現(xiàn):

void QSort(int *arr, int low, int hight){
    int pivot ;
    if (low < hight) {
         //將L->r[low,high]一分為二,算出中樞軸值 pivot;
        pivot = Partition2(arr, low, hight);
        
        printf("arr[%d] = %d\n",pivot,arr[pivot]);
        //對低子表遞歸排序;
        QSort(arr, low, pivot-1);
         //對高子表遞歸排序
        QSort(arr, pivot+1, hight);
    }
}

int* smallestK2(int* arr, int arrSize, int k, int* returnSize){
    //1.判斷數(shù)組是否為空,且arrsize 小于0則不符合排序的前提;
    //1.判斷尋找最小的K數(shù),且returnSize 空間是否開辟成功,不符合則返回Null
    if(arr == NULL || arrSize <= 0 || k <= 0 || returnSize == NULL){
        if(returnSize != NULL) *returnSize = 0;
        return NULL;
    }
       
    //進行快速排序QSort
    QSort(arr, 0, arrSize);
    
    //4. 創(chuàng)建一個數(shù)組reslut, 數(shù)組長度為k;
    int* reslutArr = malloc(sizeof(int) * k);
    *returnSize = k;
    //循環(huán)將排序后的arr數(shù)組中的前k個元素存儲到數(shù)組reslutArr 中;
    for(int i = 0; i < k; i++){
        reslutArr[i] = arr[i];
    }
    
    return reslutArr;
}
  • QSort (L,1,L->length) 中的1L->Length代碼的意思,其實就是對當(dāng)前待排序的最小下標(biāo)值low和最大下標(biāo)值high.
  • 這段代碼的核心就是求解樞軸;pivot = Partition(L,low,high). 在執(zhí)行之前,L.r數(shù)組值為{50,10,90,30,70,40,80,60,20}.
  • Partition 函數(shù)要做的,就先選取當(dāng)中一個關(guān)鍵字.比如選擇第一個關(guān)鍵字50. 然把它放在一個位置上,使得它左邊的值都比它小, 右邊的值都比它大. 我將這樣的關(guān)鍵字稱為樞軸(pivot);
  • 經(jīng)過Partition(L,1,9)之后,數(shù)組變成了 {20,10,40,30,50,70,80,60,90}; 并返回了5給pivot. 數(shù)字5表示50放置在數(shù)組下標(biāo)為5的位置.
  • 此時把原來位于50左右的2個數(shù)組{20,10,40,30}{70,80,60,90}.
  • 后面的遞歸就是調(diào)用 QSort(L,1,5-1),QSort(L,5+1,9). 其實就是對{20,10,40,30}{70,80,60,90}. 分別同樣進行Partition 操作,直到順序全部正確為止;

注意,在 QSort 函數(shù)中,最關(guān)鍵的是Partition2 函數(shù) . 這個函數(shù)的作用是:

Partition2函數(shù)的功能

  1. 選取當(dāng)中一個關(guān)鍵字作為樞軸;
  2. 將它放在一個合適的位置上, 使得它的左邊的值都比它小, 右邊的值都比它大;

1.4 Partition 函數(shù)的實現(xiàn)與分析

既然我們明確了 Partition 的作用. 就先選取當(dāng)中一個關(guān)鍵字.比如選擇第一個關(guān)鍵字50. 然把它放在一個位置上,使得它左邊的值都比它小, 右邊的值都比它大. 我將這樣的關(guān)鍵字稱為樞軸(pivot);

那么接下來,我們要解決的問題是:

  1. 那么如何尋找樞軸變量?
  2. 如果將樞軸變量放在合適的位置,并且使得左側(cè)關(guān)鍵字均比它小,且右側(cè)的均比它大;
image.png
  • 我們選擇子表中第1個記錄作為樞軸變量,pivotkey = 50;
  • 從表的兩端往中間掃描; 開始第1層循環(huán)! 循環(huán)判斷依據(jù)是low<high
  • 用高位highpivotkey 進行比較找到比樞軸小的記錄. 交換到低端位置上;
  • 比較數(shù)組L->r[high]pivotkey 進行比較. 如果low<hight 并且 L->r[high] >= pivotkey 就遞減high;
image.png
  • 判斷依據(jù): L->r[high] >= pivotkey && low < high 循環(huán)則繼續(xù)往下查找. high 遞減;
  • 此時, 如圖. 當(dāng)high = 9, low = 1; L->r[9] = 20; L->r[1] = 50; 所以不滿足循環(huán)條件,退出循環(huán), 那么此時需要交換Swap(L,1,9); 使得比pivotkey 小的數(shù)據(jù),交換到低端位置上;
image.png
image.png
  • 接下來, 用低位lowpivotkey 進行比較找到比樞軸大的記錄. 交換到高端位置上;
  • 判斷依據(jù): L->r[low] >= pivotkey && low < high循環(huán)則繼續(xù)往下查找. low++;
  • 此時,L->r[1] = 20, pivotkey = 50; L->r[low] < pivotkey; 則low++; low = 2;

![image.png](https://upload-images.jianshu.io/upload_images/4624551-108757a46aa15140?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240 "image.png")

  • 此時L->r[2] = 10, pivotkey = 50; L->r[low] < pivotkey; 則low++; low = 3;
  • 此時L->r[3] = 90, pivotkey = 50; L->r[low] > pivotkey; 則循環(huán)退出.
  • 交換L->r[low] 與 L->r[high]的值; swap(L ,3,9);
image.png
  • 第1次 往中間掃描結(jié)束!
  • 但是此時low < high . low = 3,high = 9. 還可以繼續(xù)進行第2次 往中間兩端交替向中間掃描;

Partition 函數(shù)的思路:

  • 選取第一個關(guān)鍵字作為樞軸;

  • 只要(low < high) 就循環(huán)持續(xù)的將表的兩端進行交替向中間掃描 (****<u>兩端交替循環(huán)</u>)

  • while 遍歷從[low,high]的高端位置開始找,找到比樞軸小的關(guān)鍵字(<u>高位調(diào)整循環(huán)</u>)

  • 如沒有找到,則修改范圍. 將high 遞減;

  • 如果找到進行交換到低端位置 swap(L,low,high);

  • while 遍歷從[low,high]的低端位置開始找,找到比樞軸大的關(guān)鍵字(<u>低位調(diào)整循環(huán)</u>)

  • 如果沒有找到,則修改范圍,將low 遞增;

  • 如果找到進行交換到高端位置 swap(L,low,high);

Partition 代碼實現(xiàn):

//③交換順序表L中子表的記錄,使樞軸記錄到位,并返回其所在位置
//此時在它之前(后)的記錄均不大(小)于它
int Partition(SqList *L,int low,int high){
    int pivokey;
    //pivokey 保存子表中第1個記錄作為樞軸記錄;
    pivokey = L->r[low];
    //① 從表的兩端交替地向中間掃描;
    while (low < high) {
        
        //② 比較,從高位開始,找到比pivokey更小的值的下標(biāo)位置;
        while (low < high &&  L->r[high] >= pivokey)
            high--;
        //③ 將比樞軸值小的記錄交換到低端;
        swap(L, low, high);
        //④ 比較,從低位開始,找到比pivokey更大的值的下標(biāo)位置;
        while (low < high && L->r[low] <= pivokey)
            low++;
        //⑤ 將比樞軸值大的記錄交換到高端;
        swap(L, low, high);
        
    }
    
    //返回樞軸pivokey 所在位置;
    return low;
}

最小K個數(shù)[快速排序?qū)崿F(xiàn)策略]完整代碼實現(xiàn)

int Partition2(int *L,int low,int high){
    
    int pivotkey;
    pivotkey = L[low];
    while (low < high) {
        while (low < high && L[high] >= pivotkey)  high--;
        swap(L+low, L+high);
        while (low < high && L[low] <= pivotkey) low++;
        swap(L+low, L+high);
        
    }
    return low;;
}

void QSort(int *arr, int low, int hight){
    int pivot ;
    if (low < hight) {
        pivot = Partition2(arr, low, hight);
        
        printf("arr[%d] = %d\n",pivot,arr[pivot]);
        QSort(arr, low, pivot-1);
        QSort(arr, pivot+1, hight);
    }
}

int* smallestK2(int* arr, int arrSize, int k, int* returnSize){
    //1.判斷數(shù)組是否為空,且arrsize 小于0則不符合排序的前提;
    //1.判斷尋找最小的K數(shù),且returnSize 空間是否開辟成功,不符合則返回Null
    if(arr == NULL || arrSize <= 0 || k <= 0 || returnSize == NULL){
        if(returnSize != NULL) *returnSize = 0;
        return NULL;
    }
 
    QSort(arr, 0, arrSize);
    
    //4. 創(chuàng)建一個數(shù)組reslut, 數(shù)組長度為k;
    int* reslutArr = malloc(sizeof(int) * k);
    *returnSize = k;
    //循環(huán)將排序后的arr數(shù)組中的前k個元素存儲到數(shù)組reslutArr 中;
    for(int i = 0; i < k; i++){
        reslutArr[i] = arr[i];
    }
    
    return reslutArr;
}

#define N 9
int main(int argc, const char * argv[]) {
    // insert code here...
    printf("Hello, World!\n");

    int d[10]={1,3,5,7,2,4,6,8,9};
    int resultSize;
    int *result = smallestK2(d, 8, 4, &resultSize);
    for (int i = 0; i < resultSize; i++) {
        printf("%d \n",result[i]);
    }
    
    
    return 0;
}

image.png

若有收獲,就點個贊吧

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容