數據結構(一)——常用的排序算法

排序算法

在這裏插入圖片描述

冒泡排序

  • 複雜度 n^2

  • 排序規則

冒泡排序算法的運作如下:(從後往前)
1. 比較相鄰的元素。如果第一個比第二個大,就交換他們兩個。
2. 對每一對相鄰元素作同樣的工作,從開始第一對到結尾的最後一對。在這一點,最後的元素應該會是最大的數。
3. 針對所有的元素重複以上的步驟,除了最後一個。
4. 持續每次對越來越少的元素重複上面的步驟,直到沒有任何一對數字需要比較。

  • 穩定性:冒泡排序是一種穩定排序算法

  • 實現:

    int flag = 0;
    for(int i=length-1; i>0&&flag==0; i--)
    {
        flag = 1;
        for(int j=0;j<i;j++)
        {
            if(data[j+1] < data[j])
            {
                swap(data[j],data[j+1]);
                flag = 0;
            }
        }
    }

選擇排序

  • 複雜度 n^2

  • 排序規則

    它的工作原理是每一次從待排序的數據元素中選出最小(或最大)的一個元素,存放在序列的起始位置,直到全部待排序的數據元素排完。

  • 穩定性:選擇排序是不穩定的排序方法 如:[5,5,3]

  • 實現:

    int Min = 0;
    for(int i=0; i<length-1; i++)
    {
        Min = i;
        for(int j=i+1; j<length; j++)
        {
            if(data[j] < data[Min])
                Min = j;
        }
        if(Min != i)
            swap(data[Min],data[i]);
    }

直接插入排序

  • 複雜度 n^2

  • 排序規則

    每次處理就是將無序數列的第一個元素與有序數列的元素從後往前逐個進行比較,找出插入位置,將該元素插入到有序數列的合適位置中。

  • 穩定性:插入排序是穩定的

  • 實現:

    int j = 0;
    for(int i=1; i<length; i++)
    {
        if(data[i] < data[i-1])
        {
            T temp = data[i];
            for(j = i; j>0&&temp<data[j-1]; j--)
                data[j] = data[j-1];
            data[j] = temp;
        }
    }

希爾排序

  • 複雜度

  • 排序規則

    希爾排序是把記錄按下標的一定增量分組,對每組使用直接插入排序算法排序;隨着增量逐漸減少,每組包含的關鍵詞越來越多,當增量減至1時,整個文件恰被分成一組,算法便終止。

  • 穩定性: 希爾排序是非穩定排序算法。

  • 實現:

    int i=0, j=0, k=0;
    int h = length;
    do
    {
        h = h/3 + 1;
        for(i=0;i<h;i++)
        {
            for(j=i+h; j<length; j+=h)
            {
                if(data[j] < data[j-h])
                {
                    T temp = data[j];
                    for(k=j; k>=h&&temp<data[k-h]; k-=h)
                        data[k] = data[k-h];
                    data[k] = temp;
                }
            }
        }
    }
    while(h > 1);
    

歸併排序

  • 複雜度

  • 實現:

 //原地歸併
 	template <typename T>
 	void Merge(T data[], int lo, int mid, int hi, T temp[])
 	{
 	    if(lo < hi)
 	    {
 	        int i_start = lo;
 	        int j_start = mid+1;
 	        int length = 0;
 	        while(i_start<=mid && j_start<=hi)
 	        {
 	            if(data[i_start]<=data[j_start])
 	            {
 	                temp[length] = data[i_start];
 	                i_start++;
 	                length++;
 	            }
 	            else
 	            {
 	                temp[length] = data[j_start];
 	                j_start++;
 	                length++;
 	            }
 	        }
 	        while(i_start<=mid)
 	        {
 	            temp[length] = data[i_start];
 	            i_start++;
 	            length++;
 	        }
 	        while(j_start<=hi)
 	        {
 	            temp[length] = data[j_start];
 	            j_start++;
 	            length++;
 	        }

 	        for(int i=0; i<length; i++)
 	            data[lo+i] = temp[i];
 	    }
 	}
 	//歸併排序
 	template <typename T>
 	void MergeSort(T data[], int lo, int hi, T temp[])
 	{
 	    if(lo < hi)
 	    {
 	        int mid = (lo + hi)/2;
 	        MergeSort(data,lo,mid,temp);
 	        MergeSort(data,mid+1,hi,temp);

 	        Merge(data,lo,mid,hi,temp);
 	    }
 	}

快速排序

  • 複雜度

  • 實現:(挖坑填洞的思想,直接上代碼,不難理解)

 //快速排序
 	template <typename T>
 	void QuickSort(T data[], int lo, int hi)
 	{
 	    if(hi > lo)
 	    {
 	        T temp = data[lo];
 	        int i=lo, j=hi;
 	        while(j > i)
 	        {
 	            while(i<j && data[j]>=temp)
 	                j--;
 	            if(i < j)
 	            {
 	                data[i] = data[j];
 	                i++;
 	            }

 	            while(i<j && data[i]<temp)
 	                i++;
 	            if(i < j)
 	            {
 	                data[j] = data[i];
 	                j--;
 	            }
 	        }

 	        data[i] = temp;
 	        QuickSort(data,lo,i-1);
 	        QuickSort(data,i+1,hi);
 	    }
 	}

堆排序

  • 複雜度

  • 實現:

 //大根堆調整(重點在這裏)
 	template <typename T>
 	void HeapAdjust(T data[], int index, int length)
 	{
 	    int lchild = index * 2 + 1;
 	    int rchild = index * 2 + 2;
 	    int Max = index;

 	    if(lchild < length && data[lchild] > data[Max])
 	        Max = lchild;
 	    if(rchild < length && data[rchild] > data[Max])
 	        Max = rchild;
 	    if(Max != index)
 	    {
 	        swap(data[index],data[Max]);
 	        HeapAdjust(data,Max,length);
 	    }
 	}
 	//堆排序(升序使用大根堆,先構造好大根堆,然後沒次將頭放到尾部,並且調節結點數量減一,調節根結點……)
 	template <typename T>
 	void HeapSort(T data[], int length)
 	{
 	    int i = 0;
 	    for(i=length/2; i>=0; i--)
 	        HeapAdjust(data,i,length);

 	    for(i=length-1; i>0; i--)
 	    {
 	        swap(data[0],data[i]);
 	        HeapAdjust(data,0,i);
 	    }
 	}

總結

  • 快速排序、堆排序與歸併排序的比較:

    • 快速排序是二叉查找樹(二叉查找樹)的一個空間最優化版本。不是循序地把數據項插入到一個明確的樹中,而是由快速排序組織這些數據項到一個由遞歸調用所隱含的樹中。這兩個算法完全地產生相同的比較次數,但是順序不同。對於排序算法的穩定性指標,原地分區版本的快速排序算法是不穩定的。其他變種是可以通過犧牲性能和空間來維護穩定性的。

    • 快速排序的最直接競爭者是堆排序(Heapsort)。堆排序通常比快速排序稍微慢,但是最壞情況的運行時間總是O(nlogn)。快速排序是經常比較快,除了introsort變化版本外,仍然有最壞情況性能的機會。如果事先知道堆排序將會是需要使用的,那麼直接地使用堆排序比等待introsort再切換到它還要快。堆排序也擁有重要的特點,僅使用固定額外的空間(堆排序是原地排序),而即使是最佳的快速排序變化版本也需要Θ(logn)的空間。然而,堆排序需要有效率的隨機存取才能變成可行。

    • 快速排序也與歸併排序(Mergesort)競爭,這是另外一種遞歸排序算法,但有壞情況O(n log n)運行時間的優勢。不像快速排序或堆排序,歸併排序是一個穩定排序,且可以輕易地被採用在鏈表(linked list)和存儲在慢速訪問媒體上像是磁盤存儲或網絡連接存儲的非常巨大數列。儘管快速排序可以被重新改寫使用在煉串列上,但是它通常會因爲無法隨機存取而導致差的基準選擇。歸併排序的主要缺點,是在最佳情況下需要Ω(n)額外的空間。快速排序和堆排序不穩定,歸併排序穩定。

  • 關於排序算法穩定性

    • 假定在待排序的記錄序列中,存在多個具有相同的關鍵字的記錄,若經過排序,這些記錄的相對次序保持不變,即在原序列中,r[i]=r[j],且r[i]在r[j]之前,而在排序後的序列中,r[i]仍在r[j]之前,則稱這種排序算法是穩定的;否則稱爲不穩定的。

    • 堆排序、快速排序、希爾排序、直接選擇排序不是穩定的排序算法,而基數排序、冒泡排序、直接插入排序、折半插入排序、歸併排序是穩定的排序算法。

    • 基數排序是按照低位先排序,然後收集;再按照高位排序,然後再收集;依次類推,直到最高位。有時候有些屬性是有優先級順序的,先按低優先級排序,再按高優先級排序,最後的次序就是高優先級高的在前,高優先級相同的低優先級高的在前。基數排序基於分別排序,分別收集,所以其是穩定的排序算法。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章