- 注册时间
- 2008-9-9
- 最后登录
- 1970-1-1
- 威望
- 星
- 金币
- 枚
- 贡献
- 分
- 经验
- 点
- 鲜花
- 朵
- 魅力
- 点
- 上传
- 次
- 下载
- 次
- 积分
- 3282
- 在线时间
- 小时
|
马上注册,结交更多好友,享用更多功能,让你轻松玩转社区。
您需要 登录 才可以下载或查看,没有账号?欢迎注册
×
原创地址:http://blog.csdn.net/Silitex/archive/2009/10/30/4746697.aspx
鄙人在很多排序虽然懂,但真正动起手来还是错漏百出,后来下定决心,这段时间在研究排序算法,后来根据计数排序和基数排序,想到一个他们的特例 -- 位排序。
位排序 ---
时间复杂度: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: silitex@yeah.net
- 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: silitex@yeah.net
- #include <afxtempl.h>
- #include <stdio.h>
-
- /* 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: silitex@yeah.net
- 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: silitex@yeah.net
- 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: silitex@yeah.net
- 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<ELEM_TYPE, ELEM_TYPE> aTest;
- CArray<ELEM_TYPE, ELEM_TYPE> aCopy;
- 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;
- }
复制代码 |
|