排序算法学习笔记——C语言

(一)基于比较的排序

一、插入排序

插入排序的主要操作是插入,基本思想是:每次将一个待排序的纪录按其关键码的大小插入到一个已经排好序的有序序列中,直到全部记录排好序为止。

1.直接插入排序

(1)基本思想:在插入第i个记录时,前面的i-1个记录已经排好序。将第1个记录看成是初始有序表,从第2个记录起依次插入到这个有序表中,直到第n个记录插入。要设置监视哨兵
(2)代码如下:

void insertSort(int r[],int n){
	for(int i=2;i<=n;i++){
		r[0]=r[i];//哨兵,暂存当前要插入的记录 
		int j=i-1;
		while(r[0]<r[j]){//找当前记录的插入位置 
			r[j+1]=r[j];
			j--;
		}
		r[j+1]=r[0];
	}
}

(3)算法性能分析:
最好情况(正序):比较n-1次,移动2(n-1)次,时间复杂度为O(n)
最差情况(逆序):时间复杂度为O(n²)
平均情况(随机排列):O(n²)

如何改进?
答:寻找插入位置时,可以用二分查找法,而不是逐次比较。

(4)适合于:待排序记录基本有序或待排序记录较小时

2.希尔排序

(1)基本思想:将整个待排序记录分割将相距某个“增量d”的记录组成一个子序列,d逐次减小,d1=n/2, di+1 =d1/2)成若干个子序列,在子序列内分别进行直接插入排序,待整个序列中的记录基本有序时,对全体记录进行直接插入排序
(2)代码:

void shellSort(int r[],int n){
	for(int d=n/2;d>=1;d=d/2){//分割子序列 
		for(int i=d+1;i<=n;i++){//对子序列进行直接插入排序 
			r[0]=r[i];//哨兵,暂存当前要插入的记录 
			j=i-d;
			while(j>0&&r[0]<r[j]){//加入j>0是为了防止越界 
				r[j+d]=r[j];
				j=j-d;
			}
			r[j+d]=r[0];
		}
	} 
}

(3)算法性能分析:
取决于增量的函数。开始时d较大,每个子序列的记录个数少,排序快;当d逐渐减小,每个子序列的纪录个数越多,但是由于整个序列已经基本有序,所以整体排序速度也比较快。
研究表明,时间复杂度在O(n²)与O(log2n)之间。

二、交换排序

1.冒泡排序

(1)基本思想:两两比较相邻记录的关键码,如果反序则交换,直到没有反序的记录为止。(每一趟排序都能确定一个关键码的最终位置)
(2)代码:

void bubbleSort(in r[],int n){
	for(int i=0;i<n-1;i++){      //冒泡排序,小数沉底,第i趟 
		for(int j=0;j<n-i-1;j++){  //将当前数与前面的每个数比较
			if(r[j]<r[j+1]){
				temp=r[j+1];
				r[j+1]=r[j];
				r[j]=temp;
			}
		}
	}
}

如何改进?
答:冒泡排序每次交换记录后,只能改变一对逆序记录,而快速排序则从待排序记录两端开始进行比较合适交换,并逐渐向中间靠拢,每经过一次交换,有可能改变几对逆序记录,从而加快了排序速度。

(3)算法性能分析:
最好情况(正序):比较n-1次,移动0次,时间复杂度为O(n)
最差情况(逆序):时间复杂度为O(n²)
平均情况(随机排列):O(n²)

2.快速排序

(1)基本思想:选一个轴值,通过一趟排序将记录分割成两个独立的子序列,前一部分的关键码值均小于或等于轴值,后一部分均大于轴值,然后分别对这两个子序列递归地执行快速排序,直到整个序列有序。
(2)代码:

void quickSort(int r[],int first,int end){//在first和end之间递归地进行快排
	if(first<end){
		pos=partition(r,first,end);//一次划分
		quickSort(r,first,pos-1);//对前一个序列递归快排 
		quickSort(r,pos+1,end);//对后一个序列递归快排
	}
}
int partition(int a[],int first,int end){//计算轴值 
	int i=first;
	int j=end;
	while(i<j){
		while(i<j&&a[i].key<=a[j].key)
			j--;//j从后往前扫描,直到r[j]<r[i],退出循环,将r[j]移动到r[i]的位置,使得关键码小的往前移 
			if(i<j){//交换a[i]和a[j] 
				temp=a[j];
				a[j]=a[i];
				a[i]=temp;
				i++;//改变i值,现在开始从前往后 
			}
		while(i<j&&a[i].key<=a[j].key)
			i++;//i从前往后扫描,直到r[i]>r[j],退出循环,将r[i]移动到r[j]的位置,使得关键码大的往后移
			if(i<j){//交换a[i]和a[j] 
				temp=a[j];
				a[j]=a[i];
				a[i]=temp;
				j--;//改变j值,现在开始从后往前 
			}	
	}
	return i;//直到i=j,退出循环 ,返回轴值 
}

(3)算法性能分析:
最好情况:每次划分后,左右侧子表长度相同。时间复杂度为O(nlog2n)
最差情况:每次划分后,只能得到一个比上一次划分少一个记录的子序列。时间复杂度为O(n²)
平均情况(随机排列):O(nlog2n)
(4)不适合于表基本有序的情况

三、选择排序

选择排序的主要操作是选择,主要思想是:每趟排序在当前待排序序列中选出关键码值最小的记录,添加到有序序列中。

1.简单选择排序

(1)基本思想:每趟排序在当前待排序序列中选出关键码值最小的记录,添加到有序序列中。
(2)代码:

void selectSort(int r[],int n){
	for(int i=0;i<n-1;i++){
		index=i;
		for(j=i+1;j<n;j++){//找到无序区最小的数的下标 
			if(r[j]<r[min]){
				min=j;
			}
		}
		if(min!=i){//无序区最小的记录作为有序区的第i个记录 
			temp=r[i];
			r[i]=r[min];
			r[min]=temp;
		}
	}
} 

(3)算法性能分析:
平均情况:O(n²)

2.堆排序

(1)基本思想:每次构造一个堆,将堆的根结点(最大值或最小值)添加到有序序列中,然后将它从堆中移走,并将剩余记录再调整(从最后一个分支结点=n/2处开始,从下往上递归调整) 为堆,再取出根结点,以此类推,直到堆中只有一个记录。
注意:从序号1开始顺序存储结点,方便结点处理。

堆是具有下列性质的完全二叉树
1.每个结点的值都大于等于其左右孩子的值,即为小根堆。
小根堆的根结点是所有结点的最小者
2.每个结点的值都小于等于其左右孩子的值,即为大根堆。
小根堆的根结点是所有结点的最大者
3.左右孩子仍然是堆。
4.结点i的双亲结点为i/2,左孩子是2i,右孩子是2i+1。

(2)代码:

//递归构建一个大根堆 
for(k=n/2;k>=1;k--){
	sift(r,k,n);
}
void sift(int r[],int k,int end){
	i=k;j=2*i;//j是i的左孩子 
	while(j<=end){
		if(j<end&&r[j]<r[j+1])//j<end判断是否有右子树
			j++;  //使得j停留在值大的那个孩子,即r[j]为较大的那个孩子 
		if(r[i]<r[j]){//较大的孩子和i比较 
			temp=r[i];
			r[i]=r[j];
			r[j]=temp;
		}
		//往上调整后,打破了下面的堆,则继续将下面的子树重新调整
		i=j;
		j=2*i;
	}
}

(3)算法性能分析:
平均情况:O(nlogn)

四、归并排序

(1)基本思想:归并排序是用分治思想,分治模式在每一层递归上有三个步骤:
分解:将n个元素分成个含n/2个元素的子序列。
解决:用合并排序法对两个子序列递归的排序。
合并:合并两个已排序的子序列已得到排序结果。
(2)代码:
(3)算法性能分析:
最好情况:时间复杂度为O(n)。
最差情况:时间复杂度为O(nlogn)。
平均情况:O(nlogn)

(二)不基于比较的排序

1.基数排序

(1)基本思想:对于待排序的整数序列,依次比较各个整数的个位数、十位数、百位数…,数位不够的用 0 表示;对于待排序的字符串序列,依次比较各个字符串的第一个字符、第二个字符、第三个字符…,位数不够的用 NULL 表示。

参考:
1.B站“懒猫老师”。老师有动画视频讲解,非常清晰,可以帮助快速了解算法运行原理。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

小王不熬夜.com

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值