八大排序算法(思想加代码,收藏此一篇足矣)

        三个简单排序,原理和代码实现都非常简单,后边会奉上进阶版本的排序。本文中只以不降序排序举例。

1.冒泡排序

       算法思想:每次将待排序列中最大的数放置在末尾,然后将减小待排序列长度,循环n-1次,当待排序列长度为1时,完成排序。算法平均复杂度O(n^{2}),空间复杂度O(1),是稳定排序。下边的代码在交换的时候,是用异或进行交换,可以不使用额外空间来交换两个数,不过发现仿佛速度有点慢,大家知道怎么回事就行了,面试的时候可能会被问到。

#include<bits/stdc++.h>
using namespace std;
int main() {
	int n ;
	cin>>n;
	int R[n];
	for(int i = 0; i < n; i++) {
		cin>>R[i];
	}
	//bubble sort
	for(int i = 0; i < n-1; i++) {
		for(int j = 0; j < n-1-i; j++) {
			if(R[j] > R[j+1]) {
				R[j] = R[j] ^ R[j+1];
				R[j+1] = R[j] ^ R[j+1];
				R[j] = R[j] ^ R[j+1];
			}
		}
	}
	//print
	for(int w = 0; w < n; w++) {
		cout<<R[w]<<" ";
	}
	cout<<endl;
	return 0;
}

2.直接选择排序

       算法思想:每次找到序列中最小的值。依次与序列前边的值交换,循环n-1次,排序完成。平均时间复杂度复杂度O(n^{2}),空间复杂度O(1),是不稳定排序。选择排序应该是最直接最容易理解的一种算法了。

#include<bits/stdc++.h>
using namespace std;
int main() {
	int n;
	cin>>n;
	int R[n];
	for(int i = 0; i < n; i++) {
		cin>>R[i];
	}
	//select sort
	for(int i = 0; i < n-1; i++) {
		for(int j = i; j < n; j++) {
			if(R[j] < R[i]) {
				R[j] = R[j] ^ R[i];
				R[i] = R[j] ^ R[i];
				R[j] = R[j] ^ R[i];
			}
		}
	}
	//print
	for(int w = 0; w < n; w++) {
		cout<<R[w]<<" ";
	}
	cout<<endl;
	return 0;
}

3.直接插入排序

       算法思想:顾名思义就是把数字按规则插入到指定位置上,看到其他博客用扑克牌来说明感觉很形象,即手里拿的牌是有序的,每次将新来的牌插入在正确的位置。平均时间复杂度O(n^{2}),空间复杂度O(1),是稳定排序。

#include<bits/stdc++.h>
using namespace std;
int main() {
	int n;
	cin>>n;
	int R[n];
	for(int i = 0; i < n; i++) {
		cin>>R[i];
	}
	//insert sort
	for(int i = 1; i < n; i++) {
		int j = i;
		int temp = R[j];
		while(j > 0 && R[j] >= temp)
		{
			R[j]=R[j-1];
			j--;
		}
		R[j]=temp;	
	}
	//print
	for(int w = 0; w < n; w++) {
		cout<<R[w]<<" ";
	}
	cout<<endl;
	return 0;
}

       这三个算法是最简单的排序算法,但也至关重要,后续更加先进快速的排序大多是在同样的思想上衍生出来的。


接下来是由以上三个简单排序算法进阶而来的三个高级排序算法:

4.希尔排序--直接插入排序进阶版

算法思想:将整个待排序列分成若干份子序列,对每份子序列分别使用插入排序,当对最后一份子序列(即增量因子为1的时候)使用插入排序后,整个序列即有序。希尔排序的时间复杂度很难定论,这里不深入了。希尔排序是不稳定算法。

网上找到一张图,我觉得不错,可惜不知道怎么的将第二次分组后的排序过程忽略了,还是放上来帮助大家理解,

丢失的第二步,大同小异,将每隔两位的数字一起看成一个子序列,然后使用直接插入排序。这样进行两次插入排序,两个子序列就有序了,就可以去第三步了。

看到这里,你肯定不知所以然,难道直接插入排序它不香吗,为什么要这么繁琐。所谓的希尔排序竟然要这么多次直接插入排序后才能完成排序,为什么会比直插要快呢?

当我们使用直接插入排序的时候,如果碰到数组末尾的数字与数组开头的数字位置摆放错误,那按照直插的思想,需要将中间的所有数字后移,造成效率低下,如果在一次排序中碰到很多很多个这样的情况,那时间浪费的就太多了。

希尔排序做的就是先将整体序列逐渐向有序的方向逼近。在希尔排序开始的时候,因为dk(用来分子序列的间隔)很大,所以分成的子序列会很小,虽然可能会分很多子序列,但是这个时候用直插的效率非常的高(因为短啊),随着dk的渐渐缩小,整体序列也变的越来越有序,对于整体基本有序的数组,用直插的效率也很高。

分而治之的思想又体现出来了。。。

希尔排序代码:

#include<bits/stdc++.h>
using namespace std;
const int num = 10;

void insertsort(int R[],int cnt,int dk)
{
	for(int i = dk;i<cnt;i++)
	{
		if(R[i]<R[i-dk])//如果后边的数字比前边的小 就要对当前数字在当前序列插入排序
		{
			int j = i - dk;
			int x = R[i];
			while(j>=0&&x<R[j])
			{
				R[j+dk] = R[j];
				j -= dk;
			}
			R[j+dk] = x;
		 } 
	}
	return ;
}

void shellsort(int R[],int cnt)
	{
		int div = 2;
		int dk = cnt/div;
		while(dk>=1)
		{
			insertsort(R,10,dk);
			dk/=div;
		}
		return ;
	}

int main()
{	
	int R[num] = {49,38,65,97,76,13,27,49,55,04};
	shellsort(R,num);
	for(int i = 0;i<num;i++)
	{
		cout<<R[i]<<" "<<endl;
	}
	return 0;
}

5.堆排序--直接选择排序进阶版

算法思想:要掌握堆排序,得先了解什么是堆。所谓堆,其实就是完全二叉树的形式。堆又分为大根堆和小根堆,大根堆顾名思义,即在二叉树中,根节点是最大的,每一个根节点都大于自己的左右孩子,小根堆则恰好相反,第一次接触这个概念的同学直接看图好理解点。

         那堆排序就是将我们的待排序列转换成这样的堆。那转换好后干嘛呢?

         因为此时堆的堆顶一定满足是当前堆的最大或最小值(取决与是大根堆还是小根堆,本文后边统一默认用大根堆举例),那我们就把这个堆顶的最大值放在待排数组中的最后一位,然后不管它了,将它从二叉树中移除,在我们的堆中,用最后一个叶子节点来顶替他的位置。

         这个时候我们的堆就不满足大根堆的性质了,经过一系列的操作,我们把他重新转换为大根堆,这时的堆顶又是当前二叉树中的最大值,再将堆顶放在待排数组中的倒数第二位。循环往复,直到二叉树被删没,我们的数组就有序啦!

         下边以大根堆举例:

了解了整体过程后,我们可以看到,堆排序主要由两个步骤组成:

1.建立初始大根堆。

2.拿走并替换根节点,重新维护大根堆。

首先从建立初始大根堆来说,将待排数组按顺序列为一颗完全二叉树,然后从他的最后一个非叶子节点开始操作(完全二叉树共n个节点,则第一个非叶子节点为n/2 ),比较它和儿子节点的大小,符合性质就去判断倒数第二个非叶子节点,不符合就和较大的儿子交换位置,然后再判断转换位置后还符不符合,不符合继续交换,直至符合为止,然后去判断倒数第二个非叶子节点。直到操作到根节点后,初始大根堆就建立好了。

再说维护大根堆,之前的大根堆是完好的,然而用新元素将堆顶替换掉了而导致不符合大根堆性质,所以需要维护使之符合。由于只是因为栈顶的元素变化,那我们在维护的时候只要把这个小东西摆到应该摆的位置上,大根堆就维护好了。过程和建立初始大根堆类似,不同的是,维护大根堆直接从根节点开始操作,将根节点摆放在符合的位置上,那大根堆就已经维护好了,不需要去判断别的。

堆排序代码:

#include<bits/stdc++.h>
using namespace std;
//大顶堆

void HeapAdjust(int R[],int pos,int n) {
	int temp = R[pos];
	for(int i=2*pos; i<=n; i*=2) {
		if(i<n&&R[i]<R[i+1])
			i++;//取儿子中较大的
		if(R[pos]>R[i])
			break;//当前小堆满足大顶堆性质
		else {
			R[pos] = R[i];
			pos = i;
		}
		R[pos] = temp;
	}
	return ;
}
void HeapSort(int R[],int n) {
	//构建初始大顶堆
	for(int i = n/2; i>0; i--) {
		HeapAdjust(R,i,n);
	}
	for(int i = n; i>0; i--) {
		int temp = R[1];
		R[1] = R[i];
		R[i] = temp;
		HeapAdjust(R,1,i-1);
	}
	return ;
}

void Print(int R[]) {
	for(int i=1; i<sizeof(R); i++) {
		printf("%d\n",R[i]);
	}
	return ;
}
int main() {
	int n = 8;
	//数组中的下标为0的位置不可用,随便给个数字滥竽充数,哈哈
	int R[n+1] = {99999,99,87,67,32,56,23,9,11};
	//堆排序
	HeapSort(R,n);
	Print(R);
	return 0;
}

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值