1、冒泡排序
最初在學c語言時,老師就教的這個排序算法,原理比較簡單:從數組下標為0處開始遍歷,相鄰之間進行比較,若a[i]>a[i+1],則exchange(a[i],a[i+1]),當然也可以將小的往后傳遞,將此過程不斷進行,那么最后數組就有序了。
要點:(1)每遍歷一遍,末尾就得到一個最大值(或最小值),那么接下來的遍歷是不是每次都減少一個元素就好了,因為后邊的已經排好序了啊。
(2)遍歷n-1遍就排好序了,因為最后一遍只剩一個元素了,它一定放那兒,所以最后一遍就不用遍歷了。
當然如果數據小,又懶得優化,多進行幾遍也一樣可以排序的,比如這樣:
1 for(int i=0;i<n;i++){ //遍歷了n遍 2 3 for(int j=0;j<n-1;j++){ //每次比較當前元素和下一個元素,所以循環結束條件為n-1 4 5 { 6 7 if(a[j]>a[j+1]) 8 9 { int t=a[j]; 10 11 a[j]=a[j+1]; 12 13 a[j+1]=t; } 14 15 } 16 17 }
那么標准的冒泡排序,就是算法復雜度為n*(n-i)次也就是n^2如下:
1 for(int i=0;i<a.length-1;i++){ //遍歷n-1遍就夠了 2 3 for(int j=0;j<a.length-i-1;j++){ //每次比較當前元素和下一個元素,每遍歷一遍少比較一個元素,所以循環結束條件為n-i-1 4 5 { 6 7 if(a[j]>a[j+1]) 8 9 { int t=a[j]; 10 11 a[j]=a[j+1]; 12 13 a[j+1]=t; } 14 15 } 16 17 }
2、選擇排序
這個也是學c語言的時候和冒泡一起學的,它和冒泡算法復雜度一樣,原理為:從數組下標為0處開始遍歷,每次取出剩下元素的最大值(或最小值)放在當前位置,也就是說,第一次取最大的放第一個位置,第二次取次大的放第二個位置....執行n-1次就好了,最后一個只能放最后了。
標准選擇排序代碼如下:
1 for(int i=0;i<a.length-1;i++){ //遍歷n-1遍就夠了 2 3 int k=i; //k為剩下元素中的最大值下標,初始化為當前位置 4 5 for(int j=i+1;j<a.length-1;j++){ //找出剩下元素中的最大值下標 6 7 if(a[j]>a[k]) 8 9 k=j; 10 11 } 12 13 if(k!=i){ //若最大值不是當前位置的值,則交換,每次將最大值放前邊 14 15 int t=a[k]; 16 17 a[k]=a[i]; 18 19 a[i]=t; 20 21 } 22 23 }
3、插入排序
適用於較少元素時,效率比較高,原理:從數組下標為0處開始遍歷,每次保證已經遍歷的元素為有序序列,即每次將要遍歷的數插入到前邊數列的合適位置,保證已經遍歷的元素為有序數列。如:遍歷第一個元素,因為目前前邊只有這一個元素,所以它是有序的,遍歷第二個元素,和第一個元素比較,看它插在他前邊還是后邊,這樣就保證已經遍歷的元素都有序了,之后的類似,和前邊的比較,然后插入合適的位置。
插入排序代碼如下:
1 for(int i=1;i<a.length-1;i++){ //從下標1開始,一個元素的時候一定有序啊 2 3 int j=i-1; // j初始值為前一個元素,因為要將前邊的元素一一和當前元素比較 4 5 int k=a[i]; //記錄當前位置元素的值,因為如果后邊要移動的話會覆蓋的 6 7 while(j>0&&a[j]>k){ //將大於當前元素值的都依次向前移一位,好空出適合的位置給當前元素值 8 9 a[j+1]=a[j]; 10 11 j--; 12 13 } 14 15 a[j+1]=k; //因為j處元素值小於等於k (j處跳出循環的),所以k的適合位置為j+1 16 17 }
4、堆排序
要想理解堆排序,首先你要知道最大堆,要想理解最大堆,你得知道二叉樹。
二叉樹:每個節點最多有倆個孩子節點。
最大堆:父親節點的值總是大於孩子節點的值。
當然在這里二叉樹的存儲結構不是鏈表,是使用數組存的:(1)數組下標為0處是根節點。
(2)父親節點下標*2為左孩子下標,父親節點下標*2+1為右孩子下標。
根據這倆條准則我們就可以將二叉樹存在數組了。
堆排序原理:我們知道最大堆的性質(父親節點的值總是大於孩子節點的值),那么根節點處不就是當前數列的最大值嗎,那么我們每次取根節點的值放在末尾,然后將最大堆的大小-1,更新最大堆,取根節點放后邊.....不斷執行這個過程,直到最大堆中只剩一個元素,此時數組就是一個有序數組了。
根據原理可以看出我們需要編的操作有(1)建最大堆 (2)更新最大堆,其實建立最大堆就是不斷更新最大堆的過程,如果我們將每個結點都執行一遍更新最大堆操作(即父親節點的值總是大於孩子節點的值,不符合的話將父親節點與最大的孩子交換位置),當然執行順序必須是從下往上,然后只需從非葉子節點開始執行就好了(非葉子節點就是有孩子的結點)。
堆排序代碼如下:
1 //更新最大堆操作 2 3 void dfDui(int x) { //參數為父親節點下標 4 5 int lchild=x*2; //左孩子下標 6 7 int rchild=x*2+1; //右孩子下標 8 9 int max=x; //最大值下標初始為父親下標 10 11 if(lchild<size&&a[lchild]>a[max]) //比較找出最大值 12 13 max=lchild; 14 15 if(rchild<size&&a[rchild]>a[max]) 16 17 max=rchild; 18 19 if(max!=x){ //若父親節點為最大值,則符合性質,否則交換,將最大值移到父親節點處,然后因為孩子節點處已改變,更新此節點。 20 21 int t=a[max]; 22 23 a[max]=a[x]; 24 25 a[x]=t; 26 27 dfDui(max); 28 29 } 30 31 } 32 33 //建最大堆操作 34 35 void creatDui(){ 36 37 for(int i=a.length/2+1;i>=0;i--){ //葉子結點數為結點總數一半且都在最后(可以從孩子節點下標的算法為父親節點*2看出),因此 duDui(i); // a.length/2+1處開始為非葉子節點 38 39 } 40 41 } 42 43 //堆排序操作 44 45 void sort(){ 46 47 creatDui(); //建最大堆 48 49 for(int i=size-1;i>=1;i--){ //每次將第一個數與最后一個數交換,然后大小-1,更新已經改變的根節點 50 51 int t=a[0]; 52 53 a[0]=a[size-1]; 54 55 a[size-1]=t; 56 57 size--; 58 59 dfDui(0); 60 61 } 62 63 }
5、快速排序
快速排序是實際運用中用的最多的算法,雖然它在最壞的情況下會達到n^2,但它的平均性能非常好,期望時間復雜度為nlgn,而且隱含的常數因子非常小,並且是原址排序。
快速排序原理:從一組數中任意選出一個數,將大於它的數放右邊,小於它的數放左邊,然后再從左邊和右邊的倆組數中分別執行此操作,知道組中元素數為1,此時,數組就是有序的了。
從原理中可以清楚的看出此操作為遞歸操作,其代碼如下:
1 int partsort(int a[],int l,int r){ //將比a[r]小的元素放左邊,比它大的放右邊,最后把a[r]放中間 2 3 int i=l; //i為比a[r]大的元素的下標,初始為開始位置l 4 5 for(int j=l;j<r;j++){ 6 7 if(a[j]<=a[r]){ //如果元素比a[r]小則和大的元素交換位置,目的讓小的放一起,大的放一起 8 9 int t=a[j]; //可以自己手運行幾遍這個循環 10 11 a[j]=a[i]; 12 13 a[i]=t; 14 15 i++; 16 17 } 18 19 } 20 21 int t=a[i]; //a[i]為小元素和大元素的交界處,將a[r]與之交換 22 23 a[i]=a[r]; 24 25 a[r]=t; 26 27 return i; //返回交界處下標,繼續排前邊的和后邊的這倆組 28 29 } 30 31 void quicksort(int a[],int l,int r){ 32 33 if(l<r){ //遞歸結束條件為組中只剩一個元素 34 35 int p=partsort(a,l,r); //分成倆組返回交界處 36 37 quicksort(a,l,p-1); //繼續分左邊 38 39 quicksort(a,p+1,r); 40 41 } 42 43 }
主函數中調用代碼為,若數組大小為n,則:quicksort(a,0,n-1);
待續待續哈..........