數據結構筆記--排序

經典排序算法

算法分類

十種常見排序算法可以分爲兩大類:
比較類排序:通過比較來決定元素間的相對次序,由於其時間複雜度不能突破O(nlogn),因此也稱爲非線性時間比較類排序。
非比較類排序:不通過比較來決定元素間的相對次序,它可以突破基於比較排序的時間下界,以線性時間運行,因此也稱爲線性時間非比較類排序。
在這裏插入圖片描述

算法複雜度

在這裏插入圖片描述
穩定:如果a原本在b前面,而a=b,排序之後a仍然在b的前面。
不穩定:如果a原本在b的前面,而a=b,排序之後 a 可能會出現在 b 的後面。
時間複雜度:對排序數據的總的操作次數。反映當n變化時,操作次數呈現什麼規律。
空間複雜度:是指算法在計算機內執行時所需存儲空間的度量,它也是數據規模n的函數。

冒泡排序(Bubble Sort)

  • 比較相鄰的元素。如果第一個比第二個大,就交換它們兩個;
  • 對每一對相鄰元素作同樣的工作,從開始第一對到結尾的最後一對,這樣在最後的元素應該會是最大的數;
  • 針對(N-1)個元素重複以上的步驟(除了最後一個) 重複步驟1~3,直到排序完成。
    在這裏插入圖片描述
    #include <stdio.h>
    #include <stdlib.h>
    void ShowArray(int*pArr, int len);
    void BabbleSort(int *pArr, int len);
    
    int main()
    {
    	int array[10] = {-1, 9, 5, 0, -10, 44, 77, 33, -2, -99};
    	int length = sizeof(array) / sizeof(array[0]);
    	ShowArray(array, length);
    	BabbleSort(array, length);
    	printf("排序後的數組爲:");
    	ShowArray(array, length);
    	system("PAUSE");
    	return 0;
    }
    //默認爲增序排序
    void BabbleSort(int *pArr, int len)
    {	
    	for (int i = 0; i < len - 1; i++)
    	{
    		for (int j = i+1; j < len; j++)
    		{
    			if (pArr[i] > pArr[j])
    			{
    				int tmp = pArr[i];
    				pArr[i] = pArr[j];
    				pArr[j] = tmp;
    			}
    		}
    	}
    }  
    void ShowArray(int *pArr, int len)
    {
    	for (int i=0; i<len; i++)
    	{
    		printf("%d ", pArr[i]);
    	}
    	printf("\n");
    }

選擇排序(Selection Sort)

  • 首先在未排序序列中找到最小(大)元素,存放到排序序列的起始位置
  • 再從剩餘未排序元素中繼續尋找最小(大)元素,然後放到已排序序列的末尾
  • 以此類推,直到所有元素均排序完畢。

在這裏插入圖片描述

    #include <stdio.h>
    #include <stdlib.h>
    void ShowArray(int*pArr, int len);
    void SelectionSort(int *pArr, int len);
    
    int main()
    {
    	int array[10] = { -1, 9, 5, 0, -10, 44, 77, 33, -2, -99 };
    	int length = sizeof(array) / sizeof(array[0]);
    	ShowArray(array, length);
    	SelectionSort(array, length);
    	printf("排序後的數組爲:");
    	ShowArray(array, length);
    	system("PAUSE");
    	return 0;
    }
    //默認爲增序排序
    void SelectionSort(int *pArr, int len)
    {
    	int minIndex;
    	for (int i = 0; i < len - 1; i++)
    	{
    		minIndex = i;
    		for (int j = i + 1; j < len; j++)
    		{
    			if (pArr[minIndex] > pArr[j])
    			{
    				minIndex = j; // search min value
    			}
    		}
    		int tmp = pArr[i];
    		pArr[i] = pArr[minIndex];
    		pArr[minIndex] = tmp;
    	}
    }
    
    void ShowArray(int *pArr, int len)
    {
    	for (int i = 0; i < len; i++)
    	{
    		printf("%d ", pArr[i]);
    	}
    	printf("\n");
    }

插入排序(Insertion Sort)

工作原理是通過構建有序序列,對於未排序數據,在已排序序列中從後向前掃描,找到相應位置並插入。

  • 從第一個元素開始,該元素可以認爲已經被排序;
  • 取出下一個元素,在已經排序的元素序列中從後向前掃描;
  • 如果該元素(已排序)大於新元素,將該元素移到下一位置;
  • 重複步驟3,直到找到已排序的元素小於或者等於新元素的位置;
  • 將新元素插入到該位置後;
  • 重複步驟2~5。

在這裏插入圖片描述

#include <stdio.h>
#include <stdlib.h>
void ShowArray(int*pArr, int len);
void InsertSort(int *pArr, int len);

int main()
{
	int array[10] = { -1, 9, 5, 0, -10, 44, 77, 33, -2, -99 };
	int length = sizeof(array) / sizeof(array[0]);
	ShowArray(array, length);
	InsertSort(array, length);
	printf("排序後的數組爲:");
	ShowArray(array, length);
	system("PAUSE");
	return 0;
}
//默認爲增序排序
void InsertSort(int *pArr, int len)
{
	int preIndex, curr;
	for (int i = 1; i < len; i++)
	{
		curr = pArr[i];
		preIndex = i - 1;
		while (preIndex >= 0 && pArr[preIndex] > curr)
		{
			pArr[preIndex + 1] = pArr[preIndex];
			preIndex--;
		}
		pArr[preIndex + 1] = curr;
	}
}

void ShowArray(int *pArr, int len)
{
	for (int i = 0; i < len; i++)
	{
		printf("%d ", pArr[i]);
	}
	printf("\n");
}

歸併排序(Merge Sort)

歸併排序包括"從上往下"和"從下往上"2種方式

  1. 從下往上的歸併排序:
    將待排序的數列分成若干個長度爲1的子數列,然後將這些數列兩兩合併;
    得到若干個長度爲2的有序數列,再將這些數列兩兩合併;
    得到若干個長度爲4的有序數列,再將它們兩兩合併;
    直接合併成一個數列爲止。
  2. 從上往下的歸併排序:它與"從下往上"在排序上是反方向的。它基本包括3步:
    ① 分解 – 將當前區間一分爲二,即求分裂點 mid = (low + high)/2;
    ② 求解 – 遞歸地對兩個子區間a[low…mid] 和 a[mid+1…high]進行歸併排序。遞歸的終結條件是子區間長度爲1。
    ③ 合併 – 將已排序的兩個子區間a[low…mid]和 a[mid+1…high]歸併爲一個有序的區間a[low…high]。

在這裏插入圖片描述

在這裏插入圖片描述

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

// 數組長度
#define LENGTH(array) ( (sizeof(array)) / (sizeof(array[0])) )

/*
 * 將一個數組中的兩個相鄰有序區間合併成一個
 *
 * 參數說明:
 *     a -- 包含兩個有序區間的數組
 *     start -- 第1個有序區間的起始地址。
 *     mid   -- 第1個有序區間的結束地址。也是第2個有序區間的起始地址。
 *     end   -- 第2個有序區間的結束地址。
 */
void merge(int a[], int start, int mid, int end)
{
    int *tmp = (int *)malloc((end-start+1)*sizeof(int));    // tmp是彙總2個有序區的臨時區域
    int i = start;            // 第1個有序區的索引
    int j = mid + 1;        // 第2個有序區的索引
    int k = 0;                // 臨時區域的索引

    while(i <= mid && j <= end)
    {
        if (a[i] <= a[j])
            tmp[k++] = a[i++];
        else
            tmp[k++] = a[j++];
    }

    while(i <= mid)
        tmp[k++] = a[i++];

    while(j <= end)
        tmp[k++] = a[j++];

    // 將排序後的元素,全部都整合到數組a中。
    for (i = 0; i < k; i++)
        a[start + i] = tmp[i];

    free(tmp);
}

/*
 * 歸併排序(從上往下)
 *
 * 參數說明:
 *     a -- 待排序的數組
 *     start -- 數組的起始地址
 *     endi -- 數組的結束地址
 */
void merge_sort_up2down(int a[], int start, int end)
{
    if(a==NULL || start >= end)
        return ;

    int mid = (end + start)/2;
    merge_sort_up2down(a, start, mid); // 遞歸排序a[start...mid]
    merge_sort_up2down(a, mid+1, end); // 遞歸排序a[mid+1...end]

    // a[start...mid] 和 a[mid...end]是兩個有序空間,
    // 將它們排序成一個有序空間a[start...end]
    merge(a, start, mid, end);
}


/*
 * 對數組a做若干次合併:數組a的總長度爲len,將它分爲若干個長度爲gap的子數組;
 *             將"每2個相鄰的子數組" 進行合併排序。
 *
 * 參數說明:
 *     a -- 待排序的數組
 *     len -- 數組的長度
 *     gap -- 子數組的長度
 */
void merge_groups(int a[], int len, int gap)
{
    int i;
    int twolen = 2 * gap;    // 兩個相鄰的子數組的長度

    // 將"每2個相鄰的子數組" 進行合併排序。
    for(i = 0; i+2*gap-1 < len; i+=(2*gap))
    {
        merge(a, i, i+gap-1, i+2*gap-1);
    }

    // 若 i+gap-1 < len-1,則剩餘一個子數組沒有配對。
    // 將該子數組合併到已排序的數組中。
    if ( i+gap-1 < len-1)
    {
        merge(a, i, i + gap - 1, len - 1);
    }
}

/*
 * 歸併排序(從下往上)
 *
 * 參數說明:
 *     a -- 待排序的數組
 *     len -- 數組的長度
 */
void merge_sort_down2up(int a[], int len)
{
    int n;

    if (a==NULL || len<=0)
        return ;

    for(n = 1; n < len; n*=2)
        merge_groups(a, len, n);
}

void main()
{
    int i;
    int a[] =  { -1, 9, 5, 0, -10, 44, 77, 33, -2, -99 };
    int ilen = LENGTH(a);

    printf("before sort:");
    for (i=0; i<ilen; i++)
        printf("%d ", a[i]);
    printf("\n");

    merge_sort_up2down(a, 0, ilen-1);        // 歸併排序(從上往下)
    //merge_sort_down2up(a, ilen);            // 歸併排序(從下往上)

    printf("after  sort:");
    for (i=0; i<ilen; i++)
        printf("%d ", a[i]);
    printf("\n");
}

快速排序(Quick Sort)

  • 從數列中挑出一個元素,稱爲 “基準”(pivot);
  • 重新排序數列,所有元素比基準值小的擺放在基準前面,所有元素比基準值大的擺在基準的後面(相同的數可以到任一邊)。在這個分區退出之後,該基準就處於數列的中間位置。這個稱爲分區(partition)操作;
  • 遞歸地(recursive)把小於基準值元素的子數列和大於基準值元素的子數列排序。

在這裏插入圖片描述

在這裏插入圖片描述

#include <stdio.h>
#include <stdlib.h>
 // 數組長度
#define LENGTH(array) ( (sizeof(array)) / (sizeof(array[0])) )

/*
 * 快速排序
 *
 * 參數說明:
 *     a -- 待排序的數組
 *     l -- 數組的左邊界(例如,從起始位置開始排序,則l=0)
 *     r -- 數組的右邊界(例如,排序截至到數組末尾,則r=a.length-1)
 */
void quick_sort(int a[], int l, int r)
{
	if (l < r)
	{
		int i, j, x;

		i = l;
		j = r;
		x = a[i];
		while (i < j)
		{
			while (i < j && a[j] > x)
				j--; // 從右向左找第一個小於x的數
			if (i < j)
				a[i++] = a[j];
			while (i < j && a[i] < x)
				i++; // 從左向右找第一個大於x的數
			if (i < j)
				a[j--] = a[i];
		}
		a[i] = x;
		quick_sort(a, l, i - 1); /* 遞歸調用 */
		quick_sort(a, i + 1, r); /* 遞歸調用 */
	}
}

void main()
{
	int i;
	int a[] = { -1, 9, 5, 0, -10, 44, 77, 33, -2, -99 };
	int ilen = LENGTH(a);

	printf("before sort:");
	for (i = 0; i < ilen; i++)
		printf("%d ", a[i]);
	printf("\n");

	quick_sort(a, 0, ilen - 1);

	printf("after  sort:");
	for (i = 0; i < ilen; i++)
		printf("%d ", a[i]);
	printf("\n");
	system("PAUSE");
}

堆排序(Heap Sort)

堆排序(Heapsort)是指利用堆這種數據結構所設計的一種排序算法。堆積是一個近似完全二叉樹的結構,並同時滿足堆積的性質:即子結點的鍵值或索引總是小於(或者大於)它的父節點。
最大堆進行升序排序的基本思想:

  • ① 初始化堆:將數列a[1…n]構造成最大堆。
  • ② 交換數據:將a[1]和a[n]交換,使a[n]是a[1…n]中的最大值;然後將a[1…n-1]重新調整爲最大堆。 接着,將a[1]和a[n-1]交換,使a[n-1]是a[1…n-1]中的最大值;然後將a[1…n-2]重新調整爲最大值。 依次類推,直到整個數列都是有序的。
    在這裏插入圖片描述

(最大堆的性質)在第一個元素的索引爲 0 的情形中:
性質一:索引爲i的左孩子的索引是 (2i+1);
性質二:索引爲i的右孩子的索引是(2
i+2);
性質三:索引爲i的父結點的索引是 floor((i-1)/2);

在這裏插入圖片描述

#include <stdio.h>
#include <stdlib.h>
 // 數組長度
#define LENGTH(array) ( (sizeof(array)) / (sizeof(array[0])) )
#define swap(a,b) (a^=b,b^=a,a^=b)

/*
 * (最大)堆的向下調整算法
 *
 * 注:數組實現的堆中,第N個節點的左孩子的索引值是(2N+1),右孩子的索引是(2N+2)。
 *     其中,N爲數組下標索引值,如數組中第1個數對應的N爲0。
 *
 * 參數說明:
 *     a -- 待排序的數組
 *     start -- 被下調節點的起始位置(一般爲0,表示從第1個開始)
 *     end   -- 截至範圍(一般爲數組中最後一個元素的索引)
 */
void maxheap_down(int a[], int start, int end)
{
	int c = start;            // 當前(current)節點的位置
	int l = 2 * c + 1;        // 左(left)孩子的位置
	int tmp = a[c];            // 當前(current)節點的大小
	for (; l <= end; c = l, l = 2 * l + 1)
	{
		// "l"是左孩子,"l+1"是右孩子
		if (l < end && a[l] < a[l + 1])
			l++;        // 左右兩孩子中選擇較大者,即m_heap[l+1]
		if (tmp >= a[l])
			break;        // 調整結束
		else            // 交換值
		{
			a[c] = a[l];
			a[l] = tmp;
		}
	}
}

/*
 * 堆排序(從小到大)
 *
 * 參數說明:
 *     a -- 待排序的數組
 *     n -- 數組的長度
 */
void heap_sort_asc(int a[], int n)
{
	int i;

	// 從(n/2-1) --> 0逐次遍歷。遍歷之後,得到的數組實際上是一個(最大)二叉堆。
	for (i = n / 2 - 1; i >= 0; i--)
		maxheap_down(a, i, n - 1);

	// 從最後一個元素開始對序列進行調整,不斷的縮小調整的範圍直到第一個元素
	for (i = n - 1; i > 0; i--)
	{
		// 交換a[0]和a[i]。交換後,a[i]是a[0...i]中最大的。
		swap(a[0], a[i]);
		// 調整a[0...i-1],使得a[0...i-1]仍然是一個最大堆。
		// 即,保證a[i-1]是a[0...i-1]中的最大值。
		maxheap_down(a, 0, i - 1);
	}
}

/*
 * (最小)堆的向下調整算法
 *
 * 注:數組實現的堆中,第N個節點的左孩子的索引值是(2N+1),右孩子的索引是(2N+2)。
 *     其中,N爲數組下標索引值,如數組中第1個數對應的N爲0。
 *
 * 參數說明:
 *     a -- 待排序的數組
 *     start -- 被下調節點的起始位置(一般爲0,表示從第1個開始)
 *     end   -- 截至範圍(一般爲數組中最後一個元素的索引)
 */
void minheap_down(int a[], int start, int end)
{
	int c = start;            // 當前(current)節點的位置
	int l = 2 * c + 1;        // 左(left)孩子的位置
	int tmp = a[c];            // 當前(current)節點的大小
	for (; l <= end; c = l, l = 2 * l + 1)
	{
		// "l"是左孩子,"l+1"是右孩子
		if (l < end && a[l] > a[l + 1])
			l++;        // 左右兩孩子中選擇較小者
		if (tmp <= a[l])
			break;        // 調整結束
		else            // 交換值
		{
			a[c] = a[l];
			a[l] = tmp;
		}
	}
}

/*
 * 堆排序(從大到小)
 *
 * 參數說明:
 *     a -- 待排序的數組
 *     n -- 數組的長度
 */
void heap_sort_desc(int a[], int n)
{
	int i;

	// 從(n/2-1) --> 0逐次遍歷每。遍歷之後,得到的數組實際上是一個最小堆。
	for (i = n / 2 - 1; i >= 0; i--)
		minheap_down(a, i, n - 1);

	// 從最後一個元素開始對序列進行調整,不斷的縮小調整的範圍直到第一個元素
	for (i = n - 1; i > 0; i--)
	{
		// 交換a[0]和a[i]。交換後,a[i]是a[0...i]中最小的。
		swap(a[0], a[i]);
		// 調整a[0...i-1],使得a[0...i-1]仍然是一個最小堆。
		// 即,保證a[i-1]是a[0...i-1]中的最小值。
		minheap_down(a, 0, i - 1);
	}
}

void main()
{
	int i;
	int a[] = { -1, 9, 5, 0, -10, 44, 77, 33, -2, -99 };
	int ilen = LENGTH(a);

	printf("before sort:");
	for (i = 0; i < ilen; i++)
		printf("%d ", a[i]);
	printf("\n");

	heap_sort_asc(a, ilen);            // 升序排列
	//heap_sort_desc(a, ilen);        // 降序排列

	printf("after  sort:");
	for (i = 0; i < ilen; i++)
		printf("%d ", a[i]);
	printf("\n");
	system("PAUSE");
}

希爾排序(Shell Sort)

  • 選擇一個增量序列t1,t2,…,tk,其中ti>tj,tk=1;
  • 按增量序列個數k,對序列進行k 趟排序;
  • 表中每個元素每次向前移動ti個元素距離的倍數進行比較,若大於(小於)則交換位置,直到本次移動的元素出界
  • 重複第三步,直到ti取1,結束交換

在這裏插入圖片描述

#include <stdio.h>
#include <stdlib.h>
 // 數組長度
#define LENGTH(array) ( (sizeof(array)) / (sizeof(array[0])) )

/*
 * 希爾排序
 *
 * 參數說明:
 *     a -- 待排序的數組
 *     n -- 數組的長度
 */
void shell_sort1(int a[], int n)
{
	int i, j, gap;

	// gap爲步長,每次減爲原來的一半。
	for (gap = n / 2; gap > 0; gap /= 2)
	{
		// 共gap個組,對每一組都執行直接插入排序
		for (i = 0; i < gap; i++)
		{
			for (j = i + gap; j < n; j += gap)
			{
				// 如果a[j] < a[j-gap],則尋找a[j]位置,
                            //並將後面數據的位置都後移。
				if (a[j] < a[j - gap])
				{
					int tmp = a[j];
					int k = j - gap;
					while (k >= 0 && a[k] > tmp)
					{
						a[k + gap] = a[k];
						k -= gap;
					}
					a[k + gap] = tmp;
				}
			}
		}

	}
}

/*
 * 對希爾排序中的單個組進行排序
 *
 * 參數說明:
 *     a -- 待排序的數組
 *     n -- 數組總的長度
 *     i -- 組的起始位置
 *     gap -- 組的步長
 *
 *  組是"從i開始,將相隔gap長度的數都取出"所組成的!
 */
void group_sort(int a[], int n, int i, int gap)
{
	int j;

	for (j = i + gap; j < n; j += gap)
	{
		// 如果a[j] < a[j-gap],則尋找a[j]位置,並將後面數據的位置都後移。
		if (a[j] < a[j - gap])
		{
			int tmp = a[j];
			int k = j - gap;
			while (k >= 0 && a[k] > tmp)
			{
				a[k + gap] = a[k];
				k -= gap;
			}
			a[k + gap] = tmp;
		}
	}
}

/*
 * 希爾排序
 *
 * 參數說明:
 *     a -- 待排序的數組
 *     n -- 數組的長度
 */
void shell_sort2(int a[], int n)
{
	int i, gap;

	// gap爲步長,每次減爲原來的一半。
	for (gap = n / 2; gap > 0; gap /= 2)
	{
		// 共gap個組,對每一組都執行直接插入排序
		for (i = 0; i < gap; i++)
			group_sort(a, n, i, gap);
	}
}

void main()
{
	int i;
	int a[] = { -1, 9, 5, 0, -10, 44, 77, 33, -2, -99 };
	int ilen = LENGTH(a);

	printf("before sort:");
	for (i = 0; i < ilen; i++)
		printf("%d ", a[i]);
	printf("\n");

	//shell_sort1(a, ilen);
	shell_sort2(a, ilen);

	printf("after  sort:");
	for (i = 0; i < ilen; i++)
		printf("%d ", a[i]);
	printf("\n");
	system("PAUSE");
}


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