1.插入排序:直接插入排序,希尔排序;
2.选择排序:直接选择排序,堆排序;
3.交换排序:冒泡排序,快速排序;
4.归并排序;(可以用于内存排序和外存(如硬盘)排序);
5.基数排序;
以下排序以升序为例.
注:这是初学者的笔记,理解可能存在不足,如有错误,请教我,谢谢.
基本思路:将一个数字key插入到一个有序数组中;利用一个指针(数字下标)从后向前比较,比key大,那么指针向前移动,继续向前比较,并且把该数字向后移动一位,为之后插入key留出空间,比key小,指针停止,将key插入到指针指向的后一位.
总结:时间复杂度O(N^2);空间复杂度O(1);稳定(稳定指相同数字相对顺序不发生改变);
1到10,依次从前向后;这里插入7,key = 7,用key来保存7的数字,方便数字移动时覆盖7的位置;
10 > 7,将10存入7的位置, 指针向前移动,继续比较,重复该过程;
直到找到比key小的数字,将key插入到此位置;
有序区间[0,end],待插入数在end+1位置;
int key = arr[end + 1];
//找到比key小的位置
while (arr[end] > arr[end + 1] && end >= 0)
{
//比key大,一次后移,为插入key留出位置
arr[end + 1] = arr[end];
end--;
}
//将key插入到end下一位
arr[end + 1] = key;
到此,完成了一次插入排序.一个完整的排序由许多次插入排序组成:
当一个无序的数字从起始位置开始,起始位置只有一个数字,可以认为是有序的,将第二位数字作为待插入数字key,插入到[0,0]这个区间,得到一个新的有序区间[0,1],重复上述,将排好序有序区间的下一位作为待插入数key,一次插入,直到整个数组有序,完成数组排序.
下面是代码,C语言
时间复杂度O(N^2);空间复杂度O(1)
//直接插入排序 时间复杂度O(N^2);空间复杂度O(1)
void InsertSort(int* arr, int n)
{
assert(arr);//空指针断言
for (int i = 0; i < n - 1; i++)
{
int end = i;
int key = arr[end + 1];
//找到比key小的位置
while (arr[end] > key && end >= 0)
{
//比key大,一次后移,为插入key留出位置
arr[end + 1] = arr[end];
end--;
}
//将key插入到end下一位
arr[end + 1] = key;
}
}
--- 分割线 | ---
基本思想:
这是对直接插入排序的优化,直接插入排序如果在1,2,3,4,5,6,7这个有序区间插入0,那么前面的数据都需要依次向后挪动,效率低;希尔排序包括预排序和排序,其中预排序可以解决这个.
将一个未排序数组按照间距gap分类,将间距为gap的数据作为一组进行直接插入排序.好处:1.数组挪动不在是一个挨着一个,挪动更快;2.预排序将数组从无序转换为有一定顺序,便于后面正式排序.
总结:时间复杂度O(N^1.3) ~ O(N^2);空间复杂度O(1),不稳定.
1.分组
2.预排序
代码:
其实就是将直接插入排序的间距1改为gap;
int gap = 3;
int key = arr[end + gap];
//找到比key小的位置
while (arr[end] > key && end >= 0)
{
//比key大,一次后移,为插入key留出位置
arr[end + gap] = arr[end];
end -= gap;
}
//将key插入到end下一位
arr[end + gap] = key;
arrayPrintf(arr, n);
预排序完成后,正常直接插入排序.
实际排序中,间距gap并不是一个固定的值,由数组长度确定,由大到小;
当gap>1时,预排序
当最后一次gap = 1的时候,就是直接插入排序,即正式排序;
void ShellSort(int* arr, int n)
{
int gap = n;
while (gap > 1)
{
gap = gap / 3 + 1;
//i++,而不是i += gap,这里是多组并排的思想,不是一组一组排序,而是同时进行
for (int i = 0; i < n - gap; i++)//是n - gap,不是n - 1 -gap
{
int end = i;
int key = arr[end + gap];
//找到比key小的位置
while (arr[end] > key && end >= 0)
{
//比key大,一次后移,为插入key留出位置
arr[end + gap] = arr[end];
end -= gap;
}
//将key插入到end下一位
arr[end + gap] = key;
}
}
}
--- 分割线 | ---
思路:遍历数组,选出最大的,与最后一位交换,继续遍历数组,选出次大的数,与倒数第二位交换,重复上述过程,直到排序完成.
总结:时间复杂度O(N^N),空间复杂度O(1),不稳定.
代码:
void SelectSort(int* arr, int n)
{
assert(arr);
int end = n - 1;
while (end > 0)
{
int maxIndex = 0;//设初始最大原下标位0,即第一个元素
//遍历数组,找出最大元素的下标
for (int i = 0; i <= end; i++)
{
if (arr[i] > arr[maxIndex])
{
maxIndex = i;
}
}
swap(&arr[maxIndex], &arr[end--]);
}
}
交换两元素代码:
//交换
void swap(int* p1, int* p2)
{
int tmp = *p1;
*p1 = *p2;
*p2 = tmp;
}
--- 分割线 | ---
基本思路:堆分为大堆和小堆;大堆:父亲节点大于等于子节点.小堆:父亲节点小于等于子节点;所以,根节点就是数组的最大值/最小值.
升序排列,选出最大的放在数组最后,选出次大值放在倒数第二位,重复上述过程,直到排序完成.
那么,按照这个思路,利用建立大堆选出最大值arr[0],与arr[end]交换,完成最大值的排序,将数组[0,end - 1]区间建大堆(由于只有root根节点不符合,左子树和右子树符合大堆,所以只用一次向下调整算法),这就是基本操作单元.然后将次大值arr[0]与arr[end - 1]交换,完成了次大值的排序.思路分析结束,接下来循环,直到排序完成.
总结:时间复杂度O(N*logN),空间复杂度O(1),不稳定.
注:关于堆的建立,放在二叉树的笔记章节,还没有写,之后补上.
代码
//向下调整算法
void adjustdown(int* arr, int root, int n)
{
int parents = root;//父节点
int chird = 2 * parents + 1;//左孩子
while (parents < n)
{
//找左右孩子中最大者
if (chird + 1 < n && arr[chird + 1] > arr[chird])
{
chird++;
}
//大的上浮,小的下沉
if (chird < n && arr[chird] > arr[parents])//注意条件:chird < n
{
swap(&arr[chird], &arr[parents]);
parents = chird;
chird = 2 * parents + 1;
}
else
{
break;
}
}
}
//堆排序
void HeapSort(int* arr, int n)
{
assert(arr);
//建立堆,向下调整算法
int chird = n - 1;
int root = (chird - 1) / 2;
while (root >= 0)
{
adjustdown(arr, root--, n);
}
//堆顶与末尾交换,调整最值到末尾,忽略最后一位,继续建堆
int size = n - 1;
while (size > 0)
{
swap(&arr[0], &arr[size]);
adjustdown(arr, 0, size);
size--;
}
}
--- 分割线 | ---
基本思路:
这应该是大家最开始学到排序方法,最熟悉.思想概况就是第一次遍历数组把最大值依次向后交换,直到最大值在最后一位.第二次遍历数组,将次大的元素依次交换到倒数第二位…重复,直到排序完成.
总结:时间复杂度O(n^2),空间复杂度O(1),稳定.
//冒泡排序
void BubbleSort(int* arr, int n)
{
assert(arr);
int i = 0;
int j = 0;
//外层,遍历次数,只用遍历n - 1次,当n - 1个数排序完成,最后一个数字也就排序完成
for (i = 0; i < n - 1; i++)
{
//内层,将相对大的数字向后交换.已经排序好的位置不用比较,小心数组越界
for (j = 0; j < n - 1 - i; j++)
{
if (arr[j] > arr[j + 1])
{
swap(&arr[j], &arr[j + 1]);
}
}
}
}
--- 分割线 | ---
基本思路:
在数组中选择一个基准Key,把比key大的数字放在key右边,把比key小的数字放在key左边.
这时数组区间就被分割为[0, keyIndex - 1] keyIndex [keyIndex, end],keyIndex时key的数组下标.
然后对左右区间继续分割,选出新的基准key,大的放右边,小的放左边.
不断分割,直到所有区间只有1个数字,1个数字可以认为是有序的,此时,排序完成.
总结:时间复杂度O(N*logN),空间复杂度O(logN)(递归的深度),不稳定(三数取中的交换).
为了逻辑清晰,单次排序分离出来单独作为函数.
使用前后指针法的单次排序思路:
//快速排序 单次排序
//前后指针法
int SortPart1(int* arr, int begin, int end)
{
//选择基准key,以end为基准
int key = arr[end];
int prev = begin - 1;
int cur = begin;
//目的:比key大的放左边,比key小的放左边
// [小于key区间] key [大于key区间]
while (cur <= end)
{
//cur遇到比key小的值就停下,prev++,然后交换cur与prev指向的数字
//cur后面连续跟着大于key的数字,prev后面跟着小于等于key的数字
//最后一次交换keyIndex指向的key,所以遍历完成后prev指向数字是key
//就像两个火车头: 小于等于key...(key)prev 大于key...cur
while (arr[cur] > key && cur <= end)
{
cur++;
}
if (arr[cur] <= key && cur <= end)
{
swap(&arr[++prev], &arr[cur]);
cur++;
}
}
return prev;
}
//快速排序
void QuickSort(int* arr, int begin ,int end)
{
assert(arr);
if (begin >= end)//结束递归条件
{
return;
}
int div = SortPart1(arr, begin ,end);
//[0, div -1] div [div + 1,end]
//递归,继续分割排序左右区间,结构类似二叉树,前序
QuickSort(arr, begin, div - 1);
QuickSort(arr, div + 1, end);
}
由于切分区间的基准key是数组的最后一个,当整个数组都有序(逆序)时,区间分割变得低效,左边[0,n-2],右边[n-1];
程序的运行次数变多,时间复杂度增大,为了解决这个问题,需要选择一个相对中间大小的数作为基准key,这里用到一个三数取中函数:获取数组起始位置,中间位置,末尾三个数中不大不小的值.
三数取中代码:
//三数取中
int GetMidNumber(int* arr, int begin, int end)
{
int left = begin;
int mid = (begin + end) / 2;
int right = end;
if (arr[left] > arr[mid])
{
if (arr[mid] > arr[right])
{
return mid;
}
else if(arr[right] > arr[left])
{
return left;
}
else
{
return right;
}
}
else // mid => left
{
if (arr[right] > arr[mid])
{
return mid;
}
else if (arr[left] > arr[right])
{
return left;
}
else
{
return right;
}
}
}
接下来,在单次排序函数SortPart1()的最开始加上以下代码,完成三数取中,提高效率.
//三数取中,将相对中间大小的数放在最右边,让key取为基准
int mid = GetMidNumber(arr, begin, end);
swap(&arr[mid], &arr[end]);
当然,单次排序方法不止一种,还有挖坑法,左右指针法.
//快速排序
//单次循环,挖坑法
int SortPart1(int* arr, int begin, int end)
{
//三数取中,将相对中间大小的数放在最右边,让key取为基准
int mid = GetMidNumber(arr, begin, end);
swap(&arr[mid], &arr[end]);
assert(arr);
//选最右边为key
int key = arr[end];
int tmp = end; //挖坑,初始坑
while (begin < end)
{
//选右边为初始坑,从左边开始找
//从左边开始,找到大于等于key的值
while (arr[begin] <= key && begin < end)
{
begin++;
}
if (arr[begin] > key && begin < tmp)//&& begin < tmp
{
arr[tmp] = arr[begin];//填坑
tmp = begin; //新挖的坑
begin++;
}
while (arr[end] >= key && begin < end)
{
end--;
}
if (arr[end] < key && tmp < end)//&& tmp < end; 最后begin和end同时指向的数小于key,可以交换的情况
{
arr[tmp] = arr[end];
tmp = end;
end--;
}
}
//key填到最后的坑
arr[tmp] = key;
return tmp;
}
//快速排序
//单次循环,左右指针法
int SortPart3(int* arr, int begin, int end)
{
//基准定在右边
//左指针找到比key大的时候停下,右指针找比key小的时候停下
//然后交换左右指针指向数值
//左指针先走,这样最后指针相遇位置的数值比key大,正好可以与key交换
//三数取中,避免最坏情况,逆序
int mid = GetMidNumber(arr, begin, end);
swap(&arr[mid], &arr[end]);
int key = arr[end];
int left = begin;
int right = end - 1;
// left ... right key
while (left < right)//left < right;别写错,不是left < end
{
//左指针找大
while (arr[left] <= key && left < right)//等于key的情况不用管,之后区间细分的时候等于的就变最大值,会升序
{
left++;
}
//右指针找小
while (arr[right] >= key && left < right)
{
right--;
}
//交换
if (arr[left] > key && arr[right] < key)
{
swap(&arr[left], &arr[right]);
//left++;
//right--;
}
}
//交换key到左右指针相遇处
swap(&arr[left], &arr[end]);
return left;
}
上面的三种单次排序方法在写代码前最好先画图确定思路,明确细节,不然很容易出错.
当然,除了三数取中的优化,还有小区间优化,当区间分割得比较小时,继续分割性价比没有那么高,(递归深度增加,函数调用增加,需要排序数字相对少),所以小区间可以直接排序,不在分割.
优化后的快速排序:
//快速排序
//选定一个基准值key,比key小的放在左边,比key大的放在右边
//然后排序左边和右边,就像二叉树
//直到不可再分割
// 1.0 时间复杂度O(N*logN) 空间复杂度O(logN),递归的深度
void QuickSort(int* arr, int begin, int end)
{
assert(arr);
//递归结束条件,区间不能再分
if (begin >= end)
{
return;
}
//小区间优化,减少递归深度
//排序[arr + begin ,arr + end]区间的数字
if (end - begin + 1 < 10)
{
//直接插入排序
InsertSort(arr + begin, end - begin + 1);//注意:这里的排序是从arr + begin开始,不能是arr,arr一直不变
return;
}
//递归,排序;排序左半边和右半边
int div = SortPart3(arr, begin, end);
QuickSort(arr, begin, div - 1);
QuickSort(arr, div + 1, end);
}
当然,单次排序也可以放在快速排序函数内部,单次排序单独出来的目的主要还是为了逻辑清晰,简单.
//2.0 快速排序,减少子函数版本
void QuickSort(int* arr, int begin, int end)
{
assert(arr);
//递归结束条件,区间不能再分
if (begin >= end)
{
return;
}
//递归,排序;排序左半边和右半边
int mid = GetMidNumber(arr, begin, end);
swap(&arr[mid], &arr[end]);
//选最右边为key
int key = arr[end];
int tmp = end; //挖坑,初始坑
int left = begin, right = end;
while (left < right)
{
//选右边为初始坑,从左边开始找
//从左边开始,找到大于等于key的值
while (arr[left] <= key && left < right)
{
left++;
}
if (arr[left] > key)
{
arr[tmp] = arr[left];//填坑
tmp = left; //新挖的坑
left++;
}
while (arr[right] >= key && left < right)
{
right--;
}
if (arr[right] < key)
{
arr[tmp] = arr[right];
tmp = right;
right--;
}
}
arr[tmp] = key;
int div = tmp;
QuickSort(arr, begin, div - 1);
QuickSort(arr, div + 1, end);
}
递归主要用到了栈的后进先出性质保存变量,非递归写法就需要自己实现一个数据结构栈(C语言没有提供数据结构栈,所以需要自己写),将分割后的区间[begin,end]两个端点下标存入栈,利用循环模拟实现递归.
代码实现:
//3.0 快速排序(非递归) 2.0
void QuickSortNonR(int* arr, int begin, int end)
{
assert(arr);
//assert(begin < end);
stack s;
StackInit(&s);
//[begin,end]
StackPush(&s, end);
StackPush(&s, begin);
while (!StackEmpty(&s))
{
int left = StackTop(&s);
StackPop(&s);
int right = StackTop(&s);
StackPop(&s);
//利用数据库栈模拟递归,排序;排序左半边和右半边
int div = SortPart1(arr, left, right);
//[left , div - 1] div [div + 1,right]
if (div + 1 < right)
{
StackPush(&s, right);
StackPush(&s, div + 1);
}
if (left < div - 1)
{
StackPush(&s, div - 1);
StackPush(&s, left);
}
}
//别忘了free栈
StackDestroy(&s);
}
栈的接口函数按照你自己实现的方法调用.
--- 分割线 | ---
基本思路:
从两个有序数组中依次选出较小值归并到新的数组,当两个数组遍历完成,得到的新数组就是两个数组结合后的升序排列.
由于通常待排序的数组不是有序数组,所以可以采用分治的思想,把问题拆分,连续的数值并不有序,那么就把数值拆分,例如:将1个带有n个元素的数组(1个[0, n-1]的区间.),拆分为n个只有一个元素的数组(n个[0]的区间).后者两两就可以归并为有序数组.
总结:空间换取时间,外排序,空间复杂度O(N),时间复杂度O(N*logN),稳定;
思路图:
将两个数组归并排序,即从两个数组中选出较小值放入一个临时空间tmp,归并排序好后再将tmp中有序的数组拷贝回原数组arr,为下一次归并做基础.
递归拆分后的逻辑单元是 ==> 从arr中两个有序的数组中抽取两个数字依次放入tmp排序.排序好有序的数组是arr接下来继续归并到tmp的基础,所以需要拷贝tmp到arr.
代码,因为需要申请空间,母程序不适合递归,所以这里用到一个子程序来递归.
(当然,也可以设置一个flag的静态变量用于第一次申请空间的标志,然后改变flag值,之后不再申请空间,排序完成后再初始化flag值,不过这样操作似乎比较麻烦,所以没有使用)
从上图可以看到,归并排序由几个步骤:
1.将一个无序数组拆分,直到不可拆解(只有一个元素)
2.将两个有序的数组归并排序到临时空间tmp
3.将tmp排序好的数组拷贝回原数组arr对应位置
小单元归并成大单员,大单元继续归并,直到排序完成
//归并排序
void MergeSort(int* arr, int begin, int end)
{
//临时空间存储归并后数据
int* tmp = (int*)malloc(sizeof(int) * (end - begin + 1));
if (NULL == tmp)
{
perror("malloc error");
exit(-1);
}
//1.拆分 2.归并
//begin end
_MergeSort(arr, tmp, begin, end);
//释放tmp
free(tmp);
tmp = NULL;
}
用于递归的子程序:
//代码优化 1.0
//归并排序子程序
void _MergeSort(int* arr, int* tmp, int begin, int end)
{
assert(arr);
if (begin >= end)//结束递归条件
{
return;
}
//二叉树的前序遍历 ==> 拆分
//[begin end] ==> [begin1 end1] [begin2 end2]
int begin1 = begin;
int end1 = (begin + end) / 2;
int begin2 = end1 + 1; //小细节:与if (begin >= end)相对应
int end2 = end;
_MergeSort(arr, tmp, begin1, end1);
_MergeSort(arr, tmp, begin2, end2);
//[begin1 end1] [begin2 end2]
//归并+排序
int tmpbegin = begin;
//两个有序数组中选出较小值归并到tmp数组
while (begin1 <= end1 && begin2 <= end2)
{
if (arr[begin1] < arr[begin2])
{
tmp[tmpbegin++] = arr[begin1++];
}
else
{
tmp[tmpbegin++] = arr[begin2++];
}
}
//当其中一组结束,另一组处理
while (begin1 <= end1)
{
tmp[tmpbegin++] = arr[begin1++];
}
while (begin2 <= end2)
{
tmp[tmpbegin++] = arr[begin2++];
}
//将排序好的tmp数组拷贝覆盖arr
// 因为递归拆分后的逻辑单元是 ==> 从arr中两个有序的数组中抽取两个数字依次放入tmp排序
// 排序好有序的数组是arr接下来继续归并到tmp的基础
//把每次归并后的数据放回原数组 ==> 关键:别忘了!
int i = begin;
for (i = begin; i <= end; i++)
{
arr[i] = tmp[i];
}
}
不同点:归并排序与上述排序有一点不同,归并排序可以作为外排序,再外存(硬盘)上排序.上述排序被称为内排序,只能再内存上排序.
例如,大量数据排序(超过内存存储最大值).
思路:
1.将一个大文件拆分为多个能存入内存的小文件
2.小文件在内存中排序
3.读取排序后的小文件,两两归并排序到一个新建文件tmp
4.当所有小文件归并完成,最后生成的一个tmp文件就是排序后结果
注意:
1.fscanf()的返回值判断,EOF.int ret = fscanf( , ,&num);
2.文件操作文件指针自动前移的处理
3.一边读取,一边写入(看起来)
代码:
//文件归并排序子程序:从fout1,fout2文件读取数据,一边读取一边顺序写入tmp文件
void _OutMergeSort(char* fout1, char* fout2, char* tmp)
{
FILE* f1 = fopen(fout1, "r");
FILE* f2 = fopen(fout2, "r");
FILE* f3 = fopen(tmp, "w");
//因为文件指针自动向前,不能直接用fscanf()的返回值判断文件是否结束
// (直接用可能导致文件指针前移,但上次读取数据还未使用就被覆盖)
//将fscanf()的返回值存入ret,利用ret判断,能有效控制文件指针移动
//解决一个文件中数据都比另一个文件小,文件指针不好控制问题
int num1, num2;
int ret1 = fscanf(f1, "%d\n", &num1);
int ret2 = fscanf(f2, "%d\n", &num2);
while (ret1 != EOF && ret2 != EOF)
{
if (num1 < num2)
{
fprintf(f3, "%d\n", num1);
ret1 = fscanf(f1, "%d\n", &num1);
}
else
{
fprintf(f3, "%d\n", num2);
ret2 = fscanf(f2, "%d\n", &num2);
}
}
//另一个文件剩下的数据
while (ret1 != EOF)
{
fprintf(f3, "%d\n", num1);
ret1 = fscanf(f1, "%d\n", &num1);
}
while (ret2 != EOF)
{
fprintf(f3, "%d\n", num2);
ret2 = fscanf(f2, "%d\n", &num2);
}
fclose(f1);
fclose(f2);
fclose(f3);
}
//(大)文件排序
void OutMergeSort(FILE* pf)
{
//拆分文件,直到内存能容纳
//假设拆分为10份
int arr[100] = { 0 };//拆分后存入内存
int i = 0;
int n = 100; //分割后的每个文件存储n=100个数据
char* fname[20];//分割后文件名
int fileid = 1; //分割后文件id序号
while (fscanf(pf, "%d\n", &arr[i]) != EOF)
{
if (i < n - 1)//[0 ~ n-2] 共n-1个数字;解决最后一个数据的存储问题
{ //(最后一次判断fscanf()!= EOF又存入了一个数)
i++;
}
else
{
i++; //i == n
//将分割后的文件排序:快速排序
QuickSort(arr, 0, n - 1);
//创建文件存储分割后的数据
sprintf(fname, "%d", fileid++);
//创建文件:打开,写入,关闭
FILE* fin = fopen(fname, "w");
for (int j = 0; j < i; j++)
{
fprintf(fin, "%d\n", arr[j]);
}
fclose(fin);
//初始化i
i = 0;
}
}
//当最后数据不足n个时:
//这里不用i++;最后一次读取似乎无意义,==EOF是情况,越界
sprintf(fname, "%d", fileid++);
QuickSort(arr, 0, i - 1);
FILE* fin = fopen(fname, "w");
for (int j = 0; j < i; j++)
{
fprintf(fin, "%d\n", arr[j]);
}
fclose(fin);
//归并排序
//将1,2文件归并为tmp
//直到归并到最后一个文件
char* fout1[20]; //文件名
char* fout2[20];
char* tmp[20];
sprintf(fout1, "%d", 1);
sprintf(fout2, "%d", 2);
sprintf(tmp, "1_%d.tmp", 2);
FILE* f1 = fopen(fout1, "r"); //文件指针名字
FILE* f2 = fopen(fout2, "r");
FILE* f3 = fopen(tmp, "w"); //写
_OutMergeSort(fout1, fout2, tmp);//归并排序
fclose(f1);
fclose(f2);
fclose(f3);
f1 = f2 = f3 = NULL;
//依次归并,直到分割的最后一个文件
for (int i = 3; i < fileid; i++)
{
sprintf(fout1, tmp); //将上一次归并后的文件作为这一次归并的两个文件之一
sprintf(fout2, "%d", i);
sprintf(tmp, "1_%d.tmp", i);//新创建一个文件,用于存放归并后的数据
_OutMergeSort(fout1, fout2, tmp);//归并排序
}
}
为了方便测试,添加了生成数据函数,和主函数:
//生成一个大量数据文件
void CreatFile()
{
//打开文件
FILE* pf = fopen("BigData", "w");
//写入
for (int i = 1005; i >= 0; i--)
{
fprintf(pf, "%d\n", i);
}
//关闭文件
fclose(pf);
pf = NULL;
}
int main()
{
//生成一个大量数据文件
CreatFile();
//打开文件
FILE* pf = fopen("BigData", "r");
//排序文件
OutMergeSort(pf);
//关闭文件
fclose(pf);
pf = NULL;
return 0;
}
--- 分割线 | ---
思路:
统计一个范围从min到max的数组每个数字出现的次数,出现的次数保存在一个范围num[0,Range]的新数组;其中min出现次数保存在num[0],max出现次数保存在num[Range],一一对应;num[i]记录了min+i的数字出现次数,将min+i按照出现的次数写入原数组,即可排序完成.
//只适用整数,范围集中
总结:时间复杂度O(N + Range); 空间复杂度O(Range);不稳定;
代码:
//只适用整数,范围集中
//时间复杂度O(N + Range); 空间复杂度O(Range);
void CountSort(int* a, int n)
{
//遍历,找出min,max
int min = a[0];
int max = a[0];
for (int i = 0; i < n; i++)
{
if (a[i] < min)
{
min = a[i];
}
if (a[i] > max)
{
max = a[i];
}
}
//范围
int range = max - min + 1;
int* num = (int*)calloc(range, sizeof(int)); //初始化0
//计数
for (int i = 0; i < n; i++)
{
num[a[i] - min]++; //记录min+i的数字出现次数
}
//排序
int k = 0;
for (int i = 0; i < range; i++)
{
while (num[i]-- > 0) //num[i]记录了min+i的数字出现次数
{
a[k++] = min + i;
}
}
}
--- 分割线 | ---
待续,未完成…