1. 程式人生 > >資料結構--排序演算法

資料結構--排序演算法

排序演算法

概述

排序演算法分為內部排序和外部排序兩大類。

內部排序:在計算機記憶體中完成的排序演算法
外部排序:不能再記憶體中文完成,必須在磁碟或者磁帶上完成的排序演算法

內部排序是研究的重點問題,通常我們講的八大排序演算法也主要是講的內部排序演算法。

排序演算法的穩定性和時間空間複雜度

演算法介紹

本文重點介紹以下幾種排序演算法

1.插入演算法

1.理論思想

插入排序問題的思路是將為排序元素逐一插入至已經排序的序列,從第二個元素開始插入,直至整個序列都有序終止。給定序列{49,38,65,97,76,13,27,49},從第二個元素開始插入,若遇到相等的元素則將其插入值後面

2.演算法例程

#include<stdio.h>
#include<stdlib.h>
#include<string.h>

void sort_insert(int unsort[],int sort_len)
{
    int i;
    for(i=1;i<sort_len;i++)
    {
        if(unsort[i]<unsort[i-1])
        {
            int j=i;
            int temp=unsort[i];//記錄當前位置     ,在已排序的序列中進行搜尋

            while(j>0&&unsort[j-1]>temp)//如果j-1位置的元素小於i,則將其後移
            {
                unsort[j]=unsort[j-1];
                j--;
            }
            unsort[j]=temp;//移動完畢後,將j位置的元素置為temp
        }
    }
}
int main()
{
    int a[8]={49,38,65,97,76,13,27,49};
    sort_insert(a,8);
    printf("---------sorting-----------\n");
    for (int i=0;i<8;i++)
    {
        printf("%d\t",a[i]);
    }
    printf("\n---------End-----------\n");
    return 0;
}

3.穩定性分析

插入排序的複雜度為O(N^2)

2.選擇排序

1.理論思想

選擇排序的思想是每次從未排序的序列中,選擇最小(最大)的數放在排序數列的後面,形成從小到大的排序思路。

給定序列{49,38,65,97,76,13,27,49}

第一趟找到最小數13,放到最前邊(與首位數字交換)

交換前:| 49 | 38 | 65 | 97 | 76 | 13 | 27 | 49 |

交換後:| 13 | 38 | 65 | 97 | 76 | 49 | 27 | 49 |

第二趟找到餘下數字| 38 | 65 | 97 | 76 | 49 | 27 | 49 |裡的最小數27,與當前陣列的首位數字進行交換,實際沒有交換,本來就在首位

交換前:| 13 | 38 | 65 | 97 | 76 | 49 | 27 | 49 |

交換後:| 13 | 27 | 65 | 97 | 76 | 49 | 38 | 49 |

第三趟繼續找到剩餘| 65 | 97 | 76 | 49 | 38 | 49 |數字裡的最小數38,與當前數列的首位進行交換

交換前:| 13 | 27 | 65 | 97 | 76 | 49 | 38 | 49 |

交換後:| 13 | 27 | 38 | 97 | 76 | 49 | 65 | 49 |

第四趟繼續找到剩餘| 97 | 76 | 49 | 65 | 49 |數字裡的最小數49,與當前數列的首位進行交換

交換前:| 13 | 27 | 38 | 97 | 76 | 49 | 65 | 49 |

交換後:| 13 | 27 | 38 | 49 | 76 | 97 | 65 | 49 |

第五趟從剩餘的| 76 | 97 | 65 | 49 |裡找到最小數49,與首位數字76交換位置

交換前:| 13 | 27 | 38 | 49 | 76 | 97 | 65 | 49 |

交換後:| 13 | 27 | 38 | 49 | 49 | 97 | 65 | 76 |

第六趟從剩餘的| 97 | 65 | 76 |裡找到最小數65

交換前:| 13 | 27 | 38 | 49 | 49 | 97 | 65 | 76 |

交換後:| 13 | 27 | 38 | 49 | 49 | 65 | 97 | 76 |

第七趟從剩餘的| 97 | 76 |裡找到最小數76
交換前:| 13 | 27 | 38 | 49 | 49 | 97 | 65 | 76 |

交換後:| 13 | 27 | 38 | 49 | 49 | 65 | 76 | 97 |

排序完畢輸出正確結果| 13 | 27 | 38 | 49 | 49 | 65 | 76 | 97 |

完成一趟排序,其餘步驟類似

2.演算法例程

void sort_select(int unsort[],int len)
{
    int i,j,index;
    for(i=0;i<len;i++)
    {
        int unsort_min=unsort[i],index=i;
        for(j=i;j<len;j++)
        {

            if(unsort[j]<unsort_min)
            {
                unsort_min=unsort[j];
                index=j;
            }
        }
        if(index!=i)
        {
            int temp=unsort[i];
            unsort[i]=unsort[index];
            unsort[index]=temp;
        }
    }
}

3.堆排序

1.理論思想

這裡的堆排序不是指堆疊,而是指的是二叉堆,是一種資料結構。二叉堆滿足除了底層之外,其餘的都是滿的,可以分為最大堆和最小堆兩種結構,最大堆的父節點一定大於其子節點,最小堆則相反。
滿二叉堆可以用陣列來表示,滿二叉堆和陣列的關係如下

堆排序的過程是每次都取出堆頂元素,後對堆進行調整,滿足堆的性質。

以最大堆為例,每次取出堆頂元素,與堆的最後元素進行交換,重新調整堆,
接近於最差情況下:O(N*logN)

2.演算法例程

void Adjustheap(int heap[],int i,int len);
void buildheap(int heap[],int len)
{
    for(int i=(len-1)/2;i>=0;i--)
    {
        Adjustheap(heap,i,len);
    }
}
void Adjustheap(int heap[],int i,int len)
{
    int child_left,temp;
    child_left=2*i+1;
        int min=i;
        //如果存在左兒子節點    並且左兒子節點小於母節點
        if(child_left<len&&heap[child_left]>heap[i])
        {
            min=child_left; 
        }
        //如果存在右兒子節點    並且右兒子節點小於母節點
        if(child_left+1<len&&heap[child_left+1]>heap[i])
        {
            min=child_left+1;
        }
        //如果左右兒子節點都存在的時候,且小於母節點,這個時候在兒子節點中選取最小的節點
        if(child_left+1<len&&heap[child_left+1]>heap[i]&&heap[child_left]>heap[i])
        {
            if(heap[child_left]>heap[child_left+1])
            min=child_left;
            else
                min=child_left+1;
        }
        if(min!=i)
        {
            temp=heap[i];
            heap[i]=heap[min];
            heap[min]=temp;
            Adjustheap(heap,min,len);
        }   
}
//在堆中插入一個元素
//排序
void sort_heap(int heap[],int len)
{
        buildheap(heap,len) ;//構建堆
        for(int i=len;i>0;i--)
        {
            int temp=heap[0];
            heap[0]=heap[i-1];
            heap[i-1]=temp;
            Adjustheap(heap,0,i-1);//調整母節點,只調整前幾個元素 
            //將最大數放至最後一個節點
        }
}

4.氣泡排序

1.理論思想

冒泡法排序的逐次比較交換,使大的數往下沉,小數向上冒,直至完成排序,冒泡法最壞的情況下是O(N^2),最好的情況下是O(N)。演算法實現簡單
例子為從小到大排序,

給定序列{49,38,65,97,76,13,27,49}

第一趟排序(外迴圈)

第一次兩兩比較49 > 38交換(內迴圈)

交換前狀態| 49 | 38 | 65 | 97 | 76 | 13 | 27 | 49 |

交換後狀態| 38 | 49 | 65 | 97 | 76 | 13 | 27 | 49 |

第二次兩兩比較,49 < 65 不交換

第三次兩兩比較,65 < 97 不交換

第四次兩兩比較,97 > 76 交換

交換前狀態| 38 | 49 | 65 | 97 | 76 | 13 | 27 | 49 |

交換後狀態| 38 | 49 | 65 | 76 | 97 | 13 | 27 | 49 |

第五次兩兩比較,97 < 13 交換

交換前狀態| 38 | 49 | 65 | 76 | 97 | 13 | 27 | 49 |

交換後狀態| 38 | 49 | 65 | 76 | 13 | 97 | 27 | 49 |

依次第一次完成後
| 38 | 49 | 65 | 76 | 13 | 27 | 49 | 97 |

第二趟排序(外迴圈)

交換前狀態| 38 | 49 | 65 | 76 | 13 | 27 | 49 | 97 |

交換後狀態| 38 | 49 | 65 | 13 | 27 | 49 | 76 | 97 |

第三趟排序(外迴圈)

交換前狀態| 38 | 49 | 65 | 76 | 13 | 27 | 49 | 97 |

交換後狀態| 38 | 49 | 13 | 27 | 49 | 65 | 76 | 97 |

第四趟排序(外迴圈)

交換前狀態| 38 | 49 | 65 | 76 | 13 | 27 | 49 | 97 |

交換後狀態| 38 | 13 | 27 | 49 | 49 | 65 | 76 | 97 |

。。。。。。。。。。。。

排序完畢輸出正確結果| 13 | 27 | 38 | 49 | 49 | 65 | 76 | 97 |

2.演算法例程

void sort_bubble(int unsort[],int len)
{
    int i,j,temp;
    for(i=0;i<len;i++)
    {
        for(j=0;j<len-i-1;j++)
        {
            if(unsort[j]>unsort[j+1])
            {
                     temp=unsort[j];
                    unsort[j]=unsort[j+1];
                    unsort[j+1]=temp;
            }
        }
    }
}

5.快速排序

1.理論思想

在待排序的n個記錄中任取一個記錄(通常取第一個記錄),把該記錄放入適當位置後,資料序列被此記錄劃分成兩部分。所有關鍵字比該記錄關鍵字小的記錄放置在前一部分,所有比它大的記錄放置在後一部分,並把該記錄排在這兩部分的中間(稱為該記錄歸位),這個過程稱作一趟快速排序。

給定序列{49,38,65,97,76,13,27,49}

a),先把第一項[49]取出來,

用[49]依次與其餘項進行比較,

如果比[49]小就放[49]前邊,38 13 27 49都比[49]小,所以全部放到[49]前邊

如果比[49]大就放[49]後邊,65 97 76全部放到[49]後邊

一趟排完後變成下邊這樣:

排序前 49,38,65,97,76,13,27,49

排序後 38 13 27 49 49 65 97 76

b),對前半部分[38 13 27 49]繼續進行快速排序

c) 對後半部分[65 97 76]進行快速排序

依次遞迴實現b)和c)

排序完畢輸出正確結果| 13 | 27 | 38 | 49 | 49 | 65 | 76 | 97 |

2.演算法例程

int getpart(int *heap,int begin,int end)
{

            int base=heap[begin];
            while(begin<end)
            {
                while((begin<end)&&(heap[end]>base)) end--;
                heap[begin]=heap[end];
                while((begin<end)&&(heap[begin]<=base)) begin++;
                heap[end]=heap[begin];
            }
            heap[begin]=base;
            return begin;

}

void sort_quick(int *heap,int begin,int end)
{
    if(heap==NULL||begin>=end)
        return;
    int part=getpart(heap,begin,end);
    sort_quick(heap,begin,part-1);
    sort_quick(heap,part+1,end);
}

3.複雜度分析

最好的情況下:因為每次都將序列分為兩個部分(一般二分都複雜度都和logN相關),故為 O(N*logN)

最壞的情況下:基本有序時,退化為氣泡排序,幾乎要比較N*N次,故為O(N*N)