排序——合併排序法

1、合併排序法的概念算法

合併排序(Merge Sort)就是將兩個或多個有序表合併成一個有序表。將兩個有序表合併成一個有序表的過程稱爲二路合併。數組

2、算法描述函數

    二路合併排序的基本思想是:對於兩個有序表合併,初始時,把含有n個節點的待排序序列看作由n個長度爲1的有序子表所組成,將它們依次兩兩合併,獲得長度爲2的若干有序子表,再對這些子表進行兩兩合併……一直重複到長度爲n,排序完成。測試


    1.合併排序過程
    使用二路合併排序法進行排序時,須要佔用較大的輔助空間,輔助空間的大小與待排序序列同樣多。
    下面以一組待排序的數據演示合併排序的過程,假設有9個須要排序的數據序列以下:
    69,65,90,37,92,6,28,54,34
    提示:爲了演示不能完整劃分子表的狀況,本例中使用了9個數據。
    使用合併排序法進行排序的過程如下圖1所示,具體排序過程以下:
    (1)首先將9個原數據當作9個長度爲1的有序表(只有一個數據,確定是有序的)。
    (2)將這9個有序表兩兩合併,即將第一、2個合併在一塊兒、第三、4個合在一塊兒……第9個沒有合併的,就單獨放在那裏,直接進入下一遍合併。
    (3)通過第1遍的合併,獲得長度爲2的有序表序列,再將這些長度爲2的有序表序列進行兩兩合併。
    (4)通過第2遍合併,獲得長度爲4的有序表序列,再將這些長度爲4的有序表進行兩兩合併。
    (5)通過第3遍合併,獲得長度爲8的有序表序列,以及最後只有一個元素的序列,將這兩個序列進行合併,便可完成合並排序。code


圖1合併排序過程排序


提示:二路合併排序過程當中須要進行若干遍的合併,每一遍合併包括若干次二路合併,下面首先介紹二下面首先介紹二路合併算法的描述。內存


2.合併相鄰有序表
    若是需合併的兩個有序表保存於數組A中,其中一個序列保存在下標從s到m的數組元素中,另外一個序列保存在下標從m+1到n的數組元素中。合併的結果保存在數組R(該數組爲輔助空間)中。用變量i、j分別指向兩個系列中須要比較的元素,變量k指向數組R中的序號,表示下一個要保存數據的位置,合併過程以下:
(1)取第1個系列的第i個元素A[i],與第2個系列的第1個元素A[j]比較。
(2)若A[i]≤A[j],則將A[i]複製到R[k]中,使i和k分別增長1。
(3)若A[i]>A[j],則將A[j]複製到R[k]中,使j和k分別增長1。
(4)重複步驟1至3,直到一個序列複製完爲止。
(5)將另外一個序列中未比較的數據複製到R的剩餘位置。
這樣,就完成了相鄰有序表的合併排序操做。it


3.完成一遍完整合並
    前面介紹了合併相鄰有序表的算法,接下來介紹完成一遍二路合併的算法。
設數組A中的n個記錄已分紅若干個長度爲len的有序表,最後一個有序表的長度可能小於len,要求將這些表兩兩合併成一些長度爲2*len的有序表,並把結果保存到數組R中。當n不是2*len的整數倍時,可能有如下兩種狀況:
❑一是剩下一個長度爲len的表和一個長度小於len的表,因爲二路合併算法不要求待合併的兩個有序表必須有相同的長度,可將最後這兩個表進行合併,只是合併後的有序表的長度小於2*len。
❑另外一種狀況是隻剩下一個有序表,其長度小於等於len,則將其直接複製到R中對應區間便可。io


4.合併排序
整個二路合併排序過程須要進行若干遍排序操做,第一遍排序時,有序表的長度len爲1,之後每進行一遍排序將len的長度增長一倍。假設需排序的n個數據存在數組A中,合併過程當中將使用一個輔助數組R。第一遍合併時,數組A中爲須要進行合併排序的數據,將合併的結果保存到數組R中;第二遍合併時,數組R中須要進行合併排序的數據,將合併的結果保存到數組A中;如此反覆進行,直到n個記錄成爲一個有序表時爲止。class

3、算法實現

一、合併排序法實現

/**
 * 合併相鄰有序表
 * */
void MergeStep(int a[], int r[], int s, int m, int n)
{
	int i, j, k;
	k = s;
	i = s;
	j = m+1;

	while (i<=m && j<=n) {
		if (a[i] <= a[j]) //將較小的數複雜到r中
			r[k++] = a[i++];
		else
			r[k++] = a[j++];
	}
	while (i<=m) //將未合併的部分複製到r中
		r[k++] = a[i++];

	while (j<=n)
		r[k++] = a[j++];

}


/**
 * 完成一遍二路合併的函數
 * */
void MergePass(int a[], int r[], int n, int len)
{
	int s, e;
	s = 0;	//第一個序列的起始序號

	while (s+len < n) { //至少有兩個有序段
		e = s + 2*len - 1; //第一個序列的結束序號
		if (e >= n) //最後一段可能少於len個節點
			e = n-1; //從新計算序號
		MergeStep(a, r, s, s+len-1, e); //相鄰有序段合併
		s = e+1; //下一對有序段中左段的開始下標
	}
	if (s<n)
		for (;s<n; s++)
			r[s] = a[s];
}

/**
 * 合併排序
 * */
void MergeSort(int a[], int n)
{
	int *p;
	int len = 1;
	int f = 0;

	if (!(p=(int*)malloc(sizeof(int)*n))) { //分配臨時空間保存臨時數據
		printf("分配臨時內存失敗!\n");
		exit(0);
	}

	while (len < n) {
		if (f) //交替在a和p之間來回合併
			MergePass(p, a, n, len);
		else
			MergePass(a, p, n, len);
		len *= 2;
		f = 1-f;
	}

	if (f) //若進行了排序
		for (f=0; f<n; f++)
			a[f] = p[f];
		free(p);
}

二、合併排序法測試

#include <stdio.h>
#include <stdlib.h>
#include "MergeSort.c"

#define ARRAYLEN 9

void ShowData(int arr[], int n)
{
    int i;
    for (i=0; i<n; i++)
        printf("%d ", arr[i]);
    printf("\n");

    return;
}


int main(int argc, char *argv[])
{
    int i;

    int a[ARRAYLEN] = {69, 65, 90, 37, 92, 6, 28, 54, 34};

    printf("原數據:");
    ShowData(a, ARRAYLEN);

    MergeSort(a, ARRAYLEN);
    printf("排序後:");
    ShowData(a, ARRAYLEN);
    return 0;

}

運行結果:

相關文章
相關標籤/搜索