排序概述
排序用途廣泛,比如爲數據庫查詢結果按時間排序,最小生成樹算法中對邊按權重排序,揹包問題中對物品按大小排序等等。排序算法有很多,本文主要記錄了冒泡排序、插入排序、快速排序、選擇排序、堆排序、歸併排序等幾種比較流行的算法。
冒泡排序
//冒泡排序,對數組做n-1趟掃描,每一趟把未就位的元素中的最大的元素
//放到他正確的位置上,每一趟掃描從輸入數組第一個元素開始,依次與
//它後一個元素比較,如果大於後一元素就交換兩者,無論交換與否,在
//這一趟的最大元素到達他應該在的位置之前,從原來後一元素所在的位
//置開始繼續這一趟掃描
void BufferSort(ValueType values[], int start, int end)
{
if (check_array_range(start, end, end - start + 1))
{
//i從後起,最大的元素放到最後一個位置嘛
for (int i = end; i > start; --i)
{
//j < i,這就是“這一趟的最大元素到達他應
//該在的位置之前”的意思
for (int j = start; j < i; ++j)
{
if (comp(values[j + 1], values[j]))
{
swap(values[j + 1], values[j]);
}
}
}
}
}
插入排序
//第一個元素是有序的,如果第二個元素小於第一個元素,交換二者的位置,
//此時前面的兩個元素是有序的,第三個元素看做是一個插入前面兩個元素
//組成的有序表中。每個元素都看做是將其插入到
//它前面序列中,先將前面大於帶插入元素的元素後移一個位置,
//然後將待插入元素放到空出來的位置上即可。實際上這個過
//程可以看成是以間隔爲1的一趟shell排序
void InsertSort(ValueType values[], int start, int end)
{
if (check_array_range(start, end, end - start + 1))
{
// gap = 1的一趟shell排序
shell_sort_once(values, start, end, 1);
}
}
二分插入排序
//在簡單插入排序中每個元素需要從後往前順序在他前面的有序表中尋找自己的
//位置,把這個查找過程使用二分查找,就形成了二分插入排序,假設一個元素
//B應該放在元素A的位置後,而A有多個值,那麼應該查找最後一個A的位置,然
//後把B放到這個A的位置之後
void BinaryInsertSort(ValueType values[], int start, int end)
{
if (check_array_range(start, end, end - start + 1))
{
for (int i = start + 1; i <= end; ++i)
{
ValueType temp = values[i];
int low = start, high = i - 1, mid = (low + high) / 2;
while (low <= high)
{
if (comp(temp, values[mid])) high = mid - 1;
else low = mid + 1;
mid = (low + high) / 2;
}
for (int k = i; k > low; --k) values[k] = values[k - 1];
values[low] = temp;
}
}
}
shell排序
//選定一個間隔gap,按間隔將待排序數分爲n/gap組,然後
//運行一趟shell排序,完成後減小間隔,再運行一次shell排序。
//不斷重複這個過程,直到gap遞減爲1,以間隔爲1運行最後一次
//shell排序
void ShellSort(ValueType values[], int start, int end)
{
if (check_array_range(start, end, end - start + 1))
{
int gap = end - start + 1;
do
{
gap = gap / 3 + 1;
shell_sort_once(values, start, end, gap);
} while (gap > 1);
}
}
//一趟希爾排序。取定一個間隔gap,則原數組中每隔gap個數的數分爲一組,
//例如,原數組爲12 11 10 9 8 7 6 5 4 3 2 1,gap爲3,則12 9 6 3爲一組,
//11 8 5 2爲一組,10 7 4 1爲一組。然後對每一組進行插入排序。插入排序
//是從第二個數起,按順序將它插入到前面序列中的合適位置,以12 9 6 7爲例,
//9小於12,所以先將9,複製到一個臨時變量中,然後將12後移到9所在的位置,
//再將9複製到12原來的位置上,這樣9 12就排好序了。6比前面兩個數都小,先將
//6複製到臨時變量中,然後將9 12都後移一個位置,再將6放到9所在的位置,7小於9和
//12,大於6,所以將9和12向後移一個位置,把7插入到空出來的位置上,於是排序完成
void shell_sort_once(ValueType values[], int start, int end, int gap)
{
for (int i = start + gap; i <= end; ++i) //交叉進行
{
if (comp(values[i], values[i - gap]))
{
ValueType temp = values[i];
int j = i - gap;
do
{
values[j + gap] = values[j];
j -= gap;
} while (j >= start && comp(temp, values[j]));
values[j + gap] = temp;
}
}
}
快速排序
//以數組中的某個元素作爲樞紐,將小於樞紐的元素放到樞紐的左邊,
//大於樞紐的元素都放到樞紐的右邊,這個過程叫做劃分,然後在樞紐
//左邊的序列和樞紐左邊的序列中不斷劃分,直到劃分序列中只含有不
//多於一個元素,劃分結束後,排序也就完成了,快排的關鍵就是劃分,
//根據不同的劃分方式,就得到了不同版本的快排程序
void QuickSort(ValueType values[], int start, int end)
{
if (check_array_range(start, end, end - start + 1))
{
quick_sort(values, start, end);
}
}
void RandomizedQuickSort(ValueType values[], int start, int end)
{
if (check_array_range(start, end, end - start + 1))
{
randomized_qsort(values, start, end);
}
}
void Median3QuickSort(ValueType values[], int start, int end)
{
if (check_array_range(start, end, end - start + 1))
{
median3_qsort(values, start, end);
}
}
//當一個劃分序列元素個數爲5~25時,繼續使用劃分遞歸地實現劃分序列的排序往往
//沒有使用直接插入排序快,所以就可以把插入排序與快排結合起來加快排序速度
void HybridQuickSort(ValueType values[], int start, int end,int m)
{
if (check_array_range(start, end, end - start + 1))
{
hybrid_qsort(values, start, end, m);
}
}
//有許多輸入序列重複元素很多,極端例子是序列中全部元素都一樣,
//這時候再用原來的快排算法性能就太差了。針對這種情況,可以把與
//樞紐元素一樣大的元素都聚到中間,而在左邊放置小於樞紐元素的元素,
//右邊放置大於樞紐元素的元素,然後再左右兩個子序列中快排,這樣的
//快排算法叫做三路快排。三路快排關鍵是三路劃分。
void ThreeWayQuickSort(ValueType values[], int start, int end)
{
if (check_array_range(start, end, end - start + 1))
{
three_way_qsort(values, start, end);
}
}
//一趟劃分算法
int partition(ValueType values[], int start, int end)
{
//以第一個元素作爲樞紐,從數組兩頭向中間掃描
ValueType pivot = values[start];
while (start < end)
{
//先從後往前掃描,直到遇到一個小於樞紐的元素
while (start < end && !comp(values[end], pivot)) --end;
//把這個小於樞紐的元素放到start指向的位置上,
//這是合理的,因爲這個元素小於樞紐,要放到
//樞紐最後的位置之前
values[start] = values[end];
//此時前指針指向一個小於樞紐的元素,所以下一個循環總是能推進前指針
//從前往後掃描,直到前指針遇到一個大於樞紐的元素
while (start < end && !comp(pivot, values[start])) ++start;
//剛纔後指針指向了一個小於樞紐的元素,這個元素放到了前指針推進
//之前的位置上,前指針推進後,現在指向了一個大於樞紐的元素,
//正好把它放到後指針指向的位置上
values[end] = values[start];
//此時,前指針指向的元素是一個大於樞紐的元素,但是這個元素是無效的,
//如果start還沒有遇到end,那麼外層循環會繼續,後指針也許就能找到
//一個小於樞紐的元素,放到start位置上,如果後指針沒有找到這樣
//一個元素,那麼start會等於end,於是start不再推進,會
//被自己的值賦值一次,不過這個位置上的元素還是無效的,外層循環結束
}
//外層循環結束後,start位置的值是無效的,它前面的值小於或等於樞紐,
//後面的值大於或等於樞紐,於是把樞紐放到start位置上,就完成了一次劃分
values[start] = pivot;
//start位置就是樞紐最後所在的位置
return start;
}
//在劃分區間隨機選一個數作爲樞紐,把它和第一個位置的元素交換,
//再調用partition過程
int randomized_partition(ValueType values[], int start, int end)
{
if (end == start) return start;
if (end < start) return -1;
srand(static_cast<unsigned>(time(nullptr)));
int i = rand();
i = static_cast<int>((i * 1.0 / RAND_MAX) * (end - start) + start);
swap(values[i], values[start]);
return partition(values, start, end);
}
//取區間首、尾、中間三個元素,求三個元素的中間值,然後把它和第一個
//位置的元素交換,再調用partition過程
int median3_partition(ValueType values[], int start, int end)
{
int mid = (start + end) / 2;
int k = mid;
if (comp(values[k], values[start]) && comp(values[start],values[end])) k = start;
else if (comp(values[start], values[k]) && comp(values[end], values[start])) k = end;
if (k != start) swap(values[k], values[start]);
return partition(values, start, end);
}
//將區間劃分爲三段,前段是小於樞紐的元素,中間是與樞紐相等的元素,後端是
//大於樞紐的元素.運行partition過程,有點區別就是如果前指針遇到一個與樞紐
//相等的元素時,把它放到區間前段,後指針遇到一個與樞紐相等的元素是把它放
//後段。partition過程運行完後,區間的排列如下(以6爲樞紐舉例):666234567899666,
//注意中間有一個樞紐,然後再將兩邊的樞紐交換到中間來
void three_way_partition(ValueType values[], int start, int end, int &left_pivot, int &right_pivot)
{
ValueType pivot = values[start];
int i = start,p = start,q = end,j = end;
while (i < j)
{
while (i < j)
{
if (comp(pivot, values[j])) --j;
else if (values[j] == pivot) swap(values[q--], values[j--]);
else break;
}
values[i] = values[j];
while (i < j )
{
if (comp(values[i], pivot)) ++i;
else if (values[i] == pivot) swap(values[p++], values[i++]);
else break;
}
values[j] = values[i];
}
values[i] = pivot;
j = i + 1;
i -= 1;
while (i > start && comp(values[i],values[start])) swap(values[i++], values[start++]);
while (j < end && comp(values[end], values[j])) swap(values[j++], values[end--]);
left_pivot = i;
right_pivot = j;
}
選擇排序
//先在輸入數組中選擇最小的值,如果它不是第一個元素,就把它與第一個位置上
//的元素交換,在剩餘的子數組繼續這個過程,直到倒數第二個元素。這個過程就是
//先選擇最小的元素,然後選擇第二小的元素……因此叫做選擇排序
void SelectSort(ValueType values[], int start, int end)
{
if (check_array_range(start, end, end - start + 1))
{
select_sort(values, start, end);
}
}
void select_sort(ValueType values[], int start, int end)
{
for (int i = start; i < end; ++i)
{
int k = i;
ValueType val = values[k];
for (int j = i; j < end; ++j)
{
if (comp(values[j + 1],val))
{
val = values[j + 1];
k = j + 1;
}
}
if (k != i) swap(values[k], values[i]);
}
}
堆排序
//堆排序也算是一種選擇排序,只是利用堆的性質加速了每一趟選擇最小
//元素的過程。因爲最小堆的第一個元素是最小的,所以先在整個輸入數組
//中建堆,則最小的元素被放到了第一個位置,在剩下的元素序列中建堆,
//不斷重複這個過程,直到倒數第二個元素
void HeapSort(ValueType values[], int start, int end)
{
if (check_array_range(start, end, end - start + 1))
{
for (int i = start; i < end; ++i)
{
build_heap(&values[i], 0, end - i);
}
}
}
[這篇文章記錄了堆的相關知識](http://blog.csdn.net/liao_jian/article/details/45721119)
歸併排序
//假設有兩個排好序的序列,將它們和在一起,並要保持順序性,那麼就可以
//這麼做:每次從第一個序列或第二個序列中取走一個元素,哪個序列中的第
//一個元素小就取哪個,當一個序列取完之後,把另一個序列直接接到按元素
//取走順序放置的那個序列後面,就得到了一個合併的有序列表。這個過程叫
//做合併。把輸入數組每兩個元素合併一次,得到n/2個序列,最後一個序列可
//能只有一個元素,把每個得到的序列看做一個元素,繼續兩兩合併,直到合併
//完所有的元素
void MergeSort(ValueType values[], int start, int end)
{
if (check_array_range(start, end, end - start + 1))
{
merge_sort(values, start, end);
}
}
void merge_sort(ValueType values[], int start, int end)
{
if (start < end)
{
int connect = (start + end) / 2;
merge_sort(values, start, connect);
merge_sort(values, connect + 1, end);
merge(values, start, connect, end);
}
}
//歸併排序歸併階段操作
void merge(ValueType values[], int start, int connect, int end)
{
int length1 = connect - start + 1,
length2 = end - connect;
//只需將前半段元素複製出來就行了,右半段就放在
//原來的序列中,這樣行得通是因爲複製完前半段後,
//將排序結果寫回原序列時,要麼把複製出來的元素
//重新寫回去,位置剛剛夠。要麼從原區間去一個數
//放到前面,此時後半段區間空出一個位置,使得原
//區間中剩餘的可用位置數正好等於前半區間此時剩餘
//元素的個數
ValueType* left = new ValueType[length1], //每次調用merge都會分配內存,其實可
//以使用一個全局內存空間以提高性能
*right = &values[connect + 1];// new ValueType[length2];
for (int i = start,j = 0; i <= connect; )
left[j++] = values[i++];
int i = start, j = 0, k = 0;
while ( j < length1 && k < length2)
{
if (comp(left[j], right[k])) values[i++] = left[j++];
else values[i++] = right[k++];
}
//前半區間還有剩餘,那麼說明後半區間已經取空,
//直接把前半區間剩餘元素複製到原區間中,否則,
//後半區間還有剩餘,並且已經就位
while ( j < length1)
{
values[i++] = left[j++];
}
delete []left;
}
歸併排序還可以進一步改善,在每一次合併中都要開闢內存、釋放內存,這個開銷還是比較大的,可以使用一個全局的n/2 + 1大小的內存空間替換,這樣就免去了內存申請與釋放的消耗,整個排序過程做一次就夠了。
完整實現代碼
#ifndef _SORTER_H_
#define _SORTER_H_
#include "../include/Functor.h"
#include <cstdlib>
#include <time.h>
using namespace MyDataStructure;
namespace MyTools
{
template<typename Value,typename Compare = MyDataStructure::less<Value>>
class Sorter
{
public:
typedef Value ValueType;
public:
/**************************************************************/
/* */
/* 以下說明均以從小到大排序爲例 */
/* */
/**************************************************************/
//冒泡排序,對數組做n-趟掃描,每一趟把未就位的元素中的最大的元素
//放到他正確的位置上,每一趟掃描從輸入數組第一個元素開始,依次與
//它後一個元素比較,如果大於後一元素就交換兩者,無論交換與否,在
//這一趟的最大元素到達他應該在的位置之前,從原來後一元素所在的位
//置開始重複上述過程
void BufferSort(ValueType values[], int start, int end)
{
if (check_array_range(start, end, end - start + 1))
{
//i從後起,最大的元素放到最後一個位置嘛
for (int i = end; i > start; --i)
{
//j < i,這就是“這一趟的最大元素到達他應
//該在的位置之前”的意思
for (int j = start; j < i; ++j)
{
if (comp(values[j + 1], values[j]))
{
swap(values[j + 1], values[j]);
}
}
}
}
}
//第一個元素是有序的,如果第二個元素小於第一個元素,交換二者的位置,
//此時前面的兩個元素是有序的,第三個元素看做是一個插入前面兩個元素
//組成的有序表中,加上它自己的位置,所以共有三個位置,所以第三個元
//素可以找到自己位置,這個過程一直重複到輸入數組的最後一個元素
//但是實際上這個過程可以看成是以間隔爲1的一趟shell排序
void InsertSort(ValueType values[], int start, int end)
{
if (check_array_range(start, end, end - start + 1))
{
// gap = 1的一趟shell排序
shell_sort_once(values, start, end, 1);
}
}
//在簡單插入排序中每個元素需要從後往前順序在他前面的有序表中尋找自己的
//位置,把這個查找過程使用二分查找,就形成了二分插入排序,假設一個元素
//B應該放在元素A的位置後,而A有多個值,那麼應該查找最後一個A的位置,然
//後把B放到這個A的位置之後,這樣可以減少元素移動的次數
void BinaryInsertSort(ValueType values[], int start, int end)
{
if (check_array_range(start, end, end - start + 1))
{
for (int i = start + 1; i <= end; ++i)
{
ValueType temp = values[i];
int low = start, high = i - 1, mid = (low + high) / 2;
while (low <= high)
{
if (comp(temp, values[mid])) high = mid - 1;
else low = mid + 1;
mid = (low + high) / 2;
}
for (int k = i; k > low; --k) values[k] = values[k - 1];
values[low] = temp;
}
}
}
void ShellSort(ValueType values[], int start, int end)
{
if (check_array_range(start, end, end - start + 1))
{
int gap = end - start + 1;
do
{
gap = gap / 3 + 1;
shell_sort_once(values, start, end, gap);
} while (gap > 1);
}
}
//以數組中的某個元素作爲樞紐,將小於樞紐的元素放到樞紐的左邊,
//大於樞紐的元素都放到樞紐的右邊,這個過程叫做劃分,然後在樞紐
//左邊的序列和樞紐左邊的序列中不斷劃分,直到劃分序列中只含有不
//多於一個元素,劃分結束後,排序也就完成了,快排的關鍵就是劃分,
//根據不同的劃分方式,就得到了不同版本的快排程序
void QuickSort(ValueType values[], int start, int end)
{
if (check_array_range(start, end, end - start + 1))
{
quick_sort(values, start, end);
}
}
void RandomizedQuickSort(ValueType values[], int start, int end)
{
if (check_array_range(start, end, end - start + 1))
{
randomized_qsort(values, start, end);
}
}
void Median3QuickSort(ValueType values[], int start, int end)
{
if (check_array_range(start, end, end - start + 1))
{
median3_qsort(values, start, end);
}
}
//當一個劃分序列元素個數爲5~25時,繼續使用劃分遞歸地實現劃分序列的排序往往
//沒有使用直接插入排序快,所以就可以把插入排序與快排結合起來加快排序速度
void HybridQuickSort(ValueType values[], int start, int end,int m)
{
if (check_array_range(start, end, end - start + 1))
{
hybrid_qsort(values, start, end, m);
}
}
//有許多輸入序列重複元素很多,極端例子是序列中全部元素都一樣,
//這時候再用原來的快排算法性能就太差了。針對這種情況,可以把與
//樞紐元素一樣大的元素都聚到中間,而在左邊放置小於樞紐元素的元素,
//右邊放置大於樞紐元素的元素,然後再左右兩個子序列中快排,這樣的
//快排算法叫做三路快排。三路快排關鍵是三路劃分。
void ThreeWayQuickSort(ValueType values[], int start, int end)
{
if (check_array_range(start, end, end - start + 1))
{
three_way_qsort(values, start, end);
}
}
//先在輸入數組中選擇最小的值,如果它不是第一個元素,就把它與第一個位置上
//的元素交換,在剩餘的子數組繼續這個過程,直到倒數第二個元素。這個過程就是
//先選擇最小的元素,然後選擇第二小的元素……因此叫做選擇排序
void SelectSort(ValueType values[], int start, int end)
{
if (check_array_range(start, end, end - start + 1))
{
select_sort(values, start, end);
}
}
//堆排序也算是一種選擇排序,只是利用堆的性質加速了每一趟選擇最小
//元素的過程。因爲最小堆的第一個元素是最小的,所以先在整個輸入數組
//中建堆,則最小的元素被放到了第一個位置,在剩下的元素序列中建堆,
//不斷重複這個過程,直到倒數第二個元素
void HeapSort(ValueType values[], int start, int end)
{
if (check_array_range(start, end, end - start + 1))
{
for (int i = start; i < end; ++i)
{
build_heap(&values[i], 0, end - i);
}
}
}
//假設有兩個排好序的序列,將它們和在一起,並要保持順序性,那麼就可以
//這麼做:每次從第一個序列或第二個序列中取走一個元素,哪個序列中的第
//一個元素小就取哪個,當一個序列取完之後,把另一個序列直接接到按元素
//取走順序放置的那個序列後面,就得到了一個合併的有序列表。這個過程叫
//做合併。把輸入數組每兩個元素合併一次,得到n/2個序列,最後一個序列可
//能只有一個元素,把每個得到的序列看做一個元素,繼續兩兩合併,直到合併
//完所有的元素
void MergeSort(ValueType values[], int start, int end)
{
if (check_array_range(start, end, end - start + 1))
{
merge_sort(values, start, end);
}
}
//公開劃分過程是因爲劃分算法還有其他應用,比如查找最小第k個數
int Partition(ValueType values[], int start, int end)
{
if (check_array_range(start, end, end - start + 1))
{
return partition(values, start, end);
}
return -1;
}
int RandomizedPartition(ValueType values[], int start, int end)
{
if (check_array_range(start, end, end - start + 1))
{
return randomized_partition(values, start, end);
}
return -1;
}
int Median3Partition(ValueType values[], int start, int end)
{
if (check_array_range(start, end, end - start + 1))
{
return median3_partition(values, start, end);
}
return -1;
}
public:
Compare comp;
private:
bool check_array_range(int start, int end,int n)
{
if (n <= 0 ||
start < 0 ||
end < 0 || start > end
|| n > end - start + 1)
return false;
return true;
}
//一趟劃分算法
int partition(ValueType values[], int start, int end)
{
//以第一個元素作爲樞紐,從數組兩頭向中間掃描
ValueType pivot = values[start];
while (start < end)
{
//先從後往前掃描,直到遇到一個小於樞紐的元素
while (start < end && !comp(values[end], pivot)) --end;
//把這個小於樞紐的元素放到start指向的位置上,
//這是合理的,因爲這個元素小於樞紐,要放到
//樞紐最後的位置之前
values[start] = values[end];
//此時前指針指向一個小於樞紐的元素,所以下一個循環總是能推進前指針
//從前往後掃描,直到前指針遇到一個大於樞紐的元素
while (start < end && !comp(pivot, values[start])) ++start;
//剛纔後指針指向了一個小於樞紐的元素,這個元素放到了前指針推進
//之前的位置上,前指針推進後,現在指向了一個大於樞紐的元素,
//正好把它放到後指針指向的位置上
values[end] = values[start];
//此時,前指針指向的元素是一個大於樞紐的元素,但是這個元素是無效的,
//如果start還沒有遇到end,那麼外層循環會繼續,後指針也許就能找到
//一個小於樞紐的元素,放到start位置上,如果後指針沒有找到這樣
//一個元素,那麼start會等於end,於是start不再推進,會
//被自己的值賦值一次,不過這個位置上的元素還是無效的,外層循環結束
}
//外層循環結束後,start位置的值是無效的,它前面的值小於或等於樞紐,
//後面的值大於或等於樞紐,於是把樞紐放到start位置上,就完成了一次劃分
values[start] = pivot;
//start位置就是樞紐最後所在的位置
return start;
}
//在劃分區間隨機選一個數作爲樞紐,把它和第一個位置的元素交換,
//再調用partition過程
int randomized_partition(ValueType values[], int start, int end)
{
if (end == start) return start;
if (end < start) return -1;
srand(static_cast<unsigned>(time(nullptr)));
int i = rand();
i = static_cast<int>((i * 1.0 / RAND_MAX) * (end - start) + start);
swap(values[i], values[start]);
return partition(values, start, end);
}
//取區間首、尾、中間三個元素,求三個元素的中間值,然後把它和第一個
//位置的元素交換,再調用partition過程
int median3_partition(ValueType values[], int start, int end)
{
int mid = (start + end) / 2;
int k = mid;
if (comp(values[k], values[start]) && comp(values[start],values[end])) k = start;
else if (comp(values[start], values[k]) && comp(values[end], values[start])) k = end;
if (k != start) swap(values[k], values[start]);
return partition(values, start, end);
}
//將區間劃分爲三段,前段是小於樞紐的元素,中間是與樞紐相等的元素,後端是
//大於樞紐的元素.運行partition過程,有點區別就是如果前指針遇到一個與樞紐
//相等的元素時,把它放到區間前段,後指針遇到一個與樞紐相等的元素是把它放
//後段。partition過程運行完後,區間的排列如下(以6爲樞紐舉例):666234567899666,
//注意中間有一個樞紐,然後再將兩邊的樞紐交換到中間來
void three_way_partition(ValueType values[], int start, int end, int &left_pivot, int &right_pivot)
{
ValueType pivot = values[start];
int i = start,p = start,q = end,j = end;
while (i < j)
{
while (i < j)
{
if (comp(pivot, values[j])) --j;
else if (values[j] == pivot) swap(values[q--], values[j--]);
else break;
}
values[i] = values[j];
while (i < j )
{
if (comp(values[i], pivot)) ++i;
else if (values[i] == pivot) swap(values[p++], values[i++]);
else break;
}
values[j] = values[i];
}
values[i] = pivot;
j = i + 1;
i -= 1;
while (i > start && comp(values[i],values[start])) swap(values[i++], values[start++]);
while (j < end && comp(values[end], values[j])) swap(values[j++], values[end--]);
left_pivot = i;
right_pivot = j;
}
//一趟希爾排序。取定一個間隔gap,則原數組中每隔gap個數的數分爲一組,
//例如,原數組爲12 11 10 9 8 7 6 5 4 3 2 1,gap爲3,則12 9 6 3爲一組,
//11 8 5 2爲一組,10 7 4 1爲一組。然後對每一組進行插入排序。插入排序
//是從第二個數起,按順序將它插入到前面序列中的合適位置,以12 9 6 7爲例,
//9小於12,所以先將9,複製到一個臨時變量中,然後將12後移到9所在的位置,
//再將9複製到12原來的位置上,這樣9 12就排好序了。6比前面兩個數都小,先將
//6複製到臨時變量中,然後將9 12都後移一個位置,再將6放到9所在的位置,7小於9和
//12,大於6,所以將9和12向後移一個位置,把7插入到空出來的位置上,於是排序完成
void shell_sort_once(ValueType values[], int start, int end, int gap)
{
for (int i = start + gap; i <= end; ++i) //交叉進行
{
if (comp(values[i], values[i - gap]))
{
ValueType temp = values[i];
int j = i - gap;
do
{
values[j + gap] = values[j];
j -= gap;
} while (j >= start && comp(temp, values[j]));
values[j + gap] = temp;
}
}
}
void quick_sort(ValueType values[], int start, int end)
{
if (start < end)
{
int pivot = partition(values, start, end);
quick_sort(values, start, pivot - 1);
quick_sort(values, pivot + 1, end);
}
}
void randomized_qsort(ValueType values[], int start, int end)
{
if (start < end)
{
int pivot = randomized_partition(values, start, end);
randomized_qsort(values, start, pivot - 1);
randomized_qsort(values, pivot + 1, end);
}
}
void median3_qsort(ValueType values[], int start, int end)
{
if (start < end)
{
int pivot = median3_partition(values, start, end);
median3_qsort(values, start, pivot - 1);
median3_qsort(values, pivot + 1, end);
}
}
void three_way_qsort(ValueType values[], int start, int end)
{
if (start < end)
{
int left, right;
three_way_partition(values, start, end, left, right);
three_way_qsort(values,start, left);
three_way_qsort(values, right, end);
}
}
void hybrid_qsort(ValueType values[], int start, int end,int m)
{
if (end - start <= m)
shell_sort_once(values, start, end,1);
else
{
int pivot = randomized_partition(values, start, end);
hybrid_qsort(values, start, pivot - 1,m);
hybrid_qsort(values, pivot + 1, end,m);
}
}
void select_sort(ValueType values[], int start, int end)
{
for (int i = start; i < end; ++i)
{
int k = i;
ValueType val = values[k];
for (int j = i; j < end; ++j)
{
if (comp(values[j + 1],val))
{
val = values[j + 1];
k = j + 1;
}
}
if (k != i) swap(values[k], values[i]);
}
}
void sift_down(ValueType values[],int start, int end)
{
int i = start, j = 2 * i + 1;
ValueType temp = values[i];
while (j <= end)
{
if (j < end && comp(values[j + 1],values[j] )) ++j;
if (!comp(values[j],temp )) break;
else
{
values[i] = values[j];
i = j;
j = 2 * i + 1;
}
}
values[i] = temp;
}
void build_heap(ValueType values[],int start,int end)
{
for (int i = (end - 1) / 2; i >= start; --i)
{
sift_down(values,i, end);
}
}
//歸併排序歸併階段操作
void merge(ValueType values[], int start, int connect, int end)
{
int length1 = connect - start + 1,
length2 = end - connect;
//只需將前半段元素複製出來就行了,右半段就放在
//原來的序列中,這樣行得通是因爲複製完前半段後,
//將排序結果寫回原序列時,要麼把複製出來的元素
//重新寫回去,位置剛剛夠。要麼從原區間去一個數
//放到前面,此時後半段區間空出一個位置,使得原
//區間中剩餘的可用位置數正好等於前半區間此時剩餘
//元素的個數
ValueType* left = new ValueType[length1], //每次調用merge都會分配內存,其實可
//以使用一個全局內存空間以提高性能
*right = &values[connect + 1];// new ValueType[length2];
for (int i = start,j = 0; i <= connect; )
left[j++] = values[i++];
/*for (int i = connect + 1, j = 0; i <= end;)
right[j++] = values[i++];*/
int i = start, j = 0, k = 0;
while ( j < length1 && k < length2)
{
if (comp(left[j], right[k])) values[i++] = left[j++];
else values[i++] = right[k++];
}
//前半區間還有剩餘,那麼說明後半區間已經取空,
//直接把前半區間剩餘元素複製到原區間中,否則,
//後半區間還有剩餘,並且已經就位
while ( j < length1)
{
values[i++] = left[j++];
}
/*while( k < length2)
{
values[i++] = right[k++];
}*/
delete []left;
//delete []right;
}
void merge_sort(ValueType values[], int start, int end)
{
if (start < end)
{
int connect = (start + end) / 2;
merge_sort(values, start, connect);
merge_sort(values, connect + 1, end);
merge(values, start, connect, end);
}
}
void swap(ValueType& val1, ValueType& val2)
{
ValueType temp = val1;
val1 = val2;
val2 = temp;
}
};
}
#endif
測試代碼
// SorterTest.cpp : 定義控制檯應用程序的入口點。
//
#include "stdafx.h"
#include "../Tool/Sorter.h"
#include <iostream>
using namespace MyTools;
using namespace std;
int _tmain(int argc, _TCHAR* argv[])
{
int v1[] = { 1, 3, 3, 9, 7, 9, 4, 2, 8, 10 };
Sorter<int> sorter;
sorter.BufferSort(v1, 0, 9);
for (int i = 0; i < 10; ++i)
{
cout << v1[i] << ' ';
}
cout << endl;
int v2[] = { 1, 3, 3, 9, 7, 9, 4, 2, 8, 10 };
sorter.InsertSort(v2, 0, 9);
for (int i = 0; i < 10; ++i)
{
cout << v2[i] << ' ';
}
cout << endl;
int v3[] = { 1, 3, 3, 9, 7, 9, 4, 2, 8, 10 };
sorter.BinaryInsertSort(v3, 0, 9);
for (int i = 0; i < 10; ++i)
{
cout << v3[i] << ' ';
}
cout << endl;
int v4[] = { 1, 3, 3, 9, 7, 9, 4, 2, 8, 10 };
sorter.ShellSort(v4, 0, 9);
for (int i = 0; i < 10; ++i)
{
cout << v4[i] << ' ';
}
cout << endl;
int v5[] = { 1, 3, 3, 9, 7, 9, 4, 2, 8, 10 };
sorter.QuickSort(v5, 0, 9);
for (int i = 0; i < 10; ++i)
{
cout << v5[i] << ' ';
}
cout << endl;
int v6[] = { 1, 3, 3, 9, 7, 9, 4, 2, 8, 10 };
sorter.RandomizedQuickSort(v6, 0, 9);
for (int i = 0; i < 10; ++i)
{
cout << v6[i] << ' ';
}
cout << endl;
int v7[] = { 1, 3, 3, 9, 7, 9, 4, 2, 8, 10 };
sorter.Median3QuickSort(v7, 0, 9);
for (int i = 0; i < 10; ++i)
{
cout << v7[i] << ' ';
}
cout << endl;
int v8[] = { 1, 3, 3, 9, 7, 9, 4, 2, 8, 10 };
sorter.HybridQuickSort(v8, 0, 9,5);
for (int i = 0; i < 10; ++i)
{
cout << v8[i] << ' ';
}
cout << endl;
int v9[] = { 1, 3, 3, 9, 7, 9, 4, 2, 8, 10 };
sorter.SelectSort(v9, 0, 9);
for (int i = 0; i < 10; ++i)
{
cout << v9[i] << ' ';
}
cout << endl;
int v10[] = { 1, 3, 3, 9, 7, 9, 4, 2, 8, 10 };
sorter.HeapSort(v10, 0, 9);
for (int i = 0; i < 10; ++i)
{
cout << v10[i] << ' ';
}
cout << endl;
int v11[] = { 1, 3, 3, 9, 7, 9, 4, 2, 8, 10 };
sorter.MergeSort(v11, 0, 9);
for (int i = 0; i < 10; ++i)
{
cout << v11[i] << ' ';
}
cout << endl;
Sorter<int, MyDataStructure::greater<int>> sorter1;
sorter1.MergeSort(v11, 0, 9);
for (int i = 0; i < 10; ++i)
{
cout << v11[i] << ' ';
}
cout << endl;
sorter.ThreeWayQuickSort(v11, 0, 9);
for (int i = 0; i < 10; ++i)
{
cout << v11[i] << ' ';
}
cout << endl;
return 0;
}
測試程序運行截圖:
同樣的輸入,不同的排序方法,得到了同樣的排序結果。
總結
快速排序、歸併排序、堆排序都是O(nlgn)級別的排序算法,但是一般情況下快速排序快於另外兩個算法,將插入排序與快速排序混合的排序算法性能很好,STL中實現的就是這一排序算法。選擇排序、冒泡排序、插入排序這三類排序算法都是O(n^2)級別的算法,但是一般來講選擇排序性能最好,因爲這個算法元素移動次數最少。shell排序是一種插入排序,它是改進改進插入排序的:元素少的數組,插入排序較快(對應於gap較大時),對於基本排序的數組,插入排序也較快(對應於gap較小時)。實際上還有一些O(n)級別的排序算法,如基數排序、桶排序、計數排序等,但是這些排序算法排序對象有限制,所以暫不記錄了。