2.数据结构-排序

目录

 

总览:

一、简单排序(适用于少量数据元素排序)。

Comparable接口:

1、冒泡排序。

(1)设计冒泡排序算法。

(2)冒泡排序—时间复杂度分析。 

2、选择排序。

(1)设计选择排序算法。

(2)选择排序—时间复杂度分析。

 3、插入排序。

(1)设计插入排序算法。

(2)插入排序—时间复杂度分析。

 二、高级排序。

1、希尔排序。

(1)设计希尔排序算法。

(2)希尔排序—时间复杂度分析。

2、归并排序。

(1)递归。

(2)设计归并排序算法。

(3)归并排序—时间复杂度分析。(下面的k是层数或第k层)

3、快速排序。

(1)设计快速排序算法。

(2)快速排序—时间复杂度分析。

4、排序算法的稳定性。

(1)稳定性的定义。

(2)常见排序算法的稳定性。


 

总览:

3368f2b82b854d418729dc0cad54bc97.png

一、简单排序(适用于少量数据元素排序)。

Comparable接口:

Comparable接口可以排序的原理:执行对象中的compareTo()方法。

操作对象:实现了Comparable接口的对象,并且重写compareTo()方法。

如果我们把对象写入数组中,使用Arrays.sort排序。如果我们把对象写出集合中,使用Collections.sort排序,String类可以直接排序就是因为它实现了comparable,调用sort的时候,实际执行的还是compareTo方法。(如果不用工具类,可以不继承这个接口,可以自定义一个方法实现比较大小)

//student类的重写compareTo方法
@Override
    public int compareTo(Student o) {
        return this.age - o.age;
    }
package _排序_Comparable接口;

public class TestComparable {
    public static void main(String[] args) {
        //创建两个Student对象并调用getMax()方法,完成测试
        Student s1 = new Student();
        s1.setName("张三");
        s1.setAge(18);
        Student s2 = new Student();
        s1.setName("李四");
        s1.setAge(20);
        Comparable max = getMax(s1, s2);
        System.out.println(max);
    }
    public static Comparable getMax(Comparable c1,Comparable c2){
        int result = c1.compareTo(c2);
        //如果result<0,则c1比c2小
        //如果result>0,则c1比c2大
        //如果result==0,则c1和c2一样大
        if (result >= 0) {
            return c1;
        }else {
            return c2;
        }
    }
}

1、冒泡排序。

(1)设计冒泡排序算法。

4132a873b99342f098220b33237cea57.jpeg

package 排序_冒泡排序;

public class Bubble {
    //对数组a中的元素进行排序
    public static void sort(Comparable[] a){
        for (int i=a.length-1;i>0;i--){
            for (int j=0;j<i;j++){
                if (greater(a[j], a[j+1])){
                    exch(a,j,j+1);
                }
            }
        }
    }
    //比较v元素是否大于w元素
    public static boolean greater(Comparable v,Comparable w){
        return v.compareTo(w)>0;
    }
    //数组元素i和j交换位置
    public static void exch(Comparable[] a,int i,int j){
        Comparable temp;
        temp = a[j];
        a[j] = a[i];
        a[i] = temp;
    }
}

测试该冒泡排序算法是否成功:测试成功。

package 排序_冒泡排序;

import java.util.Arrays;
public class BubbleTest {
    public static void main(String[] args) {
//        int[] a={4,5,6,3,2,1};//出现错误原因:基本数据类型没有继承Comparable接口,要用包装类型数据
        Integer[] a={4,5,6,3,2,1};
        Bubble.sort(a);
        System.out.println(Arrays.toString(a));//转化成字符串类型输出
    }
}

(2)冒泡排序—时间复杂度分析。 

执行次数为什么要再加一次:因为执行的代码有两语句。

967a82182ae44977b13b5fab1e69deae.jpeg

2、选择排序。

(1)设计选择排序算法。

bfc5c6e09979473786ca0d36296674dc.png

package 排序_选择排序;

public class Selection {
    //对数组a中的元素进行排序
    public static void sort(Comparable[] a){
        for (int i = 0; i < a.length - 1; i++) {
            //定义一个变量,记录最小元素所在的索引,默认为参数选择排序的第一个元素所在的位置
            int minIndex = i;
            for (int j = i+1; j < a.length; j++) {
                if (geater(a[minIndex],a[j])){
                    minIndex = j;
                }
            }
            //交换最小元素所在索引minIndex处的值和i索引的值
            exch(a,i,minIndex);
        }
    }
    //比较v元素是否大于w元素
    private static boolean geater(Comparable v,Comparable w){
        return v.compareTo(w) > 0;
    }
    //数组元素i和j交换位置
    private static void exch(Comparable[] a,int i,int j){
        Comparable temp;
        temp = a[i];
        a[i] = a[j];
        a[j] = temp;
    }
}

测试该选择排序算法是否成功:测试成功。

package 排序_选择排序;

import java.util.Arrays;
public class SelectionTest {
    public static void main(String[] args) {
        Integer[] a = {4,6,8,7,9,2,10,1};
        Selection.sort(a);
        System.out.println(Arrays.toString(a));
    }
}

(2)选择排序—时间复杂度分析。

cacc9e7fc2bb405a941f71bdc3086104.png

 3、插入排序。

(1)设计插入排序算法。

d181e91d496d43ffb561cbcf7d7800df.png

package 排序_插入排序;

public class Insertion {
    //对数组a中的元素进行排序
    public static void sort(Comparable[] a){
        for (int i = 1; i < a.length; i++) {
            for (int j = i; j > 0; j--) {
                if (greater(a[j-1],a[j])){
                    exch(a,j-1,j);
                }else {
                    break;
                }
            }
        }
    }
    //比较v元素是否大于w元素
    private static boolean greater(Comparable v,Comparable w){
        return v.compareTo(w) > 0;
    }
    //数组元素i和j交换位置
    private static void exch(Comparable[] a,int i,int j){
        Comparable temp;
        temp = a[i];
        a[i] = a[j];
        a[j] = temp;
    }
}

测试该插入排序算法是否成功:测试成功。

package 排序_插入排序;

import java.util.Arrays;
public class InsertionTest {
    public static void main(String[] args) {
        Integer[] a = {4,3,2,10,12,1,5,6};
        Insertion.sort(a);
        System.out.println(Arrays.toString(a));
    }
}

(2)插入排序—时间复杂度分析。

 

87d81e44b0ab4827a79d9cb17cea2255.png

 二、高级排序。

1、希尔排序。

(1)设计希尔排序算法。

3ef910da9260482391f8d7e031fe8755.png

package 排序_希尔排序;

public class Shell {
    //对数组a中的元素进行排序
    public static void sort(Comparable[] a){
        //1.根据数组a的长度,确定增长量h的初始值;
        int h = 1;
        while (h < a.length / 2){
            h = h*2 + 1;
        }
        //2.希尔排序
        while (h >= 1) {
            //排序
            //2.1找到待插入的元素
            for (int i = h; i < a.length; i++) {
                //2.2把待插入的元素插入到有序数列中
                for (int j = i; j >= h; j-=h) {
                    //待插入元素是a[j],比较a[j]和a[j-h]
                    if (greater(a[j-h],a[j])){
                        exch(a,j-h,j);
                    }else {
                        //待插入元素已经找到合适的位置后,结束循环。
                        break;
                    }
                }
            }
            //减小h的值
            h = h/2;
        }

    }
    //比较v元素是否大于w元素
    private static boolean greater(Comparable v,Comparable w){
        //降序的话,就写 w.compareTo(v) > 0;
        return v.compareTo(w) > 0;
    }
    //数组元素i和j交换位置
    private static void exch(Comparable[] a,int i,int j){
        Comparable temp;
        temp = a[i];
        a[i] = a[j];
        a[j] = temp;
    }
}

测试该希尔排序算法是否成功:测试成功。

package 排序_希尔排序;

import java.util.Arrays;
public class ShellTest {
    public static void main(String[] args) {
        Integer[] a = {9,1,2,5,7,4,8,6,3,5};
        Shell.sort(a);
        System.out.println(Arrays.toString(a));
    }
}

(2)希尔排序—时间复杂度分析。

2bdc67c9607141ba8d65ff8e31c0648b.png

b4e2716015784c54b55222f30ce80f55.png log(n):增量降到1时的次数。

n:每次的比较次数。

package 排序_希尔排序;

import java.io.*;
import java.util.ArrayList;
import java.util.Collections;

public class SortCompare {
    //调用不同的测试方法,完成测试
    public static void main(String[] args) throws IOException {
        //1.创建一个ArrayList集合,保存读取出来的整数
        ArrayList<Integer> list = new ArrayList<>();
        //2.创建缓存读取流BufferedReader,读取数据,并存储到ArrayList中
        BufferedReader br = new BufferedReader(new InputStreamReader(SortCompare.class.getClassLoader().getResourceAsStream("reverse_arr.txt")));
        String line = null;
        while ((line = br.readLine()) != null) {
            //line是字符串,把line转换成Integer,存储到集合中
            Integer i = Integer.valueOf(line);
            list.add(i);
        }
        br.close();
        //3.把ArrayList集合转化成数组
        Integer[] a = new Integer[list.size()];
        list.toArray(a);
        //4.调用测试代码完成测试
//        testShell(a);//希尔排序执行的时间为:45毫秒
        testInsertion(a);//插入排序执行的时间为:38397毫秒
    }
    //测试希尔排序
    public static void testShell(Integer[] a){
        //1.获取执行之前的时间
        long start = System.currentTimeMillis();
        //2.执行算法代码
        Shell.sort(a);
        //3.获取执行之后时间
        long end = System.currentTimeMillis();
        //4.算出程序执行的时间并输出
        System.out.println("希尔排序执行的时间为:"+(end - start)+"毫秒");
    }
    //测试插入排序
    public static void testInsertion(Integer[] a){
        //1.获取执行之前的时间
        long start = System.currentTimeMillis();
        //2.执行算法代码
        排序_插入排序.Insertion.sort(a);
        //3.获取执行之后时间
        long end = System.currentTimeMillis();
        //4.算出程序执行的时间并输出
        System.out.println("插入排序执行的时间为:"+(end - start)+"毫秒");
    }


}

2、归并排序。

(1)递归。

package 排序_递归;

public class TestFactorial {
    public static void main(String[] args) {
        //求N的阶乘
        System.out.println(factorial(12));
    }
    public static long factorial(int n){
        if (n == 1 || n == 0) {
            return 1;
        }else {
            return n * factorial(n - 1);
        }
    }
}

(2)设计归并排序算法。

3a9a333c126e46c5afc47d7c70b112d1.png

package 排序_归并排序;

public class Merge {
    //归并排序需要的辅助数组
    private static Comparable[] assist;
    //比较v元素是否小于w元素
    private static boolean less(Comparable v,Comparable w){
        return v.compareTo(w) < 0;
    }
    //数组元素i和j交换位置
    private static void exch(Comparable[] a,int i,int j){
        Comparable temp;
        temp = a[i];
        a[i] = a[j];
        a[j] = temp;
    }
    //对数组a中的元素进行排序
    public static void sort(Comparable[] a){
        //1.初始化辅助数组assist
        assist = new Comparable[a.length];
        //2.定义一个lo变脸阿甘,和hi变量,分别记录数组中最小的索引和最大的索引
        int lo = 0;
        int hi = a.length - 1;
        //3.调用sort重载方法完成数组a中,从索引lo到索引到索引hi的元素的排序
        sort(a,lo,hi);
    }
    //对数组a中从lo到hi的元素进行排序
    private static void sort(Comparable[] a,int lo,int hi){
        //做安全性校验
        if (hi <= lo) {//注意这里,之前这里写错了
            return;
        }
        //对lo到hi之间的元素进行分为两个组,(hi+lo)/2,对于index,减法是安全的,加法是不安全的(相加可能超出int范围)
        int mid = lo + (hi - lo)/2;
        //分别对每一组数据进行排序
        sort(a,lo,mid);
        sort(a,mid+1,hi);
        //再把两个组中的数据进行归并
        merge(a,lo,mid,hi);
    }
    //对数组中,从lo到mid为一组,从mid+1到hi为一组,对这两组数据进行归并
    private static void merge(Comparable[] a,int lo,int mid,int hi){
        //定义三个指针
        int i = lo;
        int p1 = lo;
        int p2 = mid + 1;
        //遍历,移动p1指针和p2指针,比较对应索引处的值,找出小的那个,放到辅助数组的对应的索引处
        while (p1 <= mid && p2 <= hi) {//注意这里是用&&(如果用||会出错)
            //比较对应索引处的值
            if (less(a[p1],a[p2])){
                assist[i++] = a[p1++];//赋值给i索引后,再加1
            }else {
                assist[i++] = a[p2++];
            }
        }
        //遍历,如果p1的指针没有走完,那么顺序移动p1指针,把对应的元素放到辅助数组的对应索引处
        while (p1 <= mid) {
            assist[i++] = a[p1++];
        }
        //遍历,如果p2的指针没有走完,那么顺序移动p2指针,把对应的元素放到辅助数组的对应索引处
        while (p2 <= hi) {
            assist[i++] = a[p2++];
        }
        //把辅助数组中的元素拷贝到原数组中
        for (int index = lo; index <= hi; index++) {
            a[index] = assist[index];
        }
    }

}

测试该归并排序算法是否成功:测试成功。

package 排序_归并排序;

import java.util.Arrays;

public class TestMerge {
    public static void main(String[] args) {
        Integer[] a = {8, 4, 5, 7, 1, 3, 6, 2,3};
        Merge.sort(a);
        System.out.println(Arrays.toString(a));
    }
}

(3)归并排序—时间复杂度分析。(下面的k是层数或第k层)

8a8751066a374ea38bd6c357ed1a6799.png

7fc3305ba48b473fb6266ac28ec6053d.png 

package 排序_归并排序;

import 排序_希尔排序.Shell;
import java.io.*;
import java.util.ArrayList;

public class SortCompare {
    //调用不同的测试方法,完成测试
    public static void main(String[] args) throws IOException {
        //1.创建一个ArrayList集合,保存读取出来的整数
        ArrayList<Integer> list = new ArrayList<>();
        //2.创建缓存读取流BufferedReader,读取数据,并存储到ArrayList中
        BufferedReader br = new BufferedReader(new InputStreamReader(排序_希尔排序.SortCompare.class.getClassLoader().getResourceAsStream("reverse_arr.txt")));
        String line = null;
        while ((line = br.readLine()) != null) {
            //line是字符串,把line转换成Integer,存储到集合中
            Integer i = Integer.valueOf(line);
            list.add(i);
        }
        br.close();
        //3.把ArrayList集合转化成数组
        Integer[] a = new Integer[list.size()];
        list.toArray(a);
        //4.调用测试代码完成测试
//        testShell(a);//希尔排序执行的时间为:45毫秒
//        testMerge(a);//归并排序执行的时间为:113毫秒

    }
    //测试希尔排序
    public static void testShell(Integer[] a){
        //1.获取执行之前的时间
        long start = System.currentTimeMillis();
        //2.执行算法代码
        Shell.sort(a);
        //3.获取执行之后时间
        long end = System.currentTimeMillis();
        //4.算出程序执行的时间并输出
        System.out.println("希尔排序执行的时间为:"+(end - start)+"毫秒");
    }
    //测试归并排序
    public static void testMerge(Integer[] a){
        //1.获取执行之前的时间
        long start = System.currentTimeMillis();
        //2.执行算法代码
        排序_归并排序.Merge.sort(a);
        //3.获取执行之后时间
        long end = System.currentTimeMillis();
        //4.算出程序执行的时间并输出
        System.out.println("归并排序执行的时间为:"+(end - start)+"毫秒");
    }
}

3、快速排序。

(1)设计快速排序算法。

e4d75d756eb54c68948447b314a5c318.png

package 排序_快速排序;

public class Quick {
    //比较v元素是否小于w元素
    private static boolean less (Comparable v,Comparable w){
        return v.compareTo(w) < 0;
    }
    //数组元素i和j交换位置
    private static void exch (Comparable[] a,int i,int j){
        Comparable temp = a[i];
        a[i] = a[j];
        a[j] = temp;
    }
    //对数组内的元素进行排序
    public static void sort(Comparable[] a){
        int lo = 0;
        int hi = a.length - 1;
        sort(a,lo,hi);
    }
    //对数组a中从索引lo到索引hi之间的元素进行排序
    private  static void sort(Comparable[] a,int lo,int hi){
        //安全性校验(以及递归出口,递归退出条件)
        if (hi <= lo) {
            return;
        }
        //需要对数组中lo索引到hi索引处的元素进行分组(左子组和右子组)
        int partition = partition(a, lo, hi);//返回的是分组的分界值所在的索引,分界值位置变换后的索引
        //让左子组有序
        sort(a,lo,partition-1);
        //让右子组有序
        sort(a,partition+1,hi);
    }
    //对数组a中,从索引lo到索引hi之间的元素进行分组,并返回分组界限对应的索引
    public static int partition(Comparable[] a,int lo,int hi){
        //确定分界值
        Comparable key = a[lo];
        //定义两个指针,分别指向待切分元素的最小索引和最大索引处的下一个位置
        int left = lo;
        int right = hi + 1;
        //切分
        while (true) {
            //先从右往左扫描,移动right指针,找到一个比分界值小的元素,停止。
            while (less(key,a[--right])) {
                //这里没必要判断,当它等于分界值的时候,循环也就结束了,压根不会进来
                //if (right == lo){ break; }
            }
            //再从左往右扫描,移动left指针,找到一个比分界值大的元素,停止
            while (less(a[++left],key)) {
                //if (left == hi){ break; }
                //修改过后的,可以减少不必要的循环
                if (left >= right){
                    break;
                }
            }
            //判断left>=right,如果是,则证明元素扫描完毕,结束循环,如果不是,则交换元素即可
            if (left >= right) {
                break;
            }else {
                exch(a,left,right);
            }
        }
        exch(a,lo,right);//注意,这里必须是right,因为left可能比right大(因为相等时left如果再++)
        //exch(a,lo,left);//错误,原因使用left和分界量交换位置
        return right;
    }
}

测试该快速排序算法是否成功:测试成功。(如果是3,1,2,3第一次分组的时候数组也是3,1,2,3,是不影响的)

package 排序_快速排序;

import java.util.Arrays;

public class QuickTest {
    public static void main(String[] args) {
//        Integer[] a = {6,1,2,7,9,3,4,5,8};
        Integer[] a = {3,1,2,3,4,3,2,3,3,2,3,2,2,1,1,1,2,3,4,5,5,5,4,4,3,3,3};
        Quick.sort(a);
        System.out.println(Arrays.toString(a));//[1, 2, 3, 4, 5, 6, 7, 8, 9]
        //[1, 1, 1, 1, 2, 2, 2, 2, 2, 2, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 4, 4, 4, 4, 5, 5, 5]
    }
}

(2)快速排序—时间复杂度分析。

注意:这里的时间复杂度分析是:O(nlogn)

1:每层(认为每层的个数都是n个)的遍历复杂度为O(n)

2:这里说的切分的次数是指层数(切了多少层,并不是指切分的次数):O(logn)

6930522ac287451496652a857e2c1d47.png

4、排序算法的稳定性。

(1)稳定性的定义。

b8d6d367b492483383eb1244266f86ad.png

(2)常见排序算法的稳定性。

494f4d1016a44b4a8dc6ca097da7b18c.png

 

 

 

评论 1
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值