首页 > 编程语言 >排序算法

排序算法

时间:2022-09-25 18:35:08浏览次数:45  
标签:arr pms int 算法 file 排序 size

内部排序

  • 这里先介绍一个概念,算法稳定性
  • 算法稳定性 -- 假设在数列中存在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,j,key;
    for(i=0;i<n;++i){
        key=arr[i];
        for(j=i+1;j<n&&key>arr[j];++j) swap...;
    }
}

// 快排	最坏情况下是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]);
	}
*/
}

标签:arr,pms,int,算法,file,排序,size
From: https://www.cnblogs.com/FlyingDoG--BoxPiG/p/16728432.html

相关文章

  • 算法练习-第四天【链表】
    链表24.两两交换链表中的节点参考:代码随想录24.两两交换链表中的节点看完题目的第一想法两两交换链表中的节点其实就是改变链表节点之间的指针将第二个节点的Next......
  • UKF和EKF算法在非线性系统中的应用比较
    参考内容:书籍《卡尔曼滤波原理及应用------matlab仿真》这本书对kalman算法的解析很清晰,MATLAB程序很全,适合初学者(如有侵权,请联系删除(qq:1491967912))之前学习了EKF算法和......
  • 王道-考研-数据结构-二叉排序树
    二叉树的应用1.二叉排序树BST,也称二叉查找树。二叉排序树或者为空树,或者为非空树,当为非空树时有如下特点:若左子树非空,则左子树上所有结点关键字值均小于根结点的关键......
  • 算法 玩转数据结构 2-4 数组中查询元素和修改元素
    1重点关注1.1toString方法范式参考coding 1.2coding 2课程内容coding 3Coding3.1coding看4packagecom.......
  • 奇偶校验算法——判断二进制数中1的个数
    方法1:时间复杂度:O(logn)n为二进制数的值 intn;intres=0;scanf("%d",&n);while(n!=0){res+=(n&1);n>>=1;}printf("%d......
  • 算法第二章3·7-4 最大子段和
    给定n个整数(可能为负数)组成的序列a[1],a[2],a[3],…,a[n],求该序列如a[i]+a[i+1]+…+a[j]的子段和的最大值。当所给的整数均为负数时,定义子段和为0。要求算法的时间复杂......
  • 10种常见的回归算法总结和介绍
    线性回归是机器学习中最简单的算法,它可以通过不同的方式进行训练。在本文中,我们将介绍以下回归算法:线性回归、Robust回归、Ridge回归、LASSO回归、ElasticNet、多项式......
  • 力扣算法之数组中出现次数超过一半的数字
    数组中有一个数字出现的次数超过数组长度的一半,请找出这个数字。你可以假设数组是非空的,并且给定的数组总是存在多数元素。示例:输入:[1,2,3,2,2,2,5,4,2]输......
  • 斐波那契查找算法
    斐波那契也称黄金分割法,通过黄金分割点找到mid值,即mid=low+F(k-1)-1 (F代表斐波那契数列)对F(k-1)-1的理解由斐波那契数列F[k]=F[k-1]+F[k-2]的性质,可以得到 (F[k]-1......
  • vue3和react虚拟DOM的diff算法区别
    vue3随着Vue3.0版本的发布,我们在使用或者对其源码进行阅读时会惊讶的发现,它又又又双叒叕变强了,尤大本人在直播中也提到新的Vue会比老的Vue有1.3到2倍的提升,它的更新机制会......