數據結構和算法基礎之時間複雜度爲O(n²)排序(偏向前端方向)

前言

在實際項目開發中,無論是後端仍是前端,最基本的操做就是數據的CRUD。換句話說,後端是根據某些條件組裝數據,前端是拿着後端提供的數據,進行數據展現。可是無論在進行數據封裝仍是展現,其中在特定的場景下,須要根據某些條件,對數據進行排序。而在既定的現有框架下,都有現成的方法對數據進行排序處理。javascript

可是,在開發中,有沒有想過,這些排序底層是如何實現的,還有就是針對不一樣的數據,不一樣的排序是否在性能方面有不一樣的體現。前端

後端的數據排序不在本文的討論中,本文只針對前端的排序的一些思路實踐.(該文只在前端範圍內討論排序的實現)java

前端案例分析

如今有一組數據以下:算法

0: {layoutId: 1028, priceCodeId: 1000408, activityId: 0, roomNum: 1}
1: {layoutId: 1028, priceCodeId: 1000408, activityId: 0, roomNum: 1}
2: {layoutId: 1028, priceCodeId: 1000408, activityId: 0, roomNum: 1}
3: {layoutId: 1029, priceCodeId: 1000409, activityId: 1, roomNum: 1}
4: {layoutId: 1029, priceCodeId: 1000409, activityId: 1, roomNum: 1}
5: {layoutId: 1269, priceCodeId: 1000410, activityId: 2, roomNum: 1}
複製代碼

如今有一個需求就是,須要根據layoutId,priceCodeId,activityId這個三個組合主鍵來對roomNum進行求和。後端

處理結果以下:數組

0: {layoutId: 1028, priceCodeId: 1000408, activityId: 0, roomNum: 3}
1: {layoutId: 1029, priceCodeId: 1000409, activityId: 1, roomNum: 2}
2: {layoutId: 1069, priceCodeId: 1000410, activityId: 2, roomNum: 1}
複製代碼

不要驚訝,這不是後臺處理邏輯,這是一個真真切切的前端數據處理邏輯。有的前端可能會說,數據處理是後臺的事。這個前端很差處理,我想說,若是是這個思惟方式,感受你被其餘語言開發工程師鄙視理所固然的。bash

因此,我信奉一個道理:框架

並不可怕,不敢正視本身的弱點纔是最可怕的。性能

其實相似上述的問題,可能用JS的一些現有的庫,可能很好實現,可是這個文章沒有選擇這些現有庫,而是以一種最原始的方式來實現。或許還能有更好的方式來實現,歡迎你們批評指導大數據

須要指出的是,這個案例和本文講的排序沒有很大的關係,可是在文末的實現的時候,用到了一些排序的思路方法和方式

在開始以前,須要明確幾個概念:原地排序穩定性

  • 原地排序:特指空間複雜度是 O(1) 的排序算法
  • 穩定性:若是待排序的序列中存在值相等的元素,通過排序以後,相等元素之間原有的前後順序不變。若是相同的值先後順序沒有發生變化,叫穩定的排序算法;若是先後順序發生變化,那對應的排序算法就叫做不穩定的排序算法

冒泡排序(Bubble Sort)

冒泡排序只會操做相鄰的兩個數據。每次冒泡操做都會對相鄰的兩個元素進行比較,看是否知足大小關係要求。若是不知足就讓它倆互換。一次冒泡會讓至少一個元素移動到它應該在的位置,重複 n 次,就完成了 n 個數據的排序工做。

如今咱們假設有29,10,14,37,14這些數據,須要按升序排序

Talk is cheap. Show you the code

arr 爲待排序數組,n爲數組個數

  • 版本1:
function BubbleSort1(arr,n){
    if(n<=1) return ;
    for(let i=0;i<n;i++){
        for(let j = i+1;j<n;j++){
            if(arr[i]>arr[j]){
                let tempValue = arr[i];
                arr[i] = arr[j];
                arr[j] = tempValue;
            }
        }
    }
    return arr;
}
複製代碼
  • 版本2:
function bubbleSort2(arr, n) {
    if (n <= 1) return;
   
   for (let i = 0; i < n; ++i) {
      // 提早退出冒泡循環的標誌位
      let flag = false;
      for (let j = 0; j < n - i - 1; ++j) {
        if (arr[j] > arr[j+1]) { // 交換
          let tmp = arr[j];
          arr[j] = arr[j+1];
          arr[j+1] = tmp;
          flag = true;  // 表示有數據交換 
        }
      }
      if (!flag) break;  // 沒有數據交換,提早退出
    }
  }
複製代碼

note

  • 上面的兩個實現方式都是升序排列,可是若是你用斷點追或者實際模擬一遍就會發現,這兩個版本的數據冒泡方向是相反的。版本1的是先把較小數據排列好,版本2是把較大數據排列好
  • 結合原地排序穩定性來分析,冒泡排序的空間複雜度爲 O(1),是一個原地排序算法。只有交換才能夠改變兩個元素的先後順序。爲了保證冒泡排序算法的穩定性,當有相鄰的兩個元素大小相等的時候,咱們不作交換,相同大小的數據在排序先後不會改變順序,因此冒泡排序是穩定的排序算法

插入排序(Insertion Sort)

插入排序具體的實現思路就是:找到已存數據列表中插入位置,將數據插入對應位置。是一個找茬算法。

思路分析

將數組中的數據分爲兩個區間已排序區間未排序區間初始已排序區間只有一個元素,就是數組的第一個元素。插入算法的核心思想是取未排序區間中的元素,在已排序區間中找到合適的插入位置將其插入,並保證已排序區間數據一直有序。重複這個過程,直到未排序區間中元素爲空,算法結束。

咱們仍是以29,10,14,37,14爲例。

Talk is cheap. Show you the code

arr 爲待排序數組,n爲數組個數

function insertionSort(arr, n) {
    if (n <= 1) return;
   
    for (let i = 1; i < n; ++i) {
      let value = arr[i];
      //肯定已排序區間,這裏的j是一個"哨兵",守衛者"邊界"
      let j = i - 1;
      // 查找插入的位置(這裏的) --j也就是從已排中找對應合適的位置
      for (; j >= 0; --j) {
        if (arr[j] > value) {
          arr[j+1] = arr[j];  // 數據移動
        } else {
          break;
        }
      }
      arr[j+1] = value; // 插入數據
    }
  }
複製代碼

note

  • 結合原地排序穩定性來分析:不須要額外的存儲空間,因此空間複雜度是 O(1),插入排序是原地排序算法。在插入排序中,對於值相同的元素,咱們能夠選擇將後面出現的元素,插入到前面出現元素的後面,這樣就能夠保持原有的先後順序不變,因此插入排序是穩定的排序算法

選擇排序(Selection Sort)

選擇排序算法的實現思路有點相似插入排序,也分已排序區間和未排序區間。可是選擇排序每次會從未排序區間中找到最小的元素,將其放到已排序區間的末尾

咱們仍是以29,10,14,37,14爲例。 屏幕錄製 2019-09-27 下午2.30.52_52.gif

Talk is cheap. Show you the code

function selectionSort(arr) {
    let len = arr.length;
    let minIndex, temp;
    for (let i = 0; i < len - 1; i++) {
        minIndex = i;
        for (let j = i + 1; j < len; j++) {
            if (arr[j] < arr[minIndex]) {     // 尋找最小的數
                minIndex = j;                 // 將最小數的索引保存
            }
        }
        temp = arr[i];
        arr[i] = arr[minIndex];
        arr[minIndex] = temp;
    }
    return arr;
}
複製代碼

note

  • 原地排序:是原地排序
  • 穩定排序:不是穩定排序,好比5,8,5,2,9 這樣一組數據,使用選擇排序算法來排序的話,第一次找到最小元素 2,與第一個 5 交換位置,那第一個 5 和中間的 5 順序就變了,因此就不穩定了。

要點彙總

針對這三種時間複雜度都爲O(n²)排序統一作一次對比:

排序方式 是否原地排序 是否穩定排序 最好 最壞 平均
冒泡排序 O(n) O(n²) O(n²)
插入排序 O(n) O(n²) O(n²)
選擇排序 × O(n²) O(n²) O(n²)

文末彩蛋

在剛開始的時候,有一個純前端的數據聚合問題。話很少說,來段代碼儘儘興。嗨皮一下

來咯,來咯。

版本1:

function polymerizationData(sourceArr, flagKeyArr, targetKeyArr) {
  let tempSourceArr = [...sourceArr];
  //肯定已排區間,默認是數組第一個
  let targetArr = tempSourceArr.splice(0, 1);
  while (tempSourceArr.length) {
    for (let i = 0; i < targetArr.length; i++) {
        //這裏的i,其實也是一個哨兵,可是他的起始位置是從已排區間的開始位置算
      let outerItem = targetArr[i];
      let j = 0;
      for (; j < tempSourceArr.length; j++) {
        //從未排區間,取值,進行數據比對
        let innerItem = tempSourceArr[j];
        let isAllEqual = true;
        for (let flagindex = 0; flagindex < flagKeyArr.length; flagindex++) {
          if (innerItem[flagKeyArr[flagindex]] != outerItem[flagKeyArr[flagindex]]) {
            isAllEqual = false;
            break;
          }
        }
        if (isAllEqual) {
          for (let targetKeyIndex = 0; targetKeyIndex < targetKeyArr.length; targetKeyIndex++) {
            outerItem[targetKeyArr[targetKeyIndex]] += innerItem[targetKeyArr[targetKeyIndex]]
          }
          tempSourceArr.splice(j, 1);
          j = -1;
        } else {
          targetArr.push(tempSourceArr.splice(j, 1)[0]);
          break;
        }
      }
    }
  }
  return targetArr;
}
複製代碼

版本2(版本1的升級版)

function AdvancePolymerizationData(sourceArr, flagKeyArr, targetKeyArr) {
  let storeDesignationKey = {};
  let tempSourceArr = [...sourceArr];
  let finalArr = tempSourceArr.splice(0, 1);
  flagKeyArr.map(item => {
    storeDesignationKey[item] = finalArr[0][item];
  })
  let i = 0, j = 0;
  for (; i < tempSourceArr.length; i++) {
    let isEqual = flagKeyArr.every(item => {
      return tempSourceArr[i][item] == storeDesignationKey[item]
    })
    if (isEqual) {
      targetKeyArr.map(item => {
        finalArr[j][item] += tempSourceArr[i][item]
      })
      tempSourceArr.splice(i, 1);
      i = -1;
    } else {
      let requirePushItem = tempSourceArr.splice(i, 1)[0];
      flagKeyArr.map(item => {
        storeDesignationKey[item] = requirePushItem[item];
      })
      finalArr.push(requirePushItem);
      j++;
      i = -1;
    }
  }
  return finalArr;
}

複製代碼

上面的代碼調用方式

AdvancePolymerizationData(sourceArray, ["layoutId", "priceCodeId", "activityId"], ["roomNum"]);

複製代碼

代碼分析

其實,相似這種的數據處理,有一個共同的點,就是須要有一個相似插入排序選擇排序已排序區,來做爲一個targetArray/finalArr。在進行isAllEqual的判斷處理就相似於在排序中的數據判斷。在知足狀況下,進行數據拼接或者數據移動

相關文章
相關標籤/搜索