位排序 ---
时间复杂度:O(n*logm), 最好和最坏的情况下都是这个时间复杂度 (此排序算法总是认为m <= n的)
空间复杂度:O(logm), 如果m小于4294967296, 即最大临时空间为32的倍数
运行时间:经过大量的数据测试,位排序的运行时间仅次于计数排序和快速排序
算法的优点:能够在空间复杂度为O(logm)上面实现O(n*logm)的算法,空间上比快速排序和堆排序的最坏情况(最坏情况其空间复杂度都为O(n))下要好
算法的缺点:m <= n
源代码:
void BitSortRecursive(ELEM_TYPE *pData, int nLow, int nHigh, UNIQUE_HASH_CALLBACK pUniqueHashFunc, int nBit)
{
int i;
int j;
int nAndMask;
if (nLow >= nHigh)
{
return;
}
else if (nLow+1 == nHigh) // 只剩下两个元素, 那么直接比较就可以了
{
if (pUniqueHashFunc(pData[nLow].eKey) > pUniqueHashFunc(pData[nHigh].eKey))
DsSwapElem(&pData[nLow], &pData[nHigh]);
return;
}
for (i = nLow, j = nHigh, nAndMask = 1<<(nBit-1); ; )
{
for (; i <= nHigh; i++)
{
if ((pUniqueHashFunc(pData[i].eKey)&nAndMask) != 0)
break;
}
for (; j > i; j--)
{
if ((pUniqueHashFunc(pData[j].eKey)&nAndMask) == 0)
break;
}
if (j > i) // 表明还找到两个这样的数
{
DsSwapElem(&pData[i++], &pData[j--]);
if (j < i)
break;
}
else
{
break;
}
}
if (nBit > 1)
{
nBit--;
BitSortRecursive(pData, nLow, i-1, pUniqueHashFunc, nBit);
BitSortRecursive(pData, i, nHigh, pUniqueHashFunc, nBit);
}
}
// 位排序 -- 由计数排序和基数排序引申出的一种排序: 位排序
// 计数排序当m的取值范围从0~65535时,申请的空间不大,排序算法是相当好用的
// 当如果m的取值范围从0~4294967295时,要申请的空间就太大了,不过可以根据基数排序的思想
// 对于HighWord先采用计数排序,然后再对LowWord再次计数排序,这个就是基数排序的思想
// 如果有人认为计数排序64K的空间申请太大了,那么缩小成2^8=256(即8位计数排序),这个空间是很小的
// 如果空间还想再小: 2^4, 2^2, 2^1..., 最后,产生了一个特例,就是2^0的位排序
// 其排序方法:
// 如果m取值范围0~65535, 先从左往右找到第一个最高位为1的数,然后从右往左找一个最高位为0的数,交换之, 直至交换完毕,那么最高位就有序了
// 再来次高位, ..., 最后一位
// 根据前面的描述,算法空间复杂度: O(logm), 时间复杂度O(nlogm)(最好和最坏都一样的时间复杂度)
// 经过大量的数据测试,位排序的运行时间仅次于计数排序和快速排序
// E-mail: [email protected]
void BitSort(ELEM_TYPE *pData, int nLen, UNIQUE_HASH_CALLBACK pUniqueHashFunc, int nUniqueHashRange)
{
int m = nUniqueHashRange-1;
int nBit;
// Get log2(m)
for (nBit = 0; m > 0; nBit++)
m >>= 1;
if (nBit == 0)
return;
BitSortRecursive(pData, 0, nLen-1, pUniqueHashFunc, nBit);
}
================================================================
下面给出完整的测试代码
================================================================
// 编译器VC6.0
// 需要设置支持MFC
// E-mail: [email protected]
#include
#include
/* The macro definition */
#define _DEBUG_OUT
typedef int ELEM_KEY_TYPE; // 关键字段类型
typedef int ELEM_REST_TYPE; // 剩余字段类型
typedef unsigned char byte;
typedef unsigned short int word;
typedef unsigned long int dword;
#define DS_BUBBLE_SORT_DATA_MAX 100000
#define DS_BUBBLE_SORT_DATA_RANGE (DS_BUBBLE_SORT_DATA_MAX)
// 单条记录的结构
typedef struct {
ELEM_KEY_TYPE eKey; // 记录的主关键字段
ELEM_REST_TYPE eRest[7]; // 记录的剩余字段, 主关键字与其他的比例暂时按照1:7分配
} ELEM_TYPE;
typedef int (*UNIQUE_HASH_CALLBACK)(ELEM_KEY_TYPE eKey);
typedef int (*SHELL_GAP_CALLBACK)(int nGap);
// 交换两个记录的数据
void DsSwapElem(ELEM_TYPE *pElem1, ELEM_TYPE *pElem2)
{
ELEM_TYPE eTemp;
eTemp = *pElem2;
*pElem2 = *pElem1;
*pElem1 = eTemp;
}
// 复制记录数据
void DsCopyElem(ELEM_TYPE *pElemDst, ELEM_TYPE *pElemSrc)
{
*pElemDst = *pElemSrc;
}
// Windows系统下使用的真随机函数
// By Silitex, at 2009/09/17
DWORD WinRand(void)
{
LARGE_INTEGER Counter;
QueryPerformanceCounter(&Counter);
return Counter.LowPart;
}
// 用于测试的一一对应的Hash函数
int UniqueHashTest(ELEM_KEY_TYPE eKey)
{
return eKey;
}
// 希尔排序的Gap选取测试函数
// 已知的最好nGap序列是: 1, 4, 10, 23, 57, 132, 301, 701, 1750,...
// 在1750之后的序列值按如下公式计算: next_step = round(step * 2.3)
int ShellGapTest(int nGap)
{
const int GAP_SEQ[] = {0, 1, 4, 10, 23, 57, 132, 301, 701, 1750, 4025, 9257, 21291, 48969, 112628, 259044};
int i;
int j;
for (i = 0, j = 0; GAP_SEQ[i] < nGap; i++)
j = i;
return GAP_SEQ[j];
}
void DebugOut(ELEM_TYPE *pData, int nLen)
{
int i;
printf("/n长度:%d/n 数据:", nLen);
for (i = 0; i < nLen; i++)
printf("%d ", pData[i].eKey);
printf("/n");
}
// 计数排序
// 计数排序适用于诸如取值个数只有m个(m远小于n)的场合(比如10000个学生的成绩范围从0~100之间取值)
// 在正常情况下,含有其他非关键字段,并且关键字段的取值并非连续的m个(比如取值0, 10, 100, 101, ...等无规律的数值),
// 这时计数排序的时间复杂度为O(nlogm); 如果要排序的数组只有一个字段,那么其时间复杂度可以直接为O(n)
// 当然研究算法,就从最普通的现象研究,在普通情况下,取m个值,我们通过唯一数值Hash算法(比如,0~0, 10~1, 100~2, 101~3)
// 进行这个Hash算法的最快的时间复杂度就是O(logm)了,因为对于无规律的取值来说,最快的就是二分查找算法了
// 所以一个标准的计数排序除了传入待排序的数组及数组长度外,还需要传入对这个数组的Hash函数指针和Hash以后的样本空间
// (这里规定Hash以后的样本空间只能够取值范围为0~m-1)
// 计数排序需要O(m)个辅助空间
// 计数排序还发现一个特点,就是即使m=n的情况下,虽然会浪费空间,但可以得到比快速排序还要少的交换次数
// E-mail: [email protected]
void CountSort(ELEM_TYPE *pData, int nLen, UNIQUE_HASH_CALLBACK pUniqueHashFunc, int nUniqueHashRange)
{
int *ElemCount = new int[nUniqueHashRange]; // 统计某一个值的出现次数
int *ElemStartPosi = new int[nUniqueHashRange]; // 某一个值的开始地址
int *ElemIncPosi = new int[nUniqueHashRange]; // 某一个值的增量地址
int ElemPosi;
int i;
int nPosi;
int nNewPosi;
int nTemp;
// 初始化统计数组
for (i = 0; i < nUniqueHashRange; i++)
{
ElemCount[i] = 0;
ElemStartPosi[i] = 0;
ElemIncPosi[i] = 0;
}
// 统计每一个值出现的频率
for (i = 0; i < nLen; i++)
ElemCount[pUniqueHashFunc(pData[i].eKey)]++;
// 把每次的计数相加,得到实际想要的位置
ElemPosi = 0;
for (i = 1; i < nUniqueHashRange; i++)
{
ElemPosi += ElemCount[i-1];
ElemStartPosi[i] = ElemPosi;
}
// 根据出现的频率进行交换它们的位置
for (nPosi = 0; nPosi < nLen; nPosi++)
{
while (TRUE)
{
nTemp = pUniqueHashFunc(pData[nPosi].eKey);
if (nPosi >= ElemStartPosi[nTemp] && nPosi < ElemStartPosi[nTemp]+ElemCount[nTemp])
break;
while (ElemIncPosi[nTemp] < ElemCount[nTemp])
{
nNewPosi = ElemStartPosi[nTemp] + ElemIncPosi[nTemp];
ElemIncPosi[nTemp]++;
if (pUniqueHashFunc(pData[nNewPosi].eKey) != nTemp)
{
DsSwapElem(&pData[nPosi], &pData[nNewPosi]);
break;
}
}
}
}
delete []ElemCount;
delete []ElemStartPosi;
delete []ElemIncPosi;
}
// 对严蔚敏书上算法的改进, 严蔚敏的算法为了节约一个变量而造成了运行时间的浪费,划不来
// 这种方法的比较次数是最少的,移动次数也是最少的
static int QuickSortPartition(ELEM_TYPE *pData, int nLow, int nHigh)
{
ELEM_TYPE eTemp;
int nPrivot;
DsCopyElem(&eTemp, &pData[nLow]);
nPrivot = nLow++;
while (nLow <= nHigh)
{
if (nLow > nPrivot) // 照样可以用(nHigh > nPrivot)
{
while (nLow <= nHigh)
{
if (pData[nHigh].eKey < eTemp.eKey)
{
DsCopyElem(&pData[nPrivot], &pData[nHigh]);
nPrivot = nHigh--;
break;
}
nHigh--;
}
}
else
{
while (nLow <= nHigh)
{
if (pData[nLow].eKey > eTemp.eKey)
{
DsCopyElem(&pData[nPrivot], &pData[nLow]);
nPrivot = nLow++;
break;
}
nLow++;
}
}
}
DsCopyElem(&pData[nPrivot], &eTemp);
return nPrivot;
}
static void QuickSortRecursive(ELEM_TYPE *pData, int nLow, int nHigh)
{
int nPrivot;
if (nLow < nHigh)
{
nPrivot = QuickSortPartition(pData, nLow, nHigh);
QuickSortRecursive(pData, nLow, nPrivot-1);
QuickSortRecursive(pData, nPrivot+1, nHigh);
}
}
// 快速排序
// E-mail: [email protected]
void QuickSort(ELEM_TYPE *pData, int nLen)
{
QuickSortRecursive(pData, 0, nLen-1);
}
void BitSortRecursive(ELEM_TYPE *pData, int nLow, int nHigh, UNIQUE_HASH_CALLBACK pUniqueHashFunc, int nBit)
{
int i;
int j;
int nAndMask;
if (nLow >= nHigh)
{
return;
}
else if (nLow+1 == nHigh) // 只剩下两个元素, 那么直接比较就可以了
{
if (pUniqueHashFunc(pData[nLow].eKey) > pUniqueHashFunc(pData[nHigh].eKey))
DsSwapElem(&pData[nLow], &pData[nHigh]);
return;
}
for (i = nLow, j = nHigh, nAndMask = 1<<(nBit-1); ; )
{
for (; i <= nHigh; i++)
{
if ((pUniqueHashFunc(pData[i].eKey)&nAndMask) != 0)
break;
}
for (; j > i; j--)
{
if ((pUniqueHashFunc(pData[j].eKey)&nAndMask) == 0)
break;
}
if (j > i) // 表明还找到两个这样的数
{
DsSwapElem(&pData[i++], &pData[j--]);
if (j < i)
break;
}
else
{
break;
}
}
if (nBit > 1)
{
nBit--;
BitSortRecursive(pData, nLow, i-1, pUniqueHashFunc, nBit);
BitSortRecursive(pData, i, nHigh, pUniqueHashFunc, nBit);
}
}
// 位排序 -- 由计数排序和基数排序引申出的一种排序: 位排序
// 计数排序当m的取值范围从0~65535时,申请的空间不大,排序算法是相当好用的
// 当如果m的取值范围从0~4294967295时,要申请的空间就太大了,不过可以根据基数排序的思想
// 对于HighWord先采用计数排序,然后再对LowWord再次计数排序,这个就是基数排序的思想
// 如果有人认为计数排序64K的空间申请太大了,那么缩小成2^8=256(即8位计数排序),这个空间是很小的
// 如果空间还想再小: 2^4, 2^2, 2^1..., 最后,产生了一个特例,就是2^0的位排序
// 其排序方法:
// 如果m取值范围0~65535, 先从左往右找到第一个最高位为1的数,然后从右往左找一个最高位为0的数,交换之, 直至交换完毕,那么最高位就有序了
// 再来次高位, ..., 最后一位
// 根据前面的描述,算法空间复杂度: O(logm), 时间复杂度O(nlogm)(最好和最坏都一样的时间复杂度)
// 经过大量的数据测试,位排序的运行时间仅次于计数排序和快速排序
// E-mail: [email protected]
void BitSort(ELEM_TYPE *pData, int nLen, UNIQUE_HASH_CALLBACK pUniqueHashFunc, int nUniqueHashRange)
{
int m = nUniqueHashRange-1;
int nBit;
// Get log2(m)
for (nBit = 0; m > 0; nBit++)
m >>= 1;
if (nBit == 0)
return;
BitSortRecursive(pData, 0, nLen-1, pUniqueHashFunc, nBit);
}
// 测试内存排序
void TestEmsSort(void)
{
CArray
CArray
ELEM_TYPE eTemp;
UINT i;
for (i = 0; i < DS_BUBBLE_SORT_DATA_MAX; i++)
{
eTemp.eKey = WinRand()%DS_BUBBLE_SORT_DATA_RANGE;
aCopy.Add(eTemp);
}
#if 0
aTest.Copy(aCopy);
BubbleSort(aTest.GetData(), aTest.GetSize());
aTest.Copy(aCopy);
BubbleSortDouble(aTest.GetData(), aTest.GetSize());
aTest.Copy(aCopy);
GnomeSort(aTest.GetData(), aTest.GetSize());
aTest.Copy(aCopy);
OddEvenSort(aTest.GetData(), aTest.GetSize());
aTest.Copy(aCopy);
SelectSort(aTest.GetData(), aTest.GetSize());
aTest.Copy(aCopy);
InsertSort(aTest.GetData(), aTest.GetSize());
aTest.Copy(aCopy);
BinInsertSort(aTest.GetData(), aTest.GetSize());
#endif
#if 0
aTest.Copy(aCopy);
ShellSort(aTest.GetData(), aTest.GetSize(), ShellGapTest);
aTest.Copy(aCopy);
MergeSort(aTest.GetData(), aTest.GetSize());
aTest.Copy(aCopy);
CombSort(aTest.GetData(), aTest.GetSize());
#endif
aTest.Copy(aCopy);
DebugOut(aTest.GetData(), aTest.GetSize());
CountSort(aTest.GetData(), aTest.GetSize(), UniqueHashTest, DS_BUBBLE_SORT_DATA_RANGE);
DebugOut(aTest.GetData(), aTest.GetSize());
#if 0
aTest.Copy(aCopy);
CountSortSimp(aTest.GetData(), aTest.GetSize(), DS_BUBBLE_SORT_DATA_RANGE);
#endif
aTest.Copy(aCopy);
DebugOut(aTest.GetData(), aTest.GetSize());
QuickSort(aTest.GetData(), aTest.GetSize());
DebugOut(aTest.GetData(), aTest.GetSize());
aTest.Copy(aCopy);
DebugOut(aTest.GetData(), aTest.GetSize());
BitSort(aTest.GetData(), aTest.GetSize(), UniqueHashTest, DS_BUBBLE_SORT_DATA_RANGE);
DebugOut(aTest.GetData(), aTest.GetSize());
#if 0
aTest.Copy(aCopy);
BitSortSimp(aTest.GetData(), aTest.GetSize(), DS_BUBBLE_SORT_DATA_RANGE);
#endif
}
int main(void)
{
TestEmsSort();
return 0;
}