關於六種排序演算法的研究
一、導論
二、排序演算法的原理和分類
三、氣泡排序法
四、選擇排序法
五、插入排序法
六、希爾排序法
七、快速排序法
八、基數排序法
一、導論
排序演算法可以說是所有演算法的基礎,基本上無論是大公司或者是小公司,只要寫程式碼肯定要寫這些程式碼,筆者最近在閒暇之時研究了六種比較經典的排序演算法,於是決定拿出來跟大家一起分享一下。
二、排序演算法的原理和分類
排序演算法大體來說可以分成兩大類,其中第一類就是通過比較的方法,這種方法比較常見的有選擇,冒泡,插入排序,希爾排序等諸多方法,第二類就是不是通過元素與元素比較的方法,其中比較常用的就是基數排序法。
一、氣泡排序法
氣泡排序法,顧名思義,就是通過與相鄰比較的方法把最小的(最大的)數字浮到最邊上,換句話說,程式碼採用的是和相鄰的部分相比較就可以了。當然了,每次比較完成之後,最後一位就不需要進行比較了。當然,氣泡排序法有一個坑,這個我之前面試的時候踩過,所以這裡要跟大家說一下。
void bubbleSequence(int *a, int numsSize)
{
for (int i=0;i<numsSize-1;i++) {
for (int j=0;j<numsSize-1-i;j++) {
if (a[j] < a[j+1]) {
int temp = a[j];
a[j] = a[j+1];
a[j+1] = temp;
}
}
}
}
一、選擇排序法
選擇排序法,顧名思義,就是首先找到全陣列中最小的(最大的)的元素,然後將其調整到邊上的位置,依次類推。那麼如何找到最小的(最大的)元素呢?因為這裡存在比較,因此可想而知需要設定兩個指標來進行比較。通過第一個數字和第二個數字來進行比較最後來得到相應的元素。
void chooseSequence(int *a, int numsSize)
{
for (int i=0;i<numsSize;i++) {
for (int j=i+1;j<numsSize;j++) {
if (a[i]<a[j]) {
int temp = a[i];
a[i] = a[j];
a[j] = temp;
}
}
}
}
二、插入排序法
插入排序法,顧名思義,就是找到一個有序數列進行插入設計,那麼如何找有序數列呢?我們可以把第一個元素當成一個有序數列,然後從第二個元素開始進行插入,它的基本演算法流程是這樣的:首先設定成一個臨時變數用來儲存這個元素,然後每次與這個元素進行比較,如果比這個元素大,那麼就把這個值向右移動,一直到小於這個值或者到尾端那麼此時將這個臨時變數進行插入。
void insertSequence(int *a, int numsSize)
{
int j=0;
for (int i=0;i<numsSize;i++) {
int current = a[i];
for (j=i-1;(current<a[j]) && (j>=0);j--) {
a[j+1] = a[j];
}
a[j+1] = current;
}
}
三、希爾排序法
希爾排序法可以說是插入排序法的一個增量部分,兩者的區別在於,插入排序法從前到後一個一個值進行插入,而希爾排序法會設定一個增量,這個增量類似於一個間隔,我們首先按照這個增量來進行插入排序(為啥用插入排序我也不知道),然後不斷的縮小間隔,一直到increment=1時我們認為此時的插入排序就是最後的一個排序序列。這個排序演算法最重要的就是間隔的確定,間隔的大小決定了演算法的執行速度。
void shellSequence(int *a, int numsSize)
{
int increment = numsSize / 3;
do {
for (int i=increment+1;i<numsSize;i++) {
int j = i - increment;
int current = a[i];
while (j >= 0 && current < a[j]) {
// printf("a[j] is %d\n",a[j]);
a[j+increment] = a[j];
j = j - increment;
}
a[j+increment] = current;
}
increment = increment - 1;
}while (increment >= 1);
}
四、快速排序法
快速排序法有點類似於選擇排序法,兩者的共同點在於兩者都要找到一個值進行比對,區別在於選擇排序法是找到最大值,而快速排序法是找到其中的中間值,然後根據這個中間值將序列分成左側和右側兩個部分。其演算法流程可以分成兩個部分,分別是填充資料+分冶法。先說第一部分填充資料法,這部分一共可以分成兩個部分,首先我們需要在這裡設定一個高位指標和低位指標,首先我們要找到一個高位指標,如果說我們的這個值大於我們的基準值那麼我們就跳過他高位指標,繼續向低走,如果小於我們的基準值,那麼我們就需要把它移動到低位來填充資料,接著我們將低位指標向上移動一個單位,同樣判斷當前值是否小於基準值,如果小於基準值,那麼我們就需要將其指標向上移動一個單位,反之我們就需要把當前的低位值移動到高位。依次類推,一直到結束。這樣就完成了填充資料法,之後我們繼續判斷左側和右側的部分同樣按照上述方法填充資料並進行判斷,知道左側指標和右側指標相等,此時結束,排序隨之完成。
void quickSequence(int *a, int low, int high)
{
if (low < high) {
int index = getIndex(a, low, high);
quickSequence(a, low, index-1); // ?index
quickSequence(a, index+1, high);
}
}
int getIndex(int *a, int low, int high)
{
int tmp = a[low];
while (low < high) {
while (low < high && a[high] >= tmp) {
high--;
}
a[low] = a[high];
while (low < high && a[low] <= tmp) {
low++;
}
a[high] = a[low];
}
a[low] = tmp;
return low;
}
這也是在這其中唯一用到遞迴的排序方法。
五、基數排序法
基數排序法是桶裝排序法的一種升級排序法,它有兩種排序方法,分別為LSB和MSB兩種,兩種排序的方法差不多,我們這裡只說LSB排序法。先說一下LSB排序的基本演算法要求:他是以最小序列為基礎的,我們首先按照個位的順序來進行排列,按照個位首先對其進行一次排序,得到一個新的序列,然後按照十位進行排序,依此類推,直到最高位來進行排序。所以這裡的核心演算法為如何通過個位來對其進行排序,其演算法順序如下:
準備工作:1、需要準備一個數組,確定各位的數字到底有幾個。我們這裡設定成count
2、需要準備一個數組,用來記錄個位數字排列完成以後的結果。我們這裡設定成bucket
正式開始:1、需要將count數組裡面的東西全部清空(這個主要是因為後續10位百位也需要用,所以也是需要的)。
2、通過函式得到其個位的結果。並且通過count來得到每一位到底有多少個。
3、通過疊加的方法來得到相應的最大位數(這個是用來確定邊界)。
4、通過位數的相關邊界來得到相應的結果(至於為什麼要用反向我不太清楚)
5、將bucket重新輸入會data函式中。
void bucketSequence(int *a, int start, int end)
{
// set count
int count[10];
for (int d=0;d<2;d++) {
for (int i=0;i<10;i++) {
count[i] = 0;
}
// put a into count
for (int index = start;index<=end;index++) {
int m = getdigit(a[index], d);
printf("m is %d\n", m);
count[m]++;
}
int *bucket = (int *)malloc(sizeof(int) * (end-start));
// add count
for (int i=1;i<10;i++) {
count[i] = count[i] + count[i-1];
}
// put count into bucket
for (int i=end;i>=start;i--) {
int j = getdigit(a[i], d);
bucket[count[j]-1] = a[i];
--count[j];
}
for (int i=start;i<=end;i++) {
printf("bucket is %d\n",bucket[i]);
}
int k = 0;
int l = 0;
while ((k<=end) && (l<=end)) {
a[k] = bucket[l];
k++;
l++;
}
free(bucket);
}
}
int getdigit(int arr,int d)
{
int a[] = {1, 10};
return ((arr/a[d]) % 10);
}
最後說一下,在研究演算法的時候一定要有一隻筆和一張紙,邊看演算法邊在草紙上把過程寫出來,這樣能夠更快的理解演算法。