以整数数组递增排序为例:
1. 选择排序 selection sort
从第一个元素开始,在全数组里寻找最小的数,和第一个元素交换;
前进至第二个元素,在第二个元素为起始的余下数组中寻找最小的数,和第二个元素交换;
可以递归实现:
SelectionSortRecursion( data, start )
{
if( start < data.length - 1 )
{
swap( data, start, findMinimumIndex(data, start) );
selectionSortRecursion( data, start+1 );
}
}
也可循坏实现。
(n-1)+(n-2)+ . . . + 1 =》n( n -1 )/2 => O(n^2) in best, average, worst cases;
特点:数组初始顺序对于比较次数没有影响。选择排序是in-place排序,即不需辅助存储空间。通常的选择排序实现都是非稳定的(not stable),即值相同的元素的初始顺序可能会被改变。
优点:最多只需n-1次交换操作,在移动数组元素需要很大代价,而比较代价很小的情况下,选择排序可能会比其他排序算法表现更好。
2. 插入排序 Insersion sort
第二个元素和第一个元素比较,若小于第一个元素,插入至第一个元素前面,若大,不变换两个元素位置。得到排好序的两个元素。第三个元素和前两个元素比较,插入到正确的位置,以此类推,直至最后一个元素。
InserstionSort( data )
{
for( pos = 1; pos < data.length; pos++)
{
current = data[pos];
for( i = 0; i < pos; i++ )
{
if( data[i] > current)
{
//将从 i 开始的 current - i 个元素向数组后方移动一个位置
arrayCopy( data, i, data, i+1, pos - i);
data[i] = current;
break;
}
}
}
}
best-case running time is O(n) when the original array is sorted。这意味着插入排序对于在已排序数组中插入新元素并排序效果较好。然而,插入排序的average和worst均是O(n^2)这意味着当n比较大且初始数组为随机无序状态时,插入排序的性能不能保证。
特点:插入排序是稳定的, in-place排序算法,对于排序小规模数组效果较好。插入排序经常被当做子模块(building block)用于构建更复杂的排序算法。
3. 快速排序
快速排序属于divide-and-conquer算法范畴,基本流程是:
选择pivot(可以随机也可以指定某一固定位置)-> 根据数组元素与pivot的值的比较(大于等于 或 小于),将数组分为左右两部分。
递归上述流程直到只剩一个元素无法分割。
Literate Programming 上的一种实现:
struct pivot_random { pivot_random() { srand(static_cast<unsigned int>(time(0))); } int operator() (int low, int high) { return low + (rand()%(high-low)); } }; int Partition(int* input, int low, int high, int pivot) { swap(input[low], input[pivot]); pivot = low; while(low <= high) { while(input[high] >= input[pivot] && high >= low) high--; if( high < low ) break; else { swap(input[high], input[pivot]); pivot = high--; } while(input[low] <= input[pivot] && high >= low) low++; if( low > high ) break; else { swap(input[low], input[pivot]); pivot= low++; } } return pivot; } void QuickSort(int* input, int low, int high, pivot_random pf) { if(high > low) { int pivot = pf(low, high); pivot = Partition(input, low, high, pivot); QuickSort(input, low, pivot-1, pf); QuickSort(input, pivot+1, high, pf); } return; }
4. 归并排序 Merge sort
归并排序也是divide-and-conquer分治算法的一种。
void Merge(int* input, int low, int high) { int* temp = new int[high-low+1]; int start1 = low, end1 = (low+high)/2, start2 = (low+high)/2+1, end2 = high; int i; for(i = 0; start1 <= end1 && start2 <= end2; i++) { if(input[start1] <= input[start2]) temp[i] = input[start1++]; else temp[i] = input[start2++]; } while(start1 <= end1) temp[i++] = input[start1++]; while(start2 <= end2) temp[i++] = input[start2++]; i--; while( i>= 0 ) { input[i+low] = temp[i]; i--; } } void MergeSort(int* input, int low, int high) { if(low < high) { MergeSort(input, low, (low+high)/2); MergeSort(input, (low+high)/2+1, high); Merge(input, low, high); } }归并排序在划分子数组时,若子数组的大小小于一定值时,如10,可以直接调用其他排序算法,如插入排序:
if( high - low < 10 )
{
InsertionSort(data, low, high);
return data;
}
这是对归并排序常用的优化方法,因为插入排序的overhead比归并排序要小,而且插入排序在小规模数组上效果较好。
归并排序对于数据量大到无法完全装入内存的数组是个很好的选择。在通常情况下,将大文件分割成许多小文件,每一个小文件都可以装入内存,排好序后再写回文件,然后调用Merge流程,输入来自各个排好序的小文件的一部分,输出直接写到最终的文件。
归并排序的best,average,worst均是O(nlog(n)),可以保证性能的上界,但是归并排序需要额外的O(n)的存储空间,远大于其他排序算法堆空间的要求。通常的归并排序算法实现是stable的但不是in-place的。