几个简单排序算法的总结

大学里面的接触的排序的理论大部分不是冒泡就是选择,实际上啊,排序算法有很多,然而接触的冒泡和选择是简单的,但不是最简单的,也不是运行效率最高的。
排序算法网上说的有9种。这篇我们只介绍冒泡排序,选择排序,插入排序这些最基本的排序。还是一样的,学习排序算法得从最简单最基础的学习。

1.冒泡排序

什么是冒泡排序?

我们通过遍历数组,将相邻的元素拿来比较,然后根据升序或者降序的原则彼此进行交换。最后将最大、或者最小的元素放在数组的末位置上,就像小鱼吐泡泡一样,将那个元素冒上来了(到了数组的末端的位置上了)。

冒泡排序的算法

1、比较相邻的元素。如果第一个比第二个大,就交换他们两个。
2、对每一对相邻元素做同样的工作,从开始第一对到结尾的最后一对。最后的元素应该会是最大的数。
3、针对所有的元素重复以上的步骤,除了最后一个。
4、持续每次对越来越少的元素重复上面的步骤,直到没有任何一对数字需要比较。

冒泡排序实例

比如:我们给出这些数
10,1,35,61,89,36,55
第一趟排序中
     第一次排序 1,10,35,61,89,36,55         这里将1和10进行了比较,10比1大。交换位置
     第二次排序 1,10,35,61,89,36,55         这里将10和35进行了比较,10比35小,不交换位置
     第三次排序 1,10,35,61,89,36,55         这里将35和61进行了比较,35比61小,不交换位置
     第四次排序 1,10,35,61,89,36,55         这里将61和89进行了比较,61比89小,不交换位置
     第五次排序 1,10,35,61,36,89,55         这里将89和36进行了比较,89比36大,交换位置
     第六次排序 1,10,35,61,36,55,89         这里将1和10进行了比较,89比55大,交换位置
因此第一趟排序进行了6次比较,将89冒到顶端位置,结束一轮排序
(img-JCZI2ML1-1586742588065)(/localImg/ArtImage/2020/02/20200215223922123.jpg)]
我们首先得知道冒泡算法是两层循环,那么就得确定i和j的范围。根据a[j]和a[j+1]进行的比较,那么由于受到j+1下标的影响,那么j首先得小于n-1,其次我们知道冒到最后的是不会再参加比较了因此还得减去i(因为i所在的次序就是数组末端已经确定好位置的个数),故j从0开始,j<n-1-i。而且由于确定n-1个数的位置(确定了n-1个位置,那么最后一个数的位置的大小关系就会确定了,那么它的位置就确定了。而且必定在首位置。。。。。。如果实在不理解,就去多想想,多画画)因此i要从0开始,i要小于n-1。确定好范围了,我们就可以写代码了。

冒泡排序代码

public static void main(String[] args) {
		int a[]=new int[10];
		Random random=new Random();
		for(int i=0;i<10;i++)
			a[i]=random.nextInt(11);  //随机生成10个0到10的数字
		System.out.println("生成的结果是:"+Arrays.toString(a));
		BubbleSort(a);
	System.out.println("排序的结果是:"+Arrays.toString(a));
	}

//冒泡排序
	public static void BubbleSort(int a[]) {
		boolean flag=false;  //标志位:若发生交换则flag为true,中断循环,否则继续循环。因为没有交换说明后面的顺序已经是升序了(优化)
		for(int i=0;i<a.length-1;i++) {
			flag=false;
			for(int j=0;j<a.length-i-1;j++) {
				//升序排列
				if(a[j]>a[j+1]) {
					//这里我们用位运算进行交换,而不用在定义变量
					a[j]=a[j]^a[j+1];
					a[j+1]=a[j]^a[j+1];
					a[j]=a[j]^a[j+1];
					
					flag=true;
				}
			}
			if(!flag) break;
		}
	}

冒泡算法的分析

我们看到啊,冒泡算法是两层for循环,那么我们在最坏条件下分析(就是每个都得遍历,每个都得比较):
我们看第18行的比较,比较算法是O(1)
但是经历了多少次排序呢?
经过两层for循环,结合上边的图。不难想到由j从0到n-2-i这n-i-1个数中得出
n-1+n-2+…+1,根据前n项和的公式得出:n(n-1)/2
因此T(n)=n(n-1)/2 +O(1),即O(n)=n^2

那么最好的情况呢。最好的情况就是正序排序。那么就是T(n)=n-1,即O(n)
因此平均时间复杂度就是O(n^2)

2、选择排序

什么是选择排序?

选择排序是一种简单直观的排序算法。它的工作原理是:第一次从待排序的数据元素中选出最小(或最大)的一个元素,存放在序列的起始位置,然后再从剩余的未排序元素中寻找到最小(大)元素,然后放到已排序的序列的开头(末尾)。以此类推,直到全部待排序的数据元素的个数为零。

选择排序的算法

1、首先记录初始下标min(一般把遍历的数组的初始位置的下标记录下来)
2、遍历数组,与a[min]进行比较,如果比它小,那么将这个下标记录下来。
3、遍历一遍之后,我们将记录下标的数组位置与初始位置进行交换
4、重复上边步骤,直到数组中元素排序完毕

选择排序的实例

我们还是以  10,1,35,61,89,36,55这些数进行排序

在这里插入图片描述
在这里插入图片描述




这就是第一轮的选择排序,大家可以去试试第二轮到最后的排序。这里不再详细说了

选择排序也是两层for循环,根据a[min]>a[j]进行分析,首先知道这个排序也是确定n-1个数的位置,因为n-1个的位置确定了,那么第n个的位置自然就确定了,一般是在末位置。那么对于i就是从0开始,i要小于n-1。对于j,因为i位置的下标已经记录了,要比较,那就从记录的下一个位置进行比较,不用去和自己再比较一次了,所以j从i+1开始,j要小于n就可以了。确定好范围了,我们就可以写代码了。

选择排序代码

public static void main(String[] args) {
		int a[]=new int[10];
		Random random=new Random();
		for(int i=0;i<10;i++)
			a[i]=random.nextInt(11);  //随机生成10个0到10的数字
		System.out.println("生成的结果是:"+Arrays.toString(a));
	    ChooseSort(a);
	    System.out.println("排序的结果是:"+Arrays.toString(a));
	 }

//简单选择排序
	public static void ChooseSort(int a[]) {
		for(int i=0;i<a.length-1;i++) {
			int min=i;  //记录初始下标
			for(int j=i+1;j<a.length;j++) {
				if(a[min]>a[j]) 
					min=j;  //记录数组中最小的值的下标
			}
			if(min!=i) {  //如果min发生了变换,就进行交换,否则就不再交换。从而减少交换次数
				a[min]=a[min]^a[i];
				a[i]=a[min]^a[i];
				a[min]=a[min]^a[i];
			}
		}
	}

选择排序算法分析

我们看到啊,选择排序算法是两层for循环,那么我们在最坏条件下分析(就是每个都得遍历,每个都得比较):
我们从i+1到n-1个遍历,这n-i-1个数能清除分析出
n-1+n-2+…+1根据前n项和的公式得出:n(n-1)/2
因此T(n)=n(n-1)/2 +O(1),即O(n)=n^2

如果是正序排序。你会发现还是T(n)=n(n-1)/2,即O(n^2),因为即使正序,它还是得需要老老实实的经历n-1次的从头遍历到尾的过程
因此平均时间复杂度就是O(n^2)

3、插入排序

什么是插入排序?

插入排序是每步将一个待排序的记录,按其关键码值的大小插入前面已经排序的文件中适当位置上,直到全部插入完为止。

插入排序的算法

插入排序的算法不止一种,这里就说一种最常用的。
将待插入的元素拿出来分别与前面数组的元素进行比较,在升序下,如果比它小,就将这个数组中的元素后移一个位置,直到拿出来的元素比前面大,即找到了位置,插进去就行。

插入排序的实例

比如:我们给出这几个数
9,5,37,15,45,25

插入排序也是两层循环,那么这两层循环是for和while的搭配。因为a[0]是直接插入,不可能去和前面的元素进行比较,因为数组前面没有元素。那么,for循环里面的i从1开始,i小于n。在比较a[j]与a[j-1]中,我们用temp记录a[i]的位置,让j是从i开始,因为j-1,所以j要大于0,当然根据数据结构顺序存储的插入算法,要想进入while循环体必定是temp<a[j-1],循环体里面进行数组元素移动操作。我们根据这样,编写代码。

插入排序代码

public static void main(String[] args) {
		int a[]=new int[10];
		Random random=new Random();
		for(int i=0;i<10;i++)
			a[i]=random.nextInt(11);  //随机生成10个0到10的数字
		System.out.println("生成的结果是:"+Arrays.toString(a));
	    InsertSort(a);
	    System.out.println("排序的结果是:"+Arrays.toString(a));
	}

        //直接插入排序
	public static void InsertSort(int a[]) {
		int temp,j;
		for(int i=1;i<a.length;i++) {
			temp=a[i];
			j=i;
			while(j>0&&temp<a[j-1]) {  //进入循环,首先将temp取出来,然后将数组中的比temp大的元素一点点往后移动。
				a[j]=a[j-1];
				j--;
			}
			a[j]=temp;  //找到目标位置将temp插进去
		}
	}

插入排序算法分析

我们在最坏条件下分析(就是每个都得遍历,每个都得比较),就是在完全逆序条件下进行分析,j从i开始到1是i个,因此得出
1+2+3+…+n-1根据前n项和的公式得出:n(n-1)/2
因此T(n)=n(n-1)/2 +O(1),即O(n)=n^2

那么最好的情况呢。最好的情况就是正序排序。T(n)=n-1+O(1),毫无疑问n-1是for进行了i-1次循环得到的,因为temp>a[j-1],进不了while循环体
而平均时间复杂度就是O(n^2)

4、排序算法的稳定性

什么是排序算法的稳定性?

假定在待排序的记录序列中,存在多个具有相同的关键字的记录,若经过排序,这些记录的相对次序保持不变,即在原序列中,r[i]=r[j],且r[i]在r[j]之前,而在排序后的序列中,r[i]仍在r[j]之前,则称这种排序算法是稳定的;否则称为不稳定的。

比如说:用3,5,5,4排序,大家看到,a[1]和a[2]具有一定的先后顺序,对第二个数进行选择排序(i=1),经过一轮排序后。min=3,即a[min]要和a[i]进行交换,变成3,4,5,5。大家看到a[2]和a[3]是5。而且重复元素(5)的值的先后顺序发生了变化,因此选择算法是不稳定的。
大家细想冒泡算法和插入算法,冒泡排序就是把小的元素往前调或者把大的元素往后调。比较是相邻的两个元素比较,交换也发生在这两个元素之间。所以,如果两个元素相等,我想你是不会再无聊地把他们俩交换一下的;如果两个相等的元素没有相邻,那么即使通过前面的两两交换把两个相邻起来,这时候也不会交换,所以相同元素的前后顺序并没有改变,所以冒泡排序是一种稳定排序算法。
插入排序是在一个已经有序的小序列的基础上,一次插入一个元素。当然,刚开始这个有序的小序列只有1个元素,就是第一个元素。比较是从有序序列的末尾开始,也就是想要插入的元素和已经有序的最大者开始比起,如果比它大则直接插入在其后面,否则一直往前找直到找到它该插入的位置。如果碰见一个和插入元素相等的,那么插入元素把想插入的元素放在相等元素的后面。所以,相等元素的前后顺序没有改变,从原无序序列出去的顺序就是排好序后的顺序,所以插入排序是稳定的。

       我从不觉得知识点只是在课堂上看看听听,在课后随便写写作业就能掌握的。孔子说过:“学而时习之。”,知识点就应该去反复锤炼,反复学习,每一遍都有每一遍的收获。而不应该收获某个知识就在洋洋得意。
发布了9 篇原创文章 · 获赞 0 · 访问量 14

猜你喜欢

转载自blog.csdn.net/qq_41926985/article/details/105481912