数据结构---排序算法总结(C语言)

本文详细介绍了各种排序算法,包括直接插入排序、希尔排序、冒泡排序、快速排序、简单选择排序、堆排序、归并排序和基数排序。每种排序算法的步骤、特点、适用场景和时间复杂度进行了说明,适用于C语言编程。

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

插入类

直接插入排序

步骤如下:
待排序的记录存放在数组r[1…n],r[1]是一个有序序列,在r[0]出设置监视哨。
(1)确定插入位置: 循环n-1次,使用顺序查找法,查找r[i](i = 2,…,n)在已排好序的序列r[1,…i-1]中得插入位置;
(2)插入元素: 将r[i[ 插入到表长为i-1的有序序列r[1,…i-1]中,直到插入r[n],得到一个表长为n的有序序列。
代码如下:

直接插入排序
void InsertSort(SqList &L)
{
	for(int i = 2;i <= L.length;i++){
		if(L.r[i].key < L.r[i-1].key){
			L.r[0] = L.r[i];	//r[0]作为监视哨,临时保存r[i]的值; 
			L.r[i] = L.r[i-1];
			for(int j = i-2;L.r[0].key < L.r[j].key; --j){
				L.r[j+1] = L.r[j];   //找到r[i]或者说此时r[0]中关键字需要插入的位置为j之后j+1处 
			}
			L.r[j+1] = L.r[0];
		} //if 
	} // for
}  

算法特点:
(1)稳定排序
(2)适用于顺序存储结构与链式存储结构,在单链表上无需移动记录,只需修改相应的指针;
(3)适用于初始记录基本有序(正序)的情况。当初始记录无序,n较大,不宜使用。
(4)时间复杂度:O(n²); 空间复杂度: O(1).

希尔排序

算法如下:
将整个待排序记录序列中相隔某个“增量”的记录分成一组,进行直接插入排序,而后逐次增加每组的数据量(即减小“增量”),经过几次分组排序后,整个序列中的记录“基本有序”时,再对全体记录进行一次直接插入排序。
代码如下:

希尔插入排序

//对顺序表L做一趟增量是dk的希尔插入排序 
void ShellInsert(SqList &L,int dk)
{
	for(i = dk+1;i <= L.length; ++i){
		if(L.r[i].key < L.r[i-dk].key){
			L.r[0] = L.r[i];
			for(j = i-dk;j>0 && L.r[0].key < L.r[j].key;j-=dk){
				L.r[j+dk] = L.r[j];
			}
			L.r[j+dk] = L.r[0];
		}//if
	} //for
} 
//按增量序列dt[0..t-1]对顺序表L作t趟希尔排序
void ShellSort(SqList &L,int dt[],int t){
	for(k = 0;k < t;++k)
	ShellInsert(L,dt[k]);
} 

算法特点:
(1)不稳定排序
(2)只能用于顺序结构
(3)增量序列中的值没有除1以外的公因子,且最后一个增量值必须为1;
(4)适合初始记录无序、n较大时的情况
(5)时间复杂度: O(n1.3) 【平均情况下】空间复杂度: O(1).

交换类

冒泡排序

步骤如下:
(1)首先将比较第一、二个记录的关键字,若为逆序,则互换;然后比较第二、三个记录的关键字,一次类推,直至第n-1、n个记录的关键字进行比较为止。其结果使得关键字最大(小)的记录被安置到最后一个记录的位置上。
(2)对前n-1个记录进行同样操作。重复该过程,第i趟是从L.r[1]到L.r[n-i+1]依次比较相邻两个记录的关键字,并在“逆序”时交换顺序。直到在某一趟排序过程中没有进行过交换记录的操作,说明序列已有序。
代码如下:

冒泡排序

void BubbleSort(SqList &L)
{
	int m = L.length - 1;
	int t,flag = 1;
	while(m > 0 && flag == 1){
		flag = 0;
		for(int j = 1;j <= m;j++){
			if(L.r[j].key > L.r[j+1].key){
				flag = 1;
				t = L.r[j].key;
				L.r[j] = L.r[j+1];
				L.r[j+1].key = t;
			}//if
		--m;
		}//for
	}//while
}

算法特点:
(1)稳定排序
(2)可用于顺序、链式存储结构
(3)移动次数较多,算法平均时间性能比直接插入排序差。当初始记录无序、n较大时,不宜采用;
(4)时间复杂度:O(n²); 空间复杂度: O(1)

快速排序

步骤如下:
选择待排序表中的第一个元素作为枢轴,将枢轴放在合适的位置,则将表L“划分”成左右2个逻辑子表,使得该枢轴大于左子表的所有记录的关键字,且小于右子表的所有记录的关键字,而后递归处理左右子表。
(1)将第一个元素作为枢轴,将枢轴记录暂存在r[0]的位置上,设两指针low和high初始值分别指向表L最左和最右的位置上;
(2)当low < high时,重复做如下处理:
low递增移动,将首次遇到比r[low]小的r[high]与r[low]交换;
然后high递减移动,将首次遇到比r[high]大的r[low]与r[high]交换;
(3)直至low = high时为止,此时low或high的位置即为枢轴在此趟排序的最终位置,原表被分为两个子表;
(4)递归处理左右子表;
代码如下:

快速排序 

//移动low、high进行记录交换 
int Partition(SqList &L,int low,int high)
{
	L.r[0] = L.r[low];    //将子表的第一个记录作为枢轴记录;
	int pivotkey = L.r[low].key;
	while(low < high){
		while(low < high && L.r[high].key >= pivotkey) --high;  
		L.r[low] = L.r[high];	//将比枢轴记录小的移到左侧; 
		while(low < high && L.r[low].key <= pivotkey) ++low;
		L.r[high] = L.r[low];	//将比枢轴记录大的移到右侧 
	} 
	L.r[low] = L.r[0];  //枢轴记录放在合适位置 
	return low;		//返回枢轴的位置 
} 

//递归处理左右子表 
void QSort(SqList L,int low,int high)
{
	int pivotloc;
	int low = 1,high = L.length; //low、high初始值 
	if(low < high){
		pivotloc = Partition(L,low,high);
		QSort(L,low,pivotloc-1);
		QSort(L,pivotloc+1,high); 
	}
} 

//对顺序表L进行快速排序 
void QuickSort(SqList &L)
{
	QSort(L,1,L.length);
}

算法特点:
(1)不稳定排序
(2)适用于顺序结构,很难用于链式结构(因为要求确定其上界和下界);
(3)适合初始记录无序、n较大时、当n较大时的情况,在平均情况下快速排序是所有内部排序方法中速度最快的一种。
(4)时间复杂度: O(nlog₂n); 空间复杂度: O(log₂n),最坏情况下为O(n).

选择类

简单选择排序

步骤如下:
(1)第一趟从r[1]开始,通过n-1次比较,从n个记录中选出关键字最小的记录,记为r[k],交换r[1]和r[k],第二趟从r[2]开始,通过n-2次比较,从n个记录中选出关键字最小的记录,记为r[k],交换r[2]和r[k];
(2)依此类推,第一趟从r[i]开始,通过n-i次比较,从n个记录中选出关键字最小的记录,记为r[k],交换r[i]和r[k],经过n-1趟,排序完成。
代码如下:

简单选择排序 

void SelectSort(SqList &L)
{
	SqList t;
	
	for(int i = 1;i < L.length;i++){
		k = i ;
		for(int j = i+1;j <= L.length;++j){   //k指向此趟排序中关键字最小的记录 
			if(L.r[j].key < L.r[k].key)		
			k = j;
		}
		if(k != i){		//交换r[k]与r[i] 
			t = L.r[i];
			L.r[i] = L.r[k];
			L.r[k] = t; 
		}
	}//for
}

算法特点:
(1)算法本事是稳定排序,但是会出现”不稳定排序“的现象;
(2)可用于顺序、链式存储结构
(3)移动次数较少,当每一记录占用的空间较多时,此方法比直接插入排序快;
(4)时间复杂度:O(n²); 空间复杂度: O(1).

堆排序

是具有以下性质的完全二叉树:每个结点的值都大于或等于其左右孩子结点的值,称为大根堆;或者每个结点的值都小于或等于其左右孩子结点的值,称为小根堆
步骤如下:
(1)建初堆:按堆的定义将排序序列r[1…n]调整为大根堆,交换r[1]和r[n],则r[n]为关键字最大的记录;
(2)调整堆:将rp1…n-1]调整为堆,交换r[1]和r[n-1],则r[n-1]为关键字次大的记录;
(3)循环n-1次,直到交换了r[1]和r[2],得到l一个非递减的有序序列。
代码如下:

堆排序 

//筛选法调整堆
void HeapAdjust(SqList &L,int s,int m)
{//假设r[s+1..m]已经是堆,将r[s..m]调整为以r[s]为根的大根堆
	SortList rc = L.r[s];
	
	for(int j = 2*s;j <= m;j*=2){
		/*如果 r[s].key < r[2s].key,交换r[s]和r[2s]。交换后,判断r[2s+1]为根的子树是否为堆,
		若不是,则重复上述过程,将以r[2s]为根的子树调整为堆,直至到叶子结点 */
		if(int i < m && L.r[j].key < L.r[j+1].key)	
			++j;
		//如果r[s].key >= r[2s].key,说明以r[2s]为根的子树已经是堆,不做调整 	
		if(rc.key >= L.r[j].key) 
			break;
			
		L.r[s] = L.r[j];
		s = j;
	}
	L.r[s] = rc;	
}
 
//建初堆 
void CreatHeap(SqList &L)
{//把无序序列L.r[1..n]建成大根堆
	int n = L.length;
	
	for(int i = n/2;i > 0;--i)	//反复调用HeapAdjust() 
	HeapAdjust(L,i,n) ;
}

//对顺序表L进行堆排序
void HeapSort(SqList &L)
{
	CreatHeap(L);
	for(int i = L.length;i > 1;--i){
		x = L.r[1];
		L.r[1] = L.r[i];
		L.r[i] = x;
		HeapAdjust(L,1,i-1);
	} //for
 

算法特点:
(1)不稳定排序
(2)只能用于顺序存储结构
(3)当n较大、关键字基本有序时,可采用堆排序;
(4)时间复杂度: 在最坏情况下,O(nlog₂n),实验研究表明,其平均性能接近于最坏性能 ; 空间复杂度: O(1).

归并类

归并排序

归并排序是将两个或两个以上的有序表合并成一个有序表的过程。假设初始序列含有n个记录,则可看成是n个有序的子序列,每个子序列的长度为1,然后两两归并,得到┌n/2┐个长度为2或1的有序子序列;再两两归并,……,如此重复,直至得到一个长度为n的有序序列为止。
步骤如下:
2路归并排序将R[low…high]中的记录归并排序后放入T[low… high]中.当序列长度写于1时,递归结束,否则:
(1)将当前序列一分为二,求出分裂点mid =low+high/2(mid为不大于该数的最大整数);
(2)对子序列R[low. …mid]递归,进行归并排序,结果放人S[low…mid]中:
(3)对子序列R[mid + 1.high]递归,进行归并排序,结果放人S[mid+ L.high]中;
(4)调用算法Merge,将有序的两个子序列S[low…mid]和S[mid + 1…high]归并为一个有序的序列T[Iow… high];
代码如下:

归并排序	

//相邻两个有序子序列的归并
void Merge (RedType R[],RedType TI,int low,int mid,int high)
 {//将有序表R[low. .mid]和R[mid+1..highJ归并为有序表T[low. .high]
 	int i = low,j = mid+1,k = low;

 	while(i <= mid && j <= high){		//将R中记录由小到大地并入T中
 		if(R[i].key <= R[j].key) T[k++]=R[i++];
		 else T[k++] = R[j++];
	}//while
	
	while(i <= mid) T[k++] = R[i++];	//将剩余的R[i..mid]复制到T中
	while(j <= high) T[k++]=R[j++];		//将剩余的R[j.high]复制到T中
}

//进行归并排序递归处理
void MSort(RedType R[],RedType T[],int loow,int high)
{//R[low..high]归并排序后放入T[low..high]
	int mid;
	
	if(low == high) T[low] = R[low];
	else{
		mid = (low + high)/2;	//将当前序列一分为二,求出分裂点mid
		MSort(R,S,low,mid);
		MSort(R,S,mid+1,high);
		Merge(S,T,low,mid,high); 
	}//else
 }
 
//对顺序表L做归并排序
void MergeSort(SqList &L)
{
	MSort(L.r,L.r,1,L.length);
 } 

算法特点:
(1)稳定排序
(2)可用于顺序存储结构,也可用于链式存储结构,且不需要附加存储空间,但递归实现时仍需要开辟相应的递归工作栈;
(3)当n较大、关键字基本有序时,可采用堆排序;
(4)时间复杂度: O(nlog₂n); 空间复杂度: O(n).

分配类

基数排序

假设记录的逻辑关键字由d个“关键字”组成,每个关键字可能取rd个值。只要从最低数位关键字起,按关键字的不同值将序列中记录“分配”到rd个队列中后再“收集”之,如此重复d次完成排序。按这种方法实现排序称之为基数排序,其中“”指的是rd的取值范围。
实例说明:{278,109,063,930,589,184,505,269,008,083}排序
(1) “建立分别表示0-9的队列”(先进先出)
(2) 第一趟分配对最低数位关键字,分配到各个队列;分配完成后收集各队列队尾元素,重新链成一个链表;
(3)第二趟分配和收集对次关键字(十位数)进行,过程与个位数相同;
(4)第三趟分配和收集对百位数进行,过程同上。至此排序完成。
代码如下:

基数排序,从小到大,下标从1开始
void RadixSort(int num[], int n) {
	int max = num[1], i,j,k, count = 0, *temp[10];
	for(i = 2; i <= n; i++) { //找最大数
		 if(max < num[i]) {
			max = num[i];
		}
	}
	//printf("%d",max);
	while(max) { //确定最大位数
		count++;
		max = max/10;
	}
	//printf("%d",count);
	for(i = 0; i < 10; i++) {
		temp[i] = (int *)malloc(sizeof(int) * (n+1));
		temp[i][0] = 0;
	}
	max = 1;
	while(count--) {
		for(i = 1; i <= n; i++) { //分桶 
			temp[num[i]/max%10][0]++;
			temp[num[i]/max%10][temp[num[i]/max%10][0]] = num[i];
		}

		k = 1;
		for(i = 0; i < 10; i++) { //合并
			for(j = 1; j <= temp[i][0]; j++) {
				num[k++] = temp[i][j];
			}
			temp[i][0] = 0;
		}
//		for(i = 1; i <= n; i++){//找最大数
//		printf("%5d", num[i]);
//		}

		max *= 10;
	}
	for(i = 0; i < 10; i++) {
		free(temp[i]);
	}
}

算法特点:
(1)稳定排序
(2)可用于链式和顺序结构
(3)适用于n值较大但是关键字较小的序列,需要知道各级关键字的主次关系和各级关键字的取值范围;
(4)时间复杂度: O(d(n+rd))【每个记录含d个关键字,关键字的取值范围为rd个值】,可以突破基于关键字比较一类方法的下界O(nlog₂n),达到O(n);空间复杂度: O(n+rd).

后续博客中会附有上述排序算法完整代码(C语言),仅供参考,部分不足之处敬请谅解。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值