數據結構與算法(C語言) | 排序算法

 排序是計算機內經常進行的一種操作,其目的是將一組“無序”的記錄序列調整爲“有序”的記錄序列。

——–假設含有n個記錄的序列爲{r1,r2,…,rn},其相應的關鍵字分別爲{k1,k2,…,kn},需確定1,2,…,n的一種排列p1,p2,…pn,使其相應的關鍵字滿足kp1<=kp2<=…<=kpn非遞減(或非遞增)關係,即使得序列成爲一個按關鍵字有序的序列{rp1,rp2,…rpn},這樣的操作就稱爲排序。

•在排序問題中,通常將數據元素稱爲記錄。

–顯然我們輸入的是一個記錄集合,排序後輸出的也是一個記錄集合。

–所以我們可以將排序看成是線性表的一種操作。

•排序的依據是關鍵字之間的大小關係,那麼對同一記錄集合,針對不同的關鍵字進行排序,可以得到不同序列。

 

內部排序和外部排序——

若待排序記錄可全部放入內存,整個排序過程不需要訪問外存便能完成,則稱此類排序問題爲內部排序;     

若待排序記錄的數量很大,以至內存一次不能容納全部記錄,在排序過程中尚需對外存進行訪問,則稱此類排序問題爲外部排序。

排序方法的穩定性——

對於一種排序方法,若排序後具有相同關鍵字的記錄仍維持原來的相對次序,則稱之爲穩定的,否則稱之爲不穩定的。   

影響排序算法性能的幾個要素——時間性能、輔助空間、算法的複雜性

 

冒泡排序(通過頻繁的交換完成排序):

1.兩兩注意是相鄰的兩個元素的意思

2.如果有n個元素需要比較n-1次,每一輪減少1次比較

3.既然叫冒泡排序,那就是從下往上兩兩比較,所以看上去就跟泡泡往上冒一樣。

 

#include<stdio.h>
void BubbleSort(int k[], int n)
{
    int i, j, temp, count1=0, count2=0;
    for(i=0 ;i<n-1 ;i++)
    {
        for(j=n-1 ;j > i ;j--)
        {
            count1++;
            if(k[j-1] > k[j])
            {
                count2++;
                temp = k[j-1];
                k[j-1]= k[j];
                k[j] = temp;

            }
        }
    }
    printf("總共進行了%d次比較,進行了%d次移動!", count1, count2);

}
int main()
{
    int i ,a[10] ={11,2,6,4,22,31,0,9,7,13};
    BubbleSort(a, 10);
    printf("排序後的結果是:");
    for(i =0;i<10 ;i++)
    {
        printf("%d ",a[i]);
    }
    return 0;
}

 

 

選擇排序:

選擇排序法 是對 定位比較交換法(也就是冒泡排序法) 的一種改進。選擇排序的基本思想是:每一趟在n-i+1(i=1,2,…n-1)個記錄中選取關鍵字最小的記錄作爲有序序列中第i個記錄。

簡單選擇排序:

#include<stdio.h>
void SlecetSort(int k[], int n)
{
    int i, j, temp, count1=0, count2=0;;
    for(i=0 ;i< n-1; i++)
    {
        int min = i;
        for(j =i+1;j<n ;j++)
        {
            count1++;
            if(k[j]<k[min])
            {
                min = j;
            }
        }
        if(min !=i)
        {
            count2++;
            temp = k[min];
            k[min] = k[i];
            k[i] = temp;
        }
    }
    printf("總共進行了%d次比較,進行了%d次移動!", count1, count2);
}
int main()
{
    int i ,a[10] ={11,2,6,4,22,31,0,9,7,13};
    SlecetSort(a, 10);
    printf("排序後的結果是:");
    for(i =0;i<10 ;i++)
    {
        printf("%d ",a[i]);
    }
    return 0;
}

 

 

直接插入排序(最簡單,基於順序查找):

void InsertSort(int k[],int n)
{
    int i, j, temp;
    for(i=1; i<n ;i++)
    {
        if(k[i]<k[i-1])
        {
            temp = k[i];
            for(j =i-1; k[j] > temp;j--)
            {
                k[j+1] = k[j];
            }
            k[j+1] = temp;
        }
    }
}

 

希爾排序(縮小增量排序):

將記錄序列分成若干子序列(邏輯上分組),分別對每個子序列進行插入排序。此時插入排序所作用的數據量比較小(每一個小組),插入的效率比較高

例如:將 n 個記錄分成 d 個子序列:

  { R[1]R[1+d]R[1+2d]R[1+kd] }

  { R[2]R[2+d]R[2+2d]R[2+kd] }

    …

  { R[d]R[2d]R[3d]R[kd]R[(k+1)d] }

其中,d 稱爲增量,它的值在排序過程中從大到小逐漸縮小,直至最後一趟排序減爲 1

就像直接插入排序跨度爲1 

實現分組有序整體不一定有序

void ShellSort(int k[], int n)
{        
    int i,j,temp;
    int dk;
     //進行分組,最開始的增量爲數組長度的一半
    for(dk = n/2; dk>0 ; dk /=2)
    {

    	//對數組做一趟希爾插入排序,dk爲本趟增量
	for( i=dk; i < n; i++ )//分別向每組的有序區域插入
	{
	    if( k[i] < k[i-dk] )
	    {
	        temp = k[i];
	
        	for( j=i-dk; k[j] > temp; j-=dk )//比較與記錄後移同時進行
	        {
	            k[j+dk] = k[j];
	        }
	
	        k[j+dk] = temp;
	    }
	} 
    }


}

希爾排序不是穩定的,雖然插入排序是穩定的,但是希爾排序在插入的時候是跳躍性插入的,有可能破壞穩定性

 

堆排序:

n 個元素的序列 ( K1, K2, …,Kn ),該序列滿足如下條件:

 

在完全二叉樹中根結點一定是堆中所有結點最大或者最小者

下標i與2i和2i+1是雙親和子女關係。

那麼把大頂堆和小頂堆用層序遍歷存入數組,則一定滿足上面的表達式。

堆排序算法

•堆排序(Heap Sort)就是利用堆進行排序的算法,它的基本思想是:

–將待排序的序列構造成一個大頂堆(或小頂堆)。

–此時,整個序列的最大值就是堆頂的根結點。將它移走(就是將其與堆數組的末尾元素交換,此時末尾元素就是最大值)。

–然後將剩餘的n-1個序列重新構造成一個堆,這樣就會得到n個元素中的此大值。

–如此反覆執行,便能得到一個有序序列了。


void swap(int k[], int i, int j)
{
	int temp;

	temp = k[i];
	k[i] = k[j];
	k[j] = temp;
}

void HeapAdjust(int k[], int s, int n)
{
	int i, temp;

	temp = k[s];

	for( i=2*s; i <= n; i*=2 )
	{
		if( i < n && k[i] < k[i+1] )
		{
			i++;
		}

		if( temp >= k[i] )
		{
			break;
		}

		k[s] = k[i];
		s = i;
	}

	k[s] = temp;
}

void HeapSort(int k[], int n)
{
	int i;

	for( i=n/2; i > 0; i-- )
	{
		HeapAdjust(k, i, n);
	}

	for( i=n; i > 1; i-- )
	{
		swap(k, 1, i);
		HeapAdjust(k, 1, i-1);
	}
}

 

 

歸併排序:

歸併排序的基本思想——

    假設初始序列含有 n 個記錄,則可看成是 n 個有序的子序列,每個子序列的長度爲1,然後兩兩歸併,得到 én/2ù 個長度爲 2 1 的有序子序列;再兩兩歸併,……,如此重複,直至得到一個長度爲 n 的有序序列爲止,這種排序方法稱爲2-路歸併排序。

  2-路歸併排序的核心操作:將兩個位置相鄰的記錄有序子序列,歸併爲一個記錄的有序序列。

 

 

 容易看出,對 n 個記錄進行歸併排序的時間複雜度爲Ο(n·log2n)。即:

    每一趟歸併的時間複雜度爲 O(n),

    總共需進行 élog2nù 趟。

 歸併排序需要和待排記錄等數量的輔助空間,即空間複雜度爲O(n)。歸併排序是需要輔助空間最多的一種排序方法。

#include<stdio.h>
#define     MAXSIZE 20
//遞歸實現歸併,並把最後的結果存放到list1
void merging(int *list1,int list1_size,int *list2, int list2_size)
{
    int i, j, k;
    int temp[MAXSIZE];

    i = j = k = 0;
    while(i < list1_size && j<list2_size)
    {
        if(list1[i] < list2[j])
        {
            temp[k++] = list1[i++];
        }
        else
        {
            temp[k++] = list2[j++];
        }
    }
    while(i < list1_size)
    {
        temp[k++] = list1[i++];
    }
    while(j < list2_size)
    {
        temp[k++] = list2[j++];
    }

    int m;
    for(m=0; m < list1_size+list2_size; m++)
    {
        list1[m] = temp[m];
    }
}
void mergeSort(int k[],int n)
{
    if(n>1)
    {
        int *list1 = k;
        int list1_size = n/2;
        int *list2 = k + n/2;
        int list2_size = n - list1_size;

        mergeSort(list1, list1_size);
        mergeSort(list2, list2_size );

        merging(list1, list1_size,list2, list2_size);
    }

}

int main()
{
	int i, a[10] = {5, 2, 6, 0, 3, 9, 1, 7, 4, 8};

	mergeSort(a, 10);

	printf("排序後的結果是:");
	for( i=0; i < 10; i++ )
	{
		printf("%d ", a[i]);
	}
	printf("\n\n");

	return 0;
}

 

//迭代實現
void MergeSort(int k[], int n)
{
	int i, next, left_min, left_max, right_min, right_max;
	int *temp = (int *)malloc(n * sizeof(int));

	for( i=1; i < n; i*=2 ) 
	{
		for( left_min=0; left_min < n-i; left_min = right_max )
		{
			right_min = left_max = left_min + i;
			right_max = left_max + i;

			if( right_max > n )
			{
				right_max = n;
			}

			next = 0;

			while( left_min < left_max && right_min < right_max )
			{
				if( k[left_min] < k[right_min] )
				{
					temp[next++] = k[left_min++];
				}
				else
				{
					temp[next++] = k[right_min++];
				}
			}

			while( left_min < left_max )
			{
				k[--right_min] = k[--left_max];
			}

			while( next > 0 )
			{
				k[--right_min] = temp[--next];
			}
		}
	}
}

 

 

 快速排序

       快速排序(Quick Sort)使用分治法策略。它的基本思想是:選擇一個基準數,通過一趟排序將要排序的數據分割成獨立的兩部分;其中一部分的所有數據都比另外一部分的所有數據都要小。然後,再按此方法對這兩部分數據分別進行快速排序,整個排序過程可以遞歸進行,以此達到整個數據變成有序序列。

流程如下:

  • 從數列中挑出一個基準值。

  • 將所有比基準值小的擺放在基準前面,所有比基準值大的擺在基準的後面(相同的數可以到任一邊);在這個分區退出之後,該基準就處於數列的中間位置。

  • 遞歸地把"基準值前面的子數列"和"基準值後面的子數列"進行排序。

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

}

 快速排序性能分析:

 快速排序的穩定性:快速排序是不穩定的算法,它不滿足穩定算法的定義;所謂算法穩定性指的是對於一個數列中的兩個相等的數a[i]=a[j],在排序前,a[i]在a[j]前面,經過排序後a[i]仍然在a[j]前,那麼這個排序算法是穩定的。

 

 

 

 

排序方法

最好情況

最壞情況

平均時間

輔助存儲

穩定性

1

直接插入

O(n)

O(n2)

O(n2)

O(1)

穩定

2

折半插入

O(n)

O(n2)

O(n2)

O(1)

穩定

3

希爾排序

——

——

——

O(1)

不穩定

4

起泡排序

O(n)

O(n2)

O(n2)

O(1)

穩定

5

快速排序

O(nlog2n)

O(n2)

O(nlog2n)

O(log2n)

不穩定

6

簡單選擇

O(n2)

O(n2)

O(n2)

O(1)

不穩定

7

堆排序

O(nlog2n)

O(nlog2n)

O(nlog2n)

O(1)

不穩定

8

歸併排序

O(nlog2n)

O(nlog2n)

O(nlog2n)

O(n)

穩定

9

基數排序

O(d(n+rd))

O(d(n+rd))

O(d(n+rd))

O(n+rd)

穩定

 

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