位排序 -- 基于计数排序和基数排序产生的特例

位排序 ---

    时间复杂度: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;
}



  • 1
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值