文章目錄
本文記錄學習七種排序(內排序)的代碼,分別用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={
{44987,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 = [44987,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) - 1
for i in range(2, length + 1):
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:]
1.2 希爾排序
要點:先分組,每個組利用插入排序先排好序,每個組排好之後整體插入排序。
優點:先使得整個序列基本有序,大的數集中在後面,小的數集中在前面,在插入排序的時候就不需要數據過大的移動。
難點:怎麼分組效率最高?基本有序 ≠ 局部有序,如果是順序選取組,很有可能組內達到有序,但整體效率還是很低,如圖:
按三個爲一組順序分組,第三組的2插入左邊排好的序列中,還是需要經過6次比較,性能並沒有很大的提升。
但如果我們按下標一定的增量分組,則可以達到整個數組的基本有序。根據一個增量序列來分組排序,當增量爲1時則是一次直接插入排序。數組長度計算出最大增量(最大增量必須小於數組長度),這裏我們最大增量選,每次將增量變爲之前的一半。即增量序列爲
可以看到在第一次分組時,r[5]沒有進行排序,降低了效率。我們看另一種增列序列,所有的元素在每一次分組都參與排序了,值得注意的是,我們的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) - 1
increment = length / 2
while increment:
for i in range(increment + 1, length + 1):
if blist[i]< blist[i - increment]:
temp = blist[i]
j = i - increment
while (j >= 0) & (blist[j] > temp):
blist[j + increment] = blist[j]
j -= increment
blist[j + increment] = temp
increment = increment / 2
print blist[1:]
2 交換排序
2.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) - 1
check = True
for i in range(1, length):
check = False
for j in range(length - i):
if blist[length - j] < blist[length - 1 - j]:
blist[length - j], blist[length - 1 - j] = blist[length - 1 - j], blist[length - j]
check = True
if check == False:
break
print blist[1:]
2.2 快速排序
超級經典的排序算法,採用分治的思想,找一個數將數組分爲兩區,分區重複處理。
快排細分起來分爲左右指針法、挖坑法、前後指針法,都是採用的遞歸實現;同時還可以使用非遞歸實現,將遞歸變爲迭代。
臨時查找的一篇博客:快速排序算法—左右指針法,挖坑法,前後指針法,遞歸和非遞歸
C version
int Partition(SqList *,int front,int back)
{
int pivotkey;
pivotvalue=L->r[front];
while(front<back)
{
while(front<back && L->r[back]>=pivotvalue)
{
back--;
}
swap(L,front,back);
while(front<back && L->r[front]<=pivotvalue)
{
front++;
}
swap(L,front,back);
}
return front;
}
void Qsort(SqList *,int front,int back)
{
int i,pivot;
if(front<back)
{
pivot=Partition(L,front,back);
Qsort(L,front,pivot-1);
Qsort(L,pivoit+1,back);
}
}
void QuickSort(SqList *L)
{
Qsort(L,1,L->length);
}
Python version
def QuickSort(blist):
def Qsort(front, back):
print blist[1:]
while front < back:
pivot = Partition(front, back)
Qsort(front, pivot - 1)
front = pivot + 1
def Partition(front,back):
pivot_value = blist[front]
while front < back:
while (front < back) & (blist[back] >= pivot_value):
back -= 1
blist[front], blist[back] = blist[back], blist[front]
while (front < back) & (blist[front] <= pivot_value):
front += 1
blist[front], blist[back] = blist[back], blist[front]
return front
length = len(blist) - 1
Qsort(1, length)
print blist[1:]
3 選擇排序
3.1 簡單選擇排序
簡單選擇排序的思路是對每一個i,在餘下序列中找最小值,記錄下標,一次交換。
C version
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);
}
}
}
python version
def SelectionSort(blist):
length = len(blist) - 1
for i in range(1,length):
min_temp = i
for j in range(i + 1, length + 1):
if blist[j] <= blist[min_temp]:
min_temp = j
if min_temp != i:
blist[i], blist[min_temp] = blist[min_temp], blist[i]
print blist[1:]
3.2 堆排序
堆排序的思路分爲兩點:1. 把數組構造成一個大頂堆。2.將最大數和數組尾交換,調整餘下的堆。
C version
void HeapAdjust(SqList *L,int front,int back)
{
int i;
L->r[0]=L->r[front];
for(i=2*front;i<=back;i*=2) // front爲根節點下標,2*front是左孩子
{
if(i<back && L->r[i]<L->r[i+1]) // 先檢查當前下標是否節點,再看如果其右孩子比左孩子大,移到右孩子
{
++i;
}
if(L->r[i] <= L->r[0]) // 如果當前值不大於根節點,沒有比較的意義
{
break;
}
L->r[front]=L->r[i]; // 把當前最大的r[i]放到頭 ,看作位置空了
front=i; // 用front記錄空的位置,檢查該位置是否最大根,相當於把遞歸HeapAjust(L,i,length)變爲迭代
}
L->r[front]->L->r[0]; // 退出循環時front爲空,且沒有孩子
}
void Heap(SqList *l)
{
int i;
for(i=L->length/2;i>0;i--) // 把數組構造成一個最大堆
{
HeapAdjust(L,i,L->length);
}
for(i=L->length;i>1;i--) // 從最後一個數開始,換第一個最大數,直到根和其左孩子交換完畢
{
swap(L,1,i); // 每一次都是第一個數和當前數換(最後一個)
HeapAdjust(L,1,i-1); // 剩下的樹爲1到length-1
}
}
Python version
def HeapSort(blist):
def HeapAdjust(front, back):
temp = blist[front]
i = 2 * front
while i <= back:
if (i < back) & (blist[i] < blist[i + 1]):
i += 1
if blist[i] <= temp:
break
blist[front] = blist[i]
front = i
i = 2 * front
blist[i / 2] = temp
length = len(blist) - 1
for i in range(1, (length / 2) + 1)[::-1]:
HeapAdjust(i, length)
i = length
while i > 1:
blist[1], blist[i] = blist[i], blist[1]
i -= 1
HeapAdjust(1,i)
print blist[1:]
4 歸併排序
感覺歸併排序思路看起來簡單,但是實現起來比較難,思考遞歸的時候很容易陷入遞歸迷宮,就像這樣……
6
void Merge(int STree[],int NTree[],int front,int pivot,int back)
{
int i,j;
for(i=front,j=pivot+1;i<=pivot && j<=back;front++)
{
if(STree[i]<STree[j])
{
NTree[front]=STree[i++];
}
else
{
NTree[front]=STree[j++];
}
}
if(i<=pivot) //這裏是個坑,必須是小於等於,只要ij指針還在其中一個組裏,就說明還有值未放入。
{
while(i<=pivot)
{
NTree[front++]=STree[i++];
}
}
if(j<=back)
{
while(j<=back)
{
NTree[front++]=STree[j++];
}
}
}
void Msort(int OTree[],int NTree[],int front,int back)
{
// OTree, NTree, STree分別是oldtree, newtree, savetree
int pivot;
int STree[MAXSIZE+1];
if(front==back)
NTree[front]=OTree[front];
else
{
pivot=(front+back)/2;
Msort(OTree,STree,front,pivot);
Msort(OTree,STree,pivot+1,back);
Merge(STree,NTree,front,pivot,back);
}
}
void MergeSort(SqList *L)
{
Msort(L->r,L->r,1,L->length);
ListPrint(L,1);
}
打印了一下過程,可以看到每一次調用Msort,NTree和STree都會重置,第一次Msort接收的OTree和NTree都是L->r,前6次之所以進入Msort時是左枝一直在遞歸,Msort沒有完成時不會調用Merge,當發現最後兩個葉子節點(第5,6次),將兩個葉子節點存在STree裏,返回時再調用第一次Merge。
我這裏打印的是剛進入Msort的變量,實際上當 front: 1 back: 1 進入Msort的之後,接受的STree空間一直是之前的,在判斷front=back之後,才把OTree中front位的值賦給了STree的front位,退出遞歸之後下一步爲Merge,這裏傳的STree地址並沒有變。
結合棧的知識可能更好理解,但我現在也被自己繞暈了。
MSort: 1
Merge: 0
front: 9 back: 2
OTree: [9,1,5,8,3,7,4,6,2]
NTree: [9,1,5,8,3,7,4,6,2]
STree: [0,268501009,0,4203696,0,-2063292794,32763,12191544,0]
MSort: 2
Merge: 0
front: 9 back: 3
OTree: [9,1,5,8,3,7,4,6,2]
NTree: [0,268501009,0,4203696,0,-2063292794,32763,12191544,0]
STree: [0,38,0,12194608,0,0,0,12194608,0]
MSort: 3
Merge: 0
front: 9 back: 5
OTree: [9,1,5,8,3,7,4,6,2]
NTree: [0,38,0,12194608,0,0,0,12194608,0]
STree: [0,38,0,12194608,0,0,0,12194608,0]
MSort: 4
Merge: 0
front: 9 back: 1
OTree: [9,1,5,8,3,7,4,6,2]
NTree: [0,38,0,12194608,0,0,0,12194608,0]
STree: [0,38,0,12194608,0,0,0,12194608,0]
MSort: 5
Merge: 0
front: 9 back: 9
OTree: [9,1,5,8,3,7,4,6,2]
NTree: [0,38,0,12194608,0,0,0,12194608,0]
STree: [0,38,0,12194608,0,0,0,12194608,0]
MSort: 6
Merge: 0
front: 1 back: 1
OTree: [9,1,5,8,3,7,4,6,2]
NTree: [9,38,0,12194608,0,0,0,12194608,0]
STree: [0,38,0,12194608,0,0,0,12194608,0]
MSort: 7
Merge: 1
front: 5 back: 5
OTree: [9,1,5,8,3,7,4,6,2]
NTree: [1,9,0,12194608,0,0,0,12194608,0]
STree: [9,1,0,12194608,0,0,0,12194608,0]
MSort: 8
Merge: 2
front: 8 back: 3
OTree: [9,1,5,8,3,7,4,6,2]
NTree: [1,5,9,12194608,0,0,0,12194608,0]
STree: [1,9,5,12194608,0,0,0,12194608,0]
MSort: 9
Merge: 2
front: 8 back: 8
OTree: [9,1,5,8,3,7,4,6,2]
NTree: [1,9,5,12194608,0,0,0,12194608,0]
STree: [0,38,0,12194608,0,0,0,12194608,0]
MSort: 10
Merge: 2
front: 3 back: 3
OTree: [9,1,5,8,3,7,4,6,2]
NTree: [1,9,5,8,0,0,0,12194608,0]
STree: [0,38,0,12194608,0,0,0,12194608,0]
MSort: 11
Merge: 4
front: 7 back: 2
OTree: [9,1,5,8,3,7,4,6,2]
NTree: [1,3,5,8,9,-2063292794,32763,12191544,0]
STree: [1,5,9,3,8,0,0,12194608,0]
MSort: 12
Merge: 4
front: 7 back: 4
OTree: [9,1,5,8,3,7,4,6,2]
NTree: [1,5,9,3,8,0,0,12194608,0]
STree: [0,38,0,12194608,0,0,0,12194608,0]
MSort: 13
Merge: 4
front: 7 back: 7
OTree: [9,1,5,8,3,7,4,6,2]
NTree: [0,38,0,12194608,0,0,0,12194608,0]
STree: [0,38,0,12194608,0,0,0,12194608,0]
MSort: 14
Merge: 4
front: 4 back: 4
OTree: [9,1,5,8,3,7,4,6,2]
NTree: [0,38,0,12194608,0,7,0,12194608,0]
STree: [0,38,0,12194608,0,0,0,12194608,0]
MSort: 15
Merge: 5
front: 6 back: 2
OTree: [9,1,5,8,3,7,4,6,2]
NTree: [1,5,9,3,8,4,7,12194608,0]
STree: [0,38,0,12194608,0,7,4,12194608,0]
MSort: 16
Merge: 5
front: 6 back: 6
OTree: [9,1,5,8,3,7,4,6,2]
NTree: [0,38,0,12194608,0,7,4,12194608,0]
STree: [0,38,0,12194608,0,0,0,12194608,0]
MSort: 17
Merge: 5
front: 2 back: 2
OTree: [9,1,5,8,3,7,4,6,2]
NTree: [0,38,0,12194608,0,7,4,6,0]
STree: [0,38,0,12194608,0,0,0,12194608,0]
17,8[1]
Msort: 17, Merge: 8
Python version
def MergeSort(blist):
def Msort(front, back):
if front == back:
return [blist[front]]
else:
pivot = (front + back) / 2
left = Msort(front, pivot)
right = Msort(pivot + 1, back)
return Merge(left, right)
def Merge(left, right):
i, j = 0, 0
s_tree = []
while i < len(left) and j < len(right):
if left[i] < right[j]:
s_tree.append(left[i])
i += 1
else:
s_tree.append(right[j])
j += 1
if i < len(left):
s_tree.extend(left[i:])
if j < len(right):
s_tree.extend(right[j:])
return s_tree
length = len(blist) - 1
alist = Msort(1, length)
print alist