日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當(dāng)前位置: 首頁 > 编程资源 > 编程问答 >内容正文

编程问答

十大经典排序算法及比较与分析 ( 动画演示 ) ( 可视化工具 )

發(fā)布時(shí)間:2023/12/15 编程问答 38 豆豆
生活随笔 收集整理的這篇文章主要介紹了 十大经典排序算法及比较与分析 ( 动画演示 ) ( 可视化工具 ) 小編覺得挺不錯(cuò)的,現(xiàn)在分享給大家,幫大家做個(gè)參考.

可視化工具及動畫展示:舊金山大學(xué) (usfca)|數(shù)據(jù)結(jié)構(gòu)可視化工具

排序算法概念及描述:1.0 十大經(jīng)典排序算法(文章部分內(nèi)容引用自改文章)
參考:鄧俊輝 的數(shù)據(jù)結(jié)構(gòu)
本文未對排序算法概念進(jìn)行詳細(xì)說明,只是提供已經(jīng)驗(yàn)證過的代碼及對算法核心進(jìn)行簡要說明
常用八種排序算法: 插入排序、希爾排序、選擇排序、冒泡排序、歸并排序、快速排序、堆排序、基數(shù)排序

·
全部代碼(github) C#版本

0X00 前言

排序算法是《數(shù)據(jù)結(jié)構(gòu)與算法》中最基本的算法之一。

排序算法可以分為內(nèi)部排序和外部排序,內(nèi)部排序是數(shù)據(jù)記錄在內(nèi)存中進(jìn)行排序,而外部排序是因排序的數(shù)據(jù)很大,一次不能容納全部的排序記錄,在排序過程中需要訪問外存。常見的內(nèi)部排序算法有:插入排序、希爾排序、選擇排序、冒泡排序、歸并排序、快速排序、堆排序、基數(shù)排序等。用一張圖概括:


關(guān)于時(shí)間復(fù)雜度
平方階 (O(n2)) 排序 各類簡單排序:直接插入、直接選擇和冒泡排序。

線性對數(shù)階 (O(nlog2n)) ( log2n 是以2為底數(shù)的n的對數(shù))排序: 快速排序、堆排序和歸并排序;

O(n1+§)) 排序 ( § 是介于 0 和 1 之間的常數(shù) ): 希爾排序

線性階 (O(n)) 排序: 基數(shù)排序,此外還有桶、箱排序。

關(guān)于穩(wěn)定性

穩(wěn)定的排序算法:冒泡排序、插入排序、歸并排序和基數(shù)排序。

不是穩(wěn)定的排序算法:選擇排序、快速排序、希爾排序、堆排序。

名詞解釋:

  • n:數(shù)據(jù)規(guī)模
  • k:"桶"的個(gè)數(shù)
  • In-place:占用常數(shù)內(nèi)存,不占用額外內(nèi)存
  • Out-place:占用額外內(nèi)存
  • 穩(wěn)定性:排序后 2 個(gè)相等鍵值的順序和排序之前它們的順序相同

0X01 冒泡排序(起泡排序)

可視化工具及動畫演示

/// <summary>/// 冒泡排序(A版本)/// 從后往前掃描待排序序列,如果前一個(gè)元素比后一個(gè)元素大,就交換它們兩個(gè),對每一對相鄰元素作同樣的工作;這樣,第一次掃描待排序的序列會找到一個(gè)最小值并將其放置在第一位,第二次掃描待排序的序列會找到一個(gè)第二小的值并將其放置在第二位,第三次掃描待排序的序列會找到一個(gè)第三小的值并將其放置在第三位,以此類推,直到將所有元素排序完畢;排序的過程就像泡泡不斷的往上冒,總是小的泡泡在最上面,大的泡泡在最下面。/// 時(shí)間復(fù)雜度:/// 雙層循環(huán)次數(shù):內(nèi)循環(huán)次數(shù) i=0(n-1),i=1(n-2),i=2(n-3),...,i=n-3(2),i=n-2(1)為等差數(shù)列,總次數(shù)=n*(0+n-1)/2=n*(n-1)/2/// 假設(shè)每次比較都需要交換,執(zhí)行內(nèi)循環(huán)一次時(shí)復(fù)雜度為2(比較一次+交換一次),所以復(fù)雜度=2*n(n-1)/2=n(n-1)/// 當(dāng)n非常大時(shí),多項(xiàng)式以冪次方最大的為標(biāo)準(zhǔn)所以復(fù)雜度O=n(n-1)=O(n*n)/// </summary>/// <param name="A"></param>void BubbleSort(int[] A){int n = A.Length;for (int i = 0; i < n - 1; i++){for (int j = 0; j < n - 1 - i; j++){if (A[j] > A[j + 1]){Swap(ref A[j + 1], ref A[j]);}}}}/// <summary>/// 冒泡排序(E版本)(最優(yōu)版本)/// 時(shí)間復(fù)雜度:/// 最優(yōu)的時(shí)間復(fù)雜度:當(dāng)數(shù)據(jù)本身是有序的時(shí)候,只會比較但是不會交換,內(nèi)循環(huán)執(zhí)行一圈就結(jié)束了,復(fù)雜度O=n-1=O(n)/// 最壞的時(shí)間復(fù)雜度:O=n(n-1)=O(n*n)/// </summary>/// <param name="A"></param>void BubbleSort_E(int[] A){int n = A.Length;bool sorted = false; //整體排序標(biāo)志,首先假定尚未排序while (!sorted){sorted = true;//假定有序for (int i = 0; i < n - 1; i++){if (A[i] > A[i + 1]){Swap(ref A[i + 1], ref A[i]);sorted = false;}}n--;//因整體排序不能保證,需要清除排序標(biāo)志}}

0X02 選擇排序(直接選擇排序)

可視化工具及動畫演示

/// <summary>/// 選擇排序(直接選擇排序)/// 一次從待排序的序列中選出最小(或最大)的一個(gè)元素,存放在已排好序的序列的后一個(gè)位置,直到全部待排序的數(shù)據(jù)元素排完;/// 時(shí)間復(fù)雜度:/// 雙層循環(huán)次數(shù):內(nèi)循環(huán)次數(shù) i=0(n),i=1(n-1),i=2(n-2),...,i=n-2(2),i=n-1(1)為等差數(shù)列,總次數(shù)=(n-1)*(0+n)/2=n*(n-1)/2/// 最壞情況每次比較都需要交換,執(zhí)行內(nèi)循環(huán)一次時(shí)復(fù)雜度為2(比較一次+交換一次),所以復(fù)雜度=2*n(n-1)/2=n(n-1),O=n(n-1)=O(n*n)/// 最優(yōu)情況每次比較都不需要交換,執(zhí)行內(nèi)循環(huán)一次時(shí)復(fù)雜度為1(比較一次),,所以復(fù)雜度=n(n-1)/2,O=n(n-1)/2=O(n*n)/// </summary>/// <param name="A"></param>void SelectionSort(int[] A){int n = A.Length;int min;for (int i = 0; i < n - 1; i++){min = i;for (int j = i; j < n; j++){if (A[min] > A[j]){min = j;}}Swap(ref A[min], ref A[i]);//Console.Write($"{i},{min}"); PrintArray(A, i, min+1);}}

0X03 插入排序(直接插入排序)

適合少量元素排序
可視化工具及動畫演示

/// <summary>/// 插入排序(直接插入排序)/// 把待排序的記錄按其關(guān)鍵碼值的大小逐個(gè)插入到一個(gè)已經(jīng)排好序的有序序列中,直到所有的記錄插入完為止,得到一個(gè)新的有序序列。/// 時(shí)間復(fù)雜度:/// 最壞情況雙層循環(huán)次數(shù):內(nèi)循環(huán)次數(shù) i=1(1),i=2(2),...,i=n-2(n-2)為等差數(shù)列,總次數(shù)=(n-2)*(1+n-2)/2=(n-2)*(n-1)/2/// 最壞情況每次比較都需要交換,執(zhí)行內(nèi)循環(huán)一次時(shí)復(fù)雜度為2(比較一次+交換一次),所以復(fù)雜度=2*(n-2)*(n-1)/2,O=(n-1)(n-2)=O(n*n)/// </summary>/// <param name="A"></param>void InsertionSort(int[] A){int n = A.Length;for (int i = 1; i < n - 1; i++) //第一個(gè)當(dāng)做有序序列{for (int j = i; j > 0 && A[j - 1] > A[j]; j--) //內(nèi)循環(huán)使用冒泡方式對前面有序序列進(jìn)行插入{Swap(ref A[j - 1], ref A[j]);}}}/// <summary>/// 插入排序(E版本)(優(yōu)化版本)/// 時(shí)間復(fù)雜度:/// 最優(yōu)情況雙層循環(huán)次數(shù):內(nèi)循環(huán)次數(shù) i=1(1),i=2(1),...,i=n-2(1),總次數(shù)=(n-2) /// 最優(yōu)情況每次比較都不需要交換,執(zhí)行內(nèi)循環(huán)一次時(shí)復(fù)雜度為1(比較一次),所以復(fù)雜度=2*(n-2),O=2(n-2)=O(n)/// </summary>/// <param name="A"></param>void InsertionSort_E(int[] A){int n = A.Length;int j, tmp;for (int i = 1; i < n - 1; i++) //第一個(gè)當(dāng)做有序序列{tmp = A[i];for (j = i; j > 0 && A[j - 1] > tmp; j--) //內(nèi)循環(huán)使用冒泡方式對前面有序序列進(jìn)行插入{A[j] = A[j - 1];}A[j] = tmp;}}

0X04 希爾排序

基于插入排序
參考學(xué)習(xí):https://baijiahao.baidu.com/s?id=1644158198885715432&wfr=spider&for=pc
可視化工具及動畫演示

/// <summary>/// 希爾排序/// 先取一個(gè)小于n的整數(shù)d1作為第一個(gè)增量,把數(shù)組元素分組,所有距離為d1的倍數(shù)的記錄放在同一個(gè)組中,先在各組內(nèi)進(jìn)行直接插入排序;然后,取第二個(gè)增量d2<d1重復(fù)上述的分組和排序,直至所取的增量 =1( < …<d2<d1),即所有記錄放在同一組中進(jìn)行直接插入排序?yàn)橹埂?gt;/// 時(shí)間復(fù)雜度:(推算不出來,如果有小伙伴推算出來歡迎解說指點(diǎn))/// 參考其他資料,復(fù)雜度和遞增序列h有關(guān)(increment sequence)/// By combining the arguments of these two theorems h-sequences with O(log(n)) elements can be derived that lead to a very good performance in practice, as for instance the h-sequence of the program (Sedgewick [Sed 96]). But unfortunately, there seems to be no h-sequence that gives Shellsort a worst case performance of O(n·log(n)) (see [Sed 96]). It is an open question whether possibly the average complexity is in O(n·log(n))/// </summary>/// <param name="A"></param>void ShellSort(int[] A){int n = A.Length;int h = 1;while (h < 3){h = h * 3 + 1;}while (h >= 1){for (int i = h; i < n; i++){for (int j = i; j >= h && A[j] < A[j - h]; j -= h){if (A[j - h] > A[j]){Swap(ref A[j - h], ref A[j]);}}}h = h / 3;}}

0X05 歸并排序

可視化工具及動畫演示

/// <summary>/// 歸并排序/// 首先兩個(gè)子序列分別是有序的(遞歸后最小數(shù)組長度為1,認(rèn)為數(shù)組長度為1時(shí)數(shù)組本身是有序的),這里對兩個(gè)子序列合并,挑選兩個(gè)子序列中最小的放入reg臨時(shí)序列中,直到兩個(gè)子序列中一個(gè)子序列被完全放入后結(jié)束,然后將另一個(gè)子序列復(fù)制到reg臨時(shí)序列中,最后臨時(shí)序列是合并后的有序序列了,將reg復(fù)制到A中/// 時(shí)間復(fù)雜度:假設(shè)遞歸一次的時(shí)間復(fù)雜度為T()/// 執(zhí)行1次遞歸的時(shí)間復(fù)雜度為T(n)=2*T(n/2)+n(兩個(gè)子序列合并,一共長度為n)/// 執(zhí)行2次遞歸的時(shí)間復(fù)雜度為T(n)=4*T(n/2)+2n/// 執(zhí)行3次遞歸的時(shí)間復(fù)雜度為T(n)=8*T(n/8)+3n/// 類似二叉樹的層數(shù),層級=log2(n)+1/// 代入得T(n)=nT(1)+log2(n)*n/// 時(shí)間復(fù)雜度O=T(n)=nT(1)+log2(n)*n=O(nlog2(n))=(nlogn)/// /// </summary>/// <param name="A"></param>public void MergeSort(int[] A){int n = A.Length;int[] reg = new int[n];MergeSort(A, reg, 0, n - 1);}void MergeSort(int[] A, int[] reg, int start, int end){if (start >= end){return;}int mid = (start + end) >> 1;int start1 = start;int end1 = mid;int start2 = mid + 1;int end2 = end;MergeSort(A, reg, start1, end1);MergeSort(A, reg, start2, end2);int k = start;//首先兩個(gè)子序列分別是有序的,這里對兩個(gè)子序列合并,挑選兩個(gè)子序列中最小的放入reg臨時(shí)序列中,直到兩個(gè)子序列中一個(gè)子序列被完全放入后結(jié)束,然后將另一個(gè)子序列復(fù)制到reg臨時(shí)序列中,最后臨時(shí)序列是合并后的有序序列了,復(fù)制會A中while (start1 <= end1 && start2 <= end2){reg[k++] = A[start1] < A[start2] ? A[start1++] : A[start2++]; //}while (start1 <= end1){reg[k++] = A[start1++];}while (start2 <= end2){reg[k++] = A[start2++];}Array.Copy(reg, start, A, start, end - start + 1);}/// <summary>/// 歸并排序(非遞歸版)/// </summary>/// <param name="A"></param>public void MergeSort_E(int[] A){int n = A.Length;int[] a = A;int[] b = new int[n];int seg, start;for (seg = 1; seg < n; seg += seg){for (start = 0; start < n; start += seg + seg){int low = start, mid = Math.Min(start + seg, n), high = Math.Min(start + seg + seg, n);int k = low;int start1 = low, end1 = mid;int start2 = mid, end2 = high;while (start1 < end1 && start2 < end2)b[k++] = a[start1] < a[start2] ? a[start1++] : a[start2++];while (start1 < end1)b[k++] = a[start1++];while (start2 < end2)b[k++] = a[start2++];}Array.Copy(b, a, n);}}

0X06 快速排序

可視化工具及動畫演示

/// <summary>/// 快速排序/// 簡單說是給基準(zhǔn)數(shù)找正確索引位置的過程./// 快速排序是對冒泡排序的一種改進(jìn)。/// 首先選取一個(gè)初始值(一般選取待排序序列的第一個(gè)值),通過一趟排序?qū)⒋判蛐蛄蟹殖蓛蓚€(gè)子序列,使左子序列的所有數(shù)據(jù)都小于這個(gè)初始值,右子序列的所有數(shù)據(jù)都大于這個(gè)初始值,然后再按此方法分別對這兩個(gè)子序列進(jìn)行排序,遞歸的進(jìn)行上面的步驟,直至每一個(gè)數(shù)據(jù)項(xiàng)都有如下性質(zhì):該數(shù)據(jù)項(xiàng)左邊的數(shù)據(jù)都小于它,右邊的數(shù)據(jù)都大于它,這樣,整個(gè)序列就有序了。/// 時(shí)間復(fù)雜度:O=O(nlogn)和歸并排序推理類似,不再展開推理了/// /// </summary>/// <param name="A"></param>public void QuickSort(int[] A){int n = A.Length;QuickSort(A, 0, n - 1);}void QuickSort(int[] A, int low, int high){if (low >= high) return;int pivot = Partition(A, low, high);QuickSort(A, low, pivot - 1);QuickSort(A, pivot + 1, high);}int Partition(int[] A, int low, int high){int pivot = A[low]; //基準(zhǔn)數(shù)選取數(shù)組第一個(gè)元素(哨兵元素)while (low < high){while (low < high && A[high] >= pivot) --high;A[low] = A[high];while (low < high && A[low] <= pivot) ++low;A[high] = A[low];}A[low] = pivot;return low;}public void QuickSort_V(int[] A){Stack<int> stack = new Stack<int>();int pivot;int low = 0;int high = A.Length - 1;int start, end;stack.Push(high);stack.Push(low);while (stack.Count > 0){start = low = stack.Pop();end = high = stack.Pop();if (low >= high)continue;pivot = A[low];while (low < high){while (low < high && A[high] >= pivot) high--;A[low] = A[high];while (low < high && A[low] <= pivot) low++;A[high] = A[low];}A[low] = pivot;stack.Push(low - 1);stack.Push(start);stack.Push(end);stack.Push(low + 1);}}

0X07 堆排序

可視化工具及動畫演示
時(shí)間復(fù)雜度參考:堆排序的時(shí)間復(fù)雜度分析

/// <summary>/// 堆排序/// 堆排序(Heapsort)是指利用堆這種數(shù)據(jù)結(jié)構(gòu)所設(shè)計(jì)的一種排序算法。/// 堆排序可以說是一種利用堆的概念來排序的選擇排序。/// 堆的性質(zhì):/// 堆積是一個(gè)近似完全二叉樹的結(jié)構(gòu),并同時(shí)滿足堆積的性質(zhì):即子結(jié)點(diǎn)的鍵值或索引總是小于(或者大于)它的父節(jié)點(diǎn)。/// -->大頂堆:每個(gè)節(jié)點(diǎn)的值都大于或等于其子節(jié)點(diǎn)的值,在堆排序算法中用于升序排列;/// -->小頂堆:每個(gè)節(jié)點(diǎn)的值都小于或等于其子節(jié)點(diǎn)的值,在堆排序算法中用于降序排列;/// 堆排序的基本思想是:將待排序序列構(gòu)造成一個(gè)大頂堆,此時(shí),整個(gè)序列的最大值就是堆頂?shù)母?jié)點(diǎn)。將其與末尾元素進(jìn)行交換,此時(shí)末尾就為最大值。然后將剩余n-1個(gè)元素重新構(gòu)造成一個(gè)堆,這樣會得到n個(gè)元素的次小值。如此反復(fù)執(zhí)行,便能得到一個(gè)有序序列了/// 時(shí)間復(fù)雜度:(參考:https://blog.csdn.net/qq_34228570/article/details/80024306/)/// 構(gòu)建初始堆復(fù)雜度:O(n)/// 交換并重建堆復(fù)雜度O(nlogn)/// 真?zhèn)€過程的復(fù)雜度O=O(n)+O(nlogn)=O(nlogn)/// /// /// </summary>/// <param name="A"></param>public void HeapSort(int[] A){int n = A.Length;int i;// 初始化構(gòu)建堆結(jié)構(gòu),i從最後一個(gè)父節(jié)點(diǎn)開始調(diào)整(n/2-1為二叉樹倒數(shù)第二層最后一個(gè)父節(jié)點(diǎn)) //構(gòu)建后的二叉樹根節(jié)點(diǎn)為整個(gè)二叉樹中最大的節(jié)點(diǎn)for (i = n / 2 - 1; i >= 0; i--) //構(gòu)建堆結(jié)構(gòu)(完全二叉樹,大頂堆)MaxHeapify(A, i, n - 1);for (i = n - 1; i > 0; i--){Swap(ref A[0], ref A[i]);MaxHeapify(A, 0, i - 1);}}void MaxHeapify(int[] A, int start, int end){// 建立父節(jié)點(diǎn)指標(biāo)和子節(jié)點(diǎn)指標(biāo)int dad = start;int son = dad * 2 + 1;while (son <= end)// 若子節(jié)點(diǎn)指標(biāo)在範(fàn)圍內(nèi)才做比較{if (son + 1 < end && A[son] < A[son + 1]) son++; // 先比較兩個(gè)子節(jié)點(diǎn)大小,選擇最大的if (A[dad] > A[son]) return;//如果父節(jié)點(diǎn)大於子節(jié)點(diǎn)代表調(diào)整完畢,直接跳出函數(shù)else{ // 否則交換父子內(nèi)容再繼續(xù)子節(jié)點(diǎn)和孫節(jié)點(diǎn)比較Swap(ref A[dad], ref A[son]);dad = son; son = dad * 2 + 1;}}}

0X08 計(jì)數(shù)排序

可視化工具及動畫演示

/// <summary>/// 計(jì)數(shù)排序/// 計(jì)數(shù)排序不是一個(gè)比較排序算法/// 計(jì)數(shù)排序的核心在于將輸入的數(shù)據(jù)值轉(zhuǎn)化為鍵存儲在額外開辟的數(shù)組空間中。作為一種線性時(shí)間復(fù)雜度的排序,計(jì)數(shù)排序要求輸入的數(shù)據(jù)必須是有確定范圍的整數(shù)。/// 計(jì)數(shù)排序類似與桶排序,也是用空間換取了時(shí)間,計(jì)數(shù)排序要求數(shù)組必須在一個(gè)確定的區(qū)間內(nèi)。/// 過程1:1. 首先找出數(shù)組的最大值和最小值;2. 遍歷數(shù)組,以數(shù)字作為鍵,該數(shù)字出現(xiàn)的次數(shù)作為值插入哈希表中;3. 在最小值到最大值這個(gè)區(qū)間內(nèi)遍歷哈希表,將數(shù)字反向插入數(shù)組中。/// 過程2:/// 根據(jù)待排序集合中最大元素和最小元素的差值范圍,申請額外空間;/// 遍歷待排序集合,將每一個(gè)元素出現(xiàn)的次數(shù)記錄到元素值對應(yīng)的額外空間內(nèi);/// 對額外空間內(nèi)數(shù)據(jù)進(jìn)行計(jì)算,得出每一個(gè)元素的正確索引位置;/// 將待排序集合每一個(gè)元素移動到計(jì)算得出的正確索引位置上。/// 時(shí)間復(fù)雜度:/// 如果原始數(shù)列的規(guī)模是n,最大最小整數(shù)的差值是m,由于代碼中第1、2、4步都涉及到遍歷原始數(shù)列,運(yùn)算量都是n,第3步遍歷統(tǒng)計(jì)數(shù)列,運(yùn)算量是m,所以總體運(yùn)算量是3n+m,去掉系數(shù),時(shí)間復(fù)雜度是O(n+m)。/// /// 空間復(fù)雜度:/// 如果不考慮結(jié)果數(shù)組,只考慮統(tǒng)計(jì)數(shù)組的話,空間復(fù)雜度是O(m)/// 計(jì)數(shù)排序的局限性:/// 當(dāng)數(shù)組最大和最小差值過大時(shí),并不適合計(jì)數(shù)排序/// 當(dāng)數(shù)組元素不是整數(shù)(不能轉(zhuǎn)化成整數(shù)計(jì)算的,浮點(diǎn)(用指數(shù)和浮點(diǎn)分部轉(zhuǎn)化)、字符等等)時(shí),也不適合用計(jì)數(shù)排序/// </summary>/// <param name="A"></param>public void CountingSort(int[] A){int n = A.Length;int[] sorting = new int[n];//1.找出數(shù)組中最大值、最小值int max = int.MinValue;int min = int.MaxValue;for (int i = 0; i < n; i++){max = Math.Max(max, A[i]);min = Math.Min(min, A[i]);}//初始化計(jì)數(shù)數(shù)組count,設(shè)長度為mint[] counting = new int[max - min + 1];//2. 對計(jì)數(shù)數(shù)組各元素賦值,設(shè)長度為mfor (int i = 0; i < n; i++)counting[A[i] - min]++;//3. 計(jì)數(shù)數(shù)組變形,新元素的值是前面元素累加之和的值for (int i = 1; i < counting.Length; i++)counting[i] += counting[i - 1];//4. 遍歷A中的元素,填充到結(jié)果數(shù)組中去,從后往前遍歷for (int i = n - 1; i >= 0; i--)sorting[--counting[A[i] - min]] = A[i];//5. 將結(jié)果復(fù)制到原始數(shù)組中Array.Copy(sorting, A, n);}

0X09 桶排序

可視化工具及動畫演示

/// <summary>/// 桶排序(Bucket Sort)(箱排序)/// 桶排序是計(jì)數(shù)排序的擴(kuò)展版本,計(jì)數(shù)排序可以看成每個(gè)桶只存儲相同元素,而桶排序每個(gè)桶存儲一定范圍的元素,通過映射函數(shù),將待排序數(shù)組中的元素映射到各個(gè)對應(yīng)的桶中,對每個(gè)桶中的元素進(jìn)行排序,最后將非空桶中的元素逐個(gè)放入原序列中。/// 算法過程:/// 根據(jù)待排序集合中最大元素和最小元素的差值范圍和映射規(guī)則,確定申請的桶個(gè)數(shù);/// 遍歷待排序集合,將每一個(gè)元素移動到對應(yīng)的桶中;/// 對每一個(gè)桶中元素進(jìn)行排序,并移動到已排序集合中。/// 時(shí)間復(fù)雜度:設(shè)桶內(nèi)比較排序?yàn)榭焖倥判?復(fù)雜度nlogn)/// 第一個(gè)循環(huán)為O(N),設(shè)桶的數(shù)量為M,平均每個(gè)桶的元素?cái)?shù)量為N/M,桶內(nèi)以快速排序?yàn)槔秊镹logN,復(fù)雜度為O(M*N/M*log2(N/M)+N)=O(N+N(log2(N)-log2(M)))/// 第二個(gè)循環(huán)為O(2N)/// 總復(fù)雜度為O(3N+N(log2(N)-log2(M)))=O(N+N(logN-LogM))/// 當(dāng)M=N時(shí) 復(fù)雜度為O(N)/// 當(dāng)M=1時(shí) 復(fù)雜度為O(N+Nlog(N))///這里桶內(nèi)排序使用的是鏈表指針方式,桶內(nèi)復(fù)雜度為O(1),可以忽略,總復(fù)雜度為O(N)/// </summary>/// <param name="A"></param>/// <param name="bucketSize">每個(gè)桶內(nèi)數(shù)據(jù)的預(yù)期數(shù)量</param>public void BucketSort(int[] A, int bucketSize = 1000){int n = A.Length;int index;//1.找出數(shù)組中最大值、最小值int max = int.MinValue;int min = int.MaxValue;for (int i = 0; i < n; i++){max = Math.Max(max, A[i]);min = Math.Min(min, A[i]);}int bucketCount = (max - min) / bucketSize + 1;LinkedList<int>[] bucket = new LinkedList<int>[bucketCount];for (int i = 0; i < n; i++){index = (A[i]-min) / bucketSize;if (bucket[index] == null)bucket[index] = new LinkedList<int>();BucketInsertSort(bucket[index], A[i]);}index = 0;for (int i = 0; i < bucket.Length; i++){LinkedList<int> linkedList = bucket[i];if (linkedList == null) continue;var current = linkedList.First;while (current != null){A[index++] = current.Value;current = current.Next;}}}/// <summary>/// 桶排序的桶內(nèi)排序,這里用的是指針選擇排序,還可使用快速排序/// </summary>/// <param name="list"></param>/// <param name="a"></param>void BucketInsertSort(LinkedList<int> list, int a){var current = list.First;if (current == null || current.Value > a){list.AddFirst(a);return;}while (current != null){if (current.Next == null || current.Next.Value > a){list.AddAfter(current, a);return;}current = current.Next;}}

0X10 基數(shù)排序

可視化工具及動畫演示

/// <summary>/// 基數(shù)排序/// 概念1:基數(shù)排序是一種非比較型整數(shù)排序算法,其原理是將整數(shù)按位數(shù)切割成不同的數(shù)字,然后按每個(gè)位數(shù)分別比較。由于整數(shù)也可以表達(dá)字符串(比如名字或日期)和特定格式的浮點(diǎn)數(shù),所以基數(shù)排序也不是只能使用于整數(shù)。/// 概念2:將所有待排序的數(shù)統(tǒng)一為相同的數(shù)位長度,數(shù)位較短的數(shù)前面補(bǔ)零,然后從低位到高位按位比較,位數(shù)字小的排在前面,大的排在后面,這樣當(dāng)比較第N位時(shí)前N-1位都是有序的,如此循環(huán)的比較,直到最高位比較完成,整個(gè)序列就是有序的了。/// 時(shí)間復(fù)雜度:/// 設(shè)待排序列為n個(gè)記錄,序列中最大值的位數(shù)為d,數(shù)字的基數(shù)為 r,則進(jìn)行鏈?zhǔn)交鶖?shù)排序的時(shí)間復(fù)雜度為O(d(n+r))。當(dāng)分配數(shù)字時(shí)要對每一個(gè)數(shù)字進(jìn)行按位比較, 而收集數(shù)字時(shí)要進(jìn)行r次收集(如十進(jìn)制數(shù)字就要進(jìn)行從0到9共10次收集操作), 故一趟分配時(shí)間復(fù)雜度為O(n),一趟收集時(shí)間復(fù)雜度為O(r),共進(jìn)行d趟分配和收集。/// /// 基數(shù)排序 vs 計(jì)數(shù)排序 vs 桶排序/// 這三種排序算法都利用了桶的概念,但對桶的使用方法上有明顯差異:/// 基數(shù)排序:根據(jù)鍵值的每位數(shù)字來分配桶;/// 計(jì)數(shù)排序:每個(gè)桶只存儲單一鍵值;/// 桶排序:每個(gè)桶存儲一定范圍的數(shù)值;/// </summary>/// <param name="A"></param>public void RadixSort(int[] A){int n = A.Length;const int BASE = 10;int exp = 1;int max = int.MinValue;int[] tmp = new int[n];for (int i = 0; i < n; i++)if (A[i] > max) max = A[i];while (max / exp > 0){int[] bucket = new int[n];for (int i = 0; i < n; i++){bucket[A[i] / exp % BASE]++;}for (int i = 1; i < n; i++){bucket[i] += bucket[i - 1];}for (int i = n - 1; i >= 0; i--){tmp[--bucket[A[i] / exp % BASE]] = A[i];}Array.Copy(tmp, A, n);exp *= BASE;}}

0X11 全部代碼(C#)

全部代碼(github)

0X12 排序算法耗時(shí)測試

全部代碼(github)
測試環(huán)境:(.Net4.6.1,win10)

數(shù)組長度50,數(shù)組元素(0–1000).
BubbleSort總共花費(fèi)0.3393ms.
BubbleSort_E總共花費(fèi)0.1743ms.
SelectionSort總共花費(fèi)0.1832ms.
InsertionSort總共花費(fèi)0.1752ms.
InsertionSort_E總共花費(fèi)0.1461ms.
ShellSort總共花費(fèi)0.2367ms.
MergeSort總共花費(fèi)0.4245ms.
MergeSort_E總共花費(fèi)0.4201ms.
QuickSort總共花費(fèi)0.3644ms.
QuickSort_V總共花費(fèi)0.4239ms.
HeapSort總共花費(fèi)0.2615ms.
CountingSort總共花費(fèi)0.2181ms.
BucketSort總共花費(fèi)3.492ms.
RadixSort總共花費(fèi)0.3766ms.

數(shù)組長度500,數(shù)組元素(0–1000).
BubbleSort總共花費(fèi)1.4969ms.
BubbleSort_E總共花費(fèi)1.7962ms.
SelectionSort總共花費(fèi)0.4618ms.
InsertionSort總共花費(fèi)1.2992ms.
InsertionSort_E總共花費(fèi)0.2969ms.
ShellSort總共花費(fèi)0.4273ms.
MergeSort總共花費(fèi)0.4269ms.
MergeSort_E總共花費(fèi)0.2781ms.
QuickSort總共花費(fèi)0.2806ms.
QuickSort_V總共花費(fèi)0.3587ms.
HeapSort總共花費(fèi)0.3837ms.
CountingSort總共花費(fèi)0.2465ms.
BucketSort總共花費(fèi)1.3181ms.
RadixSort總共花費(fèi)0.2294ms.

數(shù)組長度1000,數(shù)組元素(0–1000).
BubbleSort總共花費(fèi)4.9505ms.
BubbleSort_E總共花費(fèi)4.669ms.
SelectionSort總共花費(fèi)1.2467ms.
InsertionSort總共花費(fèi)3.395ms.
InsertionSort_E總共花費(fèi)1.3513ms.
ShellSort總共花費(fèi)1.3414ms.
MergeSort總共花費(fèi)0.5231ms.
MergeSort_E總共花費(fèi)0.4844ms.
QuickSort總共花費(fèi)0.3366ms.
QuickSort_V總共花費(fèi)0.3937ms.
HeapSort總共花費(fèi)0.4446ms.
CountingSort總共花費(fèi)0.2668ms.
BucketSort總共花費(fèi)2.6375ms.
RadixSort總共花費(fèi)0.5076ms.

數(shù)組長度5000,數(shù)組元素(0–1000).
BubbleSort總共花費(fèi)134.6438ms.
BubbleSort_E總共花費(fèi)141.5785ms.
SelectionSort總共花費(fèi)32.566ms.
InsertionSort總共花費(fèi)82.5932ms.
InsertionSort_E總共花費(fèi)17.178ms.
ShellSort總共花費(fèi)22.8029ms.
MergeSort總共花費(fèi)1.0649ms.
MergeSort_E總共花費(fèi)0.7582ms.
QuickSort總共花費(fèi)0.7838ms.
QuickSort_V總共花費(fèi)0.8333ms.
HeapSort總共花費(fèi)1.5709ms.
CountingSort總共花費(fèi)0.2693ms.
BucketSort總共花費(fèi)1.7872ms.
RadixSort總共花費(fèi)0.4634ms.

數(shù)組長度10000,數(shù)組元素(0–1000).
BubbleSort總共花費(fèi)556.6481ms.
BubbleSort_E總共花費(fèi)528.7346ms.
SelectionSort總共花費(fèi)116.9845ms.
InsertionSort總共花費(fèi)306.6125ms.
InsertionSort_E總共花費(fèi)68.4407ms.
ShellSort總共花費(fèi)88.4968ms.
MergeSort總共花費(fèi)1.8969ms.
MergeSort_E總共花費(fèi)1.3673ms.
QuickSort總共花費(fèi)1.4713ms.
QuickSort_V總共花費(fèi)1.4491ms.
HeapSort總共花費(fèi)3.3177ms.
CountingSort總共花費(fèi)0.3216ms.
BucketSort總共花費(fèi)2.9245ms.
RadixSort總共花費(fèi)0.7497ms.

數(shù)組長度30000,數(shù)組元素(0–1000).
BubbleSort總共花費(fèi)4702.0921ms.
BubbleSort_E總共花費(fèi)4660.4316ms.
SelectionSort總共花費(fèi)952.3607ms.
InsertionSort總共花費(fèi)2763.3749ms.
InsertionSort_E總共花費(fèi)610.9831ms.
ShellSort總共花費(fèi)777.4363ms.
MergeSort總共花費(fèi)5.3142ms.
MergeSort_E總共花費(fèi)3.806ms.
QuickSort總共花費(fèi)4.2744ms.
QuickSort_V總共花費(fèi)4.4204ms.
HeapSort總共花費(fèi)10.0902ms.
CountingSort總共花費(fèi)0.5658ms.
BucketSort總共花費(fèi)18.1321ms.
RadixSort總共花費(fèi)1.8263ms.

數(shù)組長度100000,數(shù)組元素(0–1000).
BubbleSort總共花費(fèi)52096.3052ms.
BubbleSort_E總共花費(fèi)52299.5447ms.
SelectionSort總共花費(fèi)10567.5827ms.
InsertionSort總共花費(fèi)30973.0239ms.
InsertionSort_E總共花費(fèi)6888.8287ms.
ShellSort總共花費(fèi)8548.7395ms.
MergeSort總共花費(fèi)17.985ms.
MergeSort_E總共花費(fèi)13.3151ms.
QuickSort總共花費(fèi)19.1267ms.
QuickSort_V總共花費(fèi)18.6859ms.
HeapSort總共花費(fèi)36.055ms.
CountingSort總共花費(fèi)1.3793ms.
BucketSort總共花費(fèi)195.1004ms.
RadixSort總共花費(fèi)6.1907ms.

通過測試數(shù)據(jù)得出:

  • 沒有空間開銷的算法中(不考慮原始數(shù)據(jù)局部有序) 小數(shù)據(jù)量(<500) 直接插入排序最優(yōu)(E版本) ;大數(shù)據(jù)量(>500) 快速排序最優(yōu)(遞歸版本,不考慮遞歸開銷)。
  • 有空間開銷的算法中 (不考慮空間開銷大小,大數(shù)據(jù)量>1000)基數(shù)排序和計(jì)數(shù)排序最優(yōu),其次是歸并排序(E版本非遞歸)

桶排序的耗時(shí)不是最優(yōu)的,這里的桶排序沒有設(shè)置桶的size,所以桶排序耗時(shí)不考慮。
如果有出入或者錯(cuò)誤望各位留言指正

0X13 十大算法比較與分析

  • 這里是參考其他文章的結(jié)論進(jìn)行了羅列,我自己的測試結(jié)論參考上一節(jié)( 0X13 )節(jié)
  • 直接插入排序 是對冒泡排序的改進(jìn),比冒泡排序快,但是只適用于數(shù)據(jù)量較小(1000 ) 的排序

  • 希爾排序 比較簡單,適用于小數(shù)據(jù)量(5000以下)的排序,比直接插入排序快、冒泡排序快,因此,希爾排序適用于小數(shù)據(jù)量的、排序速度要求不高的排序。

  • 直接選擇排序 和冒泡排序算法一樣,適用于n值較小的場合,而且是排序算法發(fā)展的初級階段,在實(shí)際應(yīng)用中采用的幾率較小。

  • 堆排序 比較適用于數(shù)據(jù)量達(dá)到百萬及其以上的排序,在這種情況下,使用遞歸設(shè)計(jì)的快速排序和歸并排序可能會發(fā)生堆棧溢出的現(xiàn)象。

  • 冒泡排序 是最慢的排序算法,是排序算法發(fā)展的初級階段,實(shí)際應(yīng)用中采用該算法的幾率比較小。

  • 快速排序 是遞歸的、速度最快的排序算法,但是在內(nèi)存有限的情況下不是一個(gè)好的選擇;而且,對于基本有序的數(shù)據(jù)序列排序,快速排序反而變得比較慢。

  • 歸并排序 比堆排序要快,但是需要的存儲空間增加一倍。

  • 基數(shù)排序 適用于規(guī)模n值很大的場合,但是只適用于整數(shù)的排序,如果對浮點(diǎn)數(shù)進(jìn)行基數(shù)排序,則必須明確浮點(diǎn)數(shù)的存儲格式,然后通過某種方式將其映射到整數(shù)上,最后再映射回去,過程復(fù)雜。

摘自常用排序算法比較與分析


冒泡排序:
效率太低,通過冒泡可以掌握swap。

選擇排序:
效率較低,但經(jīng)常使用它內(nèi)部的循環(huán)方式來找最大值和最小值。

插入排序:
雖然平均效率低,但在序列基本有序時(shí),它很快,所以也有其適用范圍。

希爾排序:
是插入排序的改良,對空間思維訓(xùn)練有幫助。

快速排序:
快排是軟件工業(yè)中最常見的常規(guī)排序法,其雙向指針掃描和分區(qū)算法是核心。

往往用于解決類似問題,特別地partition算法用來劃分不同性質(zhì)的元素,
partition->selectK,也用于著名的top問題
O(NlgN),但是如果主元不是中位數(shù)的話,特別地如果每次主元都在數(shù)組區(qū)間的一側(cè),復(fù)雜度將退化為N2
工業(yè)優(yōu)化:三點(diǎn)取中法,絕對中值法,小數(shù)據(jù)量用插入排序
快排重視子問題拆分

歸并排序:
空間換時(shí)間——逆序?qū)?shù),

歸并重視子問題的解的合并

堆排序:
用到了二叉堆數(shù)據(jù)結(jié)構(gòu),是繼續(xù)掌握樹結(jié)構(gòu)的起手式。=插排+二分查找

上面7種都是基于比較的排序,可證明它們在元素隨機(jī)順序情況下最好是NlgN的,用決策樹證明

下面三個(gè)是非比較排序,在特定情況下會比基于比較的排序要快:

計(jì)數(shù)排序:
可以說是最快的:O(N+k),k=maxOf(sourceArr),用它來解決問題時(shí)必須注意如果序列中的值分布非常廣(最大值很大,元素分布很稀疏),
空間將會浪費(fèi)很多
所以計(jì)數(shù)排序的適用范圍是:序列的關(guān)鍵字比較集中,已知邊界,且邊界較小

桶排序:
先分桶,再用其他排序方法對桶內(nèi)元素排序,按桶的編號依次檢出。(分配-收集)
用它解決問題必須注意序列的值是否均勻地分布在桶中。
如果不均勻,那么個(gè)別桶中的元素會遠(yuǎn)多于其他桶,桶內(nèi)排序用比較排序,極端情況下,全部元素在一個(gè)桶內(nèi),還是會退化成NlgN。

其時(shí)間復(fù)雜度是:時(shí)間復(fù)雜度: O(N+C),其中C=N(logN-logM),約等于NlgN
N是元素個(gè)數(shù),M是桶的個(gè)數(shù)。

基數(shù)排序:
kN級別(k是最大數(shù)的位數(shù))是整數(shù)數(shù)值型排序里面又快又穩(wěn)的,無論元素分布如何,
只開辟固定的輔助空間(10個(gè)桶)

對比桶排序,基數(shù)排序每次需要的桶的數(shù)量并不多。而且基數(shù)排序幾乎不需要任何“比較”操作,而桶排序在桶相對較少的情況下,桶內(nèi)多個(gè)數(shù)據(jù)必須進(jìn)行基于比較操作的排序。因此,在實(shí)際應(yīng)用中,對十進(jìn)制整數(shù)來說,基數(shù)排序更好用。

摘自10種排序算法的對比分析

0X14 數(shù)據(jù)結(jié)構(gòu)可視化工具

可視化工具及動畫展示地址:舊金山大學(xué)|數(shù)據(jù)結(jié)構(gòu)可視化


十大排序算法可視化工具及動畫演示展示

選擇上面的按鈕就可以播放啦

選擇上面的按鈕就可以播放啦

點(diǎn)擊向后跳就然后點(diǎn)上面的某個(gè)排序按鈕就可以重新播放啦

總結(jié)

以上是生活随笔為你收集整理的十大经典排序算法及比较与分析 ( 动画演示 ) ( 可视化工具 )的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網(wǎng)站內(nèi)容還不錯(cuò),歡迎將生活随笔推薦給好友。