说到排序,生活中我们有很多排序的方法,不同的排序方法有不同的优点和缺点,各有各的好处。这次我们来说一下四个排序,分别是选择排序、冒泡排序、插入排序和计数排序。下面就具体来看看吧!
-
选择排序
从算法逻辑上来说,选择排序是一种简单直观的排序算法。它的原理是:第一次从待排序的数据元素中选出最小(或最大)的一个元素,存放在序列的起始位置,然后再从剩余的未排序元素中寻找到最小(大)元素,然后放到已排序的序列的末尾。以此类推,直到全部待排序的数据元素的个数为零,这就是选择排序的具体流程。下面我们通过一个例子来具体看看。
class Test02{
public static void main(String[] args){
//选择排序 时间复杂度O(n^2)
selectSort();
}
//选择排序函数
public static void selectSort(){
int[] arr={8,5,9,2,7,4,6,1,3};
for(int i=0;i<arr.length-1;i++){//-1是因为没有必要进行最后一个数字的比较
for(int j=i+1;j<arr.length;j++){
if(arr[i]>arr[j]){
swap(arr,i,j);//即用-即释放
}
}
}
show(arr);
}
//交换函数
public static void swap(int[] arr,int i,int j){
//1.借助三方变量进行交换
//适用于所有的数据类型 比较通用
/*
int temp=arr[i];
arr[i]=arr[j];
arr[j]=temp;
*/
//2.借助加减法运算进行交换
//只适用于数字相关的数据类型
arr[i]=arr[i]+arr[j];
arr[j]=arr[i]-arr[j];
arr[i]=arr[i]-arr[j];
//3.借助位运算进行交换
//只适用于整数相关的数据类型
/*
int a=3;
int b=7;
a=a^b;
0011
0111
0100
b=a^b;
0100
0111
0011 ->3
a=a^b;
0100
0011
0111 ->7
*/
}
//输出函数
public static void show(int[] arr){
//[1,2,3,4,5,6,7,8,9]
String s="[";
for(int i=0;i<arr.length;i++){
if(i==arr.length-1){
s+=arr[i]+"]";
}else{
s+=arr[i]+",";
}
}
System.out.println(s);
}
}
上述代码中,我们在main函数中直接调用选择排序函数 ,相当于把其他过程都写进选择排序函数里面。然后在选择排序函数里面,我们又调用了两个函数,swap()函数和show()函数。swap函数的功能是交换两个数字的位置,而show函数的功能则是对整个数组进行遍历,最终输出。swap()函数交换两个数字的位置的方法有三种,都写在代码里面了。
综上所述,选择排序的思想就是比较+交换。具体操作方法:
第一趟:从n个记录中找出关键码最小的记录和第一个记录交换;
第二趟:从第二个记录开始的n-1个记录中再选出关键码最小的记录与第二个记录交换
以此类推......
第i趟,则从第i个记录开始的n-i+1个记录中选出关键码最小的记录与第i个记录交换,直到整个序列按关键码有序。
-
冒泡排序
冒泡排序是一种比较稳定的排序算法,它的原理其实也很简单,大概来说一下:
1.比较相邻的元素。如果第一个比第二个大,就交换他们两个。
2.对每一对相邻元素做同样的工作,从开始第一对到结尾的最后一对。在这一点,最后的元素应该会是最大的数。
3.除了最后一个元素以外对其他所有元素重复以上的步骤。
4.持续每次对越来越少的元素重复上面的步骤,直到没有任何一对数字需要比较。
这就是冒泡排序的流程图,很直观明了,我们再来看一个例子。
class Test02{
public static void main(String[] args){
//冒泡排序 时间复杂度O(n^2)
bubbleSort();
}
//冒泡排序函数
public static void bubbleSort(){
int[] arr={8,5,9,2,7,4,6,1,3};
for(int i=0;i<arr.length-1;i++){//-1是少一轮比较
for(int j=0;j<arr.length-1-i;j++){//-1避免重复比较和角标越界
if(arr[j]>arr[j+1]){
swap(arr,j,j+1);
}
}
}
show(arr);
}
//交换函数
public static void swap(int[] arr,int i,int j){
int temp=arr[i];
arr[i]=arr[j];
arr[j]=temp;
}
//输出函数
public static void show(int[] arr){
//[1,2,3,4,5,6,7,8,9]
String s="[";
for(int i=0;i<arr.length;i++){
if(i==arr.length-1){
s+=arr[i]+"]";
}else{
s+=arr[i]+",";
}
}
System.out.println(s);
}
}
关于这个swap()函数和show()函数,前面 我们都已经解释过了,这里就不再多做说明了,我就直接用了。
关于冒泡排序的思想我在这里简单说一下:它就是让数组当中相邻的两个数进行比较,数组当中比较小的数值向下沉,数值比较大的向上浮。n个数字要排序完成,总共进行n-1趟排序,每i趟的排序次数为(n-i)次,外层for循环控制循环次数,内层for循环控制相邻的两个元素进行比较。
冒泡排序的优点:每进行一趟排序,就会少比较一次。因为每进行一趟排序都会找出一个较大值。第一趟比较之后,排在最后的一个数一定是最大的一个数,第二趟排序的时候,只需要比较除了最后一个数以外的其他的数,同样也能找出一个最大的数排在参与第二趟比较的数后面,第三趟比较的时候,只需要比较除了最后两个数以外的其他的数,以此类推........也就是说,我们每进行一趟比较,每一趟就少比较一次,这在一定程度上减少了算法量。
-
插入排序
插入排序就是:假设第一个数已经是有序的,从第二个数开始,拿出第二个数进行向前插入排序,一直到最后一个数向前做插入排序,直到序列排完为止。来看一个例子
class Test02{
public static void main(String[] args){
//插入排序 时间复杂度O(n^2)
insertSort();
}
//插入排序函数
public static void insertSort(){
int[] arr={8,5,9,2,7,4,6,1,3};
int e;
int j;
for(int i=1;i<arr.length;i++){
e=arr[i];
for(j=i;j>0&&arr[j-1]>e;j--){
arr[j]=arr[j-1];
}
arr[j]=e;
}
/*
for(int i=1;i<arr.length;i++){
for(int j=i;j>0&&arr[j-1]>arr[j];j--){
swap(arr,j,j-1);
}
}
*/
show(arr);
}
/*
//交换函数
public static void swap(int[] arr,int i,int j){
int temp=arr[i];
arr[i]=arr[j];
arr[j]=temp;
}
*/
//输出函数
public static void show(int[] arr){
//[1,2,3,4,5,6,7,8,9]
String s="[";
for(int i=0;i<arr.length;i++){
if(i==arr.length-1){
s+=arr[i]+"]";
}else{
s+=arr[i]+",";
}
}
System.out.println(s);
}
}
上述插入排序函数中,注释掉的那一块是最先写的,然后进行了下优化,比较的次数能少了点。这个排序算法简单来说就是,如果当前数字的左边有数字并且左边的数字大于当前数字,则进行交换,接着进行下一个数字,直到排序完成为止。
-
计数排序
计数的优势在于在对一定范围内的整数排序时,它的复杂度为Ο(n+k)(其中k是整数的范围),它快于任何比较排序算法。当然这是一种牺牲空间换取时间的做法,但是当O(k)>O(n*log(n))的时候其效率反而不如基于比较的排序(如归并排序,堆排序)。来看个例子:
class Test02{
public static void main(String[] args){
//计数排序 时间复杂度O(n+m)
countSort();
}
//计数排序函数
public static void countSort(){
int[] arr={8,5,9,2,7,4,6,1,3,10,-3,-2,-10};
int min=arr[0];
int max=arr[0];
for(int i=0;i<arr.length;i++){//O(n)
if(arr[i]>max){
max=arr[i];
}
if(arr[i]<min){
min=arr[i];
}
}
int[] nums=new int[max-min+1];
int offset=min;
for(int i=0;i<arr.length;i++){//O(n)
nums[arr[i]-offset]++;
}
int index=0;
for(int i=0;i<nums.length;i++){//O(m)
if(nums[i]!=0){
for(int j=0;j<nums[i];j++){
arr[index++]=i+offset;
}
}
}
show(arr);
}
//输出函数
public static void show(int[] arr){
//[1,2,3,4,5,6,7,8,9]
String s="[";
for(int i=0;i<arr.length;i++){
if(i==arr.length-1){
s+=arr[i]+"]";
}else{
s+=arr[i]+",";
}
}
System.out.println(s);
}
}
它的具体流程:
第一步:找出原数组中元素值最大的,记为
max
。第二步:创建一个新数组
nums
,其长度是max
加1,其元素默认值都为0。第三步:遍历原数组中的元素,以原数组中的元素作为nums数组的索引,以原数组中的元素出现次数作为
nums
数组的元素值。第四步:arr[index++]用来表示最后的结果数组,起始索引
index
。第五步:遍历
nums
数组,找出其中元素值大于0的元素,将其对应的索引作为元素值填充到arr[index++]数组中去,每处理一次,nums
中的该元素值减1,直到该元素值不大于0,依次处理nums
中剩下的元素。第六步:返回结果数组arr[index++]。
上面这个算法流程可以用,但是它可能存在空间浪费的问题,所以我对这个算法进行了一点改进:那就是将数组长度定为max-min+1
,即不仅要找出最大值,还要找出最小值,根据两者的差值来确定数组的长度。底下这是一个草图,还有大致步骤。
总之,所有的排序算法都有互通之处,理解了一两种,其他的自然也就通了,另外,还是要多做题目,多多练习,才能孰能生巧。