排序算法一览(c语言实现)

前言

本文主要介绍四大排序思想,详情看旁栏目录。
先附上用于检验的主函数以及相关准备工作。

#include <stdio.h>
#include <stdlib.h>
#define LeftChild(i) (2*(i)+1)
#define cutoff (3)

void print(int arr[],int N) {
	for(int i=0; i<N; i++) {
		printf("%d ",arr[i]);
	}
	printf("\n");
}

void Swap(int *a,int *b) {
	int tmp=*a;
	*a=*b;
	*b=tmp;
}


int main() {
	int arr[]= {81,94,11,96,12,35,17,95,28,58,41,75,15};
	int len=sizeof(arr)/sizeof(arr[0]);
//directinsertsort(arr,len);
//binaryinsertsort(arr,len);
//shellsort(arr,len);
//Directselect(arr,len);
//heapsort(arr,len);
//Mergesort(arr,len);
//Quicksort(arr,len);
Bubblesort(arr,len);
	return 0;
}

截止我发文章前,在调用最后一个排序的算法,有想法的小伙伴可以挨个把注释去掉,试试前面几个的正确性。

正文

- 插入排序

时间复杂度:O(N^2)
平均时间复杂度:O(N^2)
最好情况:O(N) (内层for循环无需执行)

-直接插入

//direct_insert sort
void directinsertsort(int arr[],int N) {
	int tmp,i,j;
	for(i=1; i<N; i++) {
		tmp=arr[i];
		for(j=i; j>0&&arr[j-1]>tmp; j--) {
			arr[j]=arr[j-1];
		}
		arr[j]=tmp;
		print(arr,N);
	}
}

- 二分插入

//binary insert sort
void binaryinsertsort(int arr[],int N) {
	int tmp,i,j,left,right,mid;
	for(i=1; i<N; i++) {
		tmp=arr[i];
		left=0;
		right=i-1;//每次循环置已排好序的区间的左右值
		mid=(left+right)/2;
		//直接和二分的区别在while语句
		while(left<=right) {
			//确定放置区间
			mid=(left+right)/2;
			if(arr[mid]>tmp) right=mid-1;
			else left=mid+1;
		}
		for(j=i; j>0&&arr[j-1]>tmp; j--) {
			arr[j]=arr[j-1];
		}
		arr[j]=tmp;
		print(arr,N);
	}
}

- shell排序

//shellsort
void shellsort(int arr[],int N) {
	int i,j,increase,tmp;
	for(increase=N/2; increase>0; increase/=2) {
		//确定每次的增量值
		for(i=increase; i<N; i++) {
			//对所有要排序的元素进行分组
			tmp=arr[i];
			for(j=i; j>=increase&&arr[j-increase]>tmp; j-=increase) {
				//最内层的循环就是插入排序
				arr[j]=arr[j-increase];

			}
			arr[j]=tmp;

		}
		print(arr,N);
	}
}

- 选择排序

- 直接选择排序

void Directselect(int arr[],int N) {
	int i,j,k,tmp;//tmp标记待交换的值,k标记下标
	for(i=0; i<N-1; i++) { //初始指定第0个数最小,做n-1次选择比较
		k=i;
		for(j=i+1; j<N; j++) {
			if(arr[j]<arr[k]) k=j;
		}
		if(k!=i) {
			tmp=arr[i];
			arr[i]=arr[k];
			arr[k]=tmp;
		}
		print(arr,N);
	}
}

- 堆排序

基于优先队列实现,
时间复杂度:O(NlogN)
(其中,建立二叉堆所需时间为N,logN为执行N次删除最小元素再放入第二个数组的操作。)
特点:不稳定
(注意:《数据结构与算法分析——c语言描述》翻译版提到该算法“稳定”,但要结合语境,作者说的稳定指的是 平均情况与最坏情况相差不大,即比较次数与序列初始状态无关,与中文书概念中的适应性(不强)概念相对应,而中文一般提到的的 稳定 指的是对于相同的数据,排序过程中其先后顺序不变)

void PercDown(int arr[],int i,int N) {
	int child;//下标
	int tmp;
	for(tmp=arr[i]; LeftChild(i)<N; i=child) {
		child=LeftChild(i);
		if(child!=N-1&&arr[child+1]>arr[child]) {
			child++;
		}//这一层的关系满足了就往下一层次去找

		if(tmp<arr[child]) {
			arr[i]=arr[child];
		}//如果发现根比孩子大,就互换顺序
		else break;
	}
	arr[i]=tmp;

}

void heapsort(int arr[],int N) {
	int i;
	//build heap
	for(i=N/2; i>=0; i--) {
		PercDown(arr,i,N);
	}
	//deletemax
	for(i=N-1; i>0; i--) {
		Swap(&arr[0],&arr[i]);
		PercDown(arr,0,i);
		print(arr,N);
	}
}

- 归并排序

分治思想的递归算法
最坏情形:O(NlogN)
比较次数几乎是最优

//归并排序
//shell排序类似,都是一种思维方式,归并排序是一种分治递归思想
//归并排序的关键在于合并两个 已排序 的表,不关心这个表中的元素排序的细节 ,可与简单排序法组合使用
//下面给出实现方案之一
void Merge(int arr[],int tmparr[],int Lpos,int Rpos,int Rightend) {
	int i,Leftend,Numele,tmppos;
	Leftend=Rpos-1;//这里一开始错写成了=Rightend-1,导致的结果是运行时长长达25s,且排序的最后两个元素95 96的值变成了最小的11 12
	//上述提到的错误 容易从语法上发现问题,逻辑上则比较难发现
	tmppos=Lpos;
	Numele=Rightend-Lpos+1;
	while(Lpos<=Leftend&&Rpos<=Rightend) {
		if(arr[Lpos]<=arr[Rpos]) {
			tmparr[tmppos]=arr[Lpos];
			tmppos++;
			Lpos++;
		} else {
			tmparr[tmppos]=arr[Rpos];
			tmppos++;
			Rpos++;
		}
	}

	//其中一个表已经全部排完,将另一表的元素直接整合(本身已排序)
	while(Lpos<=Leftend) {
		tmparr[tmppos]=arr[Lpos];
		tmppos++;
		Lpos++;
	}
	while(Rpos<=Rightend) {
		tmparr[tmppos]=arr[Rpos];
		tmppos++;
		Rpos++;
	}

//将排好序的tmparr对arr进行覆盖,个人认为一般情况下返回tmparr也是可以的,但是这个归并实现方式嵌套了几次函数,又用到了递归,还是尽力避免歧义比较好
	for(i=0; i<Numele; i++,Rightend--) {
		arr[Rightend]=tmparr[Rightend];
	}
}



void Msort(int arr[],int tmparr[],int left,int right) {
	int center;
	if(left<right) {
		center=(left+right)/2;
		Msort(arr,tmparr,left,center);//递归地对左半部分进行排序
		Msort(arr,tmparr,center+1,right);//递归地对右半部分进行排序
		Merge(arr,tmparr,left,center+1,right);//归并排序的核心代码
	}
	//printf("Msort part run successfully!");
}

void Mergesort(int arr[],int N) {
	int *tmparr;
	tmparr=(int*)malloc(N*sizeof(int));
	if(tmparr!=NULL) {
		Msort(arr,tmparr,0,N-1);
		print(arr,N);
		free(tmparr);
	} else printf("error!no space for tmp array");
}

- 交换排序

- 快速排序

分治思想的递归算法

//quicksort
//分治的递归思想
int Median3(int arr[],int left,int right) {
	int center=(left+right)/2;
	if(arr[left]>arr[center]) {
		Swap(&arr[left],&arr[center]);
	}
	if(arr[left]>arr[right]) {
		Swap(&arr[left],&arr[right]);
	}
	if(arr[right]>arr[center]) {
		Swap(&arr[right],&arr[center]);
	}
	
	Swap(&arr[center],&arr[right-1]);//将枢纽元从待排序的数据段中分离
	return arr[right-1];//实际返回的是枢纽元 

}

void Qsort(int arr[],int left,int right) {
	int i,j;
	int pivot;
	if(left+cutoff<=right) {
		pivot=Median3(arr,left,right);
		i=left;
		j=right-1;
		while(1) {
			while(arr[i]<pivot) ++i;//最终使i指向大于枢纽元的元素
			while(arr[j]>pivot) --j; //最终使j指向小于枢纽元的元素
			if(i<j) Swap(&arr[i],&arr[j]);
			else break;
		}
		//跳出循环,说明处于j先于i的状态,不再交换
		//最后一步是将i所指向的元素与枢纽元(Median3操作将其放在数组最后一个元素)
		Swap(&arr[i],&arr[right-1]);
		Qsort(arr,left,i-1);//递归处理左半部分,在其中又生成新的枢纽元
		Qsort(arr,i+1,right);//递归处理左半部分
	}
}

void Quicksort(int arr[],int N) {
	Qsort(arr,0,N-1);
	print(arr,N);
}

- 冒泡排序

//升序 
void Bubblesort(int arr[],int N){
 int i,j,tmp;
 
 for(i=0;i<N-1;i++){
 	int Isswap=0;
 	//标识符号,可以在排好序后及时退出,减少重复操作次数 
 	//放在第一层循环内有用,如果放在循环外,这个值在循环中得不到更新,也就不起作用了 
 	for(j=0;j<N-i-1;j++){
 		if(arr[j]>arr[j+1]){
 			tmp=arr[j];
 			arr[j]=arr[j+1];
 			arr[j+1]=tmp;
 			Isswap=1;
		 }
	 }
	 if(Isswap==0) break;
	 else print(arr,N);
 }
}

总结

直接插入二分插入shell直接选择堆排序冒泡快速归并
稳定性
适应性(不清楚)
最好O(N)O(N-1)O(N)O(NlogN)
最坏O(N^2)O(N^2/2)O(N^2)O(N^2)
平均O(N^2)O(N^2)O(N^1.3)O(N^2)O(NlogN)O(N^2)O(NlogN)O(NlogN)

稳定性与适应性一栏没注明的默认为不好

结语

本文的参考书籍:
《数据结构与算法分析——c语言描述(原书第二版) Mark Allen Weiss》
《算法与数据结构——c语言描述 (第三版) 张乃孝等编著》

趁着期末复习之际,希望自己好好巩固一下算法基础,相信这对未来的深入学习也会很有帮助。
感谢你能看到这里!
时间仓促,如有错漏之处恳请指正,非常感谢!

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值