数据结构与算法

一、数据结构和算法概述

1.1什么是数据结构?

数据结构就是把数据元素按照一定的关系组织起来的集合,用来组织和存储数据。

1.2数据结构分类

传统上,我们可以把数据结构分为逻辑结构和物理结构两大类。

逻辑结构分类:

a.集合结构:集合结构中数据元素除了属于同一个集合外,他们之间没有任何其他的关系。

b.线性结构:线性结构中的数据元素之间存在一对一的关系

c.树形结构:树形结构中的数据元素之间存在一对多的层次关系

d.图形结构:图形结构的数据元素是多对多的关系

物理结构:

顺序存储结构(数组) -->把数据元素放到地址连续的存储单元里面,便于寻址。

链式存储结构(链表)。 -->存储单元可以是连续的也可以是不连续的,通过指针存放数据元素的地址,来查找相关联数据元素的位置。

1.3什么是算法?

根据一定的条件,对一些数据进行计算,得到需要的结果。

算法的两个目标:

1.花最少的时间完成需求;
2.占用最少的内存空间完成需求;

二、算法分析

1.1算法的时间复杂度分析

事后分析估算方法、事前分析估算方法(主要依赖于算法的好坏和问题的输入规模)。

1.2函数渐近增长

概念:给定两个函数f(n)和g(n),如果存在一个整数N,使得对于所有的n>N,f(n)总是比g(n)大,那么我们说f(n)的增长渐近快于g(n)。

总结:

1.算法函数中的常数可以忽略;
2.算法函数中最高次幂的常数因子可以忽略;
3.算法函数中最高次幂越小,算法效率越高。

1.3算法时间复杂度

时间复杂度的一个总结:

在这里插入图片描述

复杂程度从低到高依次为:O(1)<O(logn)<O(n)<O(nlogn)<O(n2)<O(n3)

1.4java中常见内存占用

1.基本数据类型内存占用情况:

在这里插入图片描述

2.计算机访问内存的方式都是一次一个字节

在这里插入图片描述

3.一个引用(机器地址)需要8个字节表示:
例如: int a ;,则a这个变量需要占用8个字节来表示
4.创建一个对象,比如new Date(),除了Date对象内部存储的数据(例如年月日等信息)占用的内存,该对象本身也
有内存开销,每个对象的自身开销是16个字节,用来保存对象的头信息。
5.一般内存的使用,如果不够8个字节,都会被自动填充为8字节:

在这里插入图片描述

6.java中数组被被限定为对象,他们一般都会因为记录长度而需要额外的内存,一个原始数据类型的数组一般需要
24字节的头信息(16个自己的对象开销,4字节用于保存长度以及4个填充字节)再加上保存值所需的内存。

1.5算法的空间复杂度

算法的空间复杂度计算公式记作:S(n)=O(f(n)),其中n为输入规模,f(n)为语句关于n所占存储空间的函数。

案例:
对指定的数组元素进行反转,并返回反转的内容。

# 解法1
public static int[] reverse1(int[] arr){
    int n=arr.length;//申请4个字节
    int temp;//申请4个字节
    for(int start=0,end=n-1;start<=end;start++,end--){
        temp=arr[start];
        arr[start]=arr[end];
        arr[end]=temp;
    }
    return arr;
}

#解法2
public static int[] reverse2(int[] arr){
    int n=arr.length;//申请4个字节
    int[] temp=new int[n];//申请n*4个字节+数组自身头信息开销24个字节
    for (int i = n-1; i >=0; i--) {
        temp[n-1-i]=arr[i];
    }
    return temp;
}

由于java中有内存垃圾回收机制,并且jvm对程序的内存占用也有优化(例如即时编译),我们无法精确的评估一
个java程序的内存占用情况,但是了解了java的基本内存占用,使我们可以对java程序的内存占用情况进行估算。java开发的,基本上都是服务器开发,普通情况下直接说复杂度,默认为算法的时间复杂度。

三、简单排序

1.1 Comparable接口介绍
1.定义一个学生类Student,具有年龄age和姓名username两个属性,并通过Comparable接口提供比较规则;
2.定义测试类Test,在测试类Test中定义测试方法Comparable getMax(Comparable c1,Comparable c2)完成测试

//学生类
public class Student implements Comparable<Student>{
    private String username;
    private int age;
    public String getUsername() {
    	return username;
    }
    public void setUsername(String username) {
    	this.username = username;
    }
    public int getAge() {
    	return age;
    }
    public void setAge(int age) {
    	this.age = age;
    }
    @Override
    public String toString() {
        return "Student{" +
        "username='" + username + '\'' +
        ", age=" + age +
        '}';
    }
    //定义比较规则
    @Override
    public int compareTo(Student o) {
        return this.getAge()-o.getAge();
        }
}
//测试类
public class Test {
    public static void main(String[] args) {
        Student stu1 = new Student();
        stu1.setUsername("zhangsan");
        stu1.setAge(17);
        Student stu2 = new Student();
        stu2.setUsername("lisi");
        stu2.setAge(19);
        Comparable max = getMax(stu1, stu2);
        System.out.println(max);
    }
    //测试方法,获取两个元素中的较大值
    public static Comparable getMax(Comparable c1,Comparable c2){
        int cmp = c1.compareTo(c2);
        if (cmp>=0){
            return c1;
        }else{
            return c2;
        }
    }
}

1.1 冒泡排序(Bubble)

冒泡排序API设计:时间复杂度为O(N^2)

在这里插入图片描述

//排序代码
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元素
    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 t = a[i];
        a[i]=a[j];
        a[j]=t;
    }
}
//测试代码
public class Test {
    public static void main(String[] args) {
        Integer[] a = {4, 5, 6, 3, 2, 1};
        Bubble.sort(a);
        System.out.println(Arrays.toString(a));
    }
}

1.2 选择排序(Selection)

排序原理:时间复杂度为O(N^2)
1.每一次遍历的过程中,都假定第一个索引处的元素是最小值,和其他索引处的值依次进行比较,如果当前索引处的值大于其他某个索引处的值,则假定其他某个索引出的值为最小值,最后可以找到最小值所在的索引
2.交换第一个索引处和最小值所在的索引处的值

在这里插入图片描述

public class Selection {
    //对数组a中的元素进行排序
    public static void sort(Comparable[] a){
        for (int i=0;i<a.length-1;i++){
            //假定本次遍历,最小值所在的索引是i
            int minIndex=i;
            for (int j=i+1;j<a.length;j++){
                if (greater(a[minIndex],a[j])){
                    //跟换最小值所在的索引
                    minIndex=j;
                }
            }
            //交换i索引处和minIndex索引处的值
            exch(a,i,minIndex);
        }
    }
	//比较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 t = a[i];
        a[i]=a[j];
        a[j]=t;
    }
}
//测试代码
public class Test {
    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));
    }
}

1.3 插入排序(Insertion)

排序原理:时间复杂度为O(N^2)
1.把所有的元素分为两组,已经排序的和未排序的;
2.找到未排序的组中的第一个元素,向已经排序的组中进行插入;
3.倒叙遍历已经排序的元素,依次和待插入的元素进行比较,直到找到一个元素小于等于待插入元素,那么就把待
插入元素放到这个位置,其他的元素向后移动一位;

在这里插入图片描述

插入排序代码实现:

public class Insertion {
    /*
    对数组a中的元素进行排序
    */
    public static void sort(Comparable[] a){
        for (int i=1;i<a.length;i++){
            //当前元素为a[i],依次和i前面的元素比较,找到一个小于等于a[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 t = a[i];
        a[i]=a[j];
        a[j]=t;
    }
}

二、高级排序

1.1希尔排序(Shell)

希尔排序是插入排序的一种,又称“缩小增量排序”,是插入排序算法的一种更高效的改进版本。

排序原理:
1.选定一个增长量h,按照增长量h作为数据分组的依据,对数据进行分组;
2.对分好组的每一组数据完成插入排序;
3.减小增长量,最小减为1,重复第二步操作。

在这里插入图片描述

增长量h的确定:增长量h的值每一固定的规则,我们这里采用以下规则:

int h=1
    while(h<数组的长度/2){
        h=2h+1//3,7
    }
//循环结束后我们就可以确定h的最大值;
h的减小规则为:
    h=h/2

希尔排序的代码实现:

//排序代码
public class Shell {
    /*
    对数组a中的元素进行排序
    */
    public static void sort(Comparable[] a){
        int N = a.length;
        //确定增长量h的最大值
        int h=1;
        while(h<N/2){
            h=h*2+1;
        }
        //当增长量h小于1,排序结束
        while(h>=1){
            //找到待插入的元素
            for (int i=h;i<N;i++){
                //a[i]就是待插入的元素
                //把a[i]插入到a[i-h],a[i-2h],a[i-3h]...序列中
                for (int j=i;j>=h;j-=h){
                    //a[j]就是待插入元素,依次和a[j-h],a[j-2h],a[j-3h]进行比较,如果a[j]小,那么
                    交换位置,如果不小于,a[j]大,则插入完成。
                        if (greater(a[j-h],a[j])){
                            exch(a,j,j-h);
                        }else{
                            break;
                        }
                }
            }
            h/=2;
        }
    }
    /*
    比较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 t = a[i];
        a[i]=a[j];
        a[j]=t;
    }
}
//测试代码
public class Test {
    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));
    }
}

可以使用事后分析法对希尔排序和插入排序做性能比较。

希尔排序和插入排序性能比较测试代码:

public class SortCompare {
    public static void main(String[] args) throws Exception{
        ArrayList<Integer> list = new ArrayList<>();
        //读取reverse_arr.txt文件
        BufferedReader reader = new BufferedReader(new InputStreamReader(new
                                                                         FileInputStream("reverse_shell_insertion.txt")));
        String line=null;
        while((line=reader.readLine())!=null){
            //把每一个数字存入到集合中
            list.add(Integer.valueOf(line));
        }
        reader.close();
        //把集合转换成数组
        Integer[] arr = new Integer[list.size()];
        list.toArray(arr);
        testInsertion(arr);//使用插入排序耗时:20859
        // testShell(arr);//使用希尔排序耗时:31
    }
    public static void testInsertion(Integer[] arr){
        //使用插入排序完成测试
        long start = System.currentTimeMillis();
        Insertion.sort(arr);
        long end= System.currentTimeMillis();
        System.out.println("使用插入排序耗时:"+(end-start));
    }
    public static void testShell(Integer[] arr){
        //使用希尔排序完成测试
        long start = System.currentTimeMillis();
        Shell.sort(arr);
        long end = System.currentTimeMillis();
        System.out.println("使用希尔排序耗时:"+(end-start));
    }
}

通过测试发现,在处理大批量数据时,希尔排序的性能确实高于插入排序。

1.2 归并排序(SortCompare)

递归:定义方法时,在方法内部调用方法本身,称之为递归.

注意事项:在递归中,不能无限制的调用自己,必须要有边界条件,能够让递归结束,因为每一次递归调用都会在栈内存开辟新的空间,重新执行方法,如果递归的层级太深,很容易造成栈内存溢出。

需求:请定义一个方法,使用递归完成求N的阶乘;

分析:
1!: 1
2!: 2*1=2*1!
3!: 3*2*1=3*2!
4!: 4*3*2*1=4*3!
...
n!: n*(n-1)*(n-2)...*2*1=n*(n-1)!
所以,假设有一个方法factorial(n)用来求n的阶乘,那么n的阶乘还可以表示为n*factorial(n-1)

代码实现:

public class Test {
    public static void main(String[] args) throws Exception {
        int result = factorial(5);
        System.out.println(result);
    }
    public static int factorial(int n){
        if (n==1){
            return 1;
        }
        return n*factorial(n-1);
    }
}

归并排序是建立在归并操作上的一种有效的排序算法,该算法是采用分治法的一个非常典型的应用。将已有序的子
序列合并,得到完全有序的序列;即先使每个子序列有序,再使子序列段间有序。若将两个有序表合并成一个有序
表,称为二路归并。

排序原理:时间复杂度为O(nlogn);
1.尽可能的一组数据拆分成两个元素相等的子组,并对每一个子组继续拆分,直到拆分后的每个子组的元素个数是1为止。
2.将相邻的两个子组进行合并成一个有序的大组;
3.不断的重复步骤2,直到最终只有一个组为止。

在这里插入图片描述

归并排序API设计:

在这里插入图片描述

归并原理图:

在这里插入图片描述

归并排序代码实现:

//排序代码
public class Merge {
    private static Comparable[] assist;//归并所需要的辅助数组
    /*
    对数组a中的元素进行排序
    */
    public static void sort(Comparable[] a) {
        assist = new Comparable[a.length];
        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;
        }
        int mid = lo + (hi - lo) / 2;
        //对lo到mid之间的元素进行排序;
        sort(a, lo, mid);
        //对mid+1到hi之间的元素进行排序;
        sort(a, mid+1, hi);
        //对lo到mid这组数据和mid到hi这组数据进行归并
        merge(a, lo, mid, hi);
    }
    /*
    对数组中,从lo到mid为一组,从mid+1到hi为一组,对这两组数据进行归并
    */
    private static void merge(Comparable[] a, int lo, int mid, int hi) {
        //lo到mid这组数据和mid+1到hi这组数据归并到辅助数组assist对应的索引处
        int i = lo;//定义一个指针,指向assist数组中开始填充数据的索引
        int p1 = lo;//定义一个指针,指向第一组数据的第一个元素
        int p2 = mid + 1;//定义一个指针,指向第二组数据的第一个元素
        //比较左边小组和右边小组中的元素大小,哪个小,就把哪个数据填充到assist数组中
        while (p1 <= mid && p2 <= hi) {
            if (less(a[p1], a[p2])) {
                assist[i++] = a[p1++];
            } else {
                assist[i++] = a[p2++];
            }
        }
        //上面的循环结束后,如果退出循环的条件是p1<=mid,则证明左边小组中的数据已经归并完毕,如果退
        出循环的条件是p2<=hi,则证明右边小组的数据已经填充完毕;
            //所以需要把未填充完毕的数据继续填充到assist中,//下面两个循环,只会执行其中的一个
            while(p1<=mid){
                assist[i++]=a[p1++];
            }
        while(p2<=hi){
            assist[i++]=a[p2++];
        }
        //到现在为止,assist数组中,从lo到hi的元素是有序的,再把数据拷贝到a数组中对应的索引处
        for (int index=lo;index<=hi;index++){
            a[index]=assist[index];
        }
    }
    /*
    比较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 t = a[i];
        a[i] = a[j];
        a[j] = t;
    }
}
//测试代码
public class Test {
    public static void main(String[] args) throws Exception {
        Integer[] arr = {8, 4, 5, 7, 1, 3, 6, 2};
        Merge.sort(arr);
        System.out.println(Arrays.toString(arr));
    }
}

归并排序的缺点:
需要申请额外的数组空间,导致空间复杂度提升,是典型的以空间换时间的操作。

1.3 快速排序

快速排序是对冒泡排序的一种改进。它的基本思想是:通过一趟排序将要排序的数据分割成独立的两部分,其中一
部分的所有数据都比另外一部分的所有数据都要小,然后再按此方法对这两部分数据分别进行快速排序,整个排序
过程可以递归进行,以此达到整个数据变成有序序列。

排序原理:
1.首先设定一个分界值,通过该分界值将数组分成左右两部分;
2.将大于或等于分界值的数据放到到数组右边,小于分界值的数据放到数组的左边。

3.然后,左边和右边的数据可以独立排序。对于左侧的数组数据,又可以取一个分界值,将该部分数据分成左右两
部分,同样在左边放置较小值,右边放置较大值。右侧的数组数据也可以做类似处理。
4.重复上述过程,可以看出,这是一个递归定义。通过递归将左侧部分排好序后,再递归排好右侧部分的顺序。当
左侧和右侧两个部分的数据排完序后,整个数组的排序也就完成了。

在这里插入图片描述

快速排序API设计:

切分原理:
把一个数组切分成两个子数组的基本思想:
1.找一个基准值,用两个指针分别指向数组的头部和尾部;
2.先从尾部向头部开始搜索一个比基准值小的元素,搜索到即停止,并记录指针的位置;
3.再从头部向尾部开始搜索一个比基准值大的元素,搜索到即停止,并记录指针的位置;
4.交换当前左边指针位置和右边指针位置的元素;
5.重复2,3,4步骤,直到左边指针的值大于右边指针的值停止。

在这里插入图片描述

快速排序代码实现:平均时间复杂度为O(nlogn),最优的时间复杂度为O(nlogn);最坏时间复杂度为O(n^2);

//排序代码
public class Quick {
    public static void sort(Comparable[] a) {
        int lo = 0;
        int hi = a.length - 1;
        sort(a, lo,hi);
    }
    private static void sort(Comparable[] a, int lo, int hi) {
        if (hi<=lo){
            return;
        }
        //对a数组中,从lo到hi的元素进行切分
        int partition = partition(a, lo, hi);
        //对左边分组中的元素进行排序
        //对右边分组中的元素进行排序
        sort(a,lo,partition-1);
        sort(a,partition+1,hi);
    }
    public static int partition(Comparable[] a, int lo, int hi) {
        Comparable key=a[lo];//把最左边的元素当做基准值
        int left=lo;//定义一个左侧指针,初始指向最左边的元素
        int right=hi+1;//定义一个右侧指针,初始指向左右侧的元素下一个位置
        //进行切分
        while(true){
            //先从右往左扫描,找到一个比基准值小的元素
            while(less(key,a[--right])){//循环停止,证明找到了一个比基准值小的元素
                if (right==lo){
                    break;//已经扫描到最左边了,无需继续扫描
                }
            }
            //再从左往右扫描,找一个比基准值大的元素
            while(less(a[++left],key)){//循环停止,证明找到了一个比基准值大的元素
                if (left==hi){
                    break;//已经扫描到了最右边了,无需继续扫描
                }
            }
            if (left>=right){
                //扫描完了所有元素,结束循环
                break;
            }else{
                //交换left和right索引处的元素
                exch(a,left,right);
            }
        }
        //交换最后rigth索引处和基准值所在的索引处的值
        exch(a,lo,right);
        return right;//right就是切分的界限
    }
    /*
    数组元素i和j交换位置
    */
    private static void exch(Comparable[] a, int i, int j) {
        Comparable t = a[i];
        a[i] = a[j];
        a[j] = t;
    }
    /*
    比较v元素是否小于w元素
    */
    private static boolean less(Comparable v, Comparable w) {
        return v.compareTo(w) < 0;
    }
}
//测试代码
public class Test {
    public static void main(String[] args) throws Exception {
        Integer[] arr = {6, 1, 2, 7, 9, 3, 4, 5, 8};
        Quick.sort(arr);
        System.out.println(Arrays.toString(arr));
    }
}

1.4 堆排序

堆的定义

堆通常可以被看做是一棵完全二叉树的数组对象。

在这里插入图片描述

堆的特性:

  1. 它是完全二叉树,除了树的最后一层结点不需要是满的,其它的每一层从左到右都是满的,如果最后一层结点不是满的,那么要求左满右不满。
  2. 它通常用数组来实现。具体方法就是将二叉树的结点按照层级顺序放入数组中,根结点在位置1,它的子结点在位置2和3,而子结点的子结点则分别在位置4,5,6和7,以此类推。

在这里插入图片描述

堆排序

给定一个数组:String[] arr = {“S”,“O”,“R”,“T”,“E”,“X”,“A”,“M”,“P”,“L”,“E”}

1.4.1 API设计

在这里插入图片描述

1.4.2 堆构造过程

创建一个新数组,把原数组0~length-1的数据拷贝到新数组的1~length处,再从新数组长度的一半处开始往1索引处扫描(从右往左),然后对扫描到的每一个元素做下沉调整即可。

在这里插入图片描述

//每个节点的元素大于等于子节点的元素,最大的元素为堆顶。

1.4.3 堆排序过程

对构造好的堆,我们只需要做类似于堆的删除操作,就可以完成排序。
1.将堆顶元素和堆中最后一个元素交换位置;
2.通过对堆顶元素下沉调整堆,把最大的元素放到堆顶(此时最后一个元素不参与堆的调整,因为最大的数据已经到了数组的最右边)
3.重复1~2步骤,直到堆中剩最后一个元素。

在这里插入图片描述

1.4.4 代码
//对排序代码
public class HeapSort {
    //对source数组中的数据从小到大排序
    public static void sort(Comparable[] source) {
        //1.创建一个比原数组大1的数组
        Comparable[] heap = new Comparable[source.length + 1];
        //2.构造堆
        createHeap(source,heap);
        //3.堆排序
        //3.1定义一个变量,记录heap中未排序的所有元素中最大的索引
        int N = heap.length-1;
        while(N!=1){
            //3.2交换heap中索引1处的元素和N处的元素
            exch(heap,1,N);
            N--;
            //3.3对索引1处的元素在0~N范围内做下沉操作
            sink(heap,1,N);
        }
        //4.heap中的数据已经有序,拷贝到source中
        System.arraycopy(heap,1,source,0,source.length);
    }
    //根据原数组source,构造出堆heap
    private static void createHeap(Comparable[] source, Comparable[] heap) {
        //1.把source中的数据拷贝到heap中,从heap的1索引处开始填充
        System.arraycopy(source,0,heap,1,source.length);
        //2.从heap索引的一半处开始倒叙遍历,对得到的每一个元素做下沉操作
        for (int i = (heap.length-1)/2; i>0 ; i--) {
            sink(heap,i,heap.length-1);
        }
    }
    //判断heap堆中索引i处的元素是否小于索引j处的元素
    private static boolean less(Comparable[] heap, int i, int j) {
        return heap[i].compareTo(heap[j])<0;
    }
    //交换heap堆中i索引和j索引处的值
    private static void exch(Comparable[] heap, int i, int j) {
        Comparable tmp = heap[i];
        heap[i] = heap[j];
        heap[j] = tmp;
    }
    //在heap堆中,对target处的元素做下沉,范围是0~range
    private static void sink(Comparable[] heap, int target, int range){
        //退出条件:没有子结点了
        while (2*target<=range){
            //1.找出target结点的两个子结点中的较大值
            int max=2*target;
            if (2*target+1<=range){
                //存在右子结点
                if (less(heap,2*target,2*target+1)){
                    max=2*target+1;
                }
            }
            //2.如果当前结点的值小于子结点中的较大值,则交换
            if(less(heap,target,max)){
                exch(heap,target,max);
            }
            //3.更新target的值
            target=max;
        }
    }
}
//测试代码
public class Test {
    public static void main(String[] args) throws Exception {
        String[] arr = {"S", "O", "R", "T", "E", "X", "A", "M", "P", "L", "E"};
        HeapSort.sort(arr);
        System.out.println(Arrays.toString(arr));
    }
}

1.5 计数排序

非比较排序,桶思想的一种。

算法思想:

  1. 量大但是范围小
    • 某大型企业数万名员工年龄排序
    • 如何快速得知高考名次

算法步骤

  • 花O(n)的时间扫描一下整个序列 A,获取最小值 min 和最大值 max
  • 开辟一块新的空间创建新的数组 B,长度为 ( max - min + 1)
  • 数组 B 中 index 的元素记录的值是 A 中某元素出现的次数
  • 最后输出目标整数序列,具体的逻辑是遍历数组 B,输出相应元素以及对应的个数

复杂度,时间、空间均为 O(n+k)

//Java 代码实现
public class CountingSort implements IArraySort {

    @Override
    public int[] sort(int[] sourceArray) throws Exception {
        // 对 arr 进行拷贝,不改变参数内容
        int[] arr = Arrays.copyOf(sourceArray, sourceArray.length);

        int maxValue = getMaxValue(arr);

        return countingSort(arr, maxValue);
    }

    private int[] countingSort(int[] arr, int maxValue) {
        int bucketLen = maxValue + 1;
        int[] bucket = new int[bucketLen];

        for (int value : arr) {
            bucket[value]++;
        }

        int sortedIndex = 0;
        for (int j = 0; j < bucketLen; j++) {
            while (bucket[j] > 0) {
                arr[sortedIndex++] = j;
                bucket[j]--;
            }
        }
        return arr;
    }

    private int getMaxValue(int[] arr) {
        int maxValue = arr[0];
        for (int value : arr) {
            if (maxValue < value) {
                maxValue = value;
            }
        }
        return maxValue;
    }
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值