前言
本篇文章基本是從
常用排序算法總結(jié)(一)
快速排序
引申而來(lái),其中大部分代碼和描述都來(lái)自這兩篇文章站刑。
時(shí)間復(fù)雜度
在計(jì)算機(jī)科學(xué)中际跪,算法的時(shí)間復(fù)雜度是一個(gè)函數(shù)怜姿,它定量描述了該算法的運(yùn)行時(shí)間春感。這是一個(gè)代表算法輸入值的字符串的長(zhǎng)度的函數(shù)砌创。時(shí)間復(fù)雜度常用大O符號(hào)表述虏缸,不包括這個(gè)函數(shù)的低階項(xiàng)和首項(xiàng)系數(shù)。使用這種方式時(shí)嫩实,時(shí)間復(fù)雜度可被稱為是漸近的寇钉,亦即考察輸入值大小趨近無(wú)窮時(shí)的情況。例如舶赔,如果一個(gè)算法對(duì)于任何大小為n的輸入,它至多需要 5n^3 + 3n 的時(shí)間運(yùn)行完畢谦秧,那么它的漸近時(shí)間復(fù)雜度是 O(n^3)竟纳。
空間復(fù)雜度
時(shí)間復(fù)雜度是指在計(jì)算機(jī)科學(xué)與工程領(lǐng)域完成一個(gè)算法所需要的時(shí)間,是衡量一個(gè)算法優(yōu)劣的重要參數(shù)疚鲤。時(shí)間復(fù)雜度越小锥累,說(shuō)明該算法效率越高,則該算法越有價(jià)值集歇。
空間復(fù)雜度是指計(jì)算機(jī)科學(xué)領(lǐng)域完成一個(gè)算法所需要占用的存儲(chǔ)空間桶略,一般是輸入?yún)?shù)的函數(shù)。它是算法優(yōu)劣的重要度量指標(biāo)诲宇,一般來(lái)說(shuō)际歼,空間復(fù)雜度越小,算法越好姑蓝。
列表
穩(wěn)定性
排序算法穩(wěn)定性的簡(jiǎn)單形式化定義為:如果Ai = Aj鹅心,排序前Ai在Aj之前,排序后Ai還在Aj之前纺荧,則稱這種排序算法是穩(wěn)定的旭愧。通俗地講就是保證排序前后兩個(gè)相等的數(shù)的相對(duì)順序不變。
對(duì)于不穩(wěn)定的排序算法宙暇,只要舉出一個(gè)實(shí)例输枯,即可說(shuō)明它的不穩(wěn)定性;而對(duì)于穩(wěn)定的排序算法占贫,必須對(duì)算法進(jìn)行分析從而得到穩(wěn)定的特性桃熄。需要注意的是,排序算法是否為穩(wěn)定的是由具體算法決定的靶剑,不穩(wěn)定的算法在某種條件下可以變?yōu)榉€(wěn)定的算法蜻拨,而穩(wěn)定的算法在某種條件下也可以變?yōu)椴环€(wěn)定的算法。
排序算法如果是穩(wěn)定的桩引,那么從一個(gè)鍵上排序缎讼,然后再?gòu)牧硪粋€(gè)鍵上排序,第一個(gè)鍵排序的結(jié)果可以為第二個(gè)鍵排序所用坑匠⊙福基數(shù)排序就是這樣,先按低位排序,逐次按高位排序夹纫,低位排序后元素的順序在高位也相同時(shí)是不會(huì)改變的咽瓷。
簡(jiǎn)單選擇排序
選擇排序也是一種簡(jiǎn)單直觀的排序算法。它的工作原理很容易理解:初始時(shí)在序列中找到最薪⒍铩(大)元素茅姜,放到序列的起始位置作為已排序序列;然后月匣,再?gòu)氖S辔磁判蛟刂欣^續(xù)尋找最凶耆鳌(大)元素,放到已排序序列的末尾锄开。以此類推素标,直到所有元素均排序完畢。
注意選擇排序與冒泡排序的區(qū)別:冒泡排序通過(guò)依次交換相鄰兩個(gè)順序不合法的元素位置萍悴,從而將當(dāng)前最型吩狻(大)元素放到合適的位置;而選擇排序每遍歷一次都記住了當(dāng)前最醒⒂铡(大)元素的位置计维,最后僅需一次交換操作即可將其放到合適的位置。
// 分類 -------------- 內(nèi)部比較排序
// 數(shù)據(jù)結(jié)構(gòu) ---------- 數(shù)組
// 最差時(shí)間復(fù)雜度 ---- O(n^2)
// 最優(yōu)時(shí)間復(fù)雜度 ---- O(n^2)
// 平均時(shí)間復(fù)雜度 ---- O(n^2)
// 所需輔助空間 ------ O(1)
// 穩(wěn)定性 ------------ 不穩(wěn)定
void SelectionSort(int A[], int n) {
for (int i = 0; i < n - 1; i++) { // i為已排序序列的末尾
int min = i;
for (int j = i + 1; j < n; j++) { // 未排序序列
if (A[j] < A[min]) { // 找出未排序序列中的最小值
min = j;
}
}
if (min != i){
Swap(A, min, i); // 放到已排序序列的末尾撕予,該操作很有可能把穩(wěn)定性打亂享潜,所以選擇排序是不穩(wěn)定的排序算法
}
}
}
選擇排序是不穩(wěn)定的排序算法,不穩(wěn)定發(fā)生在最小元素與A[i]交換的時(shí)刻嗅蔬。
比如序列:{ 5, 8, 5, 2, 9 }剑按,一次選擇的最小元素是2,然后把2和第一個(gè)5進(jìn)行交換澜术,從而改變了兩個(gè)元素5的相對(duì)次序艺蝴。
冒泡排序
重復(fù)地走訪過(guò)要排序的元素,依次比較相鄰兩個(gè)元素鸟废,如果他們的順序錯(cuò)誤就把他們調(diào)換過(guò)來(lái)猜敢,直到?jīng)]有元素再需要交換,排序完成盒延。這個(gè)算法的名字由來(lái)是因?yàn)樵叫?或越大)的元素會(huì)經(jīng)由交換慢慢“浮”到數(shù)列的頂端缩擂。
冒泡排序算法的運(yùn)作如下:
- 比較相鄰的元素,如果前一個(gè)比后一個(gè)大添寺,就把它們兩個(gè)調(diào)換位置胯盯。
- 對(duì)每一對(duì)相鄰元素作同樣的工作,從開(kāi)始第一對(duì)到結(jié)尾的最后一對(duì)计露。這步做完后博脑,最后的元素會(huì)是最大的數(shù)憎乙。
- 針對(duì)所有的元素重復(fù)以上的步驟,除了最后一個(gè)叉趣。
- 持續(xù)每次對(duì)越來(lái)越少的元素重復(fù)上面的步驟泞边,直到?jīng)]有任何一對(duì)數(shù)字需要比較。
// 分類 -------------- 內(nèi)部比較排序
// 數(shù)據(jù)結(jié)構(gòu) ---------- 數(shù)組
// 最差時(shí)間復(fù)雜度 ---- O(n^2)
// 最優(yōu)時(shí)間復(fù)雜度 ---- 如果能在內(nèi)部循環(huán)第一次運(yùn)行時(shí),使用一個(gè)旗標(biāo)來(lái)表示有無(wú)需要交換的可能,可以把最優(yōu)時(shí)間復(fù)雜度降低到O(n)
// 平均時(shí)間復(fù)雜度 ---- O(n^2)
// 所需輔助空間 ------ O(1)
// 穩(wěn)定性 ------------ 穩(wěn)定
void bubbleSort(int[] arr) {
for (int i = 0; i < arr.length - 1; i++) {
//設(shè)定一個(gè)標(biāo)記疗杉,若為true阵谚,則表示此次循環(huán)沒(méi)有進(jìn)行交換,也就是待排序列已經(jīng)有序烟具,排序已然完成
boolean flag = true;椭蹄。
for (int j = 0; j < arr.length - 1 - i; j++) {
if (arr[j] > arr[j + 1]) {
swap(arr,j,j+1);
flag = false;
}
}
if (flag) {
break;
}
}
}
最好的情況是已經(jīng)排好序,只需要n-1比較即可完成净赴;最差的情況是倒序,需要n-1 + n-2 + n-3 + ...+ 2 + 1 = n(n-1)/2次比較罩润,時(shí)間復(fù)雜度是O(n*n)玖翅。
這種寫(xiě)法,冒泡排序是穩(wěn)定的割以,如果改成arr[j] >= arr[j + 1]
金度,那么就是不穩(wěn)定的。
雞尾酒排序(冒泡排序的優(yōu)化)
雞尾酒排序严沥,也叫定向冒泡排序猜极,是冒泡排序的一種改進(jìn)。此算法與冒泡排序的不同處在于從低到高然后從高到低消玄,而冒泡排序則僅從低到高去比較序列里的每個(gè)元素跟伏。他可以得到比冒泡排序稍微好一點(diǎn)的效能。
// 分類 -------------- 內(nèi)部比較排序
// 數(shù)據(jù)結(jié)構(gòu) ---------- 數(shù)組
// 最差時(shí)間復(fù)雜度 ---- O(n^2)
// 最優(yōu)時(shí)間復(fù)雜度 ---- 如果序列在一開(kāi)始已經(jīng)大部分排序過(guò)的話,會(huì)接近O(n)
// 平均時(shí)間復(fù)雜度 ---- O(n^2)
// 所需輔助空間 ------ O(1)
// 穩(wěn)定性 ------------ 穩(wěn)定
void CocktailSort(int A[]) {
int n = A.length;
int left = 0; // 初始化邊界
int right = n - 1;
while (left < right) {
for (int i = left; i < right; i++) { // 前半輪,將最大元素放到后面
if (A[i] > A[i + 1]) {
Swap(A, i, i + 1);
}
}
right--;
for (int i = right; i > left; i--) { // 后半輪,將最小元素放到前面
if (A[i - 1] > A[i]) {
Swap(A, i - 1, i);
}
}
left++;
}
}
以序列(2,3,4,5,1)為例翩瓜,雞尾酒排序只需要訪問(wèn)一次序列就可以完成排序受扳,但如果使用冒泡排序則需要四次。但是在亂數(shù)序列的狀態(tài)下兔跌,雞尾酒排序與冒泡排序的效率都很差勁勘高。
簡(jiǎn)單插入排序
直接插入排序基本思想是每一步將一個(gè)待排序的記錄,插入到前面已經(jīng)排好序的有序序列中去坟桅,直到插完所有元素為止华望。
插入排序在實(shí)現(xiàn)上,通常采用in-place排序(即只需用到O(1)的額外空間的排序)仅乓,因而在從后向前掃描過(guò)程中赖舟,需要反復(fù)把已排序元素逐步向后挪位,為最新元素提供插入空間夸楣。
具體算法描述如下:
- 從第一個(gè)元素開(kāi)始建蹄,該元素可以認(rèn)為已經(jīng)被排序
- 取出下一個(gè)元素碌更,在已經(jīng)排序的元素序列中從后向前掃描
- 如果該元素(已排序)大于新元素,將該元素移到下一位置
- 重復(fù)步驟3洞慎,直到找到已排序的元素小于或者等于新元素的位置
- 將新元素插入到該位置后
- 重復(fù)步驟2~5
// 分類 ------------- 內(nèi)部比較排序
// 數(shù)據(jù)結(jié)構(gòu) ---------- 數(shù)組
// 最差時(shí)間復(fù)雜度 ---- 最壞情況為輸入序列是降序排列的,此時(shí)時(shí)間復(fù)雜度O(n^2)
// 最優(yōu)時(shí)間復(fù)雜度 ---- 最好情況為輸入序列是升序排列的,此時(shí)時(shí)間復(fù)雜度O(n)
// 平均時(shí)間復(fù)雜度 ---- O(n^2)
// 所需輔助空間 ------ O(1)
// 穩(wěn)定性 ------------ 穩(wěn)定
void insertionSort(int[] arr) {
for (int i = 1; i < arr.length; i++) {
int j = i;
while (j > 0 && arr[j] < arr[j - 1]) {
swap(arr,j,j-1);
j--;
}
}
}
簡(jiǎn)單插入排序在最好情況下痛单,需要比較n-1次,無(wú)需交換元素劲腿,時(shí)間復(fù)雜度為O(n);在最壞情況下旭绒,時(shí)間復(fù)雜度依然為O(n2)。插入排序不適合對(duì)于數(shù)據(jù)量比較大的排序應(yīng)用焦人。但是挥吵,如果需要排序的數(shù)據(jù)量很小,比如量級(jí)小于千花椭,那么插入排序還是一個(gè)不錯(cuò)的選擇忽匈。 插入排序在工業(yè)級(jí)庫(kù)中也有著廣泛的應(yīng)用,在STL的sort算法和stdlib的qsort算法中矿辽,都將插入排序作為快速排序的補(bǔ)充丹允,用于少量元素的排序(通常為8個(gè)或以下)。
二分插入排序(插入排序優(yōu)化)
對(duì)于插入排序袋倔,如果比較操作的代價(jià)比交換操作大的話雕蔽,可以采用二分查找法來(lái)減少比較操作的次數(shù),我們稱為二分插入排序宾娜。
// 分類 -------------- 內(nèi)部比較排序
// 數(shù)據(jù)結(jié)構(gòu) ---------- 數(shù)組
// 最差時(shí)間復(fù)雜度 ---- O(n^2)
// 最優(yōu)時(shí)間復(fù)雜度 ---- O(nlogn)
// 平均時(shí)間復(fù)雜度 ---- O(n^2)
// 所需輔助空間 ------ O(1)
// 穩(wěn)定性 ------------ 穩(wěn)定
void InsertionSortDichotomy(int A[]) {
int n = A.length;
for (int i = 1; i < n; i++) {
int target = A[i];
int left = 0;
int right = i - 1;
while (left <= right) { // 二分查找批狐,找到新元素在排好序的序列中的位置
int mid = (left + right) / 2;
if (A[mid] > target)
right = mid - 1;
else
left = mid + 1;
}
for (int j = i - 1; j >= left; j--) { // 整體后移一位
A[j + 1] = A[j];
}
A[left] = target;
}
}
當(dāng)n較大時(shí),二分插入排序的比較次數(shù)比直接插入排序的最差情況好得多前塔,但比直接插入排序的最好情況要差嚣艇,所當(dāng)以元素初始序列已經(jīng)接近升序時(shí),直接插入排序比二分插入排序比較次數(shù)少华弓。二分插入排序元素移動(dòng)次數(shù)與直接插入排序相同髓废,依賴于元素初始序列。
希爾排序
希爾排序该抒,也叫遞減增量排序慌洪,是插入排序的一種更高效的改進(jìn)版本。希爾排序是不穩(wěn)定的排序算法凑保。
希爾排序是基于插入排序的以下兩點(diǎn)性質(zhì)而提出改進(jìn)方法的:
- 插入排序在對(duì)幾乎已經(jīng)排好序的數(shù)據(jù)操作時(shí)冈爹,效率高,即可以達(dá)到線性排序的效率
- 插入排序一般來(lái)說(shuō)是低效的欧引,因?yàn)椴迦肱判蛎看沃荒軐?shù)據(jù)移動(dòng)一位
希爾排序通過(guò)將比較的全部元素分為幾個(gè)區(qū)域來(lái)提升插入排序的性能频伤。這樣可以讓一個(gè)元素可以一次性地朝最終位置前進(jìn)一大步。然后算法再取越來(lái)越小的步長(zhǎng)進(jìn)行排序芝此,算法的最后一步就是普通的插入排序憋肖,但是到了這步因痛,需排序的數(shù)據(jù)幾乎是已排好的了(此時(shí)插入排序較快)。
假設(shè)有一個(gè)很小的數(shù)據(jù)在一個(gè)已按升序排好序的數(shù)組的末端岸更。如果用復(fù)雜度為O(n^2)的排序(冒泡排序或直接插入排序)鸵膏,可能會(huì)進(jìn)行n次的比較和交換才能將該數(shù)據(jù)移至正確位置。而希爾排序會(huì)用較大的步長(zhǎng)移動(dòng)數(shù)據(jù)怎炊,所以小數(shù)據(jù)只需進(jìn)行少數(shù)比較和交換即可到正確位置谭企。
// 分類 -------------- 內(nèi)部比較排序
// 數(shù)據(jù)結(jié)構(gòu) ---------- 數(shù)組
// 最差時(shí)間復(fù)雜度 ---- 根據(jù)步長(zhǎng)序列的不同而不同。已知最好的為O(n(logn)^2)
// 最優(yōu)時(shí)間復(fù)雜度 ---- O(n)
// 平均時(shí)間復(fù)雜度 ---- 根據(jù)步長(zhǎng)序列的不同而不同评肆。
// 所需輔助空間 ------ O(1)
// 穩(wěn)定性 ------------ 不穩(wěn)定
void ShellSort(int A[]) {
int n = A.length;
int h = n/3;
while (h <= n) { // 生成初始增量
h = 3 * h + 1;
}
while (h >= 1){
for (int i = h; i < n; i++){
int j = i - h;
int target = A[i];
while (j >= 0 && A[j] > target){
A[j + h] = A[j];
j = j - h;
}
A[j + h] = target;
}
h = (h - 1) / 3; // 遞減增量
}
}
void ShellSort(int a[]) {
int n = a.length;
for (int gap = n/3; gap > 0; gap = gap/3) {
for(int j = gap; j < n; j++) {
for(int i = j - gap; i >= 0 && a[i] > a[i + gap]; i -= gap) {
swap(a, i, i + gap);
}
}
}
}
希爾排序是不穩(wěn)定的排序算法债查,雖然一次插入排序是穩(wěn)定的,不會(huì)改變相同元素的相對(duì)順序瓜挽,但在不同的插入排序過(guò)程中盹廷,相同的元素可能在各自的插入排序中移動(dòng),最后其穩(wěn)定性就會(huì)被打亂久橙。
比如序列:{ 3, 5, 10, 8, 7, 2, 8, 1, 20, 6 }俄占,h=2時(shí)分成兩個(gè)子序列 { 3, 10, 7, 8, 20 } 和 { 5, 8, 2, 1, 6 } ,未排序之前第二個(gè)子序列中的8在前面剥汤,現(xiàn)在對(duì)兩個(gè)子序列進(jìn)行插入排序,得到 { 3, 7, 8, 10, 20 } 和 { 1, 2, 5, 6, 8 } 排惨,即 { 3, 1, 7, 2, 8, 5, 10, 6, 20, 8 } 吭敢,兩個(gè)8的相對(duì)次序發(fā)生了改變。
歸并排序
歸并排序是創(chuàng)建在歸并操作上的一種有效的排序算法暮芭,效率為O(nlogn)鹿驼,1945年由馮·諾伊曼首次提出。
歸并排序的實(shí)現(xiàn)分為遞歸實(shí)現(xiàn)與非遞歸(迭代)實(shí)現(xiàn)辕宏。遞歸實(shí)現(xiàn)的歸并排序是算法設(shè)計(jì)中分治策略的典型應(yīng)用畜晰,我們將一個(gè)大問(wèn)題分割成小問(wèn)題分別解決,然后用所有小問(wèn)題的答案來(lái)解決整個(gè)大問(wèn)題瑞筐。非遞歸(迭代)實(shí)現(xiàn)的歸并排序首先進(jìn)行是兩兩歸并凄鼻,然后四四歸并,然后是八八歸并聚假,一直下去直到歸并了整個(gè)數(shù)組块蚌。
歸并排序算法主要依賴歸并(Merge)操作。歸并操作指的是將兩個(gè)已經(jīng)排序的序列合并成一個(gè)序列的操作膘格,歸并操作步驟如下:
- 申請(qǐng)空間峭范,使其大小為兩個(gè)已經(jīng)排序序列之和,該空間用來(lái)存放合并后的序列
- 設(shè)定兩個(gè)指針瘪贱,最初位置分別為兩個(gè)已經(jīng)排序序列的起始位置
- 比較兩個(gè)指針?biāo)赶虻脑厣纯兀x擇相對(duì)小的元素放入到合并空間辆毡,并移動(dòng)指針到下一位置
- 重復(fù)步驟3直到某一指針到達(dá)序列尾
- 將另一序列剩下的所有元素直接復(fù)制到合并序列尾
// 分類 -------------- 內(nèi)部比較排序
// 數(shù)據(jù)結(jié)構(gòu) ---------- 數(shù)組
// 最差時(shí)間復(fù)雜度 ---- O(nlogn)
// 最優(yōu)時(shí)間復(fù)雜度 ---- O(nlogn)
// 平均時(shí)間復(fù)雜度 ---- O(nlogn)
// 所需輔助空間 ------ O(n)
// 穩(wěn)定性 ------------ 穩(wěn)定
// 遞歸
void sort(int[] arr,int left,int right,int[] temp) {
if(left < right) {
int mid = (left+right)/2;
sort(arr,left,mid,temp);//左邊歸并排序,使得左子序列有序
sort(arr,mid+1,right,temp);//右邊歸并排序甜害,使得右子序列有序
merge(arr,left,mid,right,temp);//將兩個(gè)有序子數(shù)組合并操作
}
}
// 非遞歸
void MergeSortIteration(int[] arr, int len, int[] temp) { // 非遞歸(迭代)實(shí)現(xiàn)的歸并排序(自底向上)
int left, mid, right;// 子數(shù)組索引,前一個(gè)為A[left...mid]舶掖,后一個(gè)子數(shù)組為A[mid+1...right]
for (int i = 1; i < len; i *= 2) { // 子數(shù)組的大小i初始為1,每輪翻倍
left = 0;
while (left + i < len) { // 后一個(gè)子數(shù)組存在(需要?dú)w并)
mid = left + i - 1;
right = mid + i < len ? mid + i : len - 1;// 后一個(gè)子數(shù)組大小可能不夠
merge(arr, left, mid, right, temp);
left = right + 1; // 前一個(gè)子數(shù)組索引向后移動(dòng)
}
}
}
void merge(int[] arr,int left,int mid,int right,int[] temp){
int i = left;//左序列指針
int j = mid+1;//右序列指針
int t = 0;//臨時(shí)數(shù)組指針
while (i<=mid && j<=right){
if(arr[i]<=arr[j]){
temp[t++] = arr[i++];
}else {
temp[t++] = arr[j++];
}
}
while(i<=mid){//將左邊剩余元素填充進(jìn)temp中
temp[t++] = arr[i++];
}
while(j<=right){//將右序列剩余元素填充進(jìn)temp中
temp[t++] = arr[j++];
}
t = 0;
//將temp中的元素全部拷貝到原數(shù)組中
while(left <= right){
arr[left++] = temp[t++];
}
}
}
歸并排序除了可以對(duì)數(shù)組進(jìn)行排序唾那,還可以高效的求出數(shù)組小和(即單調(diào)和)以及數(shù)組中的逆序?qū)Ψ枚停斠?jiàn)這篇博文。
堆排序
堆排序是指利用堆這種數(shù)據(jù)結(jié)構(gòu)所設(shè)計(jì)的一種選擇排序算法闹获。堆排序是一種選擇排序期犬,它的最壞,最好避诽,平均時(shí)間復(fù)雜度均為O(nlogn)龟虎,它也是不穩(wěn)定排序。堆是一種近似完全二叉樹(shù)的結(jié)構(gòu)(通常堆是通過(guò)一維數(shù)組來(lái)實(shí)現(xiàn)的)沙庐,并滿足性質(zhì):以最大堆(也叫大根堆鲤妥、大頂堆)為例,其中父結(jié)點(diǎn)的值總是大于它的孩子節(jié)點(diǎn)拱雏。這篇文章詳解
該數(shù)組從邏輯上講就是一個(gè)堆結(jié)構(gòu)棉安,我們用簡(jiǎn)單的公式來(lái)描述一下堆的定義就是:
- 大頂堆:arr[i] >= arr[2i+1] && arr[i] >= arr[2i+2]
- 小頂堆:arr[i] <= arr[2i+1] && arr[i] <= arr[2i+2]
堆排序的基本思想是:將待排序序列構(gòu)造成一個(gè)大頂堆,此時(shí)铸抑,整個(gè)序列的最大值就是堆頂?shù)母?jié)點(diǎn)贡耽。將其與末尾元素進(jìn)行交換,此時(shí)末尾就為最大值鹊汛。然后將剩余n-1個(gè)元素重新構(gòu)造成一個(gè)堆蒲赂,這樣會(huì)得到n個(gè)元素的次小值。如此反復(fù)執(zhí)行刁憋,便能得到一個(gè)有序序列了滥嘴。
// 分類 -------------- 內(nèi)部比較排序
// 數(shù)據(jù)結(jié)構(gòu) ---------- 數(shù)組
// 最差時(shí)間復(fù)雜度 ---- O(nlogn)
// 最優(yōu)時(shí)間復(fù)雜度 ---- O(nlogn)
// 平均時(shí)間復(fù)雜度 ---- O(nlogn)
// 所需輔助空間 ------ O(1)
// 穩(wěn)定性 ------------ 不穩(wěn)定
public void sort(int []arr){
//1.構(gòu)建大頂堆
for(int i=arr.length/2-1;i>=0;i--){
//從第一個(gè)非葉子結(jié)點(diǎn)從下至上,從右至左調(diào)整結(jié)構(gòu)
adjustHeap(arr,i,arr.length);
}
//2.調(diào)整堆結(jié)構(gòu)+交換堆頂元素與末尾元素
for(int j=arr.length-1;j>0;j--){
swap(arr,0,j);//將堆頂元素與末尾元素進(jìn)行交換
adjustHeap(arr,0,j);//重新對(duì)堆進(jìn)行調(diào)整
}
}
/**
* 調(diào)整大頂堆(僅是調(diào)整過(guò)程至耻,建立在大頂堆已構(gòu)建的基礎(chǔ)上)
*/
public void adjustHeap(int []arr,int i,int length) {
int temp = arr[i];//先取出當(dāng)前元素i
for(int k=i*2+1;k<length;k=k*2+1) {//從i結(jié)點(diǎn)的左子結(jié)點(diǎn)開(kāi)始若皱,也就是2i+1處開(kāi)始
if(k+1 < length && arr[k] < arr[k+1]) {//如果左子結(jié)點(diǎn)小于右子結(jié)點(diǎn),k指向右子結(jié)點(diǎn)
k++;
}
if(arr[k] > temp){//如果子節(jié)點(diǎn)大于父節(jié)點(diǎn)尘颓,將子節(jié)點(diǎn)值賦給父節(jié)點(diǎn)(不用進(jìn)行交換)
arr[i] = arr[k];
i = k;
}else{
break;
}
}
arr[i] = temp;//將temp值放到最終的位置
}
堆排序是一種選擇排序是尖,整體主要由構(gòu)建初始堆+交換堆頂元素和末尾元素并重建堆兩部分組成。其中構(gòu)建初始堆經(jīng)推導(dǎo)復(fù)雜度為O(n)泥耀,在交換并重建堆的過(guò)程中饺汹,需交換n-1次,而重建堆的過(guò)程中痰催,根據(jù)完全二叉樹(shù)的性質(zhì)兜辞,[log2(n-1),log2(n-2)...1]逐步遞減迎瞧,近似為nlogn。所以堆排序時(shí)間復(fù)雜度一般認(rèn)為就是O(nlogn)級(jí)逸吵。
堆排序是不穩(wěn)定的排序算法凶硅,不穩(wěn)定發(fā)生在堆頂元素與A[i]交換的時(shí)刻。比如序列:{ 9, 5, 7, 5 }扫皱,堆頂元素是9足绅,堆排序下一步將9和第二個(gè)5進(jìn)行交換,得到序列 { 5, 5, 7, 9 }韩脑,再進(jìn)行堆調(diào)整得到{ 7, 5, 5, 9 }氢妈,重復(fù)之前的操作最后得到{ 5, 5, 7, 9 }從而改變了兩個(gè)5的相對(duì)次序。
快速排序
快速排序是由東尼·霍爾所發(fā)展的一種排序算法段多。在平均狀況下首量,排序n個(gè)元素要O(nlogn)次比較。在最壞狀況下則需要O(n^2)次比較进苍,但這種狀況并不常見(jiàn)加缘。事實(shí)上,快速排序通常明顯比其他O(nlogn)算法更快觉啊,因?yàn)樗膬?nèi)部循環(huán)可以在大部分的架構(gòu)上很有效率地被實(shí)現(xiàn)出來(lái)拣宏。
快速排序使用分治策略(Divide and Conquer),每一次我們要取一個(gè)元素作為樞紐值杠人,以這個(gè)數(shù)字來(lái)將序列劃分為兩部分勋乾。在此我們采用三數(shù)取中法,也就是取左端搜吧、中間景用、右端三個(gè)數(shù)肛冶,然后進(jìn)行排序,將中間數(shù)作為樞紐值顿苇。
// 分類 ------------ 內(nèi)部比較排序
// 數(shù)據(jù)結(jié)構(gòu) --------- 數(shù)組
// 最差時(shí)間復(fù)雜度 ---- 每次選取的基準(zhǔn)都是最大(或最辛寐)的元素蜒程,導(dǎo)致每次只劃分出了一個(gè)分區(qū),需要進(jìn)行n-1次劃分才能結(jié)束遞歸伺帘,時(shí)間復(fù)雜度為O(n^2)
// 最優(yōu)時(shí)間復(fù)雜度 ---- 每次選取的基準(zhǔn)都是中位數(shù)昭躺,這樣每次都均勻的劃分出兩個(gè)分區(qū),只需要logn次劃分就能結(jié)束遞歸伪嫁,時(shí)間復(fù)雜度為O(nlogn)
// 平均時(shí)間復(fù)雜度 ---- O(nlogn)
// 所需輔助空間 ------ 主要是遞歸造成的椓祆牛空間的使用(用來(lái)保存left和right等局部變量),取決于遞歸樹(shù)的深度张咳,一般為O(logn)帝洪,最差為O(n)
// 穩(wěn)定性 ---------- 不穩(wěn)定
public static void quickSort(int[] arr, int left, int right) {
if(left >= right) {
return;
}
int pivotPosition = partition(arr, left, right);
quickSort(arr, left, pivotPosition - 1);
quickSort(arr, pivotPosition + 1, right);
}
public static int partition(int[] arr, int left, int right) {
int pivot = arr[right];
int pivotPosition = left;
for(int i = left; i < right; i++) {
if(arr[i] < pivot) {
swap(arr, pivotPosition++, i);
}
}
swap(arr, pivotPosition, right);
return pivotPosition;
}
快速排序是不穩(wěn)定的排序算法似舵,不穩(wěn)定發(fā)生在基準(zhǔn)元素與A[tail+1]交換的時(shí)刻。
比如序列:{ 1, 3, 4, 2, 8, 9, 8, 7, 5 }葱峡,基準(zhǔn)元素是5砚哗,一次劃分操作后5要和第一個(gè)8進(jìn)行交換,從而改變了兩個(gè)元素8的相對(duì)次序砰奕。
終
關(guān)于算法蛛芥,每一次看都有不同的感受,索性總結(jié)一遍军援。