浅谈数据结构-插入排序(直接插入、希尔排序)
插入排序:數(shù)組中獲取數(shù)據(jù),遍歷數(shù)組中數(shù)據(jù)進(jìn)行比較,找到合適位置,進(jìn)行插入工作。
直接插入和希爾排序關(guān)鍵區(qū)別在于:希爾排序是有分組,然后進(jìn)行迭代,組間插入數(shù)據(jù),是一種變形的插入排序算法。
一、直接插入法
1、算法思想
上圖是一張撲克牌,在摸牌階段就是直接插入操作。
(1) 數(shù)組中下標(biāo)為1 的元素視為元素個(gè)數(shù)為 1 的有序序列。(元素0表示哨兵)
(2) 然后,我們要依次把 R1, R2, ... , RN-1 插入到這個(gè)有序序列中。所以,我們需要一個(gè)外部循環(huán),從下標(biāo) 1 掃描到 N-1 。
(3) 接下來描述插入過程。假設(shè)這是要將 Ri 插入到前面有序的序列中。由前面所述,我們可知,插入Ri時(shí),前 i-1 個(gè)數(shù)肯定已經(jīng)是有序了。
2、算法代碼
//插入排序
void SortAlgorithm::InsertSort(pSqlList pList)
{
//插入排序,有哨兵概念
int i,j;
printf("開始驗(yàn)證插入排序");
for (i =1;i<pList->length-1;i++)
{
//遍歷獲取數(shù)組中元素,如果后續(xù)元素比較小,進(jìn)行插入工作,插到這個(gè)元素左邊,如果大,繼續(xù)循環(huán)
if (pList->SqlArray[i+1] < pList->SqlArray[i])
{
//在數(shù)組中0位置為哨兵作用,獲取此次較小元素
pList->SqlArray[0] = pList->SqlArray[i+1];
//再將較小元素,和之前元素比較,如果發(fā)現(xiàn)較大的,停止,然后插入
for (j = i;j>0 && pList->SqlArray[0] < pList->SqlArray[j];j--)
{
//向后移動(dòng)數(shù)據(jù),讓其有空間插入工作,第一個(gè)是插到i+1位置,就是較小元素的位置
pList->SqlArray[j+1] = pList->SqlArray[j];
}
//插入工作,因?yàn)閖--,此處是j+1
pList->SqlArray[j+1] = pList->SqlArray[0];
}
PrintSqlList(pList);
}
}
3、代碼分析
上圖就是程序運(yùn)行結(jié)果:首相數(shù)組是{0,50,10,90,30,70,40,80,20}
1、先開始找無序的數(shù),發(fā)現(xiàn)50>10,那么選取10作為基準(zhǔn)值,開始進(jìn)行插入操作。
2、從10坐標(biāo)往前遍歷,要將10的插入正確位置。向前遍歷(比10大,繼續(xù)向前尋找),當(dāng)發(fā)現(xiàn)比10小,停止,進(jìn)行插入工作。
3、插入工作將是向后移動(dòng)數(shù)據(jù),此時(shí)50和10交換位置。第一循環(huán)停止。
4、比較50與90,是有序的,下一條記錄。
5、比較90和30,發(fā)現(xiàn)30<90進(jìn)入插入工作,將30插入到{10,50,90}有序序列中,輸出的就是{30,10,30,50,90,70,40,80,20}
6、比較90和70,同樣道理,最后輸出爭取結(jié)果。關(guān)鍵在于向有序數(shù)組中,判斷插入到正確位置。
代碼實(shí)現(xiàn)上有冒泡算法的影子,就是前后元素比較,此處不是交換,而是向前的有序數(shù)組進(jìn)行插入工作。
二、希爾排序
希爾排序是一種變異的插入算法,在引入二叉樹的概念后(分組,每次遍歷組的大小變化,同時(shí)是夸組插入,將可能比較大的元素交叉插入到后面組,較小的插入前面組),遵循先粗分再細(xì)分的原則,實(shí)現(xiàn)交叉插入。
1、算法思想
插入算法基于分組交叉插入,首先第一步是分組,確定分組大小。然后在組間進(jìn)行插入工作,所以插入時(shí)的步長為分組大小。
上圖來源于網(wǎng)絡(luò),很少說明了希爾排序算法的精髓,交叉插入工作。
在第一趟排序中,我們不妨設(shè) gap1 = N / 2 = 5,即相隔距離為 5 的元素組成一組,可以分為 5 組。
接下來,按照直接插入排序的方法對每個(gè)組進(jìn)行排序。
在第二趟排序中,我們把上次的 gap 縮小一半,即 gap2 = gap1 / 2 = 2 (取整數(shù))。這樣每相隔距離為 2 的元素組成一組,可以分為 2 組。
按照直接插入排序的方法對每個(gè)組進(jìn)行排序。
在第三趟排序中,再次把 gap 縮小一半,即gap3 = gap2 / 2 = 1。 這樣相隔距離為 1 的元素組成一組,即只有一組。
按照直接插入排序的方法對每個(gè)組進(jìn)行排序。此時(shí),排序已經(jīng)結(jié)束。
需要注意一下的是,圖中有兩個(gè)相等數(shù)值的元素 5 和 5 。我們可以清楚的看到,在排序過程中,兩個(gè)元素位置交換了。
2、希爾排序的步長
已知的最好步長序列是由Sedgewick提出的(1, 5, 19, 41, 109,...),該序列的項(xiàng)來自
這兩個(gè)算式。
當(dāng)增量序列為dlta[k]=2t-k+1 (0<=k<=t<=|log(n+1)|)時(shí),可以獲得不錯(cuò)的效果。其時(shí)間復(fù)雜度為O(n3/2),要好于直接排序。
這項(xiàng)研究也表明“比較在希爾排序中是最主要的操作,而不是交換。”用這樣步長序列的希爾排序比插入排序和堆排序都要快,甚至在小數(shù)組中比快速排序還快,但是在涉及大量數(shù)據(jù)時(shí)希爾排序還是比快速排序慢。
3、程序代碼
//希爾排序
void SortAlgorithm::ShellSort(pSqlList pList)
{
//希爾排序是插入排序的改進(jìn),先分組,再插入,而且插入是夸組插入。先有分組,在執(zhí)行插入
int i,j;
printf("開始驗(yàn)證希爾排序");
int increment = pList->length;
do
{
//小于3,時(shí),保證能循環(huán)
increment = increment/3 +1; //增量尋列
//執(zhí)行夸組插入工作,跨度大小為increment,所以j的跨度就是increment
for (i = increment +1;i<pList->length;i++)
{
//從組的后部分和前部分比較,如果后面小,進(jìn)行夸組插入工作
if (pList->SqlArray[i] < pList->SqlArray[i-increment])
{
//和插入排序很像
pList->SqlArray[0] = pList->SqlArray[i];
for( j = i-increment;j>0&&pList->SqlArray[0]<pList->SqlArray[j];j = j-increment)
{
//比較大移動(dòng)后面
pList->SqlArray[j+increment] = pList->SqlArray[j];
}
pList->SqlArray[j+increment] = pList->SqlArray[0];
}
}
PrintSqlList(pList);
} while (increment>1);
}
4、代碼分析
上述代碼中while跳出條件是步長大于1,同時(shí)在進(jìn)行插入工作是,是j=j-increment,這樣才實(shí)現(xiàn)交叉插入,向右移動(dòng)步長也是j+increment
在程序中輸入的數(shù)組為:{0,50,10,90,30,70,40,80,60,20}。步長為4,所以應(yīng)該是坐標(biāo)為1的和坐標(biāo)為5的進(jìn)行交叉比較,插入。
首先步長為4,50和70比較,50<70,不變,i++。然后10與40,后來到90和80交換,30和60不交換,最后是70和20交換,這是發(fā)現(xiàn)位置1為50,50和20,繼續(xù)交換,最后結(jié)果就是{20,20,20,80,30,50,40,90,60,70}。
第二次,此時(shí)incerment = incerment/3+1,此時(shí)incerment為2,說明步長為2,意味坐標(biāo)1和坐標(biāo)3之間交叉比較,進(jìn)行插入。當(dāng)然往后1,3,5也是需要判斷的。
第三次繼續(xù),因?yàn)閕ncrement =2,繼續(xù)循環(huán),此時(shí)incerment是1,意味兩兩交叉交換插入了,但此時(shí)序列是基本有序了,之間數(shù)據(jù)交換很小了。
三、算法分析
1、直接插入排序復(fù)雜度
從空間上看,只需要 一個(gè)記錄的輔助空間,空間的復(fù)雜度為O(1),當(dāng)最好的情況下,也就是數(shù)組是有序表,沒有移動(dòng)記錄時(shí)間復(fù)雜度為O(n)。
最壞的情況是,排序表是逆序,此時(shí)需要比較1+3+4+5+6….n-1 = (n+2)(n-1)/2,時(shí)間復(fù)雜度為O(n2).同樣的O(n2)時(shí)間復(fù)雜度,直接插入排序法比冒泡排序和簡單選擇排序的性能要好一些。
所以,數(shù)據(jù)越接近正序,直接插入排序的算法性能越好。
2、希爾排序復(fù)雜度分析
希爾排序中步長選擇很重要,Donald Shell 最初建議步長選擇為N/2并且對步長取半直到步長達(dá)到1。雖然這樣取可以比O(N2)類的算法(插入排序)更好,但這樣仍然有減少平均時(shí)間和最差時(shí)間的余地。可能希爾排序最重要的地方在于當(dāng)用較小步長排序后,以前用的較大步長仍然是有序的。
研究證明希爾排序最好情況是O(nlogn),最壞的情況是O(n*n0.5),所以比插入排序效果還是好的。
總結(jié)
以上是生活随笔為你收集整理的浅谈数据结构-插入排序(直接插入、希尔排序)的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: CAD如何设置图框?cad添加图框方法(
- 下一篇: Java中的排序问题(Java8新特性