【筆記17】打好基礎——C與python分別實現七種排序


本文記錄學習七種排序(內排序)的代碼,分別用C和python實現,必要時加圖解釋。這就是不好好學數據結構的後果

排序

內排序一覽表

C語言準備

利用結構體傳輸這個線性表。
實現定義:

#include<stdio.h>
#include<time.h>
#define MAXSIZE 10

typedef struct // 線性表存儲
{
	int r[MAXSIZE];
	int length;
}SqList;

void swap(SqList *L, int i, int j) // 交換函數
{
    int temp=L->r[i];
    L->r[i]=L->r[j];
    L->r[j]=temp;
}

void ListPrint(SqList *L, int type) // 打印該列表
{
	int i;
	printf("[",type);
	for(i=1;i<L->length;i++)
	{
		printf("%d,",L->r[i]);
	}
	printf("\b]\n");
}

void main()
{
	SqList alist={
		{0,9,1,5,8,3,7,4,6,2},
		9
	};
	InsertSort(&alist); // 直接插入
	ShellSort(&alist); // 希爾
	
	BubbleSort(&alist); //冒泡
	HeapSort(&alist); // 堆
	
	SecletionSort(&alist); // 簡單選擇
	QuickSort(&alist); // 快排

	MergingSort(&alist); // 歸併
}

python準備

if __name__ == '__main__':
    alist = [9,1,8,4,6,7,3,5,2]
    InsertionSort(alist)
    ShellSort(alist)

    BubbleSort(alist)
    HeapSort(alist)

    SelectionSort(alist)
    QuickSort(alist)

    MergingSort(alist)

1 插入排序

1.1 直接插入排序

要點:將數組第一個元素看作已經排好的序列,第二個到最後一個元素依次往這個序列裏插,只要左邊 < 該元素 < 右邊

C version

void InsertSort(SqList *L)
{
	int i,j;
	for(i=2;i<=L->length;i++) // 假設r[1]爲拍好的序列,循環至最後一位
	{
		if(L->r[i]<L->r[i-1]) // 如果該位比左邊小,執行以下,將小數移至左邊
		{
			L->r[0]=L->r[i]; // 利用r[0]存儲r[i]的值
			for(j=i-1;L->r[j] > L->r[0];j--) // 從i-1向左找一個可以插入r[i]的值,這個r[j]需要大於r[i]
			{
				L->r[j+1]=L->r[j]; // 將這個比r[i]大的值向右移一位,此時j與j+1都相等,可以看作將j空出來了
			}
			L->r[j+1]=L->r[0]; // 此時j的值小於等於r[i]=r[0],插入右邊
		}
	}
}

python version

def InsertionSort(blist):
    length = len(blist)
    for i in range(1, length):
        if blist[i] < blist[i - 1]:
            temp = blist[i]
            j = i - 1
            while (j >= 0) & (blist[j] > temp):
                blist[j + 1] = blist[j]
                j -= 1
            blist[j + 1] = temp
    print blist

1.2 希爾排序

要點:先分組,每個組利用插入排序先排好序,每個組排好之後整體插入排序。
優點:先使得整個序列基本有序,大的數集中在後面,小的數集中在前面,在插入排序的時候就不需要數據過大的移動。
難點:怎麼分組效率最高?基本有序 ≠ 局部有序,如果是順序選取組,很有可能組內達到有序,但整體效率還是很低,如圖:

44987
9
1
5
8
3
7
4
6
2

按三個爲一組順序分組,第三組的2插入左邊排好的序列中,還是需要經過6次比較,性能並沒有很大的提升。

44987
9
1
5
8
3
7
4
6
2

但如果我們按下標一定的增量分組,則可以達到整個數組的基本有序。根據一個增量序列{increment1,increment2,...,1}\{increment1, increment2, ..., 1\}來分組排序,當增量爲1時則是一次直接插入排序。數組長度計算出最大增量(最大增量必須小於數組長度),這裏我們最大增量選length/2length/2,每次將增量變爲之前的一半。即增量序列爲{5,2,1}\{5,2,1\}

在這裏插入圖片描述
可以看到在第一次分組時,r[5]沒有進行排序,降低了效率。我們看另一種增列序列{4,2,1}\{4,2,1\},所有的元素在每一次分組都參與排序了,值得注意的是,我們的i是從increment+1開始循環,相當於把 i-increment看作是固定的,往左邊插入元素。
以increment=2爲例:
i=3時r[3]與r[1]比較,往前追溯發現沒有同組的(1下標減去增量後小於0),i++;
i=4時r[4]與r[2]比較,往前追溯發現沒有同組的(2下標減去增量後小於0),i++;
i=5時r[5]與r[3]比較,往前追溯同組的爲r[1](3下標減去增量後等於1),將r[5]往該排好的序列插,完成後i++;
以此類推。
在這裏插入圖片描述
可以看出代碼變動並不是很大,只是多加了一個循環,在插入排序的循環條件多加了j>0。這裏可以學到很多編程技巧。

C version

void InsertSort(Shell *L)
{
	int i,j;
	int increment=L->hength;
	do
	{
		increment=increment/2;
		for(i=increment+1;i<=L->length;i++) //從第increment+1位開始,循環至最後一位
		{
			if(L->r[i]<L->r[i-increment]) //如果該位在組內比左邊小,執行以下,將小數插至左邊
			{
				L->r[0]=L->r[i]; //利用r[0]存儲r[i]的值
				for(j=i-increment;L->r[j] > L->r[0] && j>0;j-=increment) //從i-1向左找一個可以插入r[i]的值,這個r[j]需要大於r[i]
				{
					L->r[j+increment]=L->r[j]; //將這個比r[i]大的值向右移一位
				}
				L->r[j+increment]=L->r[0]; //此時j的值小於等於r[i]=r[0],插入右邊
			}
		}
	}
	while(increment>1)
}

python version

def ShellSort(blist):
    length = len(blist)
    increment = length

    increment_list = []
    while increment > 1:
        increment /= 2
        increment_list.append(increment)

    for incre in increment_list:
        for i in range(incre, length):
            if blist[i]< blist[i - incre]:
                temp = blist[i]
                j = i - incre
                while (j >= 0) & (blist[j] > temp):
                    blist[j + incre] = blist[j]
                    j -= incre
                blist[j + incre] = temp
    print blist

2 選擇排序

2.1 簡單選擇排序

簡單選擇排序的思路是對每一個i,在餘下序列中找最小值,記錄下標,一次交換。

void SelectionSort()
{
	int i,j,min;
	for(i=1;i<L->length;i++)
	{
		min=i;
		for(j=i+1;j<=L->length;j++)
		{
			if(L->r[j]<L->r[min]) // 在餘下序列中找比當前存儲最小值還小的
			{
				min=j;
			}	
		}
		if(min!=i) // 經過查找後如果存在更小的,交換
		{
			swap(L,i,min);
		}
	}
}

2.2 快速排序

超級經典的排序算法,採用分治的思想,找一個數將數組分爲兩區,分區重複處理。
快排細分起來分爲左右指針法、挖坑法、前後指針法,都是採用的遞歸實現;同時還可以使用非遞歸實現,將遞歸變爲迭代。

臨時查找的一篇博客:快速排序算法—左右指針法,挖坑法,前後指針法,遞歸和非遞歸

有空的時候再慢慢總結。

3 交換排序

3.1 冒泡排序

C version

冒泡排序優化前後一共有三個常見版本,第一個版本的思路不是嚴格意義的冒泡排序,而是普通的交換排序,因爲不是兩兩比較相鄰元素完成交換,思路是把與當前元素比較最小的放到前面。。

void ExchangeSort(SqList *L)
{
	int i,j;
	for(i=1;i<L->length;i++) // 從1到length-1
	{
		for(j=i+1;j<=L->length;j++) // 從i+1到length,i與j兩兩比較,區別於冒泡的相鄰比較
		{
			if(L->r[j]<L->r[i])
			{
				swap(L,i,j);
			}
		}
	}
}

第二個版本則是我們正宗的冒泡排序,從前往後掃描,依次填入當前序列最小的數值,這個找最小數的過程是從數組尾往前循環,兩兩比較,直至i+1,看起來就像最小的數字往數組頭浮。

void BubbleSort(SqList *L)
{
	int i,j;
	for(i=1;i<L->length;i++) // 從1到length-1
	{
		for(j=L->length-1;j>=i;j--) // 從數組尾開始,因爲要把小的數放到前面,交換的方向就是“浮”
		{
			//這裏注意從length-1開始,我們需要將剩下最小的放到j=i處,如果從length開始則條件爲j>i,比較爲L->r[j]<L->r[j-1],交換爲swap(L,j,j+1)
			if(L->r[j]<L->r[j+1])
			{
				swap(L,j,j+1)
			}
		}
	}
}

第三個版本是優化版本,目的是在數組在完成某次交換後已經有序,但i還是會繼續循環,並對每個i尋找最小的值。如何告訴程序目前已經有序了?思路是設一個標記,當剩下的兩兩比較都是前小於後(完全有序)則告訴循環該停止了。

void BubbleSortOptimize(SqList *L)
{
	int i,j;
	int Check=1;
	// C 語言把任何非零和非空的值假定爲 true,把零或 null 假定爲 false
	for(i=1;i<L->length && Check;i++) // 從1到length-1,循環條件是i小於length且Check不爲0
	{
		Check=0; // 如果退出時還是0,則判斷爲false,循環結束
		for(j=L->length-1;j>=i;j--) 
		{
			if(L->r[j]<L->r[j+1])
			{
				swap(L,j,j+1)
				Check=1;
			}
		}
	}
}

python version

def BubbleSort(blist):
    length = len(blist)
    check = True
    n = 0
    for i in range(length - 1):
        for j in range(length - i - 1):
            n += 1
            if blist[length -1 - j] < blist[length - 2 - j]:
                blist[length -1 - j], blist[length - 2 - j] = blist[length - 2 - j], blist[length -1 - j]
    print blist

3.2 堆排序

4 歸併排序

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