TypeScript實現陣列相關簡單演算法

MarkMan發表於2018-09-11

演算法(algorithm),在數學(算學)和電腦科學之中,為任何良定義的具體計算步驟的一個序列,常用於計算、資料處理和自動推理。精確而言,演算法是一個表示為有限長列表的有效方法。演算法應包含清晰定義的指令用於計算函式。 - 來自維基百科

寫在前面

演算法看起來在離我們一般的開發者不是很近,但是實際上又和我們的開發息息相關。不同的演算法可能用不同的時間、空間或效率來完成同樣的任務。一個演算法的優劣可以用空間複雜度與時間複雜度來衡量。現在想想大學的時候沒有好好的學習演算法和資料結構真的是後悔的吐血。本文只是簡單理解演算法,並不會深入的討論。畢竟每一個深入討論都夠喝一壺了。只是理解一下演算法的思維和實現。 畢竟9月是個跳槽黃金時期,說不定能幫上你得忙呢?

演算法在在我看來最直觀的作用就在於可以強化我們的程式設計思維邏輯。讓我麼養成是用簡單的方式去解決問題的思維方式。下面我們一起來入演算法的坑。本文中提到的相關的例子,都是相對比較簡單的。大部分來自leetcode陣列部分。程式碼都是我自己實現的,並不一定是最優解。歡迎各位大佬在issue中提交更好的實現方式。解析都寫到了程式碼註釋中。

為了避免一些不必要的錯誤,文中的示例使用Typescript編寫,JavaScript 部分程式碼在這兒,本文主要分了兩大部分 LeetCode/簡單演算法

Leetcode部分

1:刪除排序陣列中的重複項

給定一個排序陣列,你需要在原地刪除重複出現的元素,使得每個元素只出現一次,返回移除後陣列的新長度。不要使用額外的陣列空間,你必須在原地修改輸入陣列並在使用 O(1) 額外空間的條件下完成。

示例 給定陣列 nums = [1,1,2], 函式應該返回新的長度 2, 並且原陣列 nums 的前兩個元素被修改為 1, 2。 你不需要考慮陣列中超出新長度後面的元素。

/**
 * 1 刪除排序陣列中的重複項
 * 給定一個排序陣列,你需要在原地刪除重複出現的元素,使得每個元素只出現一次,返回移除後陣列的新長度。
 * 不要使用額外的陣列空間,你必須在原地修改輸入陣列並在使用 O(1) 額外空間的條件下完成。
 * 示例
 * 給定陣列 nums = [1,1,2], 
 * 函式應該返回新的長度 2, 並且原陣列 nums 的前兩個元素被修改為 1, 2。 
 * 你不需要考慮陣列中超出新長度後面的元素。
 */
const removeDuplicates =  function(nums: number[]): number {
  let i: number = 0
  for (let j = 0; j < nums.length; j++) {
    if(nums[j] !== nums[i]) {
      i++
      nums[i] = nums[j]
    }
  }
  nums.splice(i+1)
  console.log(nums)
  console.log(nums.length)
  return i + 1
}
/**
 * 解析
 * 方法 雙指標法
 * i是慢指標,j是快指標 當我們遇到 nums[j] \neq nums[i]nums[j]≠nums[i] 時,跳過重複項的執行已經結束,
 * 因此我們必須把它(nums[j]nums[j])的值複製到 nums[i + 1]nums[i+1]。然後遞增 ii,接著我們將再次重複相同的過程,直到 jj 到達陣列的末尾為止。
 * 複雜度分析:
 * 時間複雜度: O(n) 假設陣列長度是n 那麼i和j最多就是遍歷n步
 * 空間複雜度: O(1)
 */
removeDuplicates([0,0,1,1,1,2,2,3,3,4])
複製程式碼

2:買賣股票的最佳時機

給定一個陣列,它的第 i 個元素是一支給定股票第 i 天的價格。設計一個演算法來計算你所能獲取的最大利潤。你可以儘可能地完成更多的交易(多次買賣一支股票)。

注意:你不能同時參與多筆交易(你必須在再次購買前出售掉之前的股票)。

示例

輸入: [7,1,5,3,6,4]

輸出: 7

解釋: 在第 2 天(股票價格 = 1)的時候買入,在第 3 天(股票價格 = 5)的時候 賣出, 這筆交易所能獲得利潤 = 5-1 = 4 。 隨後,在第 4 天(股票價格 = 3)的時候買入,在第 5 天(股票價格 = 6)的時候賣出, 這筆交易所能獲得利潤 = 6-3 = 3 。


/**
 * 2:  買賣股票的最佳時機
 * 給定一個陣列,它的第 i 個元素是一支給定股票第 i 天的價格。
 * 設計一個演算法來計算你所能獲取的最大利潤。你最多可以完成一次交易
 * 注意:你不能同時參與多筆交易(你必須在再次購買前出售掉之前的股票)
 * 
 * 輸入: [7,1,5,3,6,4]
 * 輸出: 7
 * 解釋: 在第 2 天(股票價格 = 1)的時候買入,在第 3 天(股票價格 = 5)的時候賣出, 這筆交易所能獲得利潤 = 5-1 = 4 。
 * 隨後,在第 4 天(股票價格 = 3)的時候買入,在第 5 天(股票價格 = 6)的時候賣出, 這筆交易所能獲得利潤 = 6-3 = 3 。
 */
 // 第一種方式
const maxProfit = function (prices: number[]): number {
  if(prices.length < 2) return 0
  // 定義利潤
  let count: number = 0
  let PreMin:number =prices[0]
  // 獲取最大的單天利潤
  for (let i = 0; i < prices.length; i++) {
    count = Math.max(count, prices[i] - PreMin)
    PreMin = Math.min(PreMin, prices[i])
  }
  console.log(count)
  return count
}
/**
 * 解析: 貪心演算法
 */
console.log('=================股票最佳購買時機貪心演算法===================');
console.log(maxProfit([7,1,5,3,6,4]));
console.log('====================================');

// 第二種方式 這種方式更加簡單
const maxProfitMore = function (prices: number[]) :number{
  if(prices.length < 2) return 0
  let ret = 0
  for (let i = 0; i < prices.length; i++) {
    // 如果第二天的價格大於當天的,那麼就計算利潤
    if (prices[i+1] > prices[i]) {
         ret += prices[i+1] - prices[i]
    }
  }
  return ret
}
/**
 * 解析: 非貪心演算法 
 * 只要下一天的價錢 大於今天的價錢 那我們就賣出當前天的 最終的結果就是我們的利潤總和
 */
console.log('==================股票最佳購買時機非貪心演算法==================');
console.log(maxProfitMore([7,1,5,8,3,6,4]))
console.log('=============================================');
複製程式碼

3:旋轉陣列

給定一個陣列,將陣列中的元素向右移動 k 個位置,其中 k 是非負數。

示例 輸入: [1,2,3,4,5,6,7] 和 k = 3

輸出: [5,6,7,1,2,3,4]

解釋:

向右旋轉 1 步: [7,1,2,3,4,5,6]

向右旋轉 2 步: [6,7,1,2,3,4,5]

向右旋轉 3 步: [5,6,7,1,2,3,4]

要求

要求使用空間複雜度為 O(1) 的原地演算法。

/**
 * 3: 給定一個陣列,將陣列中的元素向右移動 k 個位置,其中 k 是非負數。
 * 要求O(1)的空間複雜度,對原陣列進行操作
 */
const rotate = function(nums: number[], k: number) {
  // 迴圈k,通過k我們可以確定需要移動的次數
  for (let i = 0; i < k; i++) {
    // 先在前面插入我們需要移動的地方
    nums.unshift(nums[nums.length -1 - i])
  }
  // 最後再去處理我們的陣列
  nums.splice(nums.length - k, k)
}
rotate([1,2,3,4,5,6,7],3)
複製程式碼

4:存在重複

給定一個整數陣列,判斷是否存在重複元素。如果任何值在陣列中出現至少兩次,函式返回 true。如果陣列中每個元素都不相同,則返回 false。

示例 輸入: [1,2,3,1] 輸出: true

/**
 * 4: 存在重複 
 * 給定一個整數陣列,判斷是否存在重複元素。
 * 如果任何值在陣列中出現至少兩次,函式返回 true。如果陣列中每個元素都不相同,則返回 false。
 * 
 * 這個一定不是最優解
 */
const containsDuplicate = function (nums: number[]) :boolean{
    // 設定flag
    let judge = false
    // 容錯判斷
    if (nums.length <= 1) {
      return judge
    }
    // 通過最簡單直白的去重的思想去處理
    let current :number[] =[]
    for (let i = 0; i < nums.length; i++) {
      if (current.indexOf(nums[i]) === -1) {
        current.push(nums[i])
      } else {
        return judge = true
      }
    }
    return judge
}
console.log('================是否存在重複演算法====================');
console.log(containsDuplicate([3,1]))
console.log('====================================');
// 這個其實是非常常見而且簡單得一個演算法 但是要考慮到得情況多一點
複製程式碼

5:只出現一次的數字

給定一個非空整數陣列,除了某個元素只出現一次以外,其餘每個元素均出現兩次。找出那個只出現了一次的元素。

示例

輸入: [2,2,1]

輸出: 1

要求 你的演算法應該具有線性時間複雜度。 不適用額外的空間來實現

/**
 * 5: 只出現一次得數字
 * 給定一個非空整數陣列,除了某個元素只出現一次以外,其餘每個元素均出現兩次。找出那個只出現了一次的元素。
 * 你的演算法應該具有線性時間複雜度。 不使用額外空間來實現
 */
const singleNumber = function(nums: number[]) :number {
  let index= -1
  // 雙層進行比對 目的是當前key和陣列中的每一個key進行比較
  nums.forEach((key, j)=> {
    //每次迴圈小遊標
    let count = 0
    for (let k = 0; k < nums.length; k++) {
      if (key === nums[k]) {
        count += 1
      }
      // 迴圈完找出符合條件的下標
      if (k === nums.length -1 && count === 1) {
        index = j
      }
    }
  })
  return nums[index]
}
console.log('=================查詢單獨數演算法===================');
console.log(singleNumber([2,2,1,3,3]))
console.log('====================================');
複製程式碼

6:兩個陣列的交集

給定兩個陣列,編寫一個函式來計算它們的交集。

示例

輸入: nums1 = [1,2,2,1], nums2 = [2,2]

輸出: [2,2]

要求

  • 輸出結果中每個元素出現的次數,應與元素在兩個陣列中出現的次數一致。
  • 我們可以不考慮輸出結果的順序。
/**
 * 6:求兩個陣列的交集
 */
const intersect = function (nums1:number[], nums2:number[]) :number[]{
  let arr:number[] = []
  for (let i = 0; i < nums1.length; i++) {
    if (nums2.indexOf(nums1[i]) !== -1 ) {
      nums2.splice(nums2.indexOf(nums1[i]), 1)
      arr.push(nums1[i])
    }
  }
  return arr
}
/**
 * 解析: 在求交集的過程中。主要的思想是關於什麼是交集。
 * 兩個陣列的重合部分理論上來講就是交集
 * 迴圈其中一個陣列nums1在後在另外一個陣列nums2中比對是否出現,如果出現的話就刪除nums2中出現過的陣列(注意是修改nums2)
 */
intersect([1,2,2,1], [2,2])
複製程式碼

7:加1

給定一個由整陣列成的非空陣列所表示的非負整數,在該數的基礎上加一.你可以假設除了整數 0 之外,這個整數不會以零開頭。

示例

輸入: [1,2,3]

輸出: [1,2,4]

/**
 * 7:加1
 * 給定一個由整陣列成的非空陣列所表示的非負整數,在該數的基礎上加一。
 * 你可以假設除了整數 0 之外,這個整數不會以零開頭。
 */
const plusOne =function (nums: number[]) :number[] {
  let j = nums.length - 1
  // js無法正常表示大於16位的整數【非科學計數】
  for (let i = nums.length - 1; i >=0; i--) {
    // 開始逐個進行加法運算
    if(i == j) {
      // 大於10的情況
      if(nums[i] + 1 >= 10){
        nums[i] = nums[i] + 1 -10
        j--
        // 最後一次迴圈
        if (i === 0) {
          nums.unshift(1)
        }
      } else {
        nums[j] ++
      }
    } else {
      break
    }
  }
  console.log(nums)
  return nums
}
/**
 * 解析: 如果使用太大的數的話轉成數字再加1是不行的,我們需要對陣列中的的單個資料進行運算,同樣的是以輔助遊標進行運算
 * 輔助遊標j的主要作用是記錄需要+1的位置,如果當前的下標不等於j那麼就跳出了迴圈:同時也提高了效能
 */
console.log('================加1演算法====================');
console.log(plusOne([8,2,1,,1,2,2,2,3,5,5,5,5,5,2,3,4,2,3,4,5,5,5,5,2,9]))
console.log('====================================');
複製程式碼

8:移動零

給定一個陣列 nums,編寫一個函式將所有 0 移動到陣列的末尾,同時保持非零元素的相對順序。

示例

輸入: [0,1,0,3,12]

輸出: [1,3,12,0,0]

要求

  • 必須在原陣列上操作,不能拷貝額外的陣列。
  • 儘量減少操作次數。
/**
 * 8: 移動零
 * 給定一個陣列 nums,編寫一個函式將所有 0 移動到陣列的末尾,同時保持非零元素的相對順序。
 */
const moveZeroes = function(nums: number[]) {
  // 0出現的個數
  let j = 0
  nums.forEach((el: number, index: number, arr: number[]) => {
    if (nums[j] === 0) {
      nums.splice(j, 1)
      nums.push(0)
    } else {
      j++ 
    }
  })
  console.log(nums)
}
/**
 * 解析: 新建一個小遊標j 這個是用來標識0出現的地方,每次移動完之後小遊標是不變化的,因為原陣列已經修改所以要固定一下游標
 * 雙遊標法在演算法真的很實用
 */
console.log('==================移動零演算法==================');
moveZeroes([1,2,0,0,0,1])
console.log('====================================');
複製程式碼

9:兩數之和

給定一個整數陣列和一個目標值,找出陣列中和為目標值的兩個數。你可以假設每個輸入只對應一種答案,且同樣的元素不能被重複利用。

示例

給定 nums = [2, 7, 11, 15], target = 9

因為 nums[0] + nums[1] = 2 + 7 = 9

所以返回 [0, 1]

/**
 * 第一種解法
 * @param nums 
 * @param target 
 */
const twoSumA = function (nums: number[], target: number) :number[] {
  console.log('兩數求和第一種解法')
  let arr = [0,0] ,flag = false
  for (let i = 0; i < nums.length; i++) {
    compare(i)
    if (flag) {
      arr = [i, compare(i)]
      break
    }
  }
  /**
   * @param num 
   */
  function compare(index: number) :number {
    for (let j = 0; j < nums.length; j++) {
      if (j!== index && nums[index] + nums[j] === target) {
        flag = true
        return j
      }
    }
  }
  return arr
}
/**
 * 第二種解法
 */
const twoSumB = function (nums: number[], target: number) :number[] {
  let arr = [0,0]
  console.log('兩數求和第二種解法')
  for (let i = 0; i < nums.length; i++) {
    for (let j = 0; j < nums.length; j++) {
      if (j!== i && nums[i] + nums[j] === target) {
        return arr = [i,j]
      }
    }
  }
  return arr
}
// 在進行一個陣列中兩個數得比較中:一定要注意在相加得時候要排除自身去進行相加。
console.log('=================兩數之和演算法===================');
console.log(twoSumA([3,2,4],6))
console.log(twoSumB([2, 7, 11, 15],9))
console.log('====================================');
複製程式碼

10:有效的數獨

判斷一個 9x9 的數獨是否有效。只需要根據以下規則,驗證已經填入的數字是否有效即可。

  1. 數字 1-9 在每一行只能出現一次。
  2. 數字 1-9 在每一列只能出現一次。
  3. 數字 1-9 在每一個以粗實線分隔的 3x3 宮內只能出現一次。

示例

給定

[
  ["5","3",".",".","7",".",".",".","."],
  ["6",".",".","1","9","5",".",".","."],
  [".","9","8",".",".",".",".","6","."],
  ["8",".",".",".","6",".",".",".","3"],
  ["4",".",".","8",".","3",".",".","1"],
  ["7",".",".",".","2",".",".",".","6"],
  [".","6",".",".",".",".","2","8","."],
  [".",".",".","4","1","9",".",".","5"],
  [".",".",".",".","8",".",".","7","9"]
]
複製程式碼

輸出 js true

說明

  • 一個有效的數獨(部分已被填充)不一定是可解的。
  • 只需要根據以上規則,驗證已經填入的數字是否有效即可。
  • 給定數獨序列只包含數字 1-9 和字元 '.' 。
  • 給定數獨永遠是 9x9 形式的。
/**
 * 10:有效得數獨
 */
let board = /* [
  ["5","3",".",".","7",".",".",".","."],
  ["6",".",".","1","9","5",".",".","."],
  [".","9","8",".",".",".",".","6","."],
  ["8",".",".",".","6",".",".",".","3"],
  ["4",".",".","8",".","3",".",".","1"],
  ["7",".",".",".","2",".",".",".","6"],
  [".","6",".",".",".",".","2","8","."],
  [".",".",".","4","1","9",".",".","5"],
  [".",".",".",".","8",".",".","7","9"] 
]*/
[["7",".",".",".","4",".",".",".","."],
 [".",".",".","8","6","5",".",".","."],
 [".","1",".","2",".",".",".",".","."],
 [".",".",".",".",".","9",".",".","."],
 [".",".",".",".","5",".","5",".","."],
 [".",".",".",".",".",".",".",".","."],
 [".",".",".",".",".",".","2",".","."],
 [".",".",".",".",".",".",".",".","."],
 [".",".",".",".",".",".",".",".","."]
]
const isValidSudoku = function (board: string[][]): boolean {
  let isPass = true
  const sudokuDeep = 9 // 數獨深度
  // 判斷行和列
  for (let i = 0; i < sudokuDeep; i++) {
    let row:any = {}
    let col:any = {}
    for (let j = 0; j < sudokuDeep; j++) {
      // 判斷行
      /**
       * 判斷方式
       * 首先判斷不為'.' => 然後判斷是否存在row物件中 
       */
      if (board[i][j] !== '.') {
        if (row[board[i][j]]) {
          console.log(board[i][j])
          return isPass = false
        } else {
          row[board[i][j]] = board[i][j]
        }
      }
      // 判斷列
      if (board[j][i] !== '.') {
        if (col[board[j][i]]) {
          console.log(board[j][i]);
          return isPass = false
        } else {
          col[board[j][i]] = board[j][i]
        }
      }
      // 判斷九宮格 通過餘數的形式判斷出來當前所處的9宮格
      // 先計算出位置
      let c = Math.floor(i/3)  // col位置
      let r = Math.floor(j/3) // row 位置
      // 勾勒出九宮格
      for (let m = c*3; m < c*3 + 3; m++) {
        for (let n = r * 3; n < r * 3 + 3; n++) {
          if (m === i && n === j) {
            // m === i && n === j 這時指向同一個位置
            continue
          } else {
            // 最重要的一次求值判斷
            if(board[m][n] != '.' && board[i][j]!== '.' && (board[i][j]) === board[m][n]) {
              return isPass = false
            } 
          }
        }
      }
      
    }
  }
  return isPass
}
console.log('=================有效數獨演算法結果===================');
console.log(isValidSudoku(board))
console.log('====================================');
複製程式碼

11:旋轉影象

給定一個 n × n 的二維矩陣表示一個影象。將影象順時針旋轉 90 度。

示例

給定

[
  [1,2,3],
  [4,5,6],
  [7,8,9]
],
複製程式碼

輸出

[
  [7,4,1],
  [8,5,2],
  [9,6,3]
]
複製程式碼

要求

你必須在原地旋轉影象,這意味著你需要直接修改輸入的二維矩陣。請不要使用另一個矩陣來旋轉影象。

/**
 * 11: 旋轉影象
 **/
const matrix = [
  [1,2,3],
  [4,5,6],
  [7,8,9]
]
// 
/* const matrix = [
  [ 5, 1, 9,11],
  [ 2, 4, 8,10],
  [13, 3, 6, 7],
  [15,14,12,16]
] */
const rotateMaps = function (matrix:number[][]) {
  const n = matrix.length
  // 倒敘迴圈進行90度的反轉
  for (let i = n-1; i >= 0; i--) {
    // 新陣列補位到原陣列中,為了是實現原地的旋轉操作,如果不需要
    for (let j = 0; j < n ; j++) {
      // console.log(`當前座標[${i},${j}]`)
      const current = matrix[i][j]
      matrix[j].push(current)
      // 沒完成一組的賦值操作,就刪除旋轉前陣列
      if(j === n - 1) {
        matrix[i].splice(0, n)
      }
    }
  }
  console.log(matrix)
  // return matrix
}

console.log('================旋轉影象演算法====================');
console.log(rotateMaps(matrix));
console.log('====================================');
複製程式碼

其他部分(持續更新...)

這一部分先出demo,後面的程式碼中的解析註釋會慢慢加上

12:查詢父親節點

fid為0代表一級,fid如果和fid為0的cid相等的話代表二級 以此類推...

/**
 * 10: 找父親節點
 * fid為0代表一級,fid如果和fid為0的cid相等的話代表二級 以此類推...
 */
const findArr = [
  {"fid":0,"cid":3,"flag":"最外層3"},
  {"fid":0,"cid":4,"flag":"最外層4"},
  {"fid":4,"cid":5,"flag":"最外層-4"},
  {"fid":5,"cid":6,"flag":"最外層-4-1"},
  {"fid":0,"cid":7,"flag":"最外層7"},
  {"fid":7,"cid":8,"flag":"最外層-7"},
  {"fid":0,"cid":9,"flag":"最外層9"},
  {"fid":9,"cid":10,"flag":"最外層9-1"},
  {"fid":9,"cid":11,"flag":"最外層9-2"},
  {"fid":11,"cid":12,"flag":"最外層9-2-1"}
]
/**
 * 第一種方法:雙遞迴方式
 * @param arr 
 */
const findfid  = function (arr: any[]): any[] {
  let newArr:any[] = []
  for (let i = 0; i < arr.length; i++) {
    let flagId = arr[i].cid // 取出來一個flag 這個是用於和下一個級別匹配的
    for (let j = 0; j < arr.length; j++) {
      const elJ = arr[j]
      if (elJ.fid === flagId) { // fid 和 上級id 匹配
        (arr[i].children = []).push(elJ)
      }
    }
    // 只存入第一等級
    arr[i].fid === 0 && newArr.push(arr[i])
  }
  return newArr
}
/**
 * 第二種方法: 使用物件儲存id 然後和fid進行對比
 * @param arr 
 */
const findfidByObj = function (arr: any[]): any[] {
  let newArr:any[] = []
  let flagObj: any = {}
  arr.forEach(v => {
    flagObj[v.cid] = v
  })
  arr.forEach (item => {
    // 根據當前遍歷物件的fid,去map物件中找到對應索引的id
    const top = flagObj[item.fid]
    if (top) {
      // 如果找到索引,那麼說明此項不在頂級當中,那麼需要把此項新增到,他對應的父級中
      (top.children || (top.children = [])).push(item)
    } else {
      // 如果沒有在map中找到對應的索引ID,那麼直接把當前的item新增到newData結果集中作為頂級
      newArr.push(item)
    }
  })
  return newArr
}
console.log('====================================');
console.log('找父親節點方式');
console.log(findfid(findArr))
console.log(findfidByObj(findArr))
console.log('====================================');
複製程式碼

13:簡單選擇排序

選擇排序(Selection sort)是一種簡單直觀的排序演算法。它的工作原理是每一次從待排序的資料元素中選出最小(或最大)的一個元素,存放在序列的起始位置,直到全部待排序的資料元素排完。 選擇排序是不穩定的排序方法。

/**
 * 交換引數
 * @param {*} arr 
 * @param {*} a 
 * @param {*} b 
 */
const swap = function(arr: number[], a:number, b:number) {
  let curr = arr[a]
  arr[a] = arr[b]
  arr[b] = curr
}
/**
 * 
 * @param {選擇排序演算法} arr 
 */
const sort = function (arr: number[]) {
  console.time()
  for (let i = 0; i < arr.length; i++) {
    //假設遍歷的當前第一個是最小的
    let minIndex = i
    //第二次遍歷把arr[minIndex]和陣列中的其他的值進行遍歷
    for (let j = 0; j < arr.length; j++) {
      if(arr[minIndex] > arr[j]){
        minIndex = j
      }
    }
    //外層迴圈做交換
    swap(arr,minIndex,i)
  }
  console.log(arr)
  console.timeEnd()
}
sort([3,6,28,123,34])
複製程式碼

14:簡單氣泡排序

氣泡排序(Bubble Sort),是一種電腦科學領域的較簡單的排序演算法。它重複地走訪過要排序的元素列,依次比較兩個相鄰的元素,如果他們的順序(如從大到小、首字母從A到Z)錯誤就把他們交換過來。走訪元素的工作是重複地進行直到沒有相鄰元素需要交換,也就是說該元素已經排序完成。

/**
 * @param {*氣泡排序演算法} arr 
 */
const bubbleSort = function (arr: number[]){
  console.log('冒泡演算法開始時間:')
  console.time()
  for (let i = 0; i < arr.length; i++) {
    // 這個迴圈時獲取到之後的項進行比較
    for (let j = i+1; j > 0; j--) {
      // 這個核心就是 如果當前項小於前一項那麼當前項向上冒泡
      if(arr[i] < arr[j-1]){
        // 冒泡交換
        swap(arr,j,j-1)
      }
    }
  }
  console.timeEnd()
  console.log(arr)
}
bubbleSort([3,123,6,28,34])
複製程式碼

15:簡單插入排序

插入排序是基於比較的排序。所謂的基於比較,就是通過比較陣列中的元素,看誰大誰小,根據結果來調整元素的位置。

//插入排序演算法
/**
 * 
 * @param {插入排序} arr 
 */
const insertSort = function (arr: number[]){
  console.time()
  for (let i = 0; i < arr.length; i++) {
    // 在一次迴圈的時候首先快取下來當前的值和上一個index 快取上一個index用來比較
    let compareIndex = i -1
    let currentValue = arr[i]
    // 在當前位置可以比較並且當前的值小於前一項的值的時候插入快取的值然後修改index
    while (compareIndex >=0 && arr[compareIndex] > currentValue) {
      arr[compareIndex + 1] = arr[compareIndex]
      compareIndex--
    }
    arr[compareIndex + 1 ] = currentValue
  }
  console.timeEnd()
  console.log(arr)
}
insertSort([3,2,1])
複製程式碼

16:簡單二分查詢演算法

二分查詢也稱為折半查詢。是指在有序的陣列裡找出指定的值,返回該值在陣列中的索引。

/**
 * 二分查詢演算法 
 * 什麼叫二分查詢? 二分查詢也稱為折半查詢。是指在有序的陣列裡找出指定的值,返回該值在陣列中的索引。
 * (1)從有序陣列的最中間元素開始查詢,如果該元素正好是指定查詢的值,則查詢過程結束。否則進行下一步; 
 * (2)如果指定要查詢的元素大於或者小於中間元素,則在陣列大於或小於中間元素的那一半區域查詢,然後重複第一步的操作; 
 * (3)重複以上過程,直到找到目標元素的索引,查詢成功;或者直到子陣列為空,查詢失敗。
 * 注意: 這個先要把陣列排序一下 在有序陣列中查詢
 * 優點是比較次數少,查詢速度快,平均效能好;
 * 其缺點是要求待查表為有序表,且插入刪除困難。因此,折半查詢方法適用於不經常變動而查詢頻繁的有序列表。
 */
/**
 * 非遞迴實現
 * @param {*} arr 
 * @param {*} target 
 */
function binarySearcNoRecursive(arr: number[], target: number){
  let low: number = 0, high: number = arr.length-1
  while(low <= high) {
    // 首先找到中間位置
    let middle = ((high + low ) / 2)
    if( target === arr[middle]){
      return middle
    } else if (target > arr[middle]){
      low = middle + 1
    } else if ( target < arr[middle] ){
      high = middle -1
    }else { 
      return -1
    }
  }
}
const result = binarySearcNoRecursive( [1,2,3,4,5,6,7,8,9,10,11,23,44,86], 23)
console.log(`二分查詢不用迴圈找到的位置:${result}`)
/**
 * 遞迴實現 迴圈呼叫自身
 * @param {*} arr 
 * @param {*} target 
 */
function binarySearcRecursive(arr: number[], low:number, high: number, target:number){
  if(low > high){
    return -1
  }
  let middle = ((high + low ) / 2)
  if(arr[middle] === target){
    return middle
  } else if(arr[middle] > target){
    high = middle -1
    binarySearcRecursive(arr, low, high, target)
  } else if(arr[middle] < target){
    low = middle + 1
    binarySearcRecursive(arr, low, high, target)
  }
}
const  recursiveRes = binarySearcNoRecursive( [1,2,3,4,5,6,7,8,9,10,11,23,44,86], 3)
console.log(`二分查詢不用迴圈找到的位置:${recursiveRes}`)
複製程式碼

總結

演算法再程式設計中佔據著相當重要的地位,語言的技術都可以速成。但是演算法需要紮實的理論知識作為地基。本文只是根據leetcode中的題目,簡單的實現一下。感受一下演算法的魅力。學習的話我建議還是系統深入的學。

程式碼地址

相應的JavaScript示例程式碼地址

原文地址 如果覺得有用得話給個⭐吧

相關文章