C语言中的内部排序

这篇博客详细介绍了C语言中几种常见的内部排序算法,包括直接插入排序、折半插入排序、希尔排序以及选择排序、堆排序、冒泡排序等。文章探讨了排序算法的时间复杂度、空间复杂度和稳定性,并提供了示例数组进行排序过程的展示。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

插入排序:
1.直接插入排序
2.折半插入排序
3.希尔排序

**排序的三种特性:
**1.时间复杂度
算法的时间复杂度是一个函数,它定性描述该算法的运行时间
2.空间复杂度
空间复杂度是对一个算法在运行过程中临时占用存储空间大小的量度
3.稳定性
初始数列中有R[i]和R[j]两个元素,两个元素相等(R[i]==R[j]),且i<j,
当经过某种排序以后,R[i]元素位置变为i’,R[j]元素位置变为j’,
如果在排序之后,i’<j’,R[i’]位置依然在R[j’]之前,那么称之为稳定排序
稳定性无关一个排序的好坏,只是考虑特定的场合

直接插入:
将下标为i的元素直接插入到下标范围[0,i-1]的数组中,插入后的数组[0,i]
依然要求保持有序
时间复杂度为O(n^2)
空间复杂度为O(1)

void insertSort(int arr[],int n){
	int i,j;
	int key = 0;
	for(i=1;i<n;i++){//i=0,只有一个元素,本身就是有序的,不需要插入,i=1从第二个元素开始,逐一往前插入,使之保持有序 
		key = arr[i];//提前保存要插入的元素	因为前面的元素往后移,会覆盖后面的元素 
		for(j=i-1;j>=0&&arr[j]>key;--j){
			arr[j+1] = arr[j];
		} 
		if(i!=j+1){//i==j+1
			arr[j+1] = key;//arr[i]=key;
		}
	}	
}

输入示例数组:
{1,8,9,3,4,7,0,2,5,6}
i=1,key=arr[1]=8,
j=0,arr[0+1]=key=8; 1,8…
i=2,key=arr[2]=9,
j=1,arr[1+1]=9; 1,8,9…
i=3,key=3
j=2,arr[3]=arr[2]=9,arr[2]=arr[1]=8,arr[1]=3; 1,3,8,9…
i=4,key=4,
j=3,arr[4]=arr[3]=9,arr[3]=arr[2]=8,arr[2]=4; 1,3,4,8,9…

折半插入排序:
将数组一分为2,与数组的最中间的元素进行比较,如果元素比中间的元素小,则保留数组的前半段,继续选取中间的元素进行比较
时间复杂度为O(n^2)
空间复杂度为O(1)

void bininsertSort(int arr[],int n){
	int left,right,mid,i,j,key;
	for(i=1;i<n;i++){//arr[1]----arr[n-1]所有的元素依次往前插入 
		key=arr[i];//保存要插入的元素arr[i] 
		for(left=0,right=i-1;left<=right;){//找到arr[i]插入的位置	折半查找 
			mid = (left+right)/2;
			if(key < arr[mid]){
				right = mid - 1;
			}else{
				left = mid + 1;
			}
		}
		//right+1位置要存储arr[i] key 
		for(j=i-1;j>right;--j){
			arr[j+1] = arr[j];
		}
		arr[right+1] = key;
	}
}

输入示例数组:
{1,8,9,3,4,7,0,2,5,6}
i=1,left=0,right=0,mid=0,
key=arr[1]=8,key>arr[mid],left=1,
j=0,arr[1]=8; 1,8
i=2,left=0,right=1,mid=0,
key=9,key>arr[mid],left=1,
j=1,arr[2]=9; 1,8,9
i=3,left=0,right=2,mid=1,
key=3,key<arr[mid],right=1,
j=2,j>right,arr[3]=arr[2]=9,arr[2]=arr[1]=8,arr[1]=3; 1,3,8,9

2路插入排序:
折半插入的一种
申请一个和原始数组相同内存的数组brr,开始时往左插,当遇到比brr[0]小的值时插入到右边brr[i-1]
时间复杂度O(n^2)
空间复杂度O(1)

void twoRoadInsert(int arr[],int n){
	int *brr = (int *)malloc(sizeof(arr[0])*n);//申请一个相同内存的数组
	int left = -1,right = n;
	int i,j;
	for(i=0;i<n;i++){
		if(left==-1||arr[i]>brr[0]){//left==-1时给brr[0]一个初始值,当元素大于最左端的元素brr[0]时插入到brr[0]后
			for(j=left;j>=0&&arr[i]<brr[j];--j){
				brr[j+1] = brr[j];
			}
			brr[j+1] = arr[i];
			++left;
		}else{//<brr[0]	如果要插入左边 左边所有元素都要移动 
			for(j=right;j<n&&arr[i]>brr[j];++j){
				brr[j-1] = brr[j];
			}
			brr[j-1] = arr[i];
			--right;
		}
	}
	for(i=right;i<n;i++){
		arr[i-right] = brr[i];
	}
	for(i=0;i<=left;i++){
		arr[n-right+i] = brr[i];
	}
}

示例数组:
1,8,9,3,4,7,0,2,5,6
i=0,left=-1,right=10,
j=-1,brr[0]=arr[0]=1,left=0; 1
i=1,left=0,right=10,
j=0,brr[1]=arr[1]=8,left=1; 1,8
i=2,left=1,right=10,
j=1,brr[2]=arr[2]=9,left=2; 1,8,9
i=3,left=2,right=10,
j=2,arr[3]<brr[2],brr[3]=brr[2]=9,brr[2]=brr[1]=8,brr[1]=3,left=3; 1,3,8,9
i=4,left=3,right=10,
j=3,arr[4]<brr[3],brr[4]=brr[3]=9,brr[3]=brr[2]=8,brr[2]=4,left=4; 1,3,4,8,9

i=6,left=5,right=10,
arr[6]<brr[0],j=right=10,brr[9]=arr[6]=0;right=9; 1,3,4,7,8,9, , , ,0

示例特殊,只使用了一次right

希尔排序:
希尔排序是把记录按下标的一定增量分组,对每组使用直接插入排序算法排序;随着增量逐渐减少,每组包含的关键词越来越多,当增量减至 1 时,整个文件恰被分成一组,算法便终止。
时间复杂度O(n^(1.3-2))
空间复杂度O(1)

void shellSort(int arr[],int n){
	//分组	分为step个小组	组内元素步长step 
	int step,i,j;
	for(step=n/2;step>0;step=step/2)//分组 
	//组内排序	[0,step)组内的第一个元素 
		for(i=step;i<n;i++){//除了组内第一个元素 其他元素都需要进行组内排序
			int key = arr[i];
			for(j=i-step;j>=0&&key<arr[j];j=j-step){
				arr[j+step] = arr[j];
			} 
			arr[j+step] = key;
		} 
}

在这里插入图片描述
选择排序:
简单选择排序
堆排序
鸡尾酒排序
二叉树排序
冒泡排序

简单选择排序(直接选择):
在未排序的数列(数列中有n个元素)中选择一个最大值(或者最小值),放到指定位置(开头或末尾),选择n-1次
时间复杂度O(n^2)
空间复杂度O(1)
不稳定的排序

void chooseSort(int arr[],int n){
	int i,j,max;
	for(i=0;i<n-1;i++){//进行n-1次循环  每次选择一个最大值 放到指定位置
		max = 0;
		for(j=1;j<n-i;j++){//在指定区间选择最大值 [0,n-1]  [0,n-2]
			if(arr[max]<arr[j]){//1 8 9 7 9 2 3
				max = j;//找出arr中最大值所在下标
			}
		}	
		if(max != n-1-i){//如果max不是区间内的最后一个,将它和最后一个交换
			int tmp = arr[max];
			arr[max] = arr[n-1-i];
			arr[n-1-i] = tmp;
		}
	}
} 

简单排序同时向前后插入:

void twoChoiceSort(int arr[],int n){
	int i,j,max,min,tmp;
	for(i=0;i<n/2;i++){//前后同时插入,最小值排开头,最大值排末尾,所以只用循环n/2次
		max = min = i;//同时从i开始找
		for(j=i+1;j<n-i;j++){//寻找范围[i,n-1-i]
			if(arr[max] < arr[j]){
				max = j;//标记最大值下标
			}else if(arr[min] > arr[j]){
				min = j;//标记最小值下标
			}
		}
		if(max != n-1-i){//最大值下标不为范围内最后一位
			tmp = arr[max];
			arr[max] = arr[n-1-i];
			arr[n-1-i] = tmp;
		}
		if(min == n-1-i){//如果min在最后一位,arr[min]中的元素被arr[max]取代,让min指向max
			min = max;
		}
		if(min != i){//最小值不为范围内的第一位
			tmp = arr[i];
			arr[i] = arr[min];
			arr[min] = tmp;
		}
	}
}

堆排序:
利用堆这种数据结构所设计的一种排序算法,堆是一个近似完全二叉树的结构
堆分为大根堆和小根堆
小根堆(小栈堆):根结点的元素均小于等于左右子树,左右子树也是小根堆
大根堆(大栈堆):根结点的元素均大于等于左右子树,左右子树也是大根堆
二叉树可以使用数组来存储(不需要使用指针),对于下标为i的父结点,其左子结点下标为2i+1,右子结点下标为2i+2
对于完全二叉树而言,可以使用数组存储,但对于非完全二叉树,内存的利用率不高

时间复杂度O(n*logn)
空间复杂度O(1)

//index要调整的结点  n是总共结点数 
void reHeap(int arr[],int index,int n){
	int child = 2*index+1;
	int key = arr[index];//需要调整的结点的元素 
	while(child<n){//叶子结点存在 
		if(child+1<n && arr[child] < arr[child+1]){//右孩子存在且比左孩子大 
			++child;	//child记录孩子中更大的那个结点下标
		} 
		if(key<arr[child]){
			arr[index] = arr[child];//把孩子的元素放在父结点处 
			index = child;
			child = 2*index+1;
		}else{
			break;
		}
	} 
	arr[index] = key;
} 
void heapSort(int arr[],int n){//把完全二叉树调整成大根堆
	int i;
	for(i=n/2;i>=0;--i)//第一个非叶子结点 n/2   从下往上调整 
		reHeap(arr,i,n); 
	for(i=0;i<n-1;i++){//循环进行n-1次
		//arr[0]和arr[n-1-i]最后一个元素交换 
		int tmp = arr[0];
		arr[0] = arr[n-1-i];
		arr[n-1-i] = tmp; 
		reHeap(arr,0,n-1-i);//不考虑最后一个元素的基础上,重新调整成大根堆  只需要调整0结点
	}
}

在这里插入图片描述
冒泡排序:
相邻两个元素相互比较,大的放后面,小的放前面,经过一次完整的比较,最大值放在了最后面,
每次都能把最大值冒到最后
时间复杂度O(n^2)
空间复杂度O(1)

void bubbleSort(int arr[],int n){
	int i,j,tmp;
	for(i=0;i<n-1;i++){
		int swap = false;
		for(j=1;j<n-i;j++){
			if(arr[j] < arr[j-1]){
				tmp = arr[j];
				arr[j] = arr[j-1];
				arr[j-1] = tmp;	
				swap = true;
			}
		}
		if(!swap){//在一次完整的冒泡过程中,没有任何两个元素交换位置 说明已经有序 
			break;
		}
	}
}

鸡尾酒排序:
鸡尾酒排序又叫双向冒泡排序
数组中的数字本是无规律的排放,先找到最小的数字,把他放到第一位,然后找到最大的数字放到最后一位。然后再找到第二小的数字放到第二位,再找到第二大的数字放到倒数第二位。以此类推,直到完成排序。
时间复杂度为O(n^2)
空间复杂度为O(1)

void cockTailSort(int arr[],int n){
	int i,j,tmp;
	for(i=0;i<n/2;i++){//双向冒泡,只需要n/2次
		bool swap = false;
		for(j=i+1;j<n-i;j++){//[1,n) [2,n-1)
			if(arr[j]<arr[j-1]){// 5 3,将最大值冒到范围内的最后一位
				tmp = arr[j];
				arr[j] = arr[j-1];
				arr[j-1] = tmp;
				swap = true;	
			}
		}
		for(j=j-2;j>i;j--){//
			if(arr[j]<arr[j-1]){
				tmp = arr[j];
				arr[j] = arr[j-1];
				arr[j-1] = tmp;
				swap = true;
			}
		}
		if(!swap){
			break;
		}
	}
} 

示例数组
范围[0,10) {1,8,9,3,4,7,0,2,5,6}
i=0,j=[1,10),{1,8,3,4,7,0,2,5,6,9},j=10
j=8,{0,1,8,3,4,7,2,5,6,9},j=0
范围[1,10) 0,{1,8,3,4,7,2,5,6,9}
i=1,j=[2,9), 0,{1,3,4,7,2,5,6,8,9},j=9
j=7, 0,{1,2,3,4,7,5,6,8,9},j=1
范围[2,10) 0,1,{2,3,4,7,5,6,8,9}
i=2,j=[3,8), 0,1,{2,3,4,5,6,7,8,9},j=5

二路归并排序:
将一个数组分为两个元素或一个元素一组
数组内含十个元素,分为3 2 3 2 个元素一组,再分为2 1 2 2 1 2个元素一组
时间复杂度O(n*logn)
空间复杂度O(n)

void megerArrs(int arr[],int left,int right){//left为左边开始坐标,right为右边开始坐标
	int mid = (left+right)/2;//范围[left,mid]  [mid+1,right]
	int llen = mid-left+1;
	int brr[llen];
	int i,j,k;
	for(i=0;i<llen;i++)
		brr[i] = arr[left+i];
	i=0;j=mid+1;k=left;
	while(i<llen && j<=right){
		if(brr[i]<arr[j]){
			arr[k++] = brr[i++];
		}else{
			arr[k++] = arr[j++];
		}
	}
	while(i<llen){
		arr[k++] = brr[i++];
	}
}
void megerSorts(int arr[],int left,int right){//[left,right]
	if(left>=right)
		return;
	int mid = (left+right)/2;//[left,mid]  [mid+1,right]
	if(mid-left>0)//左边大于1个元素 
		megerSorts(arr,left,mid);
	if(right-mid-1>0)//右边大于1个元素 
		megerSorts(arr,mid+1,right);
	megerArrs(arr,left,right); 
}

先进行分组:
left=0,right=9,mid=4
左left=0,right=4,mid=2;右left=5,right=9,mid=7
左的左left=0,right=2,mid=1;左的右left=3,right=4,mid=3;
右的左left=5,right=7,mid=6;右的右left=8,right=9,mid=8;

然后排序:

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值