調整陣列元素順序演算法

神奇的程式設計師發表於2022-04-19

前言

有一個整數陣列,我們想按照特定規則對陣列中的元素進行排序,比如:陣列中的所有奇數位於陣列的前半部分。

本文將帶大家實現這個演算法,歡迎各位感興趣的開發者閱讀本文。

實現思路

我們通過一個例項來分析下:假設有這樣一個陣列:[2, 4, 5, 6, 7, 8, 9, 11],將奇數移動到最前面後,就是:[11, 9, 5, 7, 6, 8, 4, 2]

通過觀察後,我們發現在掃描這個陣列的時候,如果發現有偶數出現在奇數的前面, 就交換他們的順序,交換之後就符合要求了。

因此,我們可以維護兩個指標:

  • 第一個指標初始化時指向陣列的第一個數字,它只向後移動;
  • 第二個指標初始化時指向陣列的最後一個數字,它只向前移動;

在兩個指標相遇之前,第一個指標總是位於第二個指標的前面。如果第一個指標指向的數字是偶數,並且第二個指標指向的數字是奇數,則交換這兩個數字。

接下來,我們來通過圖來描述下上述例子交換指標的過程,如下所示:

  • 第一個指標永遠指向偶數,如果不為偶數就向後移動;
  • 第二個指標永遠指向奇數,如果不為奇數就向前移動;
  • 當兩個指標各自指向的數都符合條件時,就交換兩個元素的位置;
  • 交換完成後,重複上述步驟,直至兩個指標相遇或者第一個指標位於第二個指標之後則代表問題已得到解決。

image-20220418224313591

實現程式碼

有了思路之後,我們來看下實現程式碼,如下所示:

export class AdjustArrayOrder {
  // 指向陣列元素的兩個指標:一個指向陣列頭部、一個指向陣列尾部
  private begin = 0;
  private end = 0;

  // 調整陣列中奇數與偶數元素的位置:奇數位於偶數前面
  reorderOddEven(arr: Array<number>): void {
    this.end = arr.length - 1;
    while (this.begin < this.end) {
      // 向後移動begin(轉成二進位制跟1做與運算,運算結果為0就表示為偶數),直至其指向偶數
      while (this.begin < this.end && (arr[this.begin] & 0x1) !== 0) {
        this.begin++;
      }

      // 向前移動end(轉成二進位制跟1做與運算,運算結果為1就表示為奇數),直至其指向奇數
      while (this.begin < this.end && (arr[this.end] & 0x1) === 0) {
        this.end--;
      }

      // begin指向了偶數,end指向了奇數
      if (this.begin < this.end) {
        // 交換兩個元素的順序
        [arr[this.begin], arr[this.end]] = [arr[this.end], arr[this.begin]];
      }
    }
    // 重置指標位置
    this.begin = 0;
    this.end = 0;
  }
}

程式碼的可擴充套件性

如果陣列中的元素不按照奇前偶後排列,我們需要將其按照大小進行劃分,所有負數都排在非負數的前面,應該怎麼做?

聰明的開發者可能已經想到了方案:雙指標的思路還是不變,我們只需修改內層while迴圈的的判斷條件即可。

這樣回答沒有問題,確實解決了這個問題,那麼如果再改改題目,我們需要把陣列中的元素分為兩部分,能被3整除的數都在不能被3整除的數前面,應該怎麼做?

經過思考後,我們發現這個問題無論再怎麼改變都有一個共同的部分:雙指標的邏輯永遠不會變。變化的只是判斷條件,那麼我們就可以把變化的部分提取成函式,當作引數讓呼叫者傳進來,這樣就完美的解決了這個問題,也正是我們所提及的程式碼的可擴充套件性

最後,我們來看下實現程式碼,如下所示:

  // 元素排序
  reorder(arr: Array<number>, checkFun: (checkVal: number) => boolean): void {
    this.end = arr.length - 1;
    while (this.begin < this.end) {
      // 向後移動begin
      while (this.begin < this.end && !checkFun(arr[this.begin])) {
        this.begin++;
      }

      // 向前移動end
      while (this.begin < this.end && checkFun(arr[this.end])) {
        this.end--;
      }

      // begin與end都指向了正確的位置
      if (this.begin < this.end) {
        // 交換兩個元素的順序
        [arr[this.begin], arr[this.end]] = [arr[this.end], arr[this.begin]];
      }
    }

測試用例

我們先來測試下奇數在偶數之前的函式處理程式碼能否正常執行,如下所示:

const adjustArrayOrder = new AdjustArrayOrder();
// 奇數在前
const arr = [2, 4, 5, 6, 7, 8, 9, 11];
adjustArrayOrder.reorderOddEven(arr);
console.log(arr);

執行結果如下所示:

image-20220418230700388

最後,我們來測試下reorder函式能否正常執行:

  • 負數在陣列的最前面
// 負數在前
const checkMinusNumber = function (val: number) {
  return val > 0;
};
const arr = [2, 4, 5, 6, 7, -8, -10 - 12, -2];
adjustArrayOrder.reorder(arr, checkMinusNumber);
console.log(arr);

image-20220418230947578

  • 能被3整除的數在陣列的最前面
const checkDivisible = function (val: number) {
  return val % 3 !== 0;
};
const arr = [2, 4, 5, 6, 3, 6, 9, 12];
adjustArrayOrder.reorder(arr, checkDivisible);
console.log(arr);

image-20220418231124400

示例程式碼

文中所舉程式碼的完整版請移步:

寫在最後

至此,文章就分享完畢了。

我是神奇的程式設計師,一位前端開發工程師。

如果你對我感興趣,請移步我的個人網站,進一步瞭解。

  • 文中如有錯誤,歡迎在評論區指正,如果這篇文章幫到了你,歡迎點贊和關注?
  • 本文首發於神奇的程式設計師公眾號,未經許可禁止轉載?

相關文章