希望疫情结束
今天开始,每天和大家分享一下学习数据结构和算法的新得和简单的案例,数据结构和算法是程序编程的内功。所以学好它至关重要。我希望可以和大家一起加持学下去,坚持将博客分享下去。
今天学习的是O(n^2)级别的排序算法----选择排序和直接插入排序
(默认情况下都是升序排序)
选择排序
在一堆待排的数据中,选择最小的元素,将其放在前面的排好序的位置,可能有点表达不清,举例说明:
比如我们有一个数组
【5,3,6,2,9】
第一轮我们找出元素中最小的元素2,将它和第一个元素交换位置
【2,3,6,5,9】
然后我们从第二个元素开始找最小的元素3,交换
从第三个元素开始找最小的元素5 交换
可以看出来选择排序就是每一次选择待排元素中的最小元素,然后放在前面排好序的元素的后面
如果还是不太明白过程,百度选择排序的动图,可以更加直观的看出
实现代码如下:
(当然为了显得高逼格一点 这里使用了泛型编程 以及可以比较自定义的对象类型)
package com.yxc.sort;
import com.yxc.domain.User;
import java.util.ArrayList;
/**
* @Auther: 小新
* @Date: 2020/2/7 09:06
* @Description:选择排序实现 效率 O(n^2)
* 思路:就是在一堆等待排列的元素中选择最小(最大)元素,将它与第一个元素交换位置,然后找到第二小(第二大)
* 元素,然后让它与第二个元素交换,以此类推下去就可以 代码实现如下
* 使用泛型编程,注意main函数中的a数据如果是整形必须定义成Integer,不能使用int,不然会出现
*/
public class SelectedSort {
public static void main(String[] args) {
Integer a[]={2,5,4,3,1,6,0,21,56,4};
Double b[]={2.0,5.2,6.8,8.5,6.88889,6.88888};
selectSort(a);
selectSort(b);
//自定义类型的排序测试
//有几个对象就创建几个对象的数组,不然会出现空指针异常
User[] user=new User[5];
user[0]=new User(1,"yxc",26);
user[1]=new User(1,"yxc1",25);
user[2]=new User(2,"yxc2",23);
user[3]=new User(9,"yxc3",23);
user[4]=new User(4,"yxc4",23);
selectSort(user);
for(int i=0;i<user.length;i++){
System.out.println(user[i]);
}
for(int i=0;i<a.length;i++){
System.out.print(a[i]+" ");
}
System.out.println();
for(int i=0;i<b.length;i++){
System.out.print(b[i]+" ");
}
}
/***
* 选择排序 升序
* @param a
*/
public static void selectSort(Comparable a[]){
int n=a.length;
//寻找[i,n]最小的元素 其中n为数字长度
for(int i=0;i<n;i++){
//记录最小元素的下标
int index=i;
for(int j=i+1;j<n;j++){
if(a[j].compareTo(a[index])<0){
index=j;
}
}
swap(a,i,index);
}
}
//交换两个元素
public static void swap(Object a[],int i,int j){
Object temp=a[i];
a[i]=a[j];
a[j]=temp;
}
}
User.java
package com.yxc.domain;
/**
* @Auther: 小新
* @Date: 2020/2/7 10:06
* @Description: 对自定义的对象类型排序
*/
public class User implements Comparable<User>{
private Integer id;
private String usernmae;
private Integer age;
//提供构造方法
public User(){}
public User(Integer id, String usernmae, Integer age) {
this.id = id;
this.usernmae = usernmae;
this.age = age;
}
/***
* 对用户排序,默认按ID的升序排列
* @param user
* @return
*/
@Override
public int compareTo(User user) {
if(this.id<user.id){
return -1;
}else if(this.id>user.id){
return 1;
}else{
//如果id相同就按照年龄排序
return this.age.compareTo(user.age);
}
}
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
public String getUsernmae() {
return usernmae;
}
public void setUsernmae(String usernmae) {
this.usernmae = usernmae;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
@Override
public String toString() {
return "User{" +
"id=" + id +
", usernmae='" + usernmae + '\'' +
", age=" + age +
'}';
}
}
介绍我们的第二种的排序算法 直接插入排序
其实生活中我们都使用过这种排序,就是打牌的时候,我们会习惯性的将牌排好序放,这个排序就类型与直接插入排序
排序原理:
我们循环每一个元素,然后将它放在前面合适的位置,这样看来,我们可以将所有数据分为两堆,排好序的和没有排好序的,每一次从没有排好序中取一个元素,将它插入到排好序的位置,而且保证按照正确的排好序
同样的例子说明问题:
【8,1,6,5,9】
首先第一个元素8默认就是排好序的。所以我们从第二个元素开始
拿出1,它比8小,所以我们要交换位置
【1,8,6,5,9】
拿出6,它比8小,交换位置,继续比较,比1大。不用交换
【1,6,8,5,9】
拿出5 比8小,交换,继续比较,比6小交换,继续比较 比1大,结束比较
以此类推下去:
另外还有一种简单的优化方式,就是每一次还需要交换,我们可以取出这个值,一直比较,直接放在合适的位置,这样在数据很大的时候效率会高一点,下面通过代码来实现算法并且比较一下效率问题
package com.yxc.sort;
import com.yxc.utils.CommonUtils;
/**
* @Auther: 小新
* @Date: 2020/2/7 15:02
* @Description:直接插入排序 O(n^2)
* 思想:循环遍历数组,将每一个元素放在前面排好序的合适的位置
*/
public class InsertionSort {
public static void main(String[] args) {
/*Integer[] arry={1,8,6,5,2,3};
insertion2(arry);
CommonUtils.printArray(arry);*/
//从测试结果可以看出来,当数据量很大的情况下,第二种方式还是更加高效一点的
Integer[] arry = CommonUtils.createRandom(10000);
//对两种排序效率的测试
insertion(arry);
insertion2(arry);
//测试结果:直接插入排序使用时间210ms
// 优化以后的直接插入排序的时间为3ms
}
/***
* 直接插入排序
* @param arry
*/
public static void insertion(Comparable[] arry){
long start = System.currentTimeMillis();
int length=arry.length;
//从第二个元素开始,第一个元素是排好序的
for(int i=1;i<length;i++){
//如果当前元素比前一个元素小,那么交换元素,然后开始继续往前比较
//如果当前元素大于前一个元素,就可以结束本次循环
for(int j=i;j>0&&(arry[j].compareTo(arry[j-1])<0);j--){
if(arry[j].compareTo(arry[j-1])<0){
CommonUtils.swap(arry,j,j-1);
}else{
break;
}
}
}
long end=System.currentTimeMillis();
System.out.println("直接插入排序使用时间"+(end-start)+"ms");
}
/***
* 直接插入的简单优化
* @param arry
*/
public static void insertion2(Comparable[] arry){
long start=System.currentTimeMillis();
int length=arry.length;
for(int i=1;i<length;i++){
//不用交换元素
Comparable temp=arry[i];
for(int j=i;j>0&&(arry[j].compareTo(arry[j-1])<0);j--){
if(temp.compareTo(arry[j-1])<0){
arry[j]=arry[j-1];
}else{
arry[j]= temp;
break;
}
}
}
long end=System.currentTimeMillis();
System.out.println("优化以后的直接插入排序的时间为"+(end-start)+"ms");
}
}
CommonUtils.java
package com.yxc.utils;
import java.util.Random;
/**
* @Auther: 小新
* @Date: 2020/2/7 10:37
* @Description:一些常用简单的的工具类
*/
public class CommonUtils {
/***
* 交换数组中两个元素的值
* @param arry
* @param i
* @param j
*/
public static void swap(Object arry[],int i,int j){
Object temp=arry[i];
arry[i]=arry[j];
arry[j]=temp;
}
/***
* 打印数组
* @param arry
*/
public static void printArray(Object[] arry){
for(int i=0;i<arry.length;i++){
System.out.print(arry[i]+" ");
}
}
/***
* 产生由随机数组成的数组
* @param n
*/
public static Integer[] createRandom(int n){
Integer[] arry=new Integer[n];
Random ran=new Random();
//如果是下面的情况,每一次产生的随机数就一样
//Random ran=new Random(1);
for(int i=0;i<n;i++){
arry[i]=ran.nextInt(1000);
}
return arry;
}
}
一些具体的代码中都有详细的注释。今天学习的两种算法都是O(n^2)级别的算法,当然这种级别的算法还有一个更加经典的就是冒泡排序,相信每一本书上第一个排序算法都会拿它举例子,大家可以自行学习。如果一个数组基本是有序的,那么我们使用直接插入排序基本可以达到O(n)级别的效率,注意我们在条件中有(arry[j].compareTo(arry[j-1])<0),如果基本有序那么可以直接跳出内循环。今天的学习就到这里结束,疫情期间,在此督促自己不断的学习。更希望疫情早点结束。为自己加油,为祖国加油!!!