常见排序算法及实现
2019獨角獸企業(yè)重金招聘Python工程師標準>>>
一、冒泡排序
???? 冒泡排序(Bubble Sort)是先從數(shù)組第一個元素開始,依次比較相鄰兩個數(shù),若前者比后者
大,就將兩者交換位置,然后處理下一對,依此類推,不斷掃描數(shù)組,直到完成排序。
???? 這個算法的名字由來是因為越大的元素會經(jīng)由交換慢慢“浮”到數(shù)列的頂端,故名。
1、算法原理
???? 冒泡排序算法的運作如下:(從后往前)
??? 1)比較相鄰的元素。如果第一個比第二個大,就交換它們兩個。
??? 2)對每一對相鄰元素作同樣的工作,從開始第一對到結(jié)尾的最后一對。在這一點,最后的元
????????? 素應(yīng)該會是最大的數(shù)。
??? 3)針對所有的元素重復(fù)以上的步驟,除了最后一個。
??? 4)持續(xù)每次對越來越少的元素重復(fù)上面的步驟,知道沒有任何一對數(shù)字需要比較。
2、算法分析
??? 時間復(fù)雜度:
? ? 若文件的初始狀態(tài)是正序的,一趟掃描即可完成排序。所需的的關(guān)鍵字比較次數(shù)為最小n-1和
記錄移動次數(shù)均為最小0。所以最好的時間復(fù)雜度為O(n)。 ?
??? 若初始文件是反序的,需要進行趟排序。每趟排序要進行次關(guān)鍵字的比較(1≤i≤n-
1),且每次比較都必須移動記錄三次來達到交換記錄位置。在這種情況下,比較和移動次數(shù)均
達到最大值。所以最壞時間復(fù)雜度為O(n^2)。
???? 因此,冒泡排序總的平均時間復(fù)雜度為O(n^2)。
??? 算法穩(wěn)定性:
? (穩(wěn)定性的解釋:假定在待排序的記錄序列中,存在多個具有相同的關(guān)鍵字的記錄,若經(jīng)過排
序,這些記錄的相對次序保持不變,即在原序列中,ri=rj,且ri在rj之前,而在排序后的序列
中,ri仍在rj之前,則稱這種排序算法是穩(wěn)定的;否則稱為不穩(wěn)定的。)
??? 冒泡排序就是把小的元素往前調(diào)或者把大的元素往后調(diào)。比較是相鄰的兩個元素比較,交換
也發(fā)生在這兩個元素之間。所以,如果兩個元素相等,則不必交換;如果兩個相等的元素沒有相
鄰,那么即使通過前面的兩兩交換把兩個相鄰起來,這時候也不會交換,所以相同元素的前后
元素的前后順序并沒有改變,所以冒泡排序是一種穩(wěn)定排序算法。
3、算法實現(xiàn)
/*冒泡排序 */#include <iostream> #include <stdio.h>void bubble_sort( int *array, int length ) {if( array == NULL || length <= 0 ){printf( "invalid input.\n" );return;}int temp;for( int i = 0; i < length - 1; i++ )for( int j = 0; j < length - 1 - i; j++ ){if( array[ j ] > array[ j + 1 ] ){temp = array[ j ];array[ j ] = array[ j + 1 ];array[ j+ 1 ] = temp;}} }void Test( const char* testName, int *array, int length ) {if( testName == NULL ){printf( "test invaild input.\n" );return;}printf( "%s begins: \n", testName );bubble_sort( array, length );printf( "after bubble sort, the result is: \n" );for( int i = 0; i < length; i++ )printf( "%d ", array[ i ] );printf( "\n" ); }// 無重復(fù)數(shù)字 void Test1() {int length = 6;int array[ ] = { 4, 2, 6, 7, 1, 5 };Test( "Test1", array, length ); }// 有重復(fù)數(shù)字 void Test2() {int length = 6;int array[ ] = { 4, 2, 6, 2, 1, 5 };Test( "Test2", array, length ); }// 輸入數(shù)組為空 void Test3() {Test( "Test3", NULL, -1 ); }int main() {Test1();Test2();Test3();return 0; }?
二、選擇排序
??? 選擇排序(Selection Sort)簡單而低效。它線性逐一掃描數(shù)組元素,從中挑出最小的元素,
將它移動到最前面(也就是與最前面的元素交換)。然后,再次線性掃描數(shù)組,找到第二小的
元素,并移到前面,如此反復(fù),直到全部元素各歸其位。
?? 1、算法原理
??? n個記錄的文件的直接選擇排序可經(jīng)過n-1趟直接選擇排序得到有序結(jié)果:
?? 1)初始狀態(tài):無序區(qū)為R[1...n],有序區(qū)為空。
?? 2)第一趟排序
???????? 在無序區(qū)R[1...n]中選出關(guān)鍵字最小的記錄R[k],將它與無序區(qū)的第一個記錄R[1]交換,使
R[1...1]和R[2...n]分別變?yōu)橛涗泜€數(shù)增加1個的新有序區(qū)和記錄個數(shù)較少1個的新無序區(qū)。
?? ......
? 3)第i趟排序
??????? 第i趟排序開始時,當前有序區(qū)和無序區(qū)分別為R[1...i-1]和R[i...n]。該趟排序從當前無序區(qū)中
選出關(guān)鍵字最小的記錄R[k],將它與無序區(qū)第一個記錄R交換,使R[1...i]和R分別標為記錄個數(shù)
增加1個的新有序區(qū)和記錄個數(shù)較少1個的新無序區(qū)。
?? 2、算法分析
??? 時間復(fù)雜度:
??? 選擇排序的交換操作介于 0 和 (n - 1) 次之間。選擇排序的比較操作為 n (n - 1) / 2 次之間。
選擇排序的賦值操作介于 0 和 3 (n - 1) 次之間。比較次數(shù)O(n^2),比較次數(shù)與關(guān)鍵字的初始
狀態(tài)無關(guān),總的比較次數(shù)N=(n-1)+(n-2)+...+1=n*(n-1)/2。所以復(fù)雜度為O(n^2)。
?? 穩(wěn)定性:
?? 選擇排序是給每個位置選擇當前元素最小的,比如給第一個位置選擇最小的,在剩余元素里面
給第二個元素選擇第二小的,依次類推,直到第n-1個元素,第n個 元素不用選擇了,因為只剩
下它一個最大的元素了。那么,在一趟選擇,如果一個元素比當前元素小,而該小的元素又出
現(xiàn)在一個和當前元素相等的元素后面,那么 交換后穩(wěn)定性就被破壞了。舉個例子,序列5 8 5 2
9,我們知道第一遍選擇第1個元素5會和2交換,那么原序列中兩個5的相對前后順序就被破壞
了,所以選擇排序是一個不穩(wěn)定的排序算法。
3、算法實現(xiàn)
/*選擇排序。 */#include <iostream> #include <stdio.h>void SelectSort( int *array, int length ) {if( array == NULL || length <= 0 ){printf( "invaild input.\n" );return;}int index = 0;// 每次循環(huán)只進行一次交換,最多進行l(wèi)en - 1次循環(huán),比冒泡進行交換的次數(shù)少。for( int i = 0; i < length - 1; i++ ){// 第一次排序時,已經(jīng)進行一次大循環(huán),因此已經(jīng)排好了1個元素// 已排好序的元素0,...,i-2,i-1// 待排元素為i,i+1,...,length-1index = i;for( int j = i + 1; j < length; j++ ){if( array[ j ] < array[ index ] ){index = j;}}// 交換if( index != i ){int temp = array[ i ];array[ i ] = array[ index ];array[ index ] = temp;}} }void Test( const char *testName, int *array, int length ) {if( testName == NULL ){printf( "test invaild input.\n" );return;}printf( "%s begins: \n", testName );SelectSort( array, length );printf( "after select sort, the result is: \n" );for( int i = 0; i < length; i++ )printf( "%d ", array[ i ] );printf( "\n" ); }// 無重復(fù)數(shù)字 void Test1() {int length = 6;int array[ ] = { 4, 2, 6, 7, 1, 5 };Test( "Test1", array, length ); }// 有重復(fù)數(shù)字 void Test2() {int length = 6;int array[ ] = { 4, 2, 6, 2, 1, 5 };Test( "Test2", array, length ); }// 輸入數(shù)組為空 void Test3() {int array[ ] = {};Test( "Test3", array, 0 ); }// 輸入數(shù)組為null,且長度異常 void Test4() {Test( "Test4", NULL, -1 ); }int main() {Test1();Test2();Test3();Test4();return 0; }三、歸并排序? ? ?
? ? ? 歸并排序(Merge-Sort)是建立在歸并操作上的一種有效的排序算法,該算法是采用分治法
(Divide and Conquer)的一個非常典型的應(yīng)用。將數(shù)組分成兩半,這兩半分別排序后,再歸
并在一起。排序某一半時,繼續(xù)沿用同樣的排序算法,最終,將歸并兩個只含一個元素的數(shù)
組。
??? 1、算法原理
??? 歸并操作的工作原理如下:
?? 1)申請空間,使其大小為兩個已經(jīng)排序序列之和,該空間用來存放合并后的序列。
?? 2)設(shè)定兩個指針,最初位置分別為兩個已經(jīng)排序序列的起始位置。
?? 3)比較兩個指針所指向的元素,選擇相對小的元素放入到合并空間,并移動指針到下一位?
???????? 置。
?? 4)重復(fù)步驟3直到某一指針超出序列尾,將另一序列剩下的所有元素直接復(fù)制到合并序列
???????? 尾。
?? 2、算法分析
??? 時間復(fù)雜度:
??? 歸并排序的效率是比較高的,設(shè)數(shù)列長為N,將數(shù)列分開成小數(shù)列一共要logN步,每一步都
是一個合并有序數(shù)列的過程,時間復(fù)雜度可以記為O(N),故一共為O(N*logN)。
?? 穩(wěn)定性:
?? 歸并排序是穩(wěn)定的排序.即相等的元素的順序不會改變.如輸入記錄 1(1) 3(2) 2(3) 2(4) 5(5) (括
號中是記錄的關(guān)鍵字)時輸出的 1(1) 2(3) 2(4) 3(2) 5(5) 中的2 和 2 是按輸入的順序.這對要排序數(shù)
據(jù)包含多個信息而要按其中的某一個信息排序,要求其它信息盡量按輸入的順序排列時很重要.這
也是它比快速排序優(yōu)勢的地方。
? 3、算法實現(xiàn)
/*歸并排序。 */#include <iostream> #include <stdio.h>void merge( int *array, int low, int mid, int high ) {int nLeft = low; // nLeft是左邊序列的下標int nRight = mid + 1; // nRight是右邊序列的下標int nMerge = 0; // nMerge是臨時存放合并的下標int *tempArray = ( int* )new int[ high - low + 1 ]; // tempArray是臨時合并序列// 掃描左邊序列和右邊序列,直到一邊掃描結(jié)束while( nLeft <= mid && nRight <= high ){if( array[ nLeft ] <= array[ nRight ] ){tempArray[ nMerge ] = array[ nLeft ];nLeft++;nMerge++;}else{tempArray[ nMerge ] = array[ nRight ];nRight++;nMerge++;}}// 若左邊序列還沒掃描完,則將其全部賦值到臨時合并序列while( nLeft <= mid ){tempArray[ nMerge ] = array[ nLeft ];nLeft++;nMerge++;}// 若右邊序列還沒掃描完,則將其全部賦值到臨時合并序列while( nRight <= high ){tempArray[ nMerge ] = array[ nRight ];nRight++;nMerge++;}// 將臨時合并序列復(fù)制到原始序列中for( nMerge = 0, nLeft = low; nLeft <= high; nLeft++, nMerge++ ){array[ nLeft ] = tempArray[ nMerge ];}delete [] tempArray; }void mergeSort( int *array, int low, int high ) {if( array == NULL || low < 0 || high < 0 || low > high ){printf( "invaild input.\n" );return;}if( low < high ){int mid = ( low + high ) / 2 ;mergeSort( array, low, mid );mergeSort( array, mid + 1, high );merge( array, low, mid, high );} }void Test( const char* testName, int *array, int low, int high ) {if( testName == NULL ){printf( "test invaild input.\n" );return;}printf( "%s begins: \n", testName );mergeSort( array, low, high );printf( "after select sort, the result is: \n" );for( int i = 0; i < high - low + 1; i++ )printf( "%d ", array[ i ] );printf( "\n" ); }// 無重復(fù)數(shù)字 void Test1() {int length = 6;int array[ ] = { 4, 2, 6, 7, 1, 5 };Test( "Test1", array, 0, 5 ); }// 有重復(fù)數(shù)字 void Test2() {int length = 6;int array[ ] = { 4, 2, 6, 2, 1, 5 };Test( "Test2", array, 0, 5 ); }// 輸入數(shù)字只有一個元素 void Test3() {int array[ ] = { 100 };Test( "Test3", array, 0, 0 ); }// 輸入數(shù)組為空 void Test4() {int emptyArray[ ] = { };Test( "Test4", emptyArray, 0, 0 ); }// 輸入數(shù)組為null,且長度異常 void Test5() {Test( "Test5", NULL, -1, -1 ); }int main() {Test1();Test2();Test3();Test4();Test5();return 0; }?? (補充:1、當數(shù)組為空,low=0,high=0時,數(shù)組本應(yīng)沒有元素,但gcc編譯后最終會打印出
一個元素,這個元素經(jīng)多次測試是上次Test結(jié)果的最后一個元素。如果上次沒有執(zhí)行Test則會出
現(xiàn)如:-1220152147 之類的未初始化結(jié)果;? 2、當然對于數(shù)組指針為NULL的情況,打印就會出
現(xiàn)段錯誤)。
?? (繼續(xù)補充:好吧,沒按捺住,又去找了為啥是上次Test結(jié)果的最后一個元素,可以從上圖看
出,原因是在初始化emptyArray[]數(shù)組時(也就是:int emptyArray[ ] = { };這句代碼上),gcc每
次都分配0xbffff0cc地址給emptyArray[],而是這個地址正是上一次Test結(jié)果中的最后一個元素地
址,而且每次array[]不管有多少元素的最后一個元素地址都是0xbffff0cc,至于原因應(yīng)該跟具體
gcc數(shù)組內(nèi)存分配策略有關(guān),當然這里就不再跟下去了,要不然就沒飯吃了╮(╯▽╰)╭ )。
四、快速排序
??? 快速排序(Quicksort)是對冒泡跑序的一種改進。它的基本思想是:隨機選擇一個元素,對
數(shù)組進行分割,將所有比它小的元素排在前面,比它大的元素則排后面。這里的分割經(jīng)由一系
列元素交換的動作完成。然后再按此方法對這兩部分數(shù)據(jù)分別進行快速排序,整個排序過程可
以遞歸進行,以此達到整個數(shù)據(jù)變成有序序列。
??? 1、算法原理
??? 基本思想:
??? 1)先從數(shù)列中取出一個數(shù)作為基準數(shù);
? ? 2)分區(qū)過程,將比這個數(shù)大的數(shù)全放到它的右邊,小于或等于它的數(shù)全放到它的左邊;
? ? 3)再對左右區(qū)間重復(fù)第二步,直到各區(qū)間只有一個數(shù)。
??? 一趟快速排序的算法是:
?? 1)設(shè)置兩個變量i、j,排序開始的時侯:i=0,j=N-1;?
?? 2)以第一個數(shù)組元素作為關(guān)鍵數(shù)據(jù),賦值給key,即key=A[0];
?? 3)從j開始向前搜索,即由后開始向前搜索(j--),找到第一個小于key的值A(chǔ)[j],將A[j]和A[i]互
???????? 換;
?? 4)從i開始向后搜索,即由前開始向后搜索(i++),找到第一個大于key的A[i],將A[i]和A[j]互換;
?? 5)重復(fù)第3、4步,直到i=j。 (3,4步中,沒找到符合條件的值,即3中A[j]不小于key,4中A[i]
??????? 不大于key的時候改變j、i的值,使得j=j-1,i=i+1,直至找到為止。找到符合條件的值,進
??????? 行交換的時候i, j指針位置不變。另外,i==j這一過程一定正好是i+或j-完成的時候,此時令
??????? 循環(huán)結(jié)束)。
?? 快速排序還有很多改進版本,如隨機選擇基準數(shù),區(qū)間內(nèi)數(shù)據(jù)較少時直接用另外的方法排序以
減小遞歸深度。
?? 2、算法分析
??? 時間復(fù)雜度:
??? 快速排序之所比較快,因為相比冒泡排序,每次交換是跳躍式的。每次排序的時候設(shè)置一個
基準點,將小于等于基準點的數(shù)全部放到基準點的左邊,將大于等于基準 點的數(shù)全部放到基準
點的右邊。這樣在每次交換的時候就不會像冒泡排序一樣每次只能在相鄰的數(shù)之間進行交換,
交換的距離就大的多了。因此總的比較和交換次數(shù) 就少了,速度自然就提高了。當然在最壞的
情況下,仍可能是相鄰的兩個數(shù)進行了交換。因此快速排序的最差時間復(fù)雜度和冒泡排序是一
樣的都是O(N2),它的平均時間復(fù)雜度為O(NlogN)。
?? 穩(wěn)定性:
?? 快速排序有兩個方向,左邊的i下標一直往右走,當a[i] <= a[center_index],其中center_index
是中樞元素的數(shù)組下標,一般取為數(shù)組第0個元素。而右邊的j下標一直往左走,當a[j] >
a[center_index]。如果i和j都走不動了,i <= j, 交換a[i]和a[j],重復(fù)上面的過程,直到i>j。 交換a[j]
和a[center_index],完成一趟快速排序。在中樞元素和a[j]交換的時候,很有可能把前面的元素
的穩(wěn)定性打亂,比如序列為 5 3 3 4 3 8 9 10 11, 現(xiàn)在中樞元素5和3(第5個元素,下標從1開始
計)交換就會把元素3的穩(wěn)定性打亂,所以快速排序是一個不穩(wěn)定的排序算法,不穩(wěn)定發(fā)生在中
樞元素和a[j] 交換的時刻。
? 3、算法實現(xiàn)
? 第一種實現(xiàn)為選取第一個元素為樞紐:
/*快速排序。(以第一個元素作為基準) */#include <iostream> #include <stdio.h>void QuickSort( int *array, int low, int high ) {if( array == NULL || low < 0 ){printf( "invalid input.\n" );return;}if( high < 0 || low > high ) // 必須加這句,因為遞歸的時候high可能為-1{return;}int first = low;int last = high;int key = array[ first ]; // 用第一個元素作為作為樞紐while( first < last ){while( first < last && array[ last ] >= key ){--last;}array[ first ] = array[ last ]; // 將比樞紐元素小的移到低端while( first < last && array[ first ] <= key ){++first;}array[ last ] = array[ first ]; // 將比樞紐元素大的移到高端}array[ first ] = key; // 樞紐到位記錄QuickSort( array, low, first - 1 );QuickSort( array, first + 1, high );}void Test( const char* testName, int* array, int low, int high ) {if( testName == NULL ){printf( "test invaild input.\n" );return;}printf( "%s begins: \n", testName );QuickSort( array, low, high );printf( "after quick sort, the result is: \n" );for( int i = 0; i < high - low + 1; i++ )printf( "%d ", array[ i ] );printf( "\n" ); }// 無重復(fù)數(shù)字 void Test1() {int array[ ] = { 4, 2, 6, 7, 1, 5 };Test( "Test1", array, 0, 5 ); }// 有重復(fù)數(shù)字 void Test2() {int array[ ] = { 4, 2, 6, 2, 1, 5 };Test( "Test2", array, 0, 5 ); }// 輸入數(shù)字只有一個元素 void Test3() {int array[ ] = { 100 };Test( "Test3", array, 0, 0 ); }// 輸入數(shù)組為空 void Test4() {int emptyArray[ ] = { };Test( "Test4", emptyArray, 0, 0 ); }// 輸入數(shù)組為null,且長度異常 void Test5() {Test( "Test5", NULL, -1, -1 ); }int main() {Test1();Test2();Test3();Test4();Test5();return 0; }?
第二種實現(xiàn)為隨機選取元素為樞紐:???
/*快速排序。(以隨機元素作為基準) */#include <stdlib.h> #include <iostream> #include <time.h> #include <stdio.h>void Swap( int *first, int *second ) {int temp = *first;*first = *second;*second = temp; } int Partition( int *array, int low, int high ) {if( array == NULL || low < 0 ){printf( "invalid input.\n" );return -1;}srand( ( unsigned )time( NULL ) ); // 利用時間設(shè)置隨機數(shù)種子int index = low + rand()%( high - low + 1 ); // 產(chǎn)生low到high的隨機數(shù)Swap( &array[ index ], &array[ low ] );int first = low;int last = high;int key = array[ first ]; // 用第一個元素作為作為樞紐while( first < last ){while( first < last && array[ last ] >= key ){--last;}array[ first ] = array[ last ]; // 將比樞紐元素小的移到低端while( first < last && array[ first ] <= key ){++first;}array[ last ] = array[ first ]; // 將比樞紐元素大的移到高端}array[ first ] = key; // 樞紐到位記錄return first; }/* int Partition( int *array, int low, int high ) {if( array == NULL || low < 0 ){printf( "invalid input.\n" );return -1;}srand( ( unsigned )time( NULL ) ); // 利用時間設(shè)置隨機數(shù)種子int index = low + rand()%( high - low + 1 ); // 產(chǎn)生low到high的隨機數(shù)Swap( &array[ index ], &array[ low ] );int small = low - 1;for( index = low; index < high; ++index ){if( array[ index ] < array[ high ] ){++small;if( small != index )Swap( &array[ index ], &array[ small ] );}}++small;Swap( &array[ small ], &array[ high ] ); return small; } */void QuickSort( int *array, int low, int high ) {if( low == high ){return;}int index = Partition( array, low, high );if( index < 0 )return;if( index > low )QuickSort( array, low, index - 1 );if( index < high )QuickSort( array, index + 1, high ); }void Test( const char* testName, int* array, int low, int high ) {if( testName == NULL ){printf( "test invaild input.\n" );return;}printf( "%s begins: \n", testName );QuickSort( array, low, high );printf( "after quick sort, the result is: \n" );for( int i = 0; i < high - low + 1; i++ )printf( "%d ", array[ i ] );printf( "\n" ); }// 無重復(fù)數(shù)字 void Test1() {int array[ ] = { 4, 2, 6, 7, 1, 5 };Test( "Test1", array, 0, 5 ); }// 有重復(fù)數(shù)字 void Test2() {int array[ ] = { 4, 2, 6, 2, 1, 5 };Test( "Test2", array, 0, 5 ); }// 輸入數(shù)字只有一個元素 void Test3() {int array[ ] = { 100 };Test( "Test3", array, 0, 0 ); }// 輸入數(shù)組為空 void Test4() {int emptyArray[ ] = { };Test( "Test4", emptyArray, 0, 0 ); }// 輸入數(shù)組為null,且長度異常 void Test5() {Test( "Test5", NULL, -1, -1 ); }int main() {Test1();Test2();Test3();Test4();Test5();return 0; }?? 隨機樞紐快速排序的Partition函數(shù)有兩種實現(xiàn)。
五、堆排序
??? 堆排序(Heapsort)是指利用堆積樹(堆)這種數(shù)據(jù)結(jié)構(gòu)所設(shè)計的一種排序算法,它是選擇排序
的一種。可以利用數(shù)組的特點快速定位指定索引的元素。堆分為大根堆和小根堆,是完全二叉
樹。大根堆的要求是每個節(jié)點的值都不大于其父節(jié)點的值,即A[PARENT[i]] >= A[i]。在數(shù)組的
非降序排序中,需要使用的就是大根堆,因為根據(jù)大根堆的要求可知,最大的值一定在堆頂。
??? 堆實際上是一棵完全二叉樹,其任何一非葉節(jié)點滿足性質(zhì): Key[i]<=key[2i+1]&&Key[i]
<=key[2i+2]或者Key[i]>=Key[2i+1]&&key>=key[2i+2]即任何一非葉節(jié)點的關(guān)鍵字不大于或者不
小于其左右孩子節(jié)點的關(guān)鍵字。堆分為大頂堆和小頂堆,滿足Key[i]>=Key[2i+1]&&
key>=key[2i+2]稱為大頂堆,滿足 Key[i]<=key[2i+1]&&Key[i]<=key[2i+2]稱為小頂堆。由上述性
質(zhì)可知大頂堆的堆頂?shù)年P(guān)鍵 字肯定是所有關(guān)鍵字中最大的,小頂堆的堆頂?shù)年P(guān)鍵字是所有關(guān)鍵
字中最小的。
??? 1、算法原理
? ?? 其基本思想(大頂堆):
??? 1)將初始待排序關(guān)鍵字序列(R1,R2....Rn)構(gòu)建成大頂堆,此堆為初始的無序區(qū);
??? 2)將堆頂元素R[1]與最后一個元素R[n]交換,此時得到新的無序區(qū)(R1,R2,......Rn-1)和新的有
序區(qū)(Rn),且滿足R[1,2...n-1]<=R[n];?
?? 3)由于交換后新的堆頂R[1]可能違反堆的性質(zhì),因此需要對當前無序區(qū)(R1,R2,......Rn-1)調(diào)整
為新堆,然后再次將R[1]與無序區(qū)最后一 個元素交換,得到新的無序區(qū)(R1,R2....Rn-2)和新的有
序區(qū)(Rn-1,Rn)。不斷重復(fù)此過程直到有序區(qū)的元素個數(shù)為n-1,則整個排序過 程完成。
???? 操作過程如下:
??? 1)初始化堆:將R[1..n]構(gòu)造為堆;
??? 2)將當前無序區(qū)的堆頂元素R[1]同該區(qū)間的最后一個記錄交換,然后將新的無序區(qū)調(diào)整為新
的堆。
?? 2、算法分析
??? 時間復(fù)雜度:
??? 堆排序其實也是一種選擇排序,是一種樹形選擇排序。只不過直接選擇排序中,為了從
R[1...n]中選擇最大記錄,需比較n-1次,然后 從R[1...n-2]中選擇最大記錄需比較n-2次。事實上
這n-2次比較中有很多已經(jīng)在前面的n-1次比較中已經(jīng)做過,而樹形選擇排序恰好利用樹形的 特
點保存了部分前面的比較結(jié)果,因此可以減少比較次數(shù)。對于n個關(guān)鍵字序列,最壞情況下每個
節(jié)點需比較log2(n)次,因此其最壞情況下時間復(fù)雜度為 O(nlogn)。
?? 穩(wěn)定性:
?? 我們知道堆的結(jié)構(gòu)是節(jié)點i的孩子為2*i和2*i+1節(jié)點,大頂堆要求父節(jié)點大于等于其2個子節(jié)
點,小頂堆要求父節(jié)點小于等于其2個子節(jié)點。在一個長為n 的序列,堆排序的過程是從第n/2開
始和其子節(jié)點共3個值選擇最大(大頂堆)或者最小(小頂堆),這3個元素之間的選擇當然不會破壞穩(wěn)
定性。但當為n /2-1, n/2-2, ...1這些個父節(jié)點選擇元素時,就會破壞穩(wěn)定性。有可能第n/2個父節(jié)
點交換把后面一個元素交換過去了,而第n/2-1個父節(jié)點把后面一個相同的元素沒 有交換,那么
這2個相同的元素之間的穩(wěn)定性就被破壞了。所以,堆排序不是穩(wěn)定的排序算法。不適合記錄較
少的排序。???
? 3、算法實現(xiàn)
/*堆排序。 */#include <iostream> #include <stdio.h>// array是待調(diào)整的堆數(shù)組,pos是待調(diào)整的數(shù)組元素的位置,length是數(shù)組的長度 // 本函數(shù)功能是:根據(jù)數(shù)組array構(gòu)建大根堆 void HeapAdjust( int *array, int pos, int length ) {if( array == NULL || pos < 0 || length <= 0 ){printf( "invalid input.\n" );return;}int child;int temp;for( ; 2 * pos + 1 < length; pos = child ){child = 2 * pos + 1; // 子結(jié)點的位置if( child < length - 1 && array[ child + 1 ] > array[ child ] ) // 得到子結(jié)點中較大的結(jié)點++child;if( array[ pos ] < array[ child ] ) // 如果較大的子結(jié)點大于父結(jié)點那么把較大的子結(jié)點往上移動,替換它的父結(jié)點{temp = array[ pos ];array[ pos ] = array[ child ];array[ child ] = temp;}else break;} }void HeapSort( int *array, int length ) {int i;// 調(diào)整序列的前半部分元素,調(diào)整完之后第一個元素是序列的最大的元素// length/2-1是最后一個非葉節(jié)點for( i = length / 2 - 1; i >= 0; --i )HeapAdjust( array, i, length );// 從最后一個元素開始對序列進行調(diào)整,不斷的縮小調(diào)整的范圍直到第一個元素for( i = length - 1; i > 0; --i ){// 把第一個元素和當前的最后一個元素交換,// 保證當前的最后一個位置的元素都是在現(xiàn)在的這個序列之中最大的array[ i ] = array[ 0 ] ^ array[ i ];array[ 0 ] = array[ 0 ] ^ array[ i ];array[ i ] = array[ 0 ] ^ array[ i ];// 不斷縮小調(diào)整heap的范圍,每一次調(diào)整完畢保證第一個元素是當前序列的最大值HeapAdjust( array, 0, i );} }void Test( const char* testName, int* array, int length ) {if( testName == NULL ){printf( "test invaild input.\n" );return;}printf( "%s begins: \n", testName );HeapSort( array, length );printf( "after heap sort, the result is: \n" );for( int i = 0; i < length; i++ )printf( "%d ", array[ i ] );printf( "\n" ); }// 無重復(fù)數(shù)字 void Test1() {int array[ ] = { 4, 2, 6, 7, 1, 5 };Test( "Test1", array, 6 ); }// 有重復(fù)數(shù)字 void Test2() {int array[ ] = { 4, 2, 6, 2, 1, 5 };Test( "Test2", array, 6 ); }// 輸入數(shù)字只有一個元素 void Test3() {int array[ ] = { 100 };Test( "Test3", array, 1 ); }// 輸入數(shù)組為空 void Test4() {int emptyArray[ ] = { };Test( "Test4", emptyArray, 0 ); }// 輸入數(shù)組為null,且長度異常 void Test5() {Test( "Test5", NULL, -1 ); }int main() {Test1();Test2();Test3();Test4();Test5();return 0; }六、基數(shù)排序
??? 基數(shù)排序(Radix sort)屬于“分配式排序”(distribution sort),又稱“桶子法”(bucket sort)
或bin sort,顧名思義,它是透過鍵值的部分資訊,將要排序的元素分配至某些“桶”中,藉以達
到排序的作用。
?? 1、算法原理
?? 1)首先根據(jù)個位數(shù)的數(shù)值,在走訪數(shù)值時將它們分配至編號0到9的桶子中,接下來將這些桶
子中的數(shù)值重新串接起來;
?? 2)接著再進行一次分配,這次是根據(jù)十位數(shù)來分配,接下來將這些桶子中的數(shù)值重新串接起
來,成為以下的數(shù)列;
?????? 這時候整個數(shù)列已經(jīng)排序完畢;如果排序的對象有三位數(shù)以上,則持續(xù)進行以上的動作直至
最高位數(shù)為止。
????? 最高位優(yōu)先(Most Significant Digit first)法,簡稱MSD法:先按k1排序分組,同一組中記錄,
關(guān)鍵碼k1相等,再對各組按k2排序分成子組,之后,對后面的關(guān)鍵碼繼續(xù)這樣的排序分 組,直
到按最次位關(guān)鍵碼kd對各子組排序后。再將各組連接起來,便得到一個有序序列。
???? 最低位優(yōu)先(Least Significant Digit first)法,簡稱LSD法:先從kd開始排序,再對kd-1進行排
序,依次重復(fù),直到對k1排序后便得到一個有序序列。
?? 2、算法分析
??? 時間復(fù)雜度:
??? 設(shè)待排序列為n個記錄,d個關(guān)鍵碼,關(guān)鍵碼的取值范圍為radix,則進行鏈式基數(shù)排序的時間
復(fù)雜度為O(d(n+radix)),其中,一趟分配時間復(fù)雜度為O(n),一趟收集時間復(fù)雜度為O(radix),
共進行d趟分配和收集。其時間復(fù)雜度為O (nlog(r)m),其中r為所采取的基數(shù),而m為堆數(shù)。
?? 穩(wěn)定性:
?? 基數(shù)排序是按照低位先排序,然后收集;再按照高位排序,然后再收集;依次類推,直到最高
位。有時候有些屬性是有優(yōu)先級順序的,先按低優(yōu)先級排序,再按高優(yōu) 先級排序,最后的次序
就是高優(yōu)先級高的在前,高優(yōu)先級相同的低優(yōu)先級高的在前。基數(shù)排序基于分別排序,分別收
集,所以其是穩(wěn)定的排序算法。??
??? 3、算法實現(xiàn)
/*基數(shù)排序。 */#include <iostream> #include <stdio.h>// 輔助函數(shù),求數(shù)據(jù)的最大位數(shù) int MaxBit( int *array, int n ) {int maxbit = 1; // 保存最大的位數(shù)int div = 10;for( int i = 0; i < n; ++i ){while( array[ i ] >= div ){div *= 10;++maxbit;}}return maxbit; }void RadixSort( int *array, int n ) {if( array == NULL || n < 0 ){printf( "invalid input.\n" );return;}int maxbit = MaxBit( array, n );int *temp = new int[ n ];int *count = new int[ 10 ]; // 計數(shù)器int radix = 1;int i, j, k;for( i = 1; i <= maxbit; i++ ) //進行maxbit次排序{for( j = 0; j < 10; j++ )count[ j ] = 0; // 每次分配前清空計數(shù)器for( j = 0; j < n; j++ ){k = ( array[ j ] / radix ) % 10; // 統(tǒng)計每個桶中的記錄數(shù)count[ k ]++;}for( j = 1; j < 10; j++ )count[ j ] = count[ j - 1 ] + count[ j ]; // 將tmp中的位置依次分配給每個桶for( j = n - 1; j >= 0; j-- ) // 將所有桶中記錄依次收集到tmp中{k = ( array[ j ] / radix ) % 10;temp[ count[ k ] - 1 ] = array[ j ];count[ k ]--;}for( j = 0; j < n; j++ ) // 將臨時數(shù)組的內(nèi)容復(fù)制到data中array[ j ] = temp[ j ];radix = radix * 10;}delete [] temp;delete [] count; }void Test( const char* testName, int* array, int n ) {if( testName == NULL ){printf( "test invaild input.\n" );return;}printf( "%s begins: \n", testName );RadixSort( array, n );printf( "after radix sort, the result is: \n" );for( int i = 0; i < n; i++ )printf( "%d ", array[ i ] );printf( "\n" ); }// 無重復(fù)數(shù)字 void Test1() {int array[ ] = { 42, 24, 61, 71, 11, 57 };Test( "Test1", array, 6 ); }// 有重復(fù)數(shù)字 void Test2() {int array[ ] = { 421, 24, 6,421, 121, 54 };Test( "Test2", array, 6 ); }// 輸入數(shù)字只有一個元素 void Test3() {int array[ ] = { 100 };Test( "Test3", array, 1 ); }// 輸入數(shù)組為空 void Test4() {int emptyArray[ ] = { };Test( "Test4", emptyArray, 0 ); }// 輸入數(shù)組為null,且長度異常 void Test5() {Test( "Test5", NULL, -1 ); }int main() {Test1();Test2();Test3();Test4();Test5();return 0; }轉(zhuǎn)載于:https://my.oschina.net/u/2537915/blog/724277
總結(jié)
- 上一篇: 在linux查看内存的大小
- 下一篇: MyBatis学习总结(16)——Myb