7種排序演算法

破衣丁發表於2019-03-27

氣泡排序

自上而下對相鄰的兩個數依次進行比較和調整,讓較大的數往下沉,較小的往上冒。即:每當兩相鄰的數比較後發現它們的排序與排序要求相反時,就將它們互換。

bubble.gif | center | 209x125

function BubbleSort(originalArray) {
  // Flag that holds info about whether the swap has occur or not.
  let swapped = false;
  // Clone original array to prevent its modification.
  const array = [...originalArray];

  for (let i = 1; i < array.length; i += 1) {
    swapped = false;

    for (let j = 0; j < array.length - i; j += 1) {

      // Swap elements if they are in wrong order.
      if (array[j + 1] < array[j]) {
        const tmp = array[j + 1];
        array[j + 1] = array[j];
        array[j] = tmp;

        // Register the swap.
        swapped = true;
      }
    }

    // If there were no swaps then array is already sorted and there is
    // no need to proceed.
    if (!swapped) {
      return array;
    }
  }

  return array;
}
複製程式碼

選擇排序

在要排序的一組數中,選出最小的一個數與第一個位置的數交換;然後在剩下的數當中再找最小的與第二個位置的數交換,如此迴圈到倒數第二個數和最後一個數比較為止。

selection.gif | center | 62x230

function selectionSort(originalArray) {
  const array = [...originalArray];

  for (let i = 0; i < array.length; i += 1) {
    let minIndex = i;
    for (let j = i + 1; j < array.length; j += 1 ) {
      if (array[j] < array[minIndex]) {
        minIndex = j;
      }
    }
    // If new minimum element has been found then swap it with current i-th element.
    if (minIndex !== i) {
      const tmp = array[i];
      array[i] = array[minIndex];
      array[minIndex] = tmp;
    }
  }
  return array;
}
複製程式碼

插入排序

在要排序的一組數中,假設前面(n-1) [n>=2] 個數已經是排好順序的,現在要把第n個數插到前面的有序數中,使得這n個數也是排好順序的。如此反覆迴圈,直到全部排好順序。

insert.gif | center | 300x180

function insertionSort(originalArray) {
  const array = [...originalArray];

  // Go through all array elements...
  for (let i = 0; i < array.length; i += 1) {
    let currentIndex = i;

    // Go and check if previous elements and greater then current one.
    // If this is the case then swap that elements.
    while (
      array[currentIndex - 1] !== undefined
      && (array[currentIndex] < array[currentIndex - 1])
    ) {
      // Swap the elements.
      const tmp = array[currentIndex - 1];
      array[currentIndex - 1] = array[currentIndex];
      array[currentIndex] = tmp;

      // Shift current index left.
      currentIndex -= 1;
    }
  }

  return array;
}
複製程式碼

堆排序

堆是具有以下性質的完全二叉樹:每個結點的值都大於或等於其左右孩子結點的值,稱為大頂堆;或者每個結點的值都小於或等於其左右孩子結點的值,稱為小頂堆。如下圖:

小頂堆 與 大頂堆

image.png | left | 261x170
image.png | left | 244x155

對應的陣列為:[100, 19, 26, 17, 3, 25, 1, 2, 7] 該陣列從邏輯上講就是一個堆結構,我們用簡單的公式來描述一下堆的定義就是: __大頂堆:arr[i] >= arr[2i+1] && arr[i] >= arr[2i+2]  __ __小頂堆:arr[i] <= arr[2i+1] && arr[i] <= arr[2i+2]  __

堆排序原理的動畫:

Heapsort-example.gif | center | 267x214

// 重新調整為大頂堆
function heapify(arr, size, index) {
  let largest = index; // 父級節點
  const left = 2 * index + 1;
  const right = 2 * index + 2;
  if (left < size && arr[left] > arr[largest]) {
      largest = left;
  }
  if (right < size && arr[right] > arr[largest]) {
      largest = right;
  }
  if (largest !== index) {
    swap(arr, index, largest);
    heapify(arr, size, largest);
  }
}
function swap(a, i, j) {
  let tmp = a[i];
  a[i] = a[j];
  a[j] = tmp;
}
//這裡的堆排序用的是最大堆
function heapSort(originalArray) {
  const arr = [...originalArray];
  const size = arr.length;
  // 建立大頂堆
  // Math.floor(size / 2 - 1) 為父級節點index
  for (let i = Math.floor(size / 2 - 1); i >= 0; i--) {
    heapify(arr, size, i);
  }
  for (let i = size - 1; i >= 0; i--) {
    // 將堆頂元素與末尾元素進行交換,使末尾元素最大
    swap(arr, 0, i);
    // 重新調整為大頂堆,直到整個堆序列有序。
    heapify(arr, i, 0);
  }
  return arr;
}
複製程式碼

歸併排序

歸併排序應用遞迴來把陣列分解成左右兩半的一個小陣列,直到這個小陣列被拆分成的元素個數<=1;然後再將小陣列兩兩合併而成有序的大陣列。這裡面就是應用到分治策略。

merge.gif | center | 196x118

function mergeSort(arr) {
  if (arr.length <= 1) {
    return arr;
  }
  // 將陣列拆分兩半
  let midleIndex =  Math.floor(arr.length / 2);
  let leftArray = arr.slice(0, midleIndex);
  let rightArray = arr.slice(midleIndex, arr.length);
  // 遞迴,將左右兩邊的繼續拆,直到每個陣列的length <= 1;
  let leftSortedArray = mergeSort(leftArray);
  let rightSortedArray = mergeSort(rightArray);
  return mergeArrays(leftSortedArray, rightSortedArray);
}

// 對拆分的陣列比較後合併
function mergeArrays(leftArray, rightArray) {
  let sortedArray = [];
  while (leftArray.length && rightArray.length) {
    let minNumElement = null;
    // 找左右兩邊陣列最小的那個
    if (leftArray[0] <= rightArray[0]) {
      minNumElement = leftArray.shift();
    } else {
      minNumElement = rightArray.shift();
    }
    // push到空陣列
    sortedArray.push(minNumElement);
  }
  if (leftArray.length) {
    sortedArray = sortedArray.concat(leftArray);
  }
  if (rightArray.length) {
    sortedArray = sortedArray.concat(rightArray);
  }
  return sortedArray;
}

複製程式碼

快速排序

  • 在陣列中找到一個基準數(pivot)
  • 分割槽,將陣列中比基準數大的放到它的右邊,比基準數小的放到它的左邊
  • 繼續對左右區間重複第二步,直到各個區間只有一個數,這時候,陣列也就有序了。

image.png | left | 208x213
quick.gif | center | 280x214

function quickSort(sourceArray) {
  const arr = [...sourceArray];
  if(arr.length <= 1) {
    return arr;
  }
  // 初始化中間元素左右兩邊陣列
  const leftArray = [];
  const rightArray = [];
  // 將第一個元素作為基準
  const pivotElement = arr.shift();
  const centerArray = [pivotElement];
  // 每個元素與基準值比較,分別放在左、中、右陣列
  while(arr.length > 0) {
    const currentElement = arr.shift();
    if (currentElement === pivotElement) {
      centerArray.push(currentElement);
    } else if (currentElement < pivotElement) {
      leftArray.push(currentElement);
    } else {
      rightArray.push(currentElement);
    }
  }
  // 對左右的陣列遞迴排序
  const leftSortedArray = quickSort(leftArray);
  const rightSortedArray = quickSort(rightArray);
  // 最後將排好序的陣列合並
  return leftSortedArray.concat(centerArray, rightSortedArray);
}
複製程式碼

希爾排序

首先取 gap = Math.floor(arr.length / 2), 將陣列分為 4 組,如下圖中相同顏色代表一組:

shell-sort-step1.1.png | center | 484x108

然後分別對 4 個小組進行插入排序,排序後的結果為:

shell-sort-step1.2.png | center | 486x108

然後,取gap = Math.floor(gap / 2),將原陣列分為 2 小組,如下圖:

shell-sort-step2.1.png | center | 484x110

然後分別對 2 個小組進行插入排序,排序後的結果為:

shell-sort-step2.2.png | center | 484x109

最後,取 d~3~ = 1,進行插入排序後得到最終結果:

shell-sort-step3.png | center | 488x178

function shellSort(originArray) {
  const arr = [...originArray];
  // 定義基準間隔
  let gap = Math.floor(arr.length / 2);
  while (gap > 0) {
    // 迴圈所有間距的元素
    for(let i = 0; i < (arr.length - gap); i += 1) {
      let currentIndex = i;
      let gapShiftedIndex = i + gap;
      console.log(gap);
      while (currentIndex >= 0) {
        // 比較交換陣列
        if(arr[gapShiftedIndex] < arr[currentIndex]) {
          let tmp = arr[currentIndex];
          arr[currentIndex] = arr[gapShiftedIndex];
          arr[gapShiftedIndex] = tmp;
        }
        gapShiftedIndex = currentIndex;
        currentIndex -= gap;
      }
    }
    gap = Math.floor(gap / 2);
  }
  return arr;
}
複製程式碼

附:7種陣列排序演算法的複雜度:

image.png | left | 428x231

相關文章