詳解直接插入排序算法與相關(guān)的Java版代碼實(shí)現(xiàn)
直接插入排序
直接插入排序的思路很容易理解,它是這樣的:
1.把待排序的數(shù)組分成已排序和未排序兩部分,初始的時(shí)候把第一個(gè)元素認(rèn)為是已排好序的。
2.從第二個(gè)元素開(kāi)始,在已排好序的子數(shù)組中尋找到該元素合適的位置并插入該位置。
3.重復(fù)上述過(guò)程直到最后一個(gè)元素被插入有序子數(shù)組中。
4.排序完成。
示例:
思路很簡(jiǎn)單,但代碼并非像冒泡排序那么好寫(xiě)。首先,如何判斷合適的位置?大于等于左邊、小于等于右邊?不行,很多邊界條件需要考慮,而且判斷次數(shù)太多。其次,數(shù)組中插入元素,必然需要移動(dòng)大量元素,如何控制它們的移動(dòng)?
實(shí)際上,這并不是算法本身的問(wèn)題,而多少和編程語(yǔ)言有點(diǎn)關(guān)系了。有時(shí)候算法本身已經(jīng)很成熟了,到了具體的編程語(yǔ)言還是要稍作改動(dòng)。這里講的是Java算法,那就拿Java說(shuō)事兒。
為了解決上述問(wèn)題,我們對(duì)第二步稍作細(xì)化,我們不從子數(shù)組的起始位置開(kāi)始比較,而從子數(shù)組尾部開(kāi)始逆序比較,只要比需要插入的數(shù)大,就向后移動(dòng)。直到不大于該數(shù)的數(shù),那么,這個(gè)空出來(lái)的位置就安放需要插入的數(shù)字。因此,我們可以寫(xiě)出以下代碼:
InsertArray.java
public class InsertArray {
// 數(shù)組
private long[] arr;
// 數(shù)組中有效數(shù)據(jù)的大小
private int elems;
// 默認(rèn)構(gòu)造函數(shù)
public InsertArray() {
arr = new long[50];
}
public InsertArray(int max) {
arr = new long[max];
}
// 插入數(shù)據(jù)
public void insert(long value) {
arr[elems] = value;
elems++;
}
// 顯示數(shù)據(jù)
public void display() {
for (int i = 0; i < elems; i++) {
System.out.print(arr[i] + " ");
}
System.out.println();
}
// 插入排序
public void insertSort() {
long select = 0L;
for(int i = 1; i < elems; i++) {
select = arr[i];
int j = 0;
for(j = i;j > 0 && arr[j - 1] >= select; j--) {
arr[j] = arr[j - 1];
}
arr[j] = select;
}
}
}
測(cè)試類(lèi):
TestInsertArray.java
public class TestInsertArray {
public static void main(String[] args) {
InsertArray iArr = new InsertArray();
iArr.insert(85);
iArr.insert(7856);
iArr.insert(12);
iArr.insert(8);
iArr.insert(5);
iArr.insert(56);
iArr.display();
iArr.insertSort();
iArr.display();
}
}
打印結(jié)果:

算法性能/復(fù)雜度
現(xiàn)在討論下直接插入算法的時(shí)間復(fù)雜度。無(wú)論輸入如何,算法總會(huì)進(jìn)行n-1輪排序。但是,由于每個(gè)元素的插入點(diǎn)是不確定的,受輸入數(shù)據(jù)影響很大,其復(fù)雜度并不是一定的。我們可以分最佳、最壞、平均三種情況討論。
1.最佳情況:由算法特點(diǎn)可知,當(dāng)待排數(shù)組本身即為正序(數(shù)組有序且順序與需要的順序相同,于我們的討論前提,即為升序)時(shí)為最佳,理由是這種情況下,每個(gè)元素只需要比較一次且無(wú)需移動(dòng)。算法的時(shí)間復(fù)雜度為O(n);
2.最壞情況:很顯然,當(dāng)待排數(shù)組為逆序時(shí)為最壞情況,這種情況下我們的每輪比較次數(shù)為i-1, 賦值次數(shù)為i。總的次數(shù)為級(jí)數(shù)2n-1的前n項(xiàng)和,即n^2.算法的時(shí)間復(fù)雜度為O(n^2);
3.平均情況:由上述分析可以得到平均情況下算法的運(yùn)算次數(shù)大約為(n^2)/2(注:這里計(jì)算以賦值和比較計(jì),若按移動(dòng)和比較,則大約為n^2/4),顯然,時(shí)間復(fù)雜度還是O(n^2)。
至于算法的空間復(fù)雜度,所有移動(dòng)均在數(shù)據(jù)內(nèi)部進(jìn)行,唯一的開(kāi)銷(xiāo)是我們引入了一個(gè)臨時(shí)變量(有的數(shù)據(jù)結(jié)構(gòu)書(shū)上稱(chēng)為“哨兵”),因此,其空間復(fù)雜度(額外空間)為O(1)。
算法穩(wěn)定性
由于只需要找到不大于當(dāng)前數(shù)的位置而并不需要交換,因此,直接插入排序是穩(wěn)定的排序方法。
算法變種
如果待排列的數(shù)據(jù)比較多,那么每次從后往前找就造成了很大的開(kāi)銷(xiāo),為了提高查找速度,可以采用二分查找(Binary Search)進(jìn)行性能優(yōu)化。由于二分查找的效率很高,保證了O(㏒n)復(fù)雜度,在數(shù)據(jù)比較多或輸入數(shù)據(jù)趨向最壞情況時(shí)可以大幅提高查找效率。在有些書(shū)上將這種方法稱(chēng)為折半插入排序。它的代碼實(shí)現(xiàn)比較復(fù)雜,以后有時(shí)間可以貼出來(lái)。
此外,還有2-路插入排序和表插入排序。2-路插入排序是在折半插入排序的基礎(chǔ)上進(jìn)一步改進(jìn),其移動(dòng)次數(shù)大為降低,大約為n^2/8。但是,它并不能避免移動(dòng)次數(shù),也不能降低復(fù)雜度級(jí)別。表插入排序則完全改變存儲(chǔ)結(jié)構(gòu),不移動(dòng)記錄,但需要維護(hù)一個(gè)鏈表,以鏈表的指針修改代替移動(dòng)記錄。因此,其復(fù)雜度仍然是O(n^2)。
有關(guān)2-路插入排序和表插入排序,可以參考嚴(yán)蔚敏、吳偉民編著的《數(shù)據(jù)結(jié)構(gòu)》一書(shū)。
算法適用場(chǎng)景
插入排序由于O(n^2)的復(fù)雜度,在數(shù)組較大的時(shí)候不適用。但是,在數(shù)據(jù)比較少的時(shí)候,是一個(gè)不錯(cuò)的選擇,一般做為快速排序的擴(kuò)充。例如,在STL的sort算法和stdlib的qsort算法中,都將插入排序作為快速排序的補(bǔ)充,用于少量元素的排序。又如,在JDK 7 java.util.Arrays所用的sort方法的實(shí)現(xiàn)中,當(dāng)待排數(shù)組長(zhǎng)度小于47時(shí),會(huì)使用插入排序。
相關(guān)文章
手把手教你使用Java實(shí)現(xiàn)在線生成pdf文檔
在實(shí)際的業(yè)務(wù)開(kāi)發(fā)的時(shí)候,常常會(huì)需要把相關(guān)的數(shù)據(jù)信息,通過(guò)一些技術(shù)手段生成對(duì)應(yīng)的PDF文件,然后返回給用戶。本文將手把手教大家如何利用Java實(shí)現(xiàn)在線生成pdf文檔,需要的可以參考一下2022-03-03
java的if else語(yǔ)句入門(mén)指南(推薦)
下面小編就為大家?guī)?lái)一篇java的if else語(yǔ)句入門(mén)指南(推薦)。小編覺(jué)得挺不錯(cuò)的,現(xiàn)在就分享給大家,也給大家做個(gè)參考。一起跟隨小編過(guò)來(lái)看看吧2016-06-06
Java實(shí)現(xiàn)簡(jiǎn)單銀行ATM功能
這篇文章主要為大家詳細(xì)介紹了Java實(shí)現(xiàn)銀行ATM簡(jiǎn)單功能,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2020-10-10
簡(jiǎn)述JAVA中堆內(nèi)存與棧內(nèi)存的區(qū)別
這篇文章主要介紹了JAVA中堆內(nèi)存與棧內(nèi)存的區(qū)別,文中講解非常細(xì)致,代碼幫助大家更好的理解和學(xué)習(xí),感興趣的朋友可以了解下2020-07-07
RocketMQ生產(chǎn)消息與消費(fèi)消息超詳細(xì)講解
這篇文章主要介紹了RocketMQ生產(chǎn)消息與消費(fèi)消息,RocketMQ可用于以三種方式發(fā)送消息:可靠的同步、可靠的異步和單向傳輸。前兩種消息類(lèi)型是可靠的,因?yàn)闊o(wú)論它們是否成功發(fā)送都有響應(yīng)2022-12-12
Kotlin?標(biāo)準(zhǔn)函數(shù)和靜態(tài)方法示例詳解
這篇文章主要為大家介紹了Kotlin?標(biāo)準(zhǔn)函數(shù)和靜態(tài)方法示例詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2022-10-10
SpringBoot多模塊打包部署Docker的項(xiàng)目實(shí)戰(zhàn)
本文通過(guò)介紹最常見(jiàn)的Maven管理的Spring Boot項(xiàng)目多模塊打包部署Docker來(lái)介紹一下項(xiàng)目部署過(guò)程中操作流程和幾個(gè)需要注意的點(diǎn),具有一定的參加價(jià)值,感興趣的可以了解一下2023-08-08
SpringBoot實(shí)現(xiàn)定時(shí)發(fā)送郵件的三種方法案例詳解
這篇文章主要介紹了SpringBoot三種方法實(shí)現(xiàn)定時(shí)發(fā)送郵件的案例,Spring框架的定時(shí)任務(wù)調(diào)度功能支持配置和注解兩種方式Spring?Boot在Spring框架的基礎(chǔ)上實(shí)現(xiàn)了繼承,并對(duì)其中基于注解方式的定時(shí)任務(wù)實(shí)現(xiàn)了非常好的支持,本文給大家詳細(xì)講解,需要的朋友可以參考下2023-03-03

