采用回调函数的内部排序算法-插入排序,希尔排序,冒泡,快排,堆排,归并排,基数排序
1.回调函数(callback function) :简而言之,回调函数就是一个通过函数指针调用的函数。
如果你把函数的指针(地址)作为参数传递给另一个函数,当这个指针被用为调用它所指向的函数时,
我们就说这是回调函数。
参考:http://hi.baidu.com/spidermanzy/blog/item/b25b00956469c6097bf48016.html
2.举例:如果想知道回调函数在实际中有什么作用,先假设有这样一种情况,
我们要编写一个库,它提供了某些排序算法的实现,
如冒泡排序、快速排序、shell排序、heap排序等等,但为使库更加通用,
不想在函数中嵌入排序逻辑,而让使用者来实现相应的逻辑;
或者,想让库可用于多种数据类型(int、float、string),此时,该怎么办呢?
可以使用函数指针,并进行回调(相对c语言而言,对于c++可以采用泛型编程template实现)。
3.内排序实现code描述:(排序稳定不稳定相对于排序是否按关键字进行,如果是则无关紧要)
1).插入排序:将待排数据插入已排好的数据集合中(配合其他排序使用,适合小量数据排序处理)。
改进的插入排序-shell排序(关键是增量的选取:2种Sedgewick和Hibbard增量方法)
2).冒泡排序,以及改进版本鸡尾酒排序(上下来回冒泡)时间复杂度:O(n^2)。稳定的交换排序。鸡尾酒排序最糟或是平均所花费的次数都是O(n^2),但如果序列在一开始已经大部分排序过的话,会接近O(n)。
3).快速排序:通过一趟排序有某个枢轴pivot将待排记录分割成独立的两部分集合S1和S2,S1<pivot<S2;几种partition方法(关键函数):Hoare_partition,Lomuto_partition,Random_partition,三数取中分割法ThreeM_partition(防止出现时间复杂度最坏情况,最有效取枢轴方法)辅助空间:O(logn);平均时间复杂度:O(nlogn)。不稳定的交换排序。
对于快速select第k大元素时思想差不多,(还有跟好的5分中数数组中中数方法)。见代码里的参考。
(算法改进:针对与最坏情况,即待排元素已经有序,达到O(N)的时间复杂度。在指针high减1和low增1的同时进行“冒泡”操作,即在相邻两个记录处于“逆序”时进行互换,同时在算法中附设两个布尔型变量分别指示指针low和high在两端向中间的移动过程中是否进行过交换记录的操作)
4).堆排序(来源于树形选择排序)简单选择排序-》树形选择排序-》堆排序。(适合大量数据排序处理)
辅助空间:O(1)
时间复杂度:O(nlogn)
5).归并排序:将两个或两个以上有序表组合一个新的有序表。(稳定排序)
实现归并排序需要和待排记录等数量的辅助空间。
辅助空间:O(n)(包括递归所用的额外空间)
时间复杂度:O(nlogn)
6).基数排序:和前面所述排序方法不同,该排序方法不需要进行记录元素之间的比较,而是借助多关键字排序的思想对单逻辑关键字进行排序的方法。
属于“分配式排序”(distribution sort),与基数排序法相关的还有桶排序(bucket sort)以及计数排序(counting sort)。其借助“分配”和“收集”两种操作对单逻辑关键字进行排序的一种内部排序方法。通过选用LSD(least Significant Digit first)还是MSD(Most Significant Digit first)方法,按关键字的不同值将序列中记录"分配"到RADIX个队列中后再"收集"之,如此反复d次(d为关键字个数比如三位数d=3)"基"指的是RADIX的取值范围r,比如数字为10【0...9】,字母为26【a...z】。
辅助空间:O(rd)
时间复杂度:O(d(n+rd))也可写成O(dn)
4.总结:
1).快排,堆排,希尔排序不稳定; 基排序和简单排序(时间复杂度为O(n^2)的排序算法:冒泡,插入)以及归并排序为稳定排序。
2).插入排序适合序列中记录基本有序和n值较小的情况,经常与其他好的排序算法一起用。
3).从平均时间性能而言,快排最佳,但是最坏情况(元素有序)下的时间性能不如堆排和归并排序,而后两者,n较大时,归并排序所需时间较堆排序省,但是需要额外的存储空间(空间换时间)。
4).基数排序的时间复杂度为O(d(n+rd))也可写成O(dn).(注意:适用于n值很大而且关键字数d较小的情况。)若关键字数d也很大时(有点像信息处理中的熵的概念),而序列中大多数记录的"最高位关键字"均不同,则亦可将"最高位关键字"不同将序列分成若干"小"的子序列,而后进行直接插入排序(分而治之的思想)。
对于基数排列因为采用多关键字的比较,所以没有采用比较方法的回调函数来进行处理。(这里只针对10进制数字的排序处理radix为10),如果对字母进行基数排序情况相对复杂,辅助空间也有所增加。
5).大型结构排序的处理方法: 交换两个结构可能是非常昂贵的操作.通过在结构中增加关键字,直接比较两结构体指针所指向的关键字来处理(这里所示例的关键字为int,和string型)。
6).对于任何一个借助“关键字间比较”进行的内部排序(除基数排序)可能达到的最快速度是什么?对于这个问题证明如下:
(时间复杂度上界O(n^2),下界为:ceil[log(n!)]为理论值,实际情况要大些。)
// 决策树(二叉树)用于证明下界的抽象概念
// 定理:(1) 深度为d的二叉树最多有2(d)个叶子
// (2) 具有L片叶子的二叉树的深度至少是[logL]
// (3) 只使用元素间比较的任何排序算法最坏情况下至少需要[logN!]次比较
// (4) 只使用元素间比较的任何排序算法需要进行O(NlogN)次比较
// LogN! = LogN + Log(N-1) + ... + Log1
// >= LogN + Log(N-1) + ... +LogN/2
// >= N/2Log(N/2)
// >= N/2LogN - N/2 = O(NLogN)
// 一般定理: 如果存在P种不同的结果要区分,而问题是YES/NO(比较判断)的形式,
// 那么通过任何算法求解该问题在某种情况下总需要[LogP]个问题.
n是要被排序的纪录数量以及k是不同键值的数量。
稳定的
不稳定的
不实用的排序算法
/**************************************************************************************************
Copyright (c) 2011 Author weedge(wy). All Rights Reserved.
*@date:2011-7-28 23:12:36 create~!
*@author: weedge (E-mail:weege@126.com)
*@comment:
首先介绍回调函数,然后将回调函数应用于内部排序算法中,最后通过代码实现之,附带添加二分查找算法。
*@modified :
date: 2011-7-29 21:36:11 添加Quicksort(),partition()函数实现快排模块。
具体讲解请参考:http://blog.csdn.net/v_JULY_v/article/details/6211155
date: 2011-8-1 23:09:12 添加Shellsort(),Insertsort()函数实现希尔和插入排序模块
以及Bubblesort(),DoubleBubblesort()函数实现冒泡和鸡尾混合排序模块。
date: 2011-8-2 22:19:12 添加HeapAdjust(),Heapsort()函数实现堆排序模块。
date: 2011-8-3 16:03:54 添加Merge(),Msort(),Mergesort()函数实现归并排序模块。
date: 2011-8-4 13:04:53 添加int_InitList(),Distribute(),Collect(),Radixsort()函数
实现基数排序模块(针对int型),参考严蔚敏c语言算法版本。
*********************************************************************************************************************/
#include <iostream>
#include <cmath>
#include <cassert>
using namespace std;
#define DEBUG
typedef char BYTE;
typedef int (__stdcall * CompareFunction) (const BYTE *velem1, const BYTE *velem2);
void swap(BYTE *velem1, BYTE *velem2, int elem_size)
{
BYTE *temp = new BYTE[elem_size];
memcpy(temp, velem1, elem_size);
memcpy(velem1, velem2, elem_size);
memcpy(velem2, temp, elem_size);
delete []temp;
}
int __stdcall CompareInts(const BYTE *velem1, const BYTE *velem2)
{
int elem1 = *(int*)velem1;
int elem2 = *(int*)velem2;
if(elem1<elem2)
return -1;
if(elem1>elem2)
return 1;
return 0;
}
int __stdcall CompareStrings(const BYTE *velem1, const BYTE *velem2)
{
const char *elem1 = (char*)velem1;
const char *elem2 = (char*)velem2;
return strcmp(elem1,elem2); //elem1 < elem2 return 小于0,elem1>elem2 return 大于0,elem1=elem2 return 0,
}
/*===================================================================
简单插入排序:在排序过程中把待排的元素插入到已排好的序列中。
稳定的插入排序,非常简单,常和其它排序一起使用(元素数目少的情况)。
===================================================================*/
void __stdcall Insertsort(BYTE *array, int size, int elem_size, CompareFunction cmpFunc)
{
int i,j;
BYTE *pTemp = new BYTE[elem_size];
for(i=1; i<size; i++)
{
memcpy(pTemp, array+i*elem_size, elem_size);//临时空间保存待插入元素。
for(j=i; (-1==(*cmpFunc)(pTemp, array+(j-1)*elem_size)) && j>0; j--)
{//待插入的元素插入到有序表的合适位置。
memcpy(array+j*elem_size, array+(j-1)*elem_size, elem_size); //非递减排序,元素后移。
}
memcpy(array+j*elem_size, pTemp, elem_size);//插入到正确位置。
}
delete []pTemp;
}
/*==========================================================================================
Shell(希尔)排序:缩小增量排序,先将整个待排记录序列分割成若干子序列分别进行插入排序,
待整个序列中的记录“基本有序”,在对全体记录进行一次直接插入排序。
不稳定的插入排序,关键是增量序列的选取delta[i],时间复杂度为:O(N^(1+x))0<x<1。(严数据结构版本)
===================================================================*/
void __stdcall ShellInsert(BYTE *array, int size, int elem_size, int delta, CompareFunction cmpFunc)
{
int i,j;
BYTE *pTemp = new BYTE[elem_size];
for(i=delta; i<size; i++)
{
if((-1==(*cmpFunc)(array+i*elem_size, array+(i-delta)*elem_size)))
{
memcpy(pTemp, array+i*elem_size, elem_size);//临时空间保存待插入元素。
for(j=i-delta; (-1==(*cmpFunc)(pTemp, array+j*elem_size)) && j>=0; j-=delta)
{//待插入的元素插入到有序表的合适位置。
memcpy(array+(j+delta)*elem_size, array+j*elem_size, elem_size); //非递减排序,元素后移。
}
memcpy(array+(j+delta)*elem_size, pTemp, elem_size);//插入到正确位置。
}
}
delete []pTemp;
}
/*===============================================================================
Shell(希尔)排序:也叫缩小增量排序.它通过比较相距一定间隔的元素来工作;
各趟比较所用的距离随着算法的进行而减小,直到只比较相邻元素的最后一趟排序为止.
分别使用Hibbard增量 和Sedgewick 增量方法。
============================================================*/
void __stdcall Shellsort(BYTE *array, int size, int elem_size, CompareFunction cmpFunc, bool useSedgewickInc =false)
{
int Increment;
if (!useSedgewickInc)
{
//Hibbard增量 奇数序列 时间复杂度:o(N^(3/2))
for(Increment = size/2,Increment -= (Increment%2)?0:1; Increment >0; Increment /= 2)
{
ShellInsert(array,size,elem_size,Increment,cmpFunc);
}
}else{
//Sedgewick Hibbard增量 奇数序列 时间复杂度:o(N^(4/3))
int sedgewick[] = {
1073643521, 603906049, 268386305, 150958081, 67084289,
37730305, 16764929, 9427969, 4188161, 2354689,
1045505, 587521, 260609, 146305, 64769,
36289, 16001, 8929, 3905, 2161,
929, 505, 209, 109, 41,
19, 5, 1, 0};
int k;
int k_cnt = sizeof(sedgewick) / sizeof(int);
for(k=0;k<k_cnt,sedgewick[k]>size;k++);
for (Increment=sedgewick[k]; k<k_cnt; Increment=sedgewick[++k])
{
ShellInsert(array,size,elem_size,Increment,cmpFunc);
}
}
}
/*=========================================================================================
冒泡排序:在排序过程中总是小数往前放,大数往后放,相当于气泡往上升。稳定的交换排序。
对于已排好序的元素序列,无需冒泡了,防止做无用功。
=============================================================*/
void __stdcall Bubblesort(BYTE *array, int size, int elem_size, CompareFunction cmpFunc)
{
bool bFinished = false;
for(int i=0; i<size && !bFinished; i++)
{
bFinished = true;
for(int j=0; j<size-i-1; j++)
{
if(1==(*cmpFunc)(array+j*elem_size, array+(j+1)*elem_size))
{
swap(array+j*elem_size, array+(j+1)*elem_size, elem_size);
bFinished = false;
}
}
}
}
/*=========================================================================================
鸡尾混合排序(冒泡排序的改进版):在排序过程中交替改变扫描方向。
即先从上扫到下,记录最后一个交换的位置,再从下扫到上,来回地进行扫描。
详情见:http://www.cnblogs.com/FlyingBread/archive/2007/01/26/630674.html
====================================================================================*/
void __stdcall DoubleBubblesort(BYTE *array, int size, int elem_size, CompareFunction cmpFunc)
{
int index, i;
int begin = 0;
int end = size-1;
index = begin;
while(begin<end)
{
for(i=begin; i<end; i++)//从上向下扫描
{
if(1==(*cmpFunc)(array+i*elem_size, array+(i+1)*elem_size))
{
swap(array+i*elem_size, array+(i+1)*elem_size, elem_size);
index = i;
}
}
end = index;//记录最后一次交换位置 -> end;
for(i=end; i>begin; i--)//从下向上扫描
{
if(1==(*cmpFunc)(array+(i-1)*elem_size, array+i*elem_size))
{
swap(array+i*elem_size, array+(i-1)*elem_size, elem_size);
index = i;
}
}
begin = index;//记录最后一次交换位置 -> begin;
}
}
/*===============================================================================================
Hoare提出的快速排序版本:以第一个元素为主元,双向扫描数组。(国内教材上一般所用的通用版本)
HOARE-PARTITION(A, p, r)
1 x ← A[p]
2 i ← p - 1
3 j ← r + 1
4 while TRUE
5 do repeat j ← j - 1
6 until A[j] ≤ x
7 repeat i ← i + 1
8 until A[i] ≥ x
9 if i < j
10 then exchange A[i] <-> A[j]
11 else return j
===================================================================================*/
int __stdcall Hoare_partition(BYTE *array, int elem_size, int low, int hight, CompareFunction cmpFunc)
{
int i = low;
int j = hight;
//BYTE *pKey = array+i*elem_size //错误。pKey所指内存空间中的值在变化。所以需要一份临时拷贝。
BYTE *pKey = new BYTE[elem_size];
memcpy(pKey,array+i*elem_size,elem_size);
while(i<j)
{
while(((-1!=(*cmpFunc)(array+j*elem_size, pKey)) && i<j )) j--;
memcpy(array+i*elem_size, array+j*elem_size, elem_size);
//swap(array+i*elem_size, array+j*elem_size, elem_size);
while(((1!=(*cmpFunc)(array+i*elem_size, pKey)) && i<j )) i++;
memcpy(array+j*elem_size, array+i*elem_size, elem_size);
//swap(array+i*elem_size, array+j*elem_size, elem_size);
}
memcpy(array+i*elem_size, pKey, elem_size);
delete []pKey;
return i;
}
/*====================================================================================================
N.Lomuto又提出了一种新的版本,此版本以最后一个元素为主元,优化了PARTITION程序,算法导论有介绍。
PARTITION(A, p, r)
1 x ← A[r] //以最后一个元素,A[r]为主元
2 i ← p - 1
3 for j ← p to r - 1 //注,j从p指向的是r-1,不是r。
4 do if A[j] ≤ x
5 then i ← i + 1
6 exchange A[i] <-> A[j]
7 exchange A[i + 1] <-> A[r]
8 return i + 1
================================================================================*/
int __stdcall Lomuto_partition(BYTE *array, int elem_size, int low, int hight, CompareFunction cmpFunc)
{
int i = low-1;
int j = low;
BYTE *pKey = new BYTE[elem_size];
memcpy(pKey,array+hight*elem_size,elem_size);
while (j<hight)
{
if ( -1==(*cmpFunc)(array+j*elem_size, pKey) )
{
i++;
swap(array+i*elem_size, array+j*elem_size, elem_size);
}
j++;
}
swap(array+(i+1)*elem_size, array+hight*elem_size, elem_size);
return i+1;
}
inline int random(int low, int hight)
{
int size = hight - low + 1;
return low + rand()%size;
}
/*========================================================================
Random_partition:随机取的元素与第一个元素交换后,与上面实现的版本情况一样。
=============================================*/
int __stdcall Random_partition(BYTE *array, int elem_size, int low, int hight, CompareFunction cmpFunc)
{
int rand = random(low,hight);
swap(array+rand*elem_size, array+low*elem_size, elem_size);//随机元素与第一个元素交换,然后以第一元素与主元。
BYTE *pKey = new BYTE[elem_size];
memcpy(pKey,array+low*elem_size,elem_size);
int i = low;
for (int j=low+1; j<=hight; j++)
{
if (1!=(*cmpFunc)(array+j*elem_size, pKey))
{
i++;
swap(array+i*elem_size, array+j*elem_size, elem_size);
}
}
swap(array+i*elem_size, array+low*elem_size, elem_size);
delete []pKey;
return i;
}
/*========================================================================
三数取中分割法:
采取data[low],data[mid],data[hight]三者之中的那个第二大的元素为主元时,
便能尽最大限度保证快速排序算法不会出现O(N^2)的最坏情况(已经经验证明)。
=============================================*/
int __stdcall ThreeM_partition(BYTE *array, int elem_size, int low, int hight, CompareFunction cmpFunc)
{
//选择data[low],data[mid],data[hight]三者之中第二大元素放在第一位置为主元。
int mid = (low+hight)/2;
if (-1==(*cmpFunc)(array+low*elem_size, array+mid*elem_size))
{
swap(array+low*elem_size, array+mid*elem_size, elem_size);
}
if (-1==(*cmpFunc)(array+hight*elem_size, array+mid*elem_size))
{
swap(array+hight*elem_size, array+mid*elem_size, elem_size);
}
if (-1==(*cmpFunc)(array+hight*elem_size, array+low*elem_size))
{
swap(array+low*elem_size, array+hight*elem_size, elem_size);
}
//swap(array+mid*elem_size, array+low*elem_size, elem_size);//第二元素与第一个元素交换,然后以第一元素与主元。
BYTE *pKey = new BYTE[elem_size];
memcpy(pKey,array+low*elem_size,elem_size);
int i = low;
for (int j=low+1; j<=hight; j++)
{
if (1!=(*cmpFunc)(array+j*elem_size, pKey))
{
i++;
if(i!=j)
swap(array+i*elem_size, array+j*elem_size, elem_size);
}
}
swap(array+i*elem_size, array+low*elem_size, elem_size);
return i;
}
/*=======================================================================================
快速排序:
算法由四步组成:(国内教材)
1. 如果S中元素个数是0或1,则返回
2. 取S中任一元素v,称之为枢纽元
3. 将S-{v}分成两个不相交的集合:S1 = {x∈S - {v},x≦v}和S2 = {x∈S - {v},x≧v}
4. 返回quicksort(S1)后,继随v,继而quicksort(S2)
一种安全的方针是随机选取枢纽元.但随机数的生成非常昂贵,根本减少不了算法其余部分的平均运行时间
推荐使用三数中值分割法。
最好:o(NLogN) 平均:o(NLogN) 最差o(N^2)
类型:内部排序(不稳定)
复杂度: o(NLogN)
参考资料:http://blog.csdn.net/ddddfw888/article/details/5783262
http://blog.csdn.net/v_JULY_v/article/details/6262915
===============================================================================*/
void __stdcall Quicksort(BYTE *array, int elem_size, int low, int hight, CompareFunction cmpFunc)
{
if(low<hight)
{
int k = ThreeM_partition(array,elem_size,low,hight,cmpFunc);
//int k = Random_partition(array,elem_size,low,hight,cmpFunc);
Quicksort(array,elem_size,low,k-1,cmpFunc);
Quicksort(array,elem_size,k+1,hight,cmpFunc);
}
}
/*==============================================================================
堆(Heap):将一维数组看成是一个完全二叉树,则堆的含义为:完全二叉树中所有
非终端结点的值均不大于(或不小于)其左右孩子结点的值。
HeapAdjust函数:只需要一个记录大小的辅助空间,来将组数调节为一个堆结构。
=============================================================================*/
void __stdcall HeapAdjust(BYTE *array, int start, int end, int elem_size, CompareFunction cmpFunc)
{
/**array[start.....end], 0<=start<=end<=(数组元素数目-1);*/
BYTE *pKey = new BYTE[elem_size];
memcpy(pKey,array+start*elem_size,elem_size);
for (int j=2*start+1; j<=end; j=2*j+1){//沿着元素较大的孩子结点向下筛选。
if( j<end && (-1==(*cmpFunc)(array+j*elem_size, array+(j+1)*elem_size)) ) j++; //j为元素大的下标。
/*start记录插入位置,最终将pKey所指的元素插入到start位置上。*/
if( -1!=(*cmpFunc)(pKey, array+j*elem_size) ) break;
memcpy(array+start*elem_size, array+j*elem_size, elem_size);
start = j;
}
memcpy(array+start*elem_size, pKey, elem_size);//将pKey所指的元素插入到start位置上。
delete []pKey;
}
/*========================================================================
堆排序(heap sort):J.Willioms 1964年提出的,这里将数组非递减排序为例:
首先将数组建立大顶堆,再输出最大堆的最大元素后,
然后使得剩余n-1个元素的序列重新建立一个最大堆,如此反复;
则得到一个有序序列,这个过程称为堆排序。
时间复杂度: o(NLogN) 不稳定选择排序。
=============================================*/
void __stdcall Heapsort(BYTE *array, int size, int elem_size, CompareFunction cmpFunc)
{
int i;
for (i=(size-1)/2; i>=0; i--)
{//把元素数组建成大顶堆。
HeapAdjust(array,i,size-1,elem_size,cmpFunc);
}
for (i=size-1; i>0; i--)
{
swap(array,array+i*elem_size,elem_size);//将堆顶元素和未经排序子序列array[0...i]中最后一个元素交换
HeapAdjust(array,0,i-1,elem_size,cmpFunc);//将array[0...i-1]重新调整为大顶堆。
}
}
/*======================================================================================================
merge(合并)算法:将有序表array[start...mid]和array[mid+1...end]归并为有序临时表temp[start...end];
然后将临时有序表中的元素赋值给array[start...end]。
对于2个链表的合并算法与这个一样,但是辅助空间为O(1).
==============================================================================*/
void __stdcall Merge(BYTE *array, BYTE *temp, int start, int mid, int end, int elem_size, CompareFunction cmpFunc)
{
int j,k;
int elem_num = end-start+1;
int start_pos = start;
for (j=mid+1,k=start; start<=mid && j<=end; k++)
{
if( -1==(*cmpFunc)(array+start*elem_size, array+j*elem_size) )
{
memcpy(temp+k*elem_size, array+start*elem_size, elem_size);
start++;
}else
{
memcpy(temp+k*elem_size, array+j*elem_size, elem_size);
j++;
}
}
if (start<=mid)
{//将剩余的array[start...mid]复制到temp[k...end]中。
while (k<=end && start<=mid)
{
memcpy(temp+k*elem_size, array+start*elem_size, elem_size);
start++;
k++;
}
}
if (j<=end)
{//将剩余的array[j...end]复制到temp[k...end]中。
while (k<=end && j<=end)
{
memcpy(temp+k*elem_size, array+j*elem_size, elem_size);
j++;
k++;
}
}
//cout<<"debug:"<<endl;
for(int i=0; i<elem_num; i++)
{//将排好序的temp[start...end] -> array[start...end]。
memcpy(array+(i+start_pos)*elem_size, temp+(i+start_pos)*elem_size, elem_size);
//cout<<*(int *)(array+(i+start_pos)*elem_size)<<endl;
}
}
/*===================================================================================
2-路归并排序(采用递归算法):
假设有10个待排元素array[0...9]。将其进行2路归并如下所示(用元素的下标表示):
0 1 2 3 4 5 6 7 8 9。
首先0和1merge 然后和 2 merge. 3和4merge,5和6 merge 然后和7 merge. 8和9merge。
然后将0,1,2,3,4 merge ; 5,6,7,8,9 merge.
最后array[0...4]和array[5...9] merge
其他更多情况依此类推。
==============================================================================*/
void __stdcall Msort(BYTE *array, BYTE *temp, int start, int end, int elem_size, CompareFunction cmpFunc)
{
int mid;
if(start<end)
{
mid = (start+end)/2;
Msort(array,temp,start,mid,elem_size,cmpFunc);
Msort(array,temp,mid+1,end,elem_size,cmpFunc);
Merge(array,temp,start,mid,end,elem_size,cmpFunc);
}
}
/*==================================================================================
归并排序(merge sort):调用【n/2h】次merge算法将array[0..n-1]中钱后相邻且
长度为h的有序段进行两两归并,得到前后相邻,长度为2h的有序段,并放在
临时空间terp[0...n-1]中,整个归并排序需要进行ciel[logN]趟。
这里采用2-路归并排序,但是实际情况很少使用。
1956年,Lester Ford和Selmer Johnson提出。
如果对于大量磁盘数据排序处理可以参考下外排序中的多路平衡归并排序。
外排序参考:http://blog.csdn.net/v_JULY_v/article/details/6451990
时间复杂度: o(NLogN) 但是需要与待排记录等数量的辅助空间,
所以相对于快排和堆排序实用性差。稳定归并排序。(严版本)
=====================================================================*/
void __stdcall Mergesort(BYTE *array, int size, int elem_size, CompareFunction cmpFunc)
{
BYTE *temp = new BYTE[size*elem_size];//分配辅助空间。
if(temp!=NULL)
{
Msort(array,temp,0,size-1,elem_size,cmpFunc);
delete []temp;
}else
{
cerr<<"No enough space for temp array~!"<<endl;
}
}
//=================================Radixsort(基数排序/严蔚敏版本)===============================
const int IntRadix = 10;//十进制数字为10个关键字0..9
const int StrRadix = 26;//字母为26个关键字a..z
/*动态链表*/
typedef struct Node{
BYTE *data;
struct Node *next;
}QNode, *QueuePtr;
typedef struct{
QueuePtr front; //对头指针
QueuePtr end; //对尾指针。
}LinkQueue;
/*静态链表(已分配空间)*/ //这里采用静态链表来作为基数排序的辅助内存数据结构。
#define MAX_NUM_OF_KEY 8 //关键字项数的最大值(d的最大值,8位数)
#define MAX_SPACE 10000
typedef char KeysType;/* 定义关键字类型为字符型 */
typedef struct{
KeysType keys[MAX_NUM_OF_KEY]; //关键字数组
int next; //伪指针,初始时记录下个元素的下标。0->1->2->3->4.....
}SLCell;
typedef struct{
SLCell r[MAX_SPACE]; //静态链表分配的空间
int keynum; //记录的当前关键字个数
int recnum; //静态链表的当前长度
}SLList; /*静态链表*/
#define SLIST_SIZE sizeof(SLList) /* 静态链表的空间大小 */
typedef int pArrType[IntRadix]; /* 指向int型(下标)指针数组类型 */
/*====================================================================
初始化:初始化静态链表SLlist(把数组array中的数据存于SLlist中)
链表中的10进制整数用字符来保存,方便其key值的分配。
这里采用比较方法的回调函数主要是选出元素的最大值,
以便计算元素的关键字的个数的keynum。
================================================================*/
void __stdcall int_InitList(SLList *SLlist, BYTE* array, int elem_size, int size, CompareFunction cmpFunc=&CompareInts)
{
memset(SLlist,0,SLIST_SIZE);
KeysType c[MAX_NUM_OF_KEY] = {0}; //辅助的关键字数组
KeysType c1[MAX_NUM_OF_KEY] = {0};//临时用于转换的关键字数组
int i,j;
BYTE *max = new BYTE[elem_size];
memcpy(max, array, elem_size); /* max为关键字的最大值 */
for(i=1; i<size; i++)
{
if( -1==(*cmpFunc)(max, array+i*elem_size) )
{
memcpy(max, array+i*elem_size, elem_size);
}
}
double log_10 = log10((double)*(int*)max);
SLlist->keynum = (int)(ceil(log_10));
SLlist->recnum = size;
for(i=1; i<=size; i++)
{//r[0]为头结点,无需对其keys赋值。
itoa(*(int*)(array+(i-1)*elem_size),c,10); /* 将10进制整型转化为字符型,存入c */
for(j=strlen(c); j<SLlist->keynum; j++) /* 若c的长度<max的位数,在c前补'0' */
{
strcpy(c1,"0");
strcat(c1,c);
strcpy(c,c1);
}
/*将转换的字符串存入静态链表中节点的关键字数组中,方便采用LSD最低位优先方法,
但后面静态链表中的元素存放到数组中有点点麻烦。*/
for(j=0; j<SLlist->keynum; j++)
SLlist->r[i].keys[j] = c[SLlist->keynum-j-1];
}
delete []max;
}
/* 返回k的映射(个位整数) */
inline int ord(char c)
{
return c-'0';
}
/*===================================================================================================
分配算法:将静态链表中的元素分配到相关关键字的队列(桶)中。(注意r[0]为头结点)
静态键表SLlist的r域中记录已按(keys[0],...,keys[i-1])有序。(采用LSD方法)
按第i个关键字keys[i]建立RADIX个子表,使同一子表中记录的keys[i]相同。
f[0..RADIX-1]和e[0..RADIX-1]分别指向各子表中第一个和最后一个记录(这里采用IntRadix)
参数:i为关键字。 f为:指向链表的头,e为:指向链表的尾。SLCell:静态链表的节点(已分配有MAX_SPACE)个节点。
=======================================================================================================*/
void __stdcall Distribute(SLCell r[],int i,pArrType f,pArrType e)
{
int j,p;
for(j=0;j<IntRadix;++j)
f[j]=0; /* 各子表初始化为空表 */
for(p=r[0].next; p; p=r[p].next)
{
j=ord(r[p].keys[i]); /* ord将记录中第i个关键字映射到[0..RADIX-1] */
if(!f[j])
f[j]=p;
else
r[e[j]].next=p;
e[j]=p; /* 将p所指的结点插入第j个子表中 */
}
}
/* 求后继函数 */
inline int succ(int i)
{
return ++i;
}
/*======================================================================
收集算法:按keys[i]自小至大地将f[0..RADIX-1]所指各子表依次链接成
一个链表,e[0..RADIX-1]为各子表的尾指针。
======================================================*/
void __stdcall Collect(SLCell r[],pArrType f,pArrType e)
{
int j,t;
for(j=0;!f[j];j=succ(j)); /* 找第一个非空子表,succ为求后继函数 */
r[0].next=f[j]; /* r[0].next指向第一个非空子表中第一个结点 */
t=e[j];
while(j<IntRadix-1)
{
for(j=succ(j);j<IntRadix-1&&!f[j];j=succ(j)); /* 找下一个非空子表 */
if(f[j])
{ /* 链接两个非空子表 */
r[t].next=f[j];
t=e[j];
}
}
r[t].next=0; /* t指向最后一个非空子表中的最后一个结点 */
}
/* 按链表输出静态链表 */
void printl(SLList L)
{
int i=L.r[0].next;
int j;
while(i)
{
for(j=L.keynum-1;j>=0;j--)
printf("%c",L.r[i].keys[j]);
printf(" ");
i=L.r[i].next;
}
}
/*旋转字符串*/
char* whirlStr(char *str)
{
assert(str!=NULL);
int len = strlen(str);
int i,j;
for(i=0,j=len-1; i!=j; i++,j--)
{
std::swap(str[i],str[j]);
}
return str;
}
/* 将静态链表的值存放到数组中,(这里只对int型数据进行了处理:atoi)其他类型待扩展。 */
void __stdcall int_getArray(SLList L, BYTE *array, int size, int elem_size)
{
int i=L.r[0].next;
int j=0;
int keys;
while(i)
{
keys = atoi( whirlStr(L.r[i].keys) );
//下面语句错误,静态链表是通过对next的改变,也就是说对下标的改变,其存储顺序还是没变。
//memcpy(array+(i-1)*elem_size, (BYTE *)&keys, elem_size);
memcpy(array+j*elem_size, (BYTE *)&keys, elem_size);
#ifdef DEBUG
cout<<*(int *)(array+j*elem_size)<<" ";
if(j==size-1) cout<<'\n';
#endif
j++;
i=L.r[i].next;
}
}
/*=============================================================================================
Radixsort(基数排序):将其分为"分配"和"收集"操作,重复关键字数目keynum次(即d次)。
SLlist是采用静态链表表示的顺序表。对SLlist作基数排序,
使得SLlist成为按关键字自小到大的有序静态链表,注意:SLlist.r[0]为头结点,
因为采用多关键字比较,不涉及元素的比较,所以不采用比较方法的回调。
========================================================*/
void __stdcall Radixsort(BYTE *array, int size, int elem_size)
{
SLList SLlist;
int_InitList(&SLlist,array,elem_size,size);
int i;
pArrType f,e;
/* 将SLlist改造为静态链表,最后一个元素的下个元素指向0,相当于一个循环静态链表 */
for(i=0; i<SLlist.recnum; ++i)
SLlist.r[i].next=i+1;
SLlist.r[SLlist.recnum].next=0;
/* 按最低位优先(LSD)依次对各关键字进行分配和收集 */
for(i=0;i<SLlist.keynum;++i)
{
Distribute(SLlist.r,i,f,e); /* 第i趟分配 */
Collect(SLlist.r,f,e); /* 第i趟收集 */
#ifdef DEBUG
/*debug*/
printf("采用LSD(最低位优先)方法分配:\n");
printf("第%d趟收集后:\n",i+1);
printl(SLlist);
printf("\n");
#endif
}
/*将排好序的静态链表SLlist的元素存入数组array中*/
int_getArray(SLlist, array, size, elem_size);
}
/*=============================================================================================
Binaryfind(而分查找算法):在已排好的数组中查找相应关键字,找到返回其位置,没找到则为0
时间复杂度:O(logn)
========================================================*/
int __stdcall Binaryfind(BYTE *array, int size, int elem_size, int low, int high, BYTE *Fkey, CompareFunction cmpFunc)
{
int mid;
while(low<=high)
{
mid = low + (high-low)/2;
if (-1==(*cmpFunc)(Fkey,array+mid*elem_size))
{
high = mid-1;
}
if (1==(*cmpFunc)(Fkey,array+mid*elem_size))
{
low = mid+1;
}
if (0==(*cmpFunc)(Fkey,array+mid*elem_size))
{
return mid+1;//返回所在位置。
}
}
return 0;
}
/**************************************************************************************/
/* ==================test sort algorithm~!================================
/* Every sort algorithm all are tested by myself
/* U can test the large-scale data files so that U need to write some
/* codes with using I/O stream.
/* If U have some questions , U can send E-mail to me, thank for your support
/* My E-mail:weege@126.com
/**********************************************************************************/
int main()
{
//====================================test int=============================
int array[] = {42123,2323,111,22321,10823,44,22,11,333,11234};
cout<<"before sorting ints by using Bubblesort"<<endl;
for(int i=0; i<10; i++)
{
cout<<array[i]<<endl;
}
/*test Quicksort*/
//Quicksort((BYTE*)array,sizeof(array[0]),0,9,&CompareInts);
/*test Insertsort and Shellsort*/
//Insertsort((BYTE*)array,10,sizeof(array[0]),&CompareInts);
//Shellsort((BYTE*)array,10,sizeof(array[0]),&CompareInts);
//Shellsort((BYTE*)array,10,sizeof(array[0]),&CompareInts,true);
/*test Heapsort*/
//Heapsort((BYTE*)array,10,sizeof(array[0]),&CompareInts);
/*test Mergesort*/
//Mergesort((BYTE*)array,10,sizeof(array[0]),&CompareInts);
/*test Radixsort*/
Radixsort((BYTE*)array, 10, sizeof(array[0]));
/*test Bubblesort and DoubleBubblesort */
//Bubblesort((BYTE*)array,10,sizeof(array[0]),&CompareInts);
//DoubleBubblesort((BYTE*)array,10,sizeof(array[0]),&CompareInts);
cout<<"after sorting "<<endl;
for(int i=0; i<10; i++)
{
cout<<array[i]<<endl;
}
int Fkey = 10823;
int sum = Binaryfind((BYTE*)array,10,sizeof(array[0]),0,9,(BYTE *)&Fkey,&CompareInts);
cout<<"find sum:"<<sum<<endl;
//====================================test string=============================
const char str[5][10] = {"estella","danielle","crissy","bo","angie"};
cout << "Before sorting strings with Quicksort\n";
for(int i=0; i<5; i++)
{
cout << str[i] << '\n';
}
/*test Bubblesort and DoubleBubblesort*/
//Bubblesort((BYTE*)str, 5, 10, &CompareStrings);
//DoubleBubblesort((BYTE*)str, 5, 10, &CompareStrings);
/*test Heapsort*/
//Heapsort((BYTE*)str, 5, 10, &CompareStrings);
/*test Insertsort and Shellsort*/
//Insertsort((BYTE*)str, 5, 10, &CompareStrings);
//Shellsort((BYTE*)str, 5, 10, &CompareStrings);
//Shellsort((BYTE*)str, 5, 10, &CompareStrings,true);
/*test Mergesort*/
//Mergesort((BYTE*)str, 5, 10, &CompareStrings);
/*test Quicksort*/
Quicksort((BYTE*)str, 10, 0, 4, &CompareStrings);
cout << "After sorting\n";
for(int i=0; i<5; i++)
{
cout << str[i] << '\n';
}
//char c[26];
//itoa(-0001,c,10);
//cout<<c<<endl;
//int a = atoi("-00001");
//cout<<a<<endl;
system("pause");
return 0;
}