排序算法
内部排序
- 这里先介绍一个概念,算法稳定性
- 算法稳定性 -- 假设在数列中存在a[i]=a[j],若在排序之前,a[i]在a[j]前面;并且排序之后,a[i]仍然在a[j]前面。则这个排序算法是稳定的!
// O(N2), 稳定的算法
void bubble_sort(int * arr,size_t n){
bool sort =false;
for(int i=0;i<n;++i){
for(int j=1;j<n-i;++j){
if(arr[j]<arr[j-1]){
swap...;
sort=true;
}
if(!sort) break;
}
}
}
// O(N2) 稳定的算法
// 分左右两个,左右有序,右边无序,无序,插入到有序中
void insert_sort(int * arr,size_t n){
for(int i=1;i<n;++i){
int key = arr[i];
for(int j=i;j>0 && key<arr[j];--j) // 从右往左找插入位置
arr[j+1] = arr[j];
if(j+1 != i) arr[j+1] = key; // 节省一次赋值的消耗
}
}
// 二分插入排序,优化了查找插入位置的时间
void bin_insert_sort(int * arr,size_t n){
for(int i=1;i<n;++i){
int L,R=i-1,key = arr[i];
while(L<R){
int mid = L + R >> 1;
if(arr[mid] <= key) L = mid+1;
else R = mid-1;
} // arr[L] <= key;
for(int j=j-1;j>L;--j) arr[j+i] = arr[j];
arr[L] = key;
}
}
void two_road_insert_sort(int * arr,size_t n){
}
/*
希尔排序的时间复杂度与增量(即,步长gap)的选取有关。
例如,当增量为1时,希尔排序退化成了直接插入排序,此时的时间复杂度为O(N²)
而Hibbard增量的希尔排序的时间复杂度为O(N^3/2^)。
不稳定的算法
*/
void shell_insert_sort(int * arr,size_t n){
int i,j,gap; // gap 步长
for(gap=n/2;gap>0;gap/=2){
for(i=gap;i<n;++i){ // 直接插入排序
int key =arr[i];
// 每次减去步长gap,
for(j=i-gap;j>=0&&key<arr[j];j-=gap) arr[j+gap] = arr[j];
if(j+gap!=i) arr[j+gap]=key;
}
}
}
// O(N2) 稳定
// 选择排序 从key后面的序列中,找到比key小的,然后交换
void select_sort(int *arr, size_t n){
int i; // 有序区的末尾位置
int j; // 无序区的起始位置
int min; // 无序区中最小元素位置
for(i=0; i<n; i++){
min=i;
// 找出"a[i+1] ... a[n]"之间的最小元素,并赋值给min。
for(j=i+1; j<n && arr[j]<arr[min]; j++) min=j;
// 若min!=i,则交换 a[i] 和 a[min]。
// 交换之后,保证了a[0] ... a[i] 之间的元素是有序的。
if(min!=i) swap(arr[i], arr[min]);
}
}
// 快排 最坏情况下是O(N2),平均的时间复杂度是O(N*lgN) 不稳定的算法
/* 假设被排序的数列中有N个数。遍历一次的时间复杂度是O(N),至少lg(N+1)次,最多N次。
快速排序是采用的分治法进行遍历的,我们将它看作一棵二叉树,它需要遍历的次数就是二叉树的深度
而根据完全二叉树的定义,它的深度至少是lg(N+1)。因此,快速排序的遍历次数最少是lg(N+1)次。
*/
void quick_sort(int * arr,size_t n){
if(n<=1) return;
int ket= arr[0];
int L=0,R=n-1;
while(L<R){
while(L<R && key<=arr[R]) --R; //从右向左找第一个小于key的数
if(L<R) arr[L++]=arr[R];
while(L<R && arr[L]<=key) ++L; //从右向左找第一个大于key的数
arr[R]=arr[L];
if(L<R) arr[R--]=arr[L];
}
arr[L] = key;
// [0,L-1] L-1>0
if(L>1) quick_sort(arr,L);
// [L+1,n-1] n>L+1 为什么不是 n-1>L+1
if(L+1<n) quick_sort(arr+L+1,n-L-1);
}
/*
堆排序 O(N*lgN)
不稳定算法原因:
交换数据是比较父结点和子节点之间的数据,
所以,即便是存在两个数值相等的兄弟节点,它们的相对顺序在排序也可能发生变化
实现原理:
*/
void reheap(int * arr,int pos,size_t n){
// pos ,要调节的元素下标
int key = arr[pos];
int child = 2*pos +1; // pos 左孩子
while(child < n){// 左孩子存在
//右孩子存在且右孩子大
if(child+1<n && arr[child]<arr[child+1]) ++child;
if(key < arr[child]){ // 于较大孩子交换值
arr[pos] = arr[child];
pos = child;
child = 2*pos +1;
}else break;
}
arr[pos] = key;
}
void heap_sort(int * arr,size_t n){
int i;
//调整成大根堆 从最后一个非叶子节点开始调整
for(i=n/2-1;i>=0;--i) reheap(arr,i,n);
for(i=n-1;i>0;--i){
//把arr[0]和最后一个元素交换位置
swap(arr[0],arr[i]);
//0下标元素发生改变 只需要重新调整0位置的元素即可
reheap(arr,0,i);
}
}
// 鸡尾酒排序
void cocktail_sort(int * arr,size_t n){
int i,j;
for(i=0;i<n/2;++i){//进行n/2次循环 每次选择最大值 和 最小值 下标
int minIndex = i;
int maxIndex = i;
for(j=i+1;j<n-i;++j){
if(arr[j]<arr[minIndex]) minIndex = j;
else if(arr[maxIndex] < arr[j]) maxIndex = j;
}
int tmp = 0;
if(minIndex != i){
tmp = arr[minIndex];
arr[minIndex] = arr[i];
arr[i] = tmp;
}
if(maxIndex == i) maxIndex = minIndex;
if(maxIndex != n-i-1){
tmp = arr[maxIndex];
arr[maxIndex] = arr[n-i-1];
arr[n-i-1] = tmp;
}
}
}
/*
归并排序 O(N*lgN),且是稳定算法
1. 分解 -- 将当前区间一分为二,即求分裂点 mid = (low + high)/2;
2. 求解 -- 递归地对两个子区间a[low...mid] 和 a[mid+1...high]进行归并排序。递归的终结条件是子区间长度为1。
3. 合并 -- 将已排序的两个子区间a[low...mid]和 a[mid+1...high]归并为一个有序的区间a[low...high]。
*/
// 这里用的算法,是直接传递数组首地址加进步值,整体思路一致
void merge_part_sort(int * arr,size_t n){
const int mid = n/2; // 记录分界线
int brr[mid];//用于存储[0,mid) 数据
int i,j,k;
for(i=0;i<mid;++i) brr[i] = arr[i];
//i记录元素比较后放置的位置 j记录brr的下标位置 k记录的是arr[mid-n)部分下标
i=0,j=0,k=mid;
while(j<mid && k<n){ //brr[0,mid)和arr[mid-n)两部分数据都还有
if(brr[j] < arr[k]) arr[i++] = brr[j++]; //[0,mid) < [mid,n)
else arr[i++] = arr[k++]; //[0,mid) >= [mid,n)
}
while(j<mid) arr[i++] = brr[j++];
// arr[k,n) 本身就是升序的,所以不需要管
}
void merge_sort(int * arr,size_t n){
if(arr==NULL || n<=1) return;
int mid = n/2; //[0,mid) [mid,n)
merge_sort(arr,mid); //[0,mid) 区间中的元素排序
merge_sort(arr+mid,n-mid); //[mid,n) 区间中的元素排序
merge_part_sort(arr,n); // 区间中的元素排序
}
/**
* 桶排序
* n -- 数组a的长度
* max -- 数组a中最大值的范围
*/
void bucketSort(int * arr, size_t n, int max)
{
if (arr==NULL || n<1 || max<1) return ;
int i,j;
int buckets[max];
memset(buckets, 0, max*sizeof(int));
// 1. 计数
for(i = 0; i < n; i++) buckets[a[i]]++;
// 2. 排序
for (i = 0, j = 0; i < max; i++)
{
while( (buckets[i]--) >0 ) // 桶内元素个数>0
arr[j++] = i;
}
}
/*
基数排序
MSD:先从高位开始进行排序,在每个关键字上,可采用桶排序
LSD:先从低位开始进行排序,在每个关键字上,可采用计数排序
*/
void radix_sort(int * arr,size_t n){
int exp;
// 指数。当对数组按各位进行排序时,exp=1;按十位进行排序时,exp=10;...
int max = get_max(arr, n); // 数组a中的最大值
int tmp[n];
int count[10]; //计数器
// 从个位开始,对数组a按"指数"进行 计数排序
int i,j;
for (exp = 1; max/exp > 0; exp *= 10){
for(i = 0; i < 10; i++) count[j] = 0; //每次分配前清空计数器
for(i = 0; i < n; i++)
++count[(arr[j] / exp) % 10]; //统计每个桶中的记录数
for(i = 1; i < 10; i++)
count[j] = count[j - 1] + count[j]; //将tmp中的位置依次分配给每个桶
for(i = n - 1; i >= 0; i--) //将所有桶中记录依次收集到tmp中
{
tmp[count[k] - 1] = arr[(arr[j] / exp) % 10];
count[k]--;
}
for(i = 0; i < n; i++) //将临时数组的内容复制到data中
arr[j] = tmp[j];
}
}
/*
适用于密集且数据跨度小的序列
计数排序(Counting Sort)不是基于比较的排序算法,
其核心在于将输入的数据值转化为键存储在额外开辟的数组空间中。
作为一种线性时间复杂度的排序,计数排序要求输入的数据必须是有确定范围的整数。
*/
void count_sort(int * arr,size_t n){
int max = arr[0],min = arr[0];
int i,j;
for(i=0;i<n;++i){ // 找出待排序的数组中最大和最小的元素,
if(arr[i]>max) max = arr[i];
else if(arr[i]<min) min = arr[i];
}
int cnt = max-min+1; // 计算出存储数据作为数组下标
int flag[cnt]; // 存储数据出现频率的数组范围 [min,max]
for(i=0;i<cnt;++i) flag[i] = 0; // 清零
//计算出数据对应的下标,存入频率数组,出现1次值为1,以后++;
//反向填充目标数组:便利频率数组,
for(i=0;i<n;++i) ++flag[arr[i]-min];
// 通过index反向计算出原始值(index + min),依次加入目标数组。
for(i=0,j=0;i<cnt;++i){
while(flag[i]>0){
arr[j++] = i+min;
--flag[i];
}
}
}
/*
基数排序:根据键值的每位数字来分配桶;
计数排序:每个桶只存储单一键值;
桶排序:每个桶存储一定范围的数值;
*/
外部排序
//每个归并段的数据量为10个
#define NUM_OF_SEGMENT 10
long num_of_file(const char *file){
FILE *fp = fopen(file,"r");
if(fp == NULL){
return -1;
}
fseek(fp,0,SEEK_END);
long fs = ftell(fp);//文件大小 字节 long
fclose(fp);
return fs/4;
}
#define FILE_NAME 48
typedef struct MergeSegment{
char file[FILE_NAME];
size_t size; //归并段中数据的总数
size_t num; //这个归并段已经读取的个数
FILE *fp;
}MSM;
//这个归并段是否处理完成了 处理完成了表示没有数据了
bool is_completed(MSM *pms){
return pms->size == pms->num;
}
int load_data(MSM *pms,int data[]){
if(is_completed(pms)){
return 0;
}
int ret = fread(data,sizeof(int),NUM_OF_SEGMENT,pms->fp);
pms->num += ret;
return ret;
}
int save_data(MSM *pms,int data[],size_t num){
int ret = fwrite(data,sizeof(int),num,pms->fp);
pms->size += ret;
return ret;
}
void fclose_file(MSM *pms){
fclose(pms->fp);
}
int open_file_read(MSM *pms){
pms->fp = fopen(pms->file,"rb");
if(pms->fp == NULL){
printf("open %s failed!\n",pms->file);
return -1;
}
return 0;
}
int open_file_write(MSM *pms){
pms->fp = fopen(pms->file,"wb");
if(pms->fp == NULL){
return -1;
}
return 0;
}
//init_segment(file,msms,segs);
void init_segment(const char *file,MSM *msms,int segs){
int data[NUM_OF_SEGMENT] = {};
int i;
FILE *fp = fopen(file,"rb");
for(i=0;i<segs;++i){
int ret = fread(data,sizeof(int),NUM_OF_SEGMENT,fp);
quick_sort(data,ret);
//printf("ret = %d\n",ret);
save_data(&msms[i],data,ret);
fclose_file(&msms[i]);
}
}
void merge(MSM *pm1,MSM *pm2,MSM *pres){
open_file_read(pm1);
open_file_read(pm2);
printf("[1]原始文件数据:fs1 = %d fn1 = %d, fs2 = %d fn1 = %d\n",pm1->size,pm1->num,pm2->size,pm2->num);
//strcpy(pres->file,"1"); //文件同名了
strcpy(pres->file,"ab");//这个地方错了 不能直接在名字前加一个1 因为 之前有0-19.txt 会提前删除掉数据
strcat(pres->file,pm1->file);
pres->fp = NULL;
pres->num = pres->size = 0;
open_file_write(pres);
int data1[NUM_OF_SEGMENT] = {};
int data2[NUM_OF_SEGMENT] = {};
int i=0,j=0;
int cnt1 = 0,cnt2 = 0;
cnt1 = load_data(pm1,data1);
cnt2 = load_data(pm2,data2);
while(i<cnt1 && j<cnt2){
if(data1[i] < data2[j]){
save_data(pres,&data1[i],1);
++i;
}else{
save_data(pres,&data2[j],1);
++j;
}
if(i==cnt1 && !is_completed(pm1)){
cnt1 = load_data(pm1,data1);
i = 0;
}
if(j==cnt2 && !is_completed(pm2)){
cnt2 = load_data(pm2,data2);
j = 0;
}
}
while(i<cnt1){
save_data(pres,&data1[i],cnt1-i);
if(is_completed(pm1)){
break;
}
cnt1 = load_data(pm1,data1);
i = 0;
}
while(j<cnt2){
save_data(pres,&data2[j],cnt2-j);
if(is_completed(pm2)){
break;
}
cnt2 = load_data(pm2,data2);
j = 0;
}
printf("[2]原始文件数据:fs1 = %d fn1 = %d, fs2 = %d fn1 = %d\n",pm1->size,pm1->num,pm2->size,pm2->num);
printf("写入文件大小 res fs=%d\n",pres->size);
}
//假设文件中存储的都是int类型 如果要写通用 传递比较函数 每一个数据的字节大小
int outside_sort(const char *file){
assert(file!=NULL);
long cnt = num_of_file(file);
if(cnt == -1){
return -1;
}
printf("%d\n",cnt);
//归并段的个数
int segs = cnt/NUM_OF_SEGMENT;
if(cnt%NUM_OF_SEGMENT>0){
++segs;
}
MSM msms[segs];
int i;
for(i=0;i<segs;++i){
sprintf(msms[i].file,"%d.txt",i);
msms[i].num = 0;
msms[i].size = 0;
msms[i].fp = NULL;
open_file_write(&msms[i]);//打开文件用于写
}
init_segment(file,msms,segs);
while(segs>1){
MSM res = {};
int cnt = 0;
for(i=0;i<segs;i+=2){
if(i+1<segs){
merge(&msms[i],&msms[i+1],&res);
fclose_file(&msms[i]);
unlink(msms[i].file);
fclose_file(&msms[i+1]);
unlink(msms[i+1].file); //删除文件
fclose_file(&res);
msms[cnt++] = res;
}else{
msms[cnt++] = msms[i]; //剩下最后一个没有可以归并的
}
}
segs = cnt;
}
//msms[0];
printf("\n----------------------\n");
FILE *fp = fopen(msms[0].file,"rb");
int d = 0;
while(fread(&d,4,1,fp)>0){
printf("%d ",d);
}
printf("\n");
fclose(fp);
/*
for(i=0;i<segs;++i){
open_file_read(&msms[i]);
int data[NUM_OF_SEGMENT] = {};
int ret = load_data(&msms[i],data);
int j;
for(j=0;j<ret;++j){
printf("%d ",data[j]);
}
printf("\n");
fclose_file(&msms[i]);
}
*/
}
本文来自博客园,作者:打工搬砖日记,转载请注明原文链接:https://www.cnblogs.com/FlyingDoG--BoxPiG/p/16728432.html