【快速排序算法詳解】Java/Go/Python/JS/C不同語言實(shí)現(xiàn)

【快速排序算法詳解】Java/Go/Python/JS/C不同語言實(shí)現(xiàn)

說明

快速排序(QuickSort),又稱分區(qū)交換排序(partition-exchange sort)五垮,簡(jiǎn)稱快排抠忘。快排是一種通過基準(zhǔn)劃分區(qū)塊结耀,再不斷交換左右項(xiàng)的排序方式留夜,其采用了分治法,減少了交換的次數(shù)图甜。它的基本思想是:通過一趟排序?qū)⒁判虻臄?shù)據(jù)分割成獨(dú)立的兩部分碍粥,其中一部分的所有數(shù)據(jù)都比另外一部分的所有數(shù)據(jù)都要小,然后再按此方法對(duì)這兩部分?jǐn)?shù)據(jù)分別進(jìn)行快速排序黑毅,整個(gè)排序過程可以遞歸或迭代進(jìn)行嚼摩,以此讓整個(gè)數(shù)列變成有序序列。

實(shí)現(xiàn)過程

  1. 在待排序區(qū)間找到一個(gè)基準(zhǔn)點(diǎn)(pivot),便于理解一般是位于數(shù)組中間的那一項(xiàng)枕面。
  2. 逐個(gè)循環(huán)數(shù)組將小于基準(zhǔn)的項(xiàng)放左側(cè)愿卒,將大于基準(zhǔn)的項(xiàng)放在右側(cè)。一般通過交換的方式來實(shí)現(xiàn)潮秘。
  3. 將基準(zhǔn)點(diǎn)左側(cè)全部項(xiàng)和基點(diǎn)右側(cè)全部項(xiàng)分別通過遞歸(或迭代)方式重復(fù)第1項(xiàng)掘猿,直到所有數(shù)組都交換完成。

示意圖

quick1.png
quick2.gif

性能分析

平均時(shí)間復(fù)雜度:O(NlogN)
最佳時(shí)間復(fù)雜度:O(NlogN)
最差時(shí)間復(fù)雜度:O(N^2)
空間復(fù)雜度:根據(jù)實(shí)現(xiàn)方式的不同而不同唇跨,可以查看不同版本的源碼

代碼

Java

  /* 方式1,標(biāo)準(zhǔn)遞歸版本稠通。需要左右不斷交換,無需新建數(shù)組买猖。 */
  static int[] quickSort1(int arr[], int low, int high) {
    int i = low > 0 ? low : 0;
    int j = high;
    int midIndex = (i + j) / 2;
    int pivot = arr[midIndex];

    System.out.println(
        " i=" + i + ", j=" + j + ", midIndex=" + midIndex + ", pivot=" + pivot + "  arr[]=" + Arrays.toString(arr));

    // 當(dāng)左側(cè)小于等于右側(cè)則表示還有值沒有對(duì)比改橘,需要繼續(xù)
    while (i <= j) {
      // 當(dāng)左側(cè)小于基準(zhǔn)時(shí)查找位置右移,直到找出比基準(zhǔn)值大的位置來
      while (arr[i] < pivot) {
        System.out.println("arr[i] < pivot: i=" + i + ", j=" + j + ", pivot=" + pivot);
        i++;
      }
      // 當(dāng)前右側(cè)大于基準(zhǔn)時(shí)左移玉控,直到找出比基準(zhǔn)值小的位置來
      while (arr[j] > pivot) {
        System.out.println("arr[i] > pivot: i=" + i + ", j=" + j + ", pivot=" + pivot);
        j--;
      }

      System.out.println("low=" + low + ", high=" + high + ", i=" + i + ", j=" + j + ", pivot=" + pivot);

      // 當(dāng)左側(cè)位置小于右側(cè)時(shí)飞主,將數(shù)據(jù)交換,小的交換到基準(zhǔn)左側(cè)高诺,大的交換到右側(cè)
      if (i <= j) {
        int tmp = arr[j];
        arr[j] = arr[i];
        arr[i] = tmp;
        // 縮小搜查范圍碌识,直到左側(cè)都小于基數(shù),右側(cè)都大于基數(shù)
        i++;
        j--;
      }
    }
    // 左側(cè)小于基數(shù)位置虱而,不斷遞歸左邊部分
    if (low < j) {
      System.out.println("  low < j:recursion: low=" + low + ", high=" + high + ", i=" + i + ", j=" + j + ", midIndex="
          + midIndex + ", pivot=" + pivot);
      quickSort1(arr, low, j);
    }
    // 基數(shù)位置小于右側(cè)筏餐,不斷遞歸右側(cè)部分
    if (i < high) {
      System.out.println("  i < high:recursion: low=" + low + ", high=" + high + ", i=" + i + ", j=" + j + ", midIndex="
          + midIndex + ", pivot=" + pivot);
      quickSort1(arr, i, high);
    }

    return arr;
  }

Python

# 標(biāo)準(zhǔn)遞歸版本。需要左右不斷交換牡拇,無需新建數(shù)組魁瞪。
def quick_sort2(arr, left=None, right=None):

    i = left = left if left is not None else 0
    j = right = right if right is not None else len(arr) - 1
    mid_index = (i + j) / 2
    pivot = arr[mid_index]

    # 當(dāng)左側(cè)小于等于右側(cè)則表示還有值沒有對(duì)比,需要繼續(xù)
    while (i <= j):
        # 當(dāng)左側(cè)小于基準(zhǔn)時(shí)查找位置右移惠呼,直到找出比基準(zhǔn)值大的位置來
        while (arr[i] < pivot):
            print('arr[i] < pivot:',
                  ' i=' + str(i) + ' j=' + str(j) + ' pivot=' + str(pivot))
            i = i + 1

        # 當(dāng)前右側(cè)大于基準(zhǔn)時(shí)左移导俘,直到找出比基準(zhǔn)值小的位置來
        while (arr[j] > pivot):
            print('arr[j] > pivot:',
                  ' i=' + str(i) + ' j=' + str(j) + ' pivot=' + str(pivot))
            j -= 1

        print(
            '  left=' + str(left) + ' right=' + str(right) + ' i=' + str(i) +
            ' j=' + str(j) + ' mid_index=' + str(mid_index) + ' pivot=' +
            str(pivot) + ' arr[]=', arr)

        # 當(dāng)左側(cè)位置小于右側(cè)時(shí),將數(shù)據(jù)交換剔蹋,小的交換到基準(zhǔn)左側(cè)旅薄,大的交換到右側(cè)
        if (i <= j):
            [arr[i], arr[j]] = [arr[j], arr[i]]
            # 縮小搜查范圍,直到左側(cè)都小于基數(shù)泣崩,右側(cè)都大于基數(shù)
            i += 1
            j -= 1

    # 左側(cè)小于基數(shù)位置少梁,不斷遞歸左邊部分
    if (left < j):
        print(
            'left < j:recursion:  left=' + str(left) + ' right=' + str(right) +
            ' i=' + str(i) + ' j=' + str(j) + 'arr[]', arr)
        quick_sort2(arr, left, j)

    # 基數(shù)位置小于右側(cè),不斷遞歸右側(cè)部分
    if (i < right):
        print(
            'i < right:recursion:  left=' + str(left) + ' right=' +
            str(right) + ' i=' + str(i) + ' j=' + str(j) + 'arr[]', arr)
        quick_sort2(arr, i, right)

    return arr

Go

// 把數(shù)組分按照基準(zhǔn)值分為左右兩部分律想,再返回新的中間位置作為排序的pivot
func partition(arr []int, left int, right int) int {
  // pivot基準(zhǔn)可以任意挑選猎莲,這里取右側(cè)
  var pivotIndex = right
  var pivot = arr[pivotIndex]
  var swapIndex = left - 1
  for i := left; i < right; i++ {
    // 如果小于基準(zhǔn)則進(jìn)行交換
    if arr[i] < pivot {
      swapIndex++
      arr[swapIndex], arr[i] = arr[i], arr[swapIndex]
    }
  }
  swapIndex++
  arr[swapIndex], arr[pivotIndex] = arr[pivotIndex], arr[swapIndex]
  fmt.Println("partition:", arr, swapIndex, arr[swapIndex], arr[left:swapIndex], arr[swapIndex:right])
  return swapIndex
}

// 方式2, 標(biāo)準(zhǔn)遞歸版本绍弟。左右不斷分區(qū)交換技即,無需新建數(shù)組。
func quickSort2(arr []int, left int, right int) []int {
  if left < right {
    var pivot = partition(arr, left, right)
    quickSort2(arr, left, pivot-1)
    quickSort2(arr, pivot+1, right)
  }
  return arr
}

JS

// 方式4, 非遞歸版本樟遣。需要交換而叼,無需新建數(shù)組身笤,利用stack或queue遍歷。
function quickSort4(arr, left, right) {
  left = left !== undefined ? left : 0
  right = right !== undefined ? right : arr.length - 1

  var stack = []
  var i, j, midIndex, pivot, tmp
  // 與標(biāo)準(zhǔn)遞歸版相同葵陵,只是將遞歸改為遍歷棧的方式
  // 先將左右各取一個(gè)入棧
  stack.push(left)
  stack.push(right)

  while (stack.length) {
    // 如果棧內(nèi)還有數(shù)據(jù)液荸,則一并馬上取出,其他邏輯與標(biāo)準(zhǔn)遞歸版同
    j = right = stack.pop()
    i = left = stack.pop()
    midIndex = Math.floor((i + j) / 2)
    pivot = arr[midIndex]
    while (i <= j) {
      while (arr[i] < pivot) {
        console.log('arr[i] < pivot:', ' i=' + i + ' j=' + j + ' pivot=' + pivot + 'arr[]=' + arr)
        i++
      }
      while (arr[j] > pivot) {
        console.log('arr[j] > pivot:', ' i=' + i + ' j=' + j + ' pivot=' + pivot + 'arr[]=' + arr)
        j--
      }

      if (i <= j) {
        tmp = arr[j]
        arr[j] = arr[i]
        arr[i] = tmp
        i++
        j--
      }
    }
    if (left < j) {
      // 與遞歸版不同脱篙,這里添加到棧中娇钱,以便繼續(xù)循環(huán)
      console.log('left < j:recursion:  left=' + left + ' right=' + right + ' i=' + i + ' j=' + j + 'arr[]=' + arr)
      stack.push(left)
      stack.push(j)
    }
    if (i < right) {
      console.log('i < right:recursion:  left=' + left + ' right=' + right + ' i=' + i + ' j=' + j + 'arr[]=' + arr)
      stack.push(i)
      stack.push(right)
    }
  }
  return arr
}

TS

  // 1. 方式1, 遞歸新建數(shù)組版本。無需交換绊困,每個(gè)分區(qū)都是新數(shù)組文搂,數(shù)量龐大
  quickSort1(arr: Array<number>) {
    // 數(shù)組長(zhǎng)度為1就不再分級(jí)
    if (arr.length <= 1) {
      return arr
    }
    console.log('split array:', arr)

    var pivot: number
    const left = Array<number>()
    const right = Array<number>()
    // 設(shè)置中間數(shù)
    var midIndex = Math.floor(arr.length / 2)
    pivot = arr[midIndex]

    for (var i = 0, l = arr.length; i < l; i++) {
      console.log(
        'i=' + i + ' midIndex=' + midIndex + ' pivot=' + pivot + ' arr[]=' + arr
      )
      // 當(dāng)中間基數(shù)等于i時(shí),跳過當(dāng)前秤朗。中間數(shù)遞歸完成時(shí)合并
      if (midIndex === i) {
        continue
      }
      // 當(dāng)前數(shù)組端里面的項(xiàng)小于基數(shù)則添加到左側(cè)
      if (arr[i] < pivot) {
        left.push(arr[i])
        // 大于等于則添加到右側(cè)
      } else {
        right.push(arr[i])
      }
    }

    arr = this.quickSort1(left).concat(pivot, this.quickSort1(right))
    // 遞歸調(diào)用遍歷左側(cè)和右側(cè)煤蹭,再將中間值連接起來
    return arr
  }

C

/** 方式1,標(biāo)準(zhǔn)遞歸版本。需要左右不斷交換取视,無需新建數(shù)組硝皂。*/
void *quickSort1(int arr[], int low, int high)
{
  int i = low > 0 ? low : 0;
  int j = high;
  int midIndex = (i + j) / 2;
  int pivot = arr[midIndex];

  // 當(dāng)左側(cè)小于等于右側(cè)則表示還有值沒有對(duì)比,需要繼續(xù)
  while (i <= j)
  {
    // 當(dāng)左側(cè)小于基準(zhǔn)時(shí)查找位置右移作谭,直到找出比基準(zhǔn)值大的位置來
    while (arr[i] < pivot)
    {
      printf("\r\narr[i] < pivot: i=%d, j=%d, pivot=%d", i, j, pivot);
      i++;
    }
    // 當(dāng)前右側(cè)大于基準(zhǔn)時(shí)左移稽物,直到找出比基準(zhǔn)值小的位置來
    while (arr[j] > pivot)
    {
      printf("\r\narr[i] > pivot: i=%d, j=%d, pivot=%d", i, j, pivot);
      j--;
    }

    printf("\r\n  low=%d, high=%d, i=%d, j=%d, midIndex=%d, pivot=%d", low, high, i, j, midIndex, pivot);

    // 當(dāng)左側(cè)位置小于右側(cè)時(shí),將數(shù)據(jù)交換折欠,小的交換到基準(zhǔn)左側(cè)姨裸,大的交換到右側(cè)
    if (i <= j)
    {
      swap(&arr[i], &arr[j]);
      // 縮小搜查范圍,直到左側(cè)都小于基數(shù)怨酝,右側(cè)都大于基數(shù)
      i++;
      j--;
    }
  }
  // 左側(cè)小于基數(shù)位置傀缩,不斷遞歸左邊部分
  if (low < j)
  {
    printf("\r\n  low < j:recursion:  low=%d, high=%d, i=%d, j=%d, midIndex=%d, pivot=%d", low, high, i, j, midIndex, pivot);
    quickSort1(arr, low, j);
  }
  // 基數(shù)位置小于右側(cè),不斷遞歸右側(cè)部分
  if (i < high)
  {
    printf("\r\n  i < high:recursion:  low=%d, high=%d, i=%d, j=%d, midIndex=%d, pivot=%d", low, high, i, j, midIndex, pivot);
    quickSort1(arr, i, high);
  }

  return arr;
}

C++

void swap(int *a, int *b)
{
  int temp = *a;
  *a = *b;
  *b = temp;
}
void printArray(int *arr, int size)
{
  for (int i = 0; i < size; i++)
  {
    std::cout << arr[i] << " ";
  }
  std::cout << std::right;
}

// 把數(shù)組分按照基準(zhǔn)值分為左右兩部分农猬,再返回新的中間位置作為排序的pivot
int partition(int *arr, int left, int right)
{
  // 這里的pivot以右側(cè)為準(zhǔn)
  int pivotIndex = right;
  int pivot = arr[pivotIndex];
  // 記錄交換的位置
  int swapIndex = left - 1;
  for (int i = left; i < right; i++)
  {
    // 如果小于基準(zhǔn)則進(jìn)行交換
    // 把小于基數(shù)的逐個(gè)往左側(cè)放赡艰,大于基數(shù)的往右側(cè)放
    if (arr[i] < pivot)
    {
      swapIndex++;
      swap(&arr[swapIndex], &arr[i]);
    }
  }
  swapIndex++;
  // 最后把原基數(shù)調(diào)換到分區(qū)線的位置,并返回分區(qū)線位置
  swap(&arr[swapIndex], &arr[pivotIndex]);
  return swapIndex;
}

void quickSort(int *arr, int left, int right)
{
  if (left < right)
  {
    int pivot = partition(arr, left, right);
    quickSort(arr, left, pivot - 1);
    quickSort(arr, pivot + 1, right);
  }
}

鏈接

快速排序算法源碼:https://github.com/microwind/algorithms/tree/master/sorts/quicksort

其他排序算法源碼:https://github.com/microwind/algorithms

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末斤葱,一起剝皮案震驚了整個(gè)濱河市慷垮,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌揍堕,老刑警劉巖料身,帶你破解...
    沈念sama閱讀 222,000評(píng)論 6 515
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異衩茸,居然都是意外死亡芹血,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,745評(píng)論 3 399
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來幔烛,“玉大人啃擦,你說我怎么就攤上這事《鲂” “怎么了令蛉?”我有些...
    開封第一講書人閱讀 168,561評(píng)論 0 360
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)狡恬。 經(jīng)常有香客問我珠叔,道長(zhǎng),這世上最難降的妖魔是什么弟劲? 我笑而不...
    開封第一講書人閱讀 59,782評(píng)論 1 298
  • 正文 為了忘掉前任运杭,我火速辦了婚禮,結(jié)果婚禮上函卒,老公的妹妹穿的比我還像新娘辆憔。我一直安慰自己,他們只是感情好报嵌,可當(dāng)我...
    茶點(diǎn)故事閱讀 68,798評(píng)論 6 397
  • 文/花漫 我一把揭開白布虱咧。 她就那樣靜靜地躺著,像睡著了一般锚国。 火紅的嫁衣襯著肌膚如雪腕巡。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 52,394評(píng)論 1 310
  • 那天血筑,我揣著相機(jī)與錄音绘沉,去河邊找鬼。 笑死豺总,一個(gè)胖子當(dāng)著我的面吹牛车伞,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播喻喳,決...
    沈念sama閱讀 40,952評(píng)論 3 421
  • 文/蒼蘭香墨 我猛地睜開眼另玖,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來了表伦?” 一聲冷哼從身側(cè)響起谦去,我...
    開封第一講書人閱讀 39,852評(píng)論 0 276
  • 序言:老撾萬榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎蹦哼,沒想到半個(gè)月后鳄哭,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 46,409評(píng)論 1 318
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡纲熏,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 38,483評(píng)論 3 341
  • 正文 我和宋清朗相戀三年妆丘,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了锄俄。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 40,615評(píng)論 1 352
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡飘痛,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出容握,到底是詐尸還是另有隱情宣脉,我是刑警寧澤,帶...
    沈念sama閱讀 36,303評(píng)論 5 350
  • 正文 年R本政府宣布剔氏,位于F島的核電站塑猖,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏谈跛。R本人自食惡果不足惜羊苟,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,979評(píng)論 3 334
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望感憾。 院中可真熱鬧蜡励,春花似錦、人聲如沸阻桅。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,470評(píng)論 0 24
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽嫂沉。三九已至稽寒,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間趟章,已是汗流浹背杏糙。 一陣腳步聲響...
    開封第一講書人閱讀 33,571評(píng)論 1 272
  • 我被黑心中介騙來泰國(guó)打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留蚓土,地道東北人宏侍。 一個(gè)月前我還...
    沈念sama閱讀 49,041評(píng)論 3 377
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像蜀漆,于是被迫代替她去往敵國(guó)和親负芋。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,630評(píng)論 2 359

推薦閱讀更多精彩內(nèi)容