raptor五个数排序流程图_数据结构与算法(一):排序(上)
做這個(gè)系列一是記錄自己的學(xué)習(xí)過(guò)程,二是整合目前我所接觸的比較好的資料,給出最直觀,最通俗的算法解釋
總體概況
十大排序算法:(比較排序):冒泡、選擇、插入、歸并、快速、希爾、堆排序
基數(shù)排序、桶排序、計(jì)數(shù)排序
? ? ? ? ? ? ?
冒泡排序(Bubble Sort)
流程
從頭開(kāi)始比較相鄰的一對(duì)元素,如果前者大于后者,則交換。如果前者小于后者,則不變
經(jīng)過(guò)第一輪循環(huán),最末尾的元素就是最大的元素
經(jīng)過(guò)第二輪循環(huán),倒數(shù)第二個(gè)元素為第二大的元素
……
經(jīng)過(guò)N輪循環(huán),則數(shù)組已從小到大排序
第一輪循環(huán)用代碼表示
int[] array = {23, 53, 1, 32, 5, 77, 64};for (int begin = 1; begin < array.length; begin++) { if (array[begin] < array[begin-1]){ int tmp = array[begin]; array[begin] = array[begin-1]; array[begin-1] = tmp;//交換 }}for (int i = 0; i < array.length; i++) { System.out.print(array[i]+" ");}首先自己定義一個(gè)數(shù)組,并用for循環(huán)進(jìn)行從頭到尾的遍歷,這邊從1開(kāi)始是因?yàn)槲覀冃枰玫絙egin-1的值,begin-1是非負(fù)數(shù),所以begin從1開(kāi)始
array[begin-1]為前者 ?array[begin] 即為后者
按我們邏輯:前者大于后者時(shí)交換,所以接下來(lái)是一個(gè)典型的交換方法
原數(shù)組:[23 , 53 , 1 , 32 , 5 , 77 , 64]
在一輪循環(huán)結(jié)束看一下數(shù)組的變化:
? ? ? ? ? ? ?
此時(shí)最后一位已經(jīng)固定了(最大值),下一次循環(huán)循環(huán)到倒數(shù)第二位,這么依次下來(lái)需要用到外層循環(huán)(直到固定到第一位):
for (int end = array.length - 1; end > 0; end--) { for (int begin = 1; begin <= end; begin++) { if (array[begin] < array[begin - 1]) { int tmp = array[begin]; array[begin] = array[begin - 1]; array[begin - 1] = tmp;//交換 } }}這就是著名的冒泡排序!
完全有序情況優(yōu)化
如果拿到的數(shù)組就是有序的,那我們還需要經(jīng)歷這兩重for循環(huán)嗎?
目前的代碼不管數(shù)組是有序還是無(wú)序,都要經(jīng)歷整個(gè)過(guò)程
所以我們對(duì)此進(jìn)行優(yōu)化
在第一次掃描元素后,我們就知道數(shù)組是否有序
在開(kāi)始時(shí)定義一個(gè)元素sorted = true,如果進(jìn)入了循環(huán)中的if語(yǔ)句,則把sorted置為false
定義sorted的值我們放在外層循環(huán)中。原因是可能整個(gè)數(shù)組在開(kāi)始時(shí)候有序,這是最理想的情況,還有的情況就是在循環(huán)了幾次之后發(fā)現(xiàn)變成有序的了,這時(shí)候后面的循環(huán)就沒(méi)有必要進(jìn)行下去了。所以需要在每一次掃描前把sorted置為true。
for (int end = array.length - 1; end > 0; end--) { boolean sorted = true; for (int begin = 1; begin <= end; begin++) { if (array[begin] < array[begin - 1]) { int tmp = array[begin]; array[begin] = array[begin - 1]; array[begin - 1] = tmp;//交換 sorted = false; } } if (sorted) break;}顯而易見(jiàn),通過(guò)這種方式我們處理了在進(jìn)行幾次循環(huán)后完全有序的情況,確實(shí)比之前的快了不少,但是還不夠優(yōu)化。原因是很少能出現(xiàn)這種完全有序的情況。
所以遇到中間局部有序的情況,需要進(jìn)一步優(yōu)化
局部有序情況優(yōu)化
每次掃描數(shù)據(jù)的時(shí)候,記錄最后一次交換的位置
定義一個(gè)索引指向最后一次交換的位置
for (int end = array.length - 1; end > 0; end--) { int sortedIndex = 1; for (int begin = 1; begin <= end; begin++) { if (array[begin] < array[begin - 1]) { int tmp = array[begin]; array[begin] = array[begin - 1]; array[begin - 1] = tmp;//交換 sortedIndex = begin; } } end = sortedIndex;}而這邊索引值的初始值設(shè)為1,原因是當(dāng)數(shù)組本來(lái)就完全有序的時(shí)候(不經(jīng)過(guò)中間的if判斷),我們不需要進(jìn)行循環(huán),所以在后面end置為sortedIndex的初始值。
最壞、平均情況(sortedIndex完全沒(méi)有起作用)
時(shí)間復(fù)雜度:O(n^2)
最好情況(數(shù)據(jù)完全有序)
時(shí)間復(fù)雜度:O(n)
空間復(fù)雜度:O(1)
穩(wěn)定性
如果兩個(gè)數(shù)據(jù)排序內(nèi)容相同,但是是不穩(wěn)定的。
? ? ? ? ? ? ?
假如有一個(gè)學(xué)生數(shù)據(jù)類型(名字,分?jǐn)?shù))
當(dāng)兩個(gè)學(xué)生的成績(jī)相等,排序的時(shí)候是不管名字的。所以誰(shuí)在前誰(shuí)在后還不一定,這就導(dǎo)致了不穩(wěn)定性
冒泡排序無(wú)疑是穩(wěn)定的
原因是:遇到兩個(gè)相等的,是不會(huì)交換的
if (array[begin] < array[begin - 1])原地算法
不依賴額外的資源或者依賴少數(shù)的額外資源,僅依靠輸出來(lái)覆蓋輸入
空間復(fù)雜度為O(1)的都可以認(rèn)為是原地算法
冒泡排序?qū)儆谠厮惴?/strong>
選擇排序(Selection Sort)
流程
從序列中找出最大的那個(gè)元素,然后與最末尾的元素交換位置
執(zhí)行完一輪后,最末尾的那個(gè)元素就是最大的元素
忽略上述找到的最大元素,重復(fù)執(zhí)行步驟1
選擇排序與冒泡排序比較像,兩者都是兩重循環(huán)
for (int end = array.length - 1; end > 0; end--) { int maxIndex = 0; for (int begin = 0; begin <= end; begin++) { if (array[maxIndex]<= array[begin]){ maxIndex = begin; } } int tmp = array[maxIndex]; array[maxIndex] = array[end]; array[end] = tmp;}選擇排序的交換次數(shù)是遠(yuǎn)遠(yuǎn)小于冒泡排序的,平均性能優(yōu)于冒泡排序
最好、最壞情況:
時(shí)間復(fù)雜度:O(n^2)
空間復(fù)雜度:O(1)
選擇排序?qū)儆诜€(wěn)定排序
優(yōu)化
利用堆的數(shù)據(jù)結(jié)構(gòu),在取最大時(shí)時(shí)間復(fù)雜度可以到達(dá)O(lLogn),所以加上一層循環(huán)后時(shí)間復(fù)雜度可以到達(dá)O(nLogn)
堆排序(Heap Sort)
堆排序可以認(rèn)為是對(duì)選擇排序的一種優(yōu)化
流程
對(duì)序列進(jìn)行原地建堆(heapify)
重復(fù)執(zhí)行以下操作,知道堆的元素?cái)?shù)量為1
交換堆頂元素與尾元素
堆的元素?cái)?shù)量減1
對(duì)0位置進(jìn)行1次siftDown操作
流程圖
? ? ? ? ? ? ?
|
V
? ? ? ? ? ? ?
|
V
? ? ?? ? ? ?
|
V
??? ? ? ?
...以此類推
由于需要將所有的排序分布在不同的類,所以這邊我們將代碼重構(gòu)了一下,寫了一個(gè)Sort的父類包含元素的比較以及元素的交換:
public abstract class Sort { protected Integer[] array; protected int cmpCount; protected int swapCount; public void sort(Integer[] array) { if (array == null || array.length < 2) return; this.array = array; sort(); } protected abstract void sort(); /* * 返回值等于=0,說(shuō)明相等 * 返回值小于0,代表array[i1] * 返回值大于0,array[i1]>array[i2] */ protected int cmp(int i1, int i2) { cmpCount++; return array[i1] - array[i2]; } protected int cmpElements(Integer v1,Integer v2){ cmpCount++; return v1-v2; } /* * 交換 */ protected void swap(Integer i1, Integer i2) { swapCount++; int tmp = array[i1]; array[i1] = array[i2]; array[i2] = tmp; }讓所有的排序繼承這個(gè)父類(Sort)
堆排序的邏輯很簡(jiǎn)單:
我們這邊運(yùn)用最大堆(最大的數(shù)在堆頂,每個(gè)節(jié)點(diǎn)的左子樹(shù)小于該節(jié)點(diǎn),每個(gè)節(jié)點(diǎn)的右子樹(shù)大于該節(jié)點(diǎn))
關(guān)于如何原地建堆這邊不闡述了,這是基本的數(shù)據(jù)結(jié)構(gòu)
程序代碼如下:
public class HeapSort extends Sort{ private int heapSize; @Override protected void sort() { //原地建堆 heapSize = array.length; for (int i = (heapSize>>1)-1; i >=0; i--) { siftDown(i); //將數(shù)組中的元素放入堆中 } while (heapSize>1){ //交換堆頂元素和尾部元素 swap(0,--heapSize); //對(duì)換上來(lái)的元素調(diào)整位置siftDown siftDown(0); } } private void siftDown(int index){ Integer element = array[index]; int half = heapSize >>1; while(index int childIndex = (index<<1)+1; Integer child = array[childIndex]; int rightIndex = childIndex+1; if (rightIndex0){ child = array[childIndex = rightIndex]; } if (cmpElements(element,child)>=0) break; array[index] = child; index = childIndex; } array[index] = element;siftDown不多闡述,這是關(guān)于堆中調(diào)整位置的方法。有興趣的可以自行查看網(wǎng)上資料
在數(shù)據(jù)足夠多,數(shù)據(jù)量大的時(shí)候,堆排序的優(yōu)勢(shì)就非常明顯了
時(shí)間復(fù)雜度:O(nLogn)
空間復(fù)雜度:O(1)
堆排序不是一個(gè)穩(wěn)定的排序
插入排序(Insertion Sort)
插入排序非常類似于撲克牌的排序
? ? ? ? ? ? ?
流程
在執(zhí)行過(guò)程中,插入排序會(huì)將序列分為兩個(gè)部分
頭部是已排好序的,尾部是待排序的
從頭開(kāi)始掃描每一個(gè)元素
每當(dāng)掃描到一個(gè)元素,就將它插入到合適的位置,使得頭部數(shù)據(jù)依然保持有序
? ? ? ? ? ? ?
代碼如下:
for (int begin = 1; begin < array.length; begin++) { int cur = begin; while (cur > 0 && cmp(cur, cur - 1) < 0) { swap(cur, cur - 1); cur--; }}逆序?qū)?Inversion)
什么是逆序?qū)?#xff1f;
數(shù)組<2,3,8,6,1>的逆序?qū)?#xff1a;<2,1> <3,1> <8,1> <8,6> <6,1>
插入排序的時(shí)間復(fù)雜度與逆序?qū)Φ臄?shù)量成正比關(guān)
逆序?qū)Φ臄?shù)量越多,插入排序的時(shí)間復(fù)雜度越高
最壞、平均時(shí)間復(fù)雜度:O(n^2)
最好時(shí)間復(fù)雜度:O(n) ? (數(shù)組完全升序)
空間復(fù)雜度:O(1)
插入排序?qū)儆诜€(wěn)定的排序
當(dāng)逆序?qū)Φ臄?shù)量極少時(shí),插入排序的效率特別高
甚至比O(nLogn)級(jí)別的快速排序還要快
數(shù)據(jù)量不是特別大的時(shí)候,插入排序的效率也是非常好的
優(yōu)化
思路是將【交換】轉(zhuǎn)為【挪動(dòng)】
先將待插入的元素備份
頭部有序數(shù)據(jù)中比待插入元素大的,都朝尾部方向挪動(dòng)一個(gè)位置
將待插入元素放到最終的合適位置
? ? ? ? ? ? ?
for (int begin = 1; begin < array.length; begin++) { int cur = begin; int v = array[cur]; while (cur > 0 && cmpElements(v, array[cur - 1]) < 0) { array[cur] = array[cur-1]; cur--; } array[cur] = v;}二分搜索(Binary Search)
如何確定一個(gè)元素在數(shù)組中的位置?(假設(shè)數(shù)組里面全是整數(shù))
如果是無(wú)序數(shù)組,從第0個(gè)位置開(kāi)始遍歷搜索,平均時(shí)間復(fù)雜度:O(n)
如果是有序數(shù)組,可以使用二分搜索,最壞時(shí)間復(fù)雜度:O(Logn)
思路
有序數(shù)組中在[begin,end]中搜索v這個(gè)數(shù),我們需要先找到begin跟end中間的數(shù)mid
如果v
如果v>mid的時(shí)候,則可以判斷v在[mid+1,end]中,此時(shí)把mid+1賦值給begin
如果正好v=mid的時(shí)候,直接返回
? ? ? ? ? ? ?
案例:
? ? ? ? ? ? ?
實(shí)現(xiàn)方法
public static int indexOf(Integer[] array, int v) { if (array == null || array.length == 0) return -1; int begin = 0; int end = array.length; while (begin < end) { int mid = (begin + end) >> 1; if (v < array[mid]) { end = mid; } else if (v > array[mid]) { begin = mid + 1; } else { return mid; } } throw new IllegalArgumentException("Not Found");}邏輯比較簡(jiǎn)單,代碼也通俗易懂
但是同時(shí)出現(xiàn)了問(wèn)題,正如上文所說(shuō)的穩(wěn)定性問(wèn)題:如果存在多個(gè)重復(fù)的值,那么我們二分查找的是哪一個(gè)呢?
答案是不確定的,這跟元素的位置有關(guān)
>1;"],[20,"\n","24:\"KBKJ\"|36:79|7:0"],[20," if (v> 1;"],[20,"\n","24:\"Z0nU\"|36:79|7:0"],[20," if (cmp(index, mid) < 0) {"],[20,"\n","24:\"JUmJ\"|36:79|7:0"],[20," end = mid;"],[20,"\n","24:\"f2Nm\"|36:79|7:0"],[20," } else {"],[20,"\n","24:\"sD02\"|36:79|7:0"],[20," begin = mid +1;"],[20,"\n","24:\"CYCF\"|36:79|7:0"],[20," }"],[20,"\n","24:\"NCcX\"|36:79|7:0"],[20," }"],[20,"\n","24:\"lDv5\"|36:79|7:0"],[20," return begin;"],[20,"\n","24:\"Y5vW\"|36:79|7:0"],[20,"}"],[20,"\n","24:\"yVGI\"|36:79|7:0"],[20,"\n","24:\"Y8G6\""],[20,"插入排序函數(shù)中的循環(huán)每次獲取到待插入元素需要插入的位置","8:1"],[20,"\n","24:\"AyZE\""],[20,"\n","24:\"x7NN\""],[20,"for (int begin = 1; begin < array.length; begin++) {"],[20,"\n","24:\"7EzZ\"|36:79"],[20," int v = array[begin];//備份一次待插入元素"],[20,"\n","24:\"FFRG\"|36:79"],[20," int insertIndex = search(begin);"],[20,"\n","24:\"tOfZ\"|36:79"],[20," //將(insertIndex,begin)的所有元素向右挪動(dòng)1個(gè)位置"],[20,"\n","24:\"PICk\"|36:79"],[20," for (int i = begin; i >insertIndex ; i--) {"],[20,"\n","24:\"2ImW\"|36:79"],[20," array[i] = array[i-1];"],[20,"\n","24:\"pyHj\"|36:79"],[20," }"],[20,"\n","24:\"WSEY\"|36:79"],[20," array[insertIndex] = v;"],[20,"\n","24:\"cUvn\"|36:79"],[20,"}"],[20,"\n","24:\"57UP\"|36:79"],[20,"\n","24:\"hvpI\""],[20,"需要注意的是,使用了二分搜索后,只是減少了比較次數(shù),但是插入排序的平均時(shí)間復(fù)雜度還是O(n^2)","8:1"],[20,"\n","24:\"T83u\""],[20,"\n","24:\"wS2z\""],[20,"\n","24:\"Vwog\""]]">二分搜索優(yōu)化
在元素v的插入過(guò)程中,我們可以用二分搜索找到合適的位置,再將元素v插入
上面所鋪墊的二分查找的返回值是數(shù)組的index(索引),所以這邊可以使用索引的方式插入
? ? ? ? ? ? ?
要求二分搜索返回的插入位置:第一個(gè)大于元素v的位置
如果v=5,則返回2
如果v=1,則返回0
如果v=8,則返回5
....
實(shí)例
? ? ? ? ? ? ?
當(dāng)我們while循環(huán)結(jié)束后,begin與end的值相等,所以這時(shí)候插入位置就是begin/end的值
public static int search(Integer[] array, int v) { if (array == null || array.length == 0) return -1; int begin = 0; int end = array.length; while(begin < end){ int mid = (begin+end)>>1; if (v end = mid; }else{ begin=mid+1; } } return begin;}在排序中使用二分搜索的方法插入元素
/* * 利用二分搜索找到index位置元素的待插入位置 */private int search(int index) { int begin = 0; int end = index; while (begin < end) { int mid = (begin + end) >> 1; if (cmp(index, mid) < 0) { end = mid; } else { begin = mid +1; } } return begin;}插入排序函數(shù)中的循環(huán)每次獲取到待插入元素需要插入的位置
for (int begin = 1; begin < array.length; begin++) { int v = array[begin];//備份一次待插入元素 int insertIndex = search(begin); //將(insertIndex,begin)的所有元素向右挪動(dòng)1個(gè)位置 for (int i = begin; i >insertIndex ; i--) { array[i] = array[i-1]; } array[insertIndex] = v;}需要注意的是,使用了二分搜索后,只是減少了比較次數(shù),但是插入排序的平均時(shí)間復(fù)雜度還是O(n^2)
?第一次寫技術(shù)文章,有的地方描述的可能不是很清楚,如果有更好的想法或者更加優(yōu)化的方法,不介意的話可以找作者討論一下哦?~
總結(jié)
以上是生活随笔為你收集整理的raptor五个数排序流程图_数据结构与算法(一):排序(上)的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。
- 上一篇: excel字符串和单元格拼接_Excel
- 下一篇: jquery控制只监听数字_JQuery