算法1:排序問題(1)


  大道理什麼的我已經不想多講,直接上一些讓自己重新理解問題的筆記纔是硬核之事。今天我寫的代碼是排序問題,這是一個很古老,也是算法開課繞不開的問題,有很多種形態各樣的排序算法——冒泡排序、插入排序、歸併排序,快速排序、堆排序、希爾排序等等。
  今天我首先想接觸的是插入排序和歸併排序兩種。首先來看一下插入排序——

插入排序

  主要思路是將一個新的數據插入到一個已經排好序的數組中,然後依次進行最終完成排序,這個算法的時間複雜度是O(n2)O(n^2),主要的問題存在於,由於數組是連續的空間,因此插入操作,實際上是兩個相鄰的數組之間比較大小然後交換位置的過程的重複。
——我先寫出僞代碼

## 輸入:數組A
## 輸出:排好序的數組A
for i in 2 to n:
    for j in i to 0:
        if A[j] < A[j - 1] ## 根據結果從大到小還是從小到大決定大於小於號
            swap(A[j - 1], A[j])

以下是插入排序的C++代碼實現:

    #include <iostream>
    #define Length(A, B) (sizeof(A) / sizeof(B))
    void insert_sort(int *, int);
    void swap(int &, int &);
    void display(int *, int);

    int main(int argc, char const *argv[])
    {
        int A[] = {3, 2, 5, 4, 8, 7, 6};
        int length = Length(A, int);

        display(A, length);
        insert_sort(A, length);

        display(A, length);
        return 0;
    }

    void insert_sort(int A[], int length)
    {
        if (A == nullptr)
            return;

        if (length <= 0)
            return;

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

    void swap(int &num1, int &num2)
    {
        int temp_num = num1;
        num1 = num2;
        num2 = temp_num;
    }

    void display(int A[], int length)
    {
        for (int i = 0; i < length; ++i)
        {
            std::cout << A[i] <<std::endl;
        }
    }

P.S. 在這裏我強調一個小問題,就是在計算給定數組的長度的時候,我們採用的方法是這樣的

int A[] = {a1, ..., an};
int length = sizeof(A) / sizeof(A[0]);

但是如果,我們的數組是一個參數傳來的,那麼這樣做就會出現問題:

int A[] = {a1, ..., an};
int length(int A[])
{
    if (A == nullptr)    return 0;
    return sizeof(A) / sizeof(A[0]);
}

實際上,上述第一段代碼的運行結果是n, 而第二段代碼的運行結果是2。那麼這是怎麼回事呢?——實際上,對於數組而言,數組名可以看成是指針,但是他卻和指針並不能完全等同起來。在c++中進行參數傳遞,數組名傳入的實際上是自己的首地址,因此此時進行sizeof得到的其實是整型數組的長度即8,然後再取除以sizeof(int)這樣得到的結果就是2。

歸併排序

  歸併排序算是一個非常典型的分治法的具體使用案例了,總體上說來就是將原數組分成長度相等的兩部分,然後分別對他們進行排序,最後將排好序的兩個子序列進行合併,(快排是將數組按大小分成兩部分,然後進行迭代),依次遞歸程序,就實現了排序算法。
  歸併排序的時間複雜度滿足以下的時間遞推式:
T(n)=2T(n2)+O(n)T(n) = 2T(\dfrac{n}{2}) + O(n)
  因此他的時間複雜度爲O(nlgn)O(n\lg n)
——我先寫出僞代碼

## 輸入:數組A,數組的起始位置,數組長度length
## 輸出:排好序的數組A

def merge_sort(A, left, length):
	interval = length / 2
	merge_sort(A, left, interval)
	merge_sort(A, left + interval, length - interval)
	
	merge(A_left, A_right)

  以下我寫出他的c++代碼實現:

void merge_sort(int A[], int left, int length)
{
	if (length == 1 || A == nullptr)
		return;

	int interval = length / 2;
	/***
	*	將原數組等距離二分
	*/
	int n_left = left, n_right = left + interval;
	merge_sort(A, n_left, interval);
	merge_sort(A, n_right, length - interval);


	/***
	*	將原數組二分完成之後,再對兩部分已排好序的數組進行合併
	*/
	
	int *temp_array = new int[length]; 

	int count = 0;
	
	/***
	*	構造臨時數組,先存在臨時數組,然後再複製給原來的數組
	*/
	while (n_right < length + left && n_left < interval + left)
	{
		temp_array[count ++] = A[n_left] <= A[n_right] ? A[n_left ++] : A[n_right ++];
	}

	/***
	*	若左邊數組的數組已經完成合並,右邊的數組還未合併完
	*	這樣就可以直接將臨時數組的前count個數複製回原來的數組
	*/
	if (n_left == interval + left && n_right <= left + length)
	{
		for (int i = 0; i < count; ++ i)
		{
			A[i + left] = temp_array[i];
		}
	}

	/***
	*	若右邊數組的數組已經完成合並,左邊的數組還未合併完
	*	這樣就先將左邊的最大數先放到原數組的右邊,
	*	然後將臨時數組的前count個數複製回原來的數組
	*/
	if (n_left <= interval + left && n_right == left + length)
	{
		for (int i = left + interval - 1; i >= n_left; -- i)
		{
			A[length + i - interval] = A[i];
		}

		for (int i = 0; i < count; ++ i)
		{
			A[i + left] = temp_array[i];
		}
	}

	delete []temp_array;
}

  P.S.還有一個版本的歸併排序,只是它的參數需要的少一點,我也把它貼在下面:

void merge_sort(int A[], int length)
{
	if (A == nullptr || length <= 1)
		return;

	int interval = length / 2;

	merge_sort(A, interval);
	merge_sort(A + interval, length - interval);

	int left = 0, right = interval;
	int *temp_array = new int[length];
	int count = 0;

	while (left < interval && right < length)
		temp_array[count ++] = A[left] <= A[right] ? A[left ++] : A[right ++];

	if (left == interval && right <= length)
	{
		for (int i = 0; i < count; ++ i)
		{
			A[i] = temp_array[i];
		}
	}

	if (right == length && left <= interval)
	{
		for (int i = interval - 1; i >= left ; -- i)
		{
			A[i + length - interval] = A[i];
		}

		for (int i = 0; i < count; ++ i)
		{
			A[i] = temp_array[i];
		}
	}

	delete []temp_array;
}

  思路同上,那麼接下來我依然是進行總結。

總體來說,歸併排序的思路很清晰,很明瞭,但爲什麼我的代碼還是寫了這麼久?!原因很長時間是因爲我再處理邊界的時候除了問題,沒有想明白一個邊界問題。因此在這裏我記錄下來,du對於邊界問題一定要仔細考慮,千萬不可隨意書寫,到最後debug太難受了。
P.S.其次,我想說明,對於缺少左邊界參數歸併版本,實際上就是將數組的偏移直接作爲參數傳入函數進行處理,這樣就能直接將左邊界寫死,完成代碼的實現。另外通過指針訪問數組,實際上只需要在首地址的基礎之上加上偏移,然後解引用便獲得相應下標得值。eg:A[] = {1, 2, 3, 4, 5}想要訪問A[3]得途徑有A[3]、*(A + 3)——
在這裏插入圖片描述

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