【数据结构与算法】排序 冒泡、插入、选择 O(n^2)
冒泡、插入、選擇 O(n2) 基于比較
快排、歸并 O(nlogn) 基于比較
計(jì)數(shù)、基數(shù)、桶 O(n) 不基于比較
一、如何分析一個(gè)排序算法?
1)最好情況、最壞情況、平均情況時(shí)間復(fù)雜度
2)時(shí)間復(fù)雜度的系數(shù)、常數(shù)、低階:排序的數(shù)據(jù)量比較小時(shí)考慮
3)比較次數(shù)和交換(或移動(dòng))次數(shù)
二、冒泡排序
1.排序原理
1)冒泡排序只會(huì)操作相鄰的兩個(gè)數(shù)據(jù)。
2)對(duì)相鄰兩個(gè)數(shù)據(jù)進(jìn)行比較,看是否滿足大小關(guān)系要求,若不滿足讓它倆互換。
3)一次冒泡會(huì)讓至少一個(gè)元素移動(dòng)到它應(yīng)該在的位置,重復(fù)n次,就完成了n個(gè)數(shù)據(jù)的排序工作。
4)優(yōu)化:若某次冒泡不存在數(shù)據(jù)交換,則說(shuō)明已經(jīng)達(dá)到完全有序,所以終止冒泡。
2.代碼實(shí)現(xiàn)
// 冒泡排序,a表示數(shù)組,n表示數(shù)組大小 public void bubbleSort(int[] a, int n) {if (n <= 1) return;for (int i = 0; i < n; ++i) {// 提前退出冒泡循環(huán)的標(biāo)志位boolean flag = false;for (int j = 0; j < n - i - 1; ++j) {if (a[j] > a[j+1]) { // 交換int tmp = a[j];a[j] = a[j+1];a[j+1] = tmp;flag = true; // 表示有數(shù)據(jù)交換 }}if (!flag) break; // 沒(méi)有數(shù)據(jù)交換,提前退出} }3.性能分析
1)執(zhí)行效率:最小時(shí)間復(fù)雜度、最大時(shí)間復(fù)雜度、平均時(shí)間復(fù)雜度
最小時(shí)間復(fù)雜度:數(shù)據(jù)完全有序時(shí),只需進(jìn)行一次冒泡操作即可,時(shí)間復(fù)雜度是O(n)。
最大時(shí)間復(fù)雜度:數(shù)據(jù)倒序排序時(shí),需要n次冒泡操作,時(shí)間復(fù)雜度是O(n^2)。
平均時(shí)間復(fù)雜度:通過(guò)有序度和逆序度來(lái)分析。
對(duì)于包含n個(gè)數(shù)據(jù)的數(shù)組進(jìn)行冒泡排序,平均交換次數(shù)是多少呢?最壞的情況初始有序度為0,所以要進(jìn)行n*(n-1)/2交換。最好情況下,初始狀態(tài)有序度是n*(n-1)/2,就不需要進(jìn)行交互。我們可以取個(gè)中間值n*(n-1)/4,來(lái)表示初始有序度既不是很高也不是很低的平均情況。
換句話說(shuō),平均情況下,需要n*(n-1)/4次交換操作,比較操作可定比交換操作多,而復(fù)雜度的上限是O(n2),所以平均情況時(shí)間復(fù)雜度就是O(n2)。
2)空間復(fù)雜度:每次交換僅需1個(gè)臨時(shí)變量,故空間復(fù)雜度為O(1),是原地排序算法。
3)算法穩(wěn)定性:如果兩個(gè)值相等,就不會(huì)交換位置,故是穩(wěn)定排序算法。
三、 插入排序(Insertion Sort)
1.算法原理
首先,我們將數(shù)組中的數(shù)據(jù)分為2個(gè)區(qū)間,即已排序區(qū)間和未排序區(qū)間。初始已排序區(qū)間只有一個(gè)元素,就是數(shù)組的第一個(gè)元素。插入算法的核心思想就是取未排序區(qū)間中的元素,在已排序區(qū)間中找到合適的插入位置將其插入,并保證已排序區(qū)間中的元素一直有序。重復(fù)這個(gè)過(guò)程,直到未排序中元素為空,算法結(jié)束。
2.代碼實(shí)現(xiàn)
// 插入排序,a表示數(shù)組,n表示數(shù)組大小 public void insertionSort(int[] a, int n) {if (n <= 1) return;for (int i = 1; i < n; ++i) {int value = a[i];int j = i - 1;// 查找插入的位置for (; j >= 0; --j) {if (a[j] > value) {a[j+1] = a[j]; // 數(shù)據(jù)移動(dòng)} else {break;}}a[j+1] = value; // 插入數(shù)據(jù)} }3.性能分析
1)時(shí)間復(fù)雜度:最好、最壞、平均情況
如果要排序的數(shù)組已經(jīng)是有序的,我們并不需要搬移任何數(shù)據(jù)。只需要遍歷一遍數(shù)組即可,所以時(shí)間復(fù)雜度是O(n)。如果數(shù)組是倒序的,每次插入都相當(dāng)于在數(shù)組的第一個(gè)位置插入新的數(shù)據(jù),所以需要移動(dòng)大量的數(shù)據(jù),因此時(shí)間復(fù)雜度是O(n2)。而在一個(gè)數(shù)組中插入一個(gè)元素的平均時(shí)間復(fù)雜都是O(n),插入排序需要n次插入,所以平均時(shí)間復(fù)雜度是O(n2)。
2)空間復(fù)雜度:從上面的代碼可以看出,插入排序算法的運(yùn)行并不需要額外的存儲(chǔ)空間,所以空間復(fù)雜度是O(1),是原地排序算法。
3)算法穩(wěn)定性:在插入排序中,對(duì)于值相同的元素,我們可以選擇將后面出現(xiàn)的元素,插入到前面出現(xiàn)的元素的后面,這樣就保持原有的順序不變,所以是穩(wěn)定的。
四、選擇排序(Selection Sort)
1.算法原理
選擇排序算法也分已排序區(qū)間和未排序區(qū)間。但是選擇排序每次會(huì)從未排序區(qū)間中找到最小的元素,并將其放置到已排序區(qū)間的末尾。
2.代碼實(shí)現(xiàn)
/*** 選擇排序* @param a 待排序數(shù)組* @param n 數(shù)組長(zhǎng)度*/ public static void selectSort(int[] a, int n) { if(n<=0) return;for(int i=0;i<n;i++){int min=i;for(int j=i;j<n;j++){if(a[j] < a[min]) min=j;}if(min != i){int temp=a[i];a[i]=a[min];a[min]=temp;}} }3.性能分析
1)時(shí)間復(fù)雜度:最好、最壞、平均情況
選擇排序的最好、最壞、平均情況時(shí)間復(fù)雜度都是O(n2)。為什么?因?yàn)闊o(wú)論是否有序,每個(gè)循環(huán)都會(huì)完整執(zhí)行,沒(méi)得商量。
2)空間復(fù)雜度:
選擇排序算法空間復(fù)雜度是O(1),是一種原地排序算法。
3)算法穩(wěn)定性:
選擇排序算法不是一種穩(wěn)定排序算法,比如[5,8,5,2,9]這個(gè)數(shù)組,使用選擇排序算法第一次找到的最小元素就是2,與第一個(gè)位置的元素5交換位置,那第一個(gè)5和中間的5的順序就變量,所以就不穩(wěn)定了。正因如此,相對(duì)于冒泡排序和插入排序,選擇排序就稍微遜色了。
五、冒泡排序和插入排序的時(shí)間復(fù)雜度都是 O(n^2),都是原地排序算法,為什么插入排序要比冒泡排序更受歡迎呢?
冒泡排序移動(dòng)數(shù)據(jù)有3條賦值語(yǔ)句,而選擇排序的交換位置的只有1條賦值語(yǔ)句,因此在有序度相同的情況下,冒泡排序時(shí)間復(fù)雜度是選擇排序的3倍,所以,選擇排序性能更好。
冒泡排序中數(shù)據(jù)的交換操作: if (a[j] > a[j+1]) { // 交換int tmp = a[j];a[j] = a[j+1];a[j+1] = tmp;flag = true; }插入排序中數(shù)據(jù)的移動(dòng)操作: if (a[j] > value) {a[j+1] = a[j]; // 數(shù)據(jù)移動(dòng) } else {break; }筆記整理來(lái)源: 王爭(zhēng) 數(shù)據(jù)結(jié)構(gòu)與算法之美
創(chuàng)作挑戰(zhàn)賽新人創(chuàng)作獎(jiǎng)勵(lì)來(lái)咯,堅(jiān)持創(chuàng)作打卡瓜分現(xiàn)金大獎(jiǎng)總結(jié)
以上是生活随笔為你收集整理的【数据结构与算法】排序 冒泡、插入、选择 O(n^2)的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。
- 上一篇: Python 生成器 迭代器
- 下一篇: [Leetcode][第24题][JAV