1,合并排序
合并排序,用的是分治法的思想,顾名思义,就是分而治之,这个排序的方法也有一个基本单元,这个基本单元的输入是两个有序数组,输出是一个有序的数组,这个有序数组的元素是原来 两个有序数组的集合,在有这个基本的单元的基础上,将一个待排序的数组合理的划分为单个元素的数组即此时是有序的,然后两两合并为多个有2个元素的有序数组,然后继续两两合并为多个有4个元素的有序数组,依次类推,直到只有一个有序数组为止。
如何实现这个基本单元的呢?既然是排序,无非就是比较、赋值等一些操作,以确定每一个元素所应在的位置,在具体实现上,有一个时间与空间的选择,一般实现合并,会选择一个额外的辅助空间以暂存排序的结果,这个空间大小等于输入的两个有序数组的大小之和。这样,可以避免很多移动数组元素的操作。如果不用辅助空间的话,在时间复杂度上,可能就不会令人满意了。
具体的过程就是比较两个有序数组的大小,小的放入辅助空间,被放入元素所在的数组的和辅助空间的当前关注位置加一,如果某一个数组完全放入了,另一个剩下的元素直接复制进去就好了。最后,可以将辅助空间中的结果复制进原来的两个数组组成的空间中去,以为下一次合并做好准备。
如何合理地划分待排序数组来使用上边的基本单元来实现排序呢?划分的过程中,通常数组长度从1开始,每次增长一倍,直到等于最大长度,可以用循环来遍历所有的过程,不过,最简便的一个实现是使用递归函数,因为每次的合并使用的都是同一个方法。
最后,关于辅助空间的实现,在每次合并中,需要的辅助空间的大小是原来两个有序数组的大小之和,一次合并之后,结果会转移到原来的两个数组之中,辅助空间在这次合并中使用完毕,下一次合并可以重新使用,所以辅助空间的大小以满足最大的一个合并空间要求就好,就是原来待排序数组的大小。一般可以将辅助空间的申请与释放包括在合并排序函数中。
/**************************************************
* 功能: 合并两个有序数组
* 参数:
* array: 包含要合并的数据的数组,分为前后两个部分
* i1_begin: 第一个数据的起始下标
* i1_end: 第一个的末尾
* i2_begin: 第二个起始
* i2_end: 第二个末尾
* temp: 合并需要的临时内存,大小为两个有序数组之和
* 返回:
* 0
*/
int merge_array(int array[], int i1_begin, int i1_end, int i2_begin, int i2_end, int temp[])
{
int i1 = i1_begin,
i2 = i2_begin,
i_temp = 0;
while (i1 <= i1_end && i2 <= i2_end)
{
if (array[i1] < array[i2])
{
temp[i_temp++] = array[i1++];
}
else
{
temp[i_temp++] = array[i2++];
}
}
while (i1 <= i1_end)
{
temp[i_temp++] = array[i1++];
}
while (i2 <= i2_end)
{
temp[i_temp++] = array[i2++];
}
i_temp = 0;
for (i1 = i1_begin; i1 <= i1_end; i1++)
{
array[i1] = temp[i_temp++];
}
for (i2 = i2_begin; i2 <= i2_end; i2++)
{
array[i2] = temp[i_temp++];
}
return 0;
}
/*********************************************
* 功能: 基本合并排序,非降序
* 参数:
* array: 待排序的数组
* i_begin: 第一个待排序的下标
* i_end: 最后一个待排序的下标
* 返回:
* 0
*/
int sort_merge_(int array[], int i_begin, int i_end, int temp[])
{
int index;
if (i_begin < i_end)
{
index = (i_begin + i_end) / 2;
sort_merge_(array, i_begin, index, temp);
sort_merge_(array, index + 1, i_end, temp);
merge_array(array, i_begin, index, index + 1, i_end, temp);
}
return 0;
}
/**********************************************
* 功能: 合并排序,非降序
* 参数:
* array: 数组地址
* array_num: 数组长度
* 返回:
* 0: 成功
* -1: 失败
*/
int sort_merge(int array[], int array_num)
{
int *ptr_int = 0;
if ((ptr_int = (int *)malloc(array_num * sizeof(int))) == 0)
{
return -1;
}
sort_merge_(array, 0, array_num - 1, ptr_int);
free(ptr_int);
return 0;
}
2,快速排序
快速排序,用的也是分治法的思想,是从整体到局部逐渐细化的过程。
快速排序在细化过程中,也有一个基本单元,这个单元的输入是一个待排序的数组,输出是一个被数组中一个元素至少分成两半的数组,只有一个数组且只有一个元素除外,比如一半全部小于等于这个元素,一半大于等于,同时这个进行分割的元素独立出来在中间。当然,也可以有其他分割的方式,不过细节上处理起来就不一样了,然后将这个单元输出的数组再继续分别作为这个单元的输入,直到只有一个元素的数组为止就完成了排序。
如何实现这个基本单元呢?首先,整体功能是进行分割,那么,就要确定分割的原则,也即是分成哪几个区间,大小关系如何。其中大小关系这个是个人都可以明白,不影响大的方向。一般,有两种分割的方法,一个是备份一个元素,每一次左边或右边的搜索结果会替换并成为一个一次备份点,这样最后可以使这个分割的元素独立于两个区间,如果把分割元素看成一个独立的区间的话,那么,最终可能的分割结果是两个区间或者三个区间,肯定可以完成分割任务的。另一个方法是没有备份之说,左右同时搜索,结果相互替换,不过样的话不能将分割点独立出来,最终的可能情况是分为了两个或者一个区间,如果是一个区间,那么,不能完成分割任务,并且进行递归的话,永远不会回归的。不过可以通过在分割中动态的调整分割点来避免避免一个区间分割结果的出现,一般可以用随机数来选择分割点这种方法。
如何实现排序呢?基本单元的输出作为下一个的输入,直至输出的一个分割区间只有一个元素位置就完成了排序。
这个是第一种方法的实现
/*******************************************************************
* 功能: 分割数据为,左边小于等于,右边大于等于一个关键字
* 参数:
* array: 数组地址
* begin: 要操作的第一个数据的下标
* end: 最后一个下标
* 返回:
* low: 分割点索引
*/
int partition_array_1(int array[], int begin, int end)
{
int low = begin; // 搜索时的左索引
int high = end; // 右索引
int i_key; // 关键字的索引
int key; // 关键字的备份
// 确定关键字索引,即分割点,选择好的分割点可以改善排序性能
i_key = begin;
// 使关键字交换到最左边并备份,以对随后的搜索做初始化
if (i_key != begin)
{
SWAP(array[i_key], array[begin]);
}
key = array[begin];
// 元素个数大于1的话,进行分割
while (low < high)
{
// 从右开始,遇到小于关键字的元素,或者左右搜索位置重合时,结束搜索,此时,high记录的为从左至右第一个大于关键字的元素索引,或者是最后一个被备份点的索引
while (low < high && array[high] >= key)
{
high--;
}
array[low] = array[high]; // 将右边不符合大小关系的元素转移到左边,low初始为begin,begin已被交换为关键字并备份,每次,low都为上一个被转移的元素的位置
// 从左开始搜索
while (low < high && array[low] <= key)
{
low++;
}
array[high] = array[low]; // 将左边不符合的转移至右边,每次high为上一个被转移的元素的位置
}
array[low] = key; // 将第一个备份的元素即关键字放入最后一个被备份的位置,即分割点处
return low; // 返回l分割点low,以便进行下一次分割
}
/***********************************************
* 功能: 快速排序,非降序
* 参数:
* array: 数组地址
* begin: 数据开始下标
* end: 末尾下标
* 返回:
* 0
*/
int sort_quick_1(int array[], int begin, int end)
{
int index; // 用于记录分割点索引
if (begin < end)
{
index = partition_array_1(array, begin, end);
sort_quick_1(array, begin , index - 1);
sort_quick_1(array, index + 1, end);
}
return 0;
}
第二种
#define SWAP(a, b) {\
(a) ^= (b); (b) ^= (a); (a) ^= (b); \
} // 交换的两个数据的内存地址不能相同
/*******************************************************************
* 功能: 分割数据为,左边小于等于,右边大于等于一个关键字
* 参数:
* array: 数组地址
* begin: 要操作的第一个数据的下标
* end: 最后一个下标
* 返回:
* low: 分割后右边的数组的第一个索引
*/
int partition_array_2(int array[], int begin, int end)
{
int low = begin; // 搜索时的左索引
int high = end; // 右索引
int i_key; // 关键字的索引
int key; // 关键字
i_key = rand() % (end - begin + 1) + begin;
key = array[i_key];
low--;
high++;
while (1)
{
do
{
low++;
} while (array[low] < key);
do
{
high--;
} while (array[high] > key);
if (low >= high)
{
break;
}
else
{
SWAP(array[low], array[high]);
}
}
return low;
}
/***********************************************
* 功能: 快速排序,非降序
* 参数:
* array: 数组地址
* begin: 数据开始下标
* end: 末尾下标
* 返回:
* 0
*/
int sort_quick_2(int array[], int begin, int end)
{
int index; // 用于记录分割点索引
if (begin < end)
{
index = partition_array_2(array, begin, end);
sort_quick_2(array, begin, index - 1);
sort_quick_2(array, index, end);
}
return 0;
}