Java数据结构与算法 一3

简介: Java数据结构与算法 一3

递归

  • 自己调用自己,每次的变量都不一样



规范


执行一个方法时,就创建一个新的受保护的独立空间(栈空间)

方法的局部变量是独立的,不会相互影响, 比如n变量

如果方法中使用的是引用类型变量(比如数组),就会共享该引用类型的数据

递归必须向退出递归的条件逼近,否则就是无限递归,出现StackOverflowError(栈溢出), 死龟了

当一个方法执行完毕,或者遇到return,就会返回,遵守谁调用,就将结果 返回给谁,同时当方法执行完毕或者返回时,该方法也就执行完毕

递归 - 迷宫回溯问题

  1. 小球得到的路径,和程序员 设置的找路策略有关即:找 路的上下左右的顺序相关
  2. 再得到小球路径时,可以先 使用(下右上左),再改成(上 右下左),看看路径是不是有变化
  3. 测试回溯现象
package com.recursion;
/**
 * @author Kcs 2022/8/25
 */
public class MiGong {
    public static void main(String[] args) {
        //二维数组模拟迷宫
        int[][] map = new int[8][7];
        //1.表示墙
        //上下两行置为 1
        for (int i = 0; i < 7; i++) {
            map[0][i] = 1;
            map[7][i] = 1;
        }
        //左右两侧的列置为 1
        for (int i = 0; i < 8; i++) {
            map[i][0] = 1;
            map[i][6] = 1;
        }
        //挡板
        map[3][1] = 1;
        map[3][2] = 1;
        System.out.println("原地图:");
        for (int i = 0; i < 8; i++) {
            for (int j = 0; j < 7; j++) {
                System.out.print(map[i][j] + " ");
            }
            System.out.println();
        }
        //设置起点
        // setWay(map, 1, 1);
        setWay2(map, 1, 1);
        //新的地图,标识过的
        System.out.println("走过的路径:");
        for (int i = 0; i < 8; i++) {
            for (int j = 0; j < 7; j++) {
                System.out.print(map[i][j] + " ");
            }
            System.out.println();
        }
    }
    /**
     * @param map 地图
     * @param i 起始位置
     * @param j
     * @return true ,false
     * 当 map[i][j]为 0:还没有走的点,1:墙,2:走过的路径,3:走不通的点,(6,5)为终点
     * 策略一: 下 → 右 → 上 → 左,走不通,再回溯
     * 策略二:下 → 左 → 上 → 右
     * 策略三:上 → 右 → 下 → 左
     * 策略很多,可以自己尝试修改,本次使用的策略为策略一,但是存在最优路径
     */
    public static boolean setWay(int[][] map, int i, int j) {
        if (map[6][5] == 2) {
            return true;
        } else {
            if (map[i][j] == 0) {
                // 假设可以走
                map[i][j] = 2;
                //对应策略的顺序进行修改
                if (setWay(map, i + 1, j)) {
                    //向下走
                    return true;
                } else if (setWay(map, i, j + 1)) {
                    //向右走
                    return true;
                } else if (setWay(map, i - 1, j)) {
                    //向上走
                    return true;
                } else if (setWay(map, i, j - 1)) {
                    //向左走
                    return true;
                } else {
                    //走不通的起点
                    map[i][j] = 3;
                    return false;
                }
            } else {
                return false;
            }
        }
    }
    /**
     * 策略三:上 → 右 → 下 → 左
     * @param map
     * @param i
     * @param j
     * @return
     */
    public static boolean setWay2(int[][] map, int i, int j) {
        if (map[6][5] == 2) {
            return true;
        } else {
            if (map[i][j] == 0) {
                // 假设可以走
                map[i][j] = 2;
                //对应策略的顺序进行修改
                if (setWay2(map, i - 1, j)) {
                    //向上走
                    return true;
                } else if (setWay2(map, i, j + 1)) {
                    //向右走
                    return true;
                } else if (setWay2(map, i + 1, j)) {
                    //向下走
                    return true;
                } else if (setWay2(map, i, j - 1)) {
                    //向左走
                    return true;
                } else {
                    //走不通的起点
                    map[i][j] = 3;
                    return false;
                }
            } else {
                return false;
            }
        }
    }
}

八皇后问题(92种)

问题描述:在一个8 × 8 的国际象棋摆放 8个皇后,使其不能互相攻击

要求:任意两个皇后都不能处于同一列、同一行或者同一斜线上,问有多少种摆法



分析


第一个皇后先放第一行第一列

第二个皇后放在第二行第一列、然后判断是否OK, 如果不OK,继续放在 第二列、第三列、依次把所有列都放完,找到一个合适

继续第三个皇后,还是第一列、第二列……直到第8个皇后也能放在一个 不冲突的位置,算是找到了一个正确解

当得到一个正确解时,在栈回退到上一个栈时,就会开始回溯,即将第 一个皇后,放到第一列的所有正确解,全部得到.

然后回头继续第一个皇后放第二列,后面继续循环执行 1,2,3,4的步骤

说明:


理论上应该创建一个二维数组来表示棋盘,但是实际上可以通过算法, 用一个一维数组即可解决问题. arr[8] = {0 , 4, 7, 5, 2, 6, 1, 3}

对应arr 下标表示第几行,即第几个皇后,arr[i] = val , val 表示第 i + 1 个皇后,放在第 i + 1行的 第val+1列

package com.recursion;
/**
 * @author Kcs 2022/8/28
 */
public class Queue8 {
    /**
     * 皇后总数
     */
    int max = 8;
    /**
     * 定义数组array,保留皇后放置的位置
     */
    int[] array = new int[max];
    /**
     * 统计一共有多少种解法
     */
    static int count = 0;
    public static void main(String[] args) {
        //测试
        Queue8 queue8 = new Queue8();
        queue8.check(0);
        System.out.printf("共有解法:%d", count);
    }
    /**
     * 放置第n个皇后
     * check每一次递归时,都会进入到for循环
     */
    private void check(int n) {
        //8皇后已经放完
        if (n == max) {
            print();
            return;
        }
        //依次放入皇后,并判断是否冲突
        for (int i = 0; i < max; i++) {
            //本皇后n,放置行的首列
            array[n] = i;
            //皇后放置第i列,判断是否与前面的皇后的列是否冲突
            if (judge(n)) {
                //不冲突,接着下一个皇后, 递归
                check(n + 1);
            }
            //冲突,回去到上一行 array[n] = i,将第n个皇后,放置在本行的后移一个位置
        }
    }
    /**
     * 查看放置的皇后是否与前面放置的皇后是否产生冲突
     * n: 第n个皇后
     */
    private boolean judge(int n) {
        for (int i = 0; i < n; i++) {
            //判断皇后是否在同一列上,或者统一斜线上(Math.abs(n - i) == Math.abs(array[n] - array[i] n:表示列上,
            // 比如 n = 1 时,array[n] = array[1] = 1 ,arr[i] = val , val 表示第i+1个皇后,放在第i+1行的第val+1列
            // 同一行就不要判断, n不断在增加
            if (array[i] == array[n] || Math.abs(n - i) == Math.abs(array[n] - array[i])) {
                return false;
            }
        }
        return true;
    }
    /**
     * 输出皇后的位置
     */
    public void print() {
        count++;
        for (int i = 0; i < array.length; i++) {
            System.out.print(array[i] + " ");
        }
        System.out.println();
    }
}

排序算法

  • 排序是一种数据结构,不是算法,指定的顺序进行排序的过程。

内部排序

  • 将需要处理的所有数据都加载到内存处理器中去进行排序

外部排序

  • 数据量过大,无法全部加载到内 存中,需要借助外部存储进行 排序


常见排序算法3.png


算法时间复杂度

度量一个程序(算法)执行时间的两种方法


  1. 事后统计的方法
    存在两个问题:一是要想对设计的算法的运行性能进行评测,需要实际运行该程序;
    二是所得时间的统计量依赖于计算机的硬件、软件等环境因素, 要在同一台计算机的相同状态下运行,才能比较那个算法速度更快。
  2. 事前估算的方法
    通过分析某个算法的时间复杂度来判断哪个算法更优


时间频率

一个算法花费的时间与算法中语句的执行次数成正比例,哪个算法 中语句执行次数多,它花费时间就多。一个算法中的语句执行次数称为语句频 度或时间频度。记为T(n)。

举例

8cb15e31abf36af6fb43a705770ba6cc.png




df2a5bc1231fa510aa2a1177c5ec5c36.png



7c8d364b6879a470a76a8b00e734c2b1.png


时间复杂

一般情况下,算法中的基本操作语句的重复执行次数是问题规模n的某个函 数,用T(n)表示,若有某个辅助函数f(n),使得当n趋近于无穷大时,T(n) / f(n) 的极限值为不等于零的常数,则称f(n)是T(n)的同数量级函数。记作 T(n)=O ( f(n) ),称O( f(n) ) 为算法的渐进时间复杂度,简称时间复杂度。

T(n) 不同,但时间复杂度可能相同。 如:T(n)=n²+7n+6 与 T(n)=3n²+2n+2 它 们的T(n) 不同,但时间复杂度相同,都为O(n²)。

计算时间复杂度

用常数1代替运行时间中的所有加法常数 T(n)=n²+7n+6 => T(n)=n²+7n+1

修改后的运行次数函数中,只保留最高阶项 T(n)=n²+7n+1 => T(n) = n²

去除最高阶项的系数 T(n) = n²=> T(n) = n² => O(n²)

————————————————


969221330c82e2582f3642280e3d6128.png


平方阶:2个 for 循环

常见的算法时间复杂度由小到大依次为:Ο(1)<Ο(log2n)<Ο(n)<Ο(nlog2n)<Ο(n 2 )< Ο(n3 )< Ο(nk ) <Ο(2n ) ,随着问题规模n的不断增大,上述时间复杂度不断增大,算法的 执行效率越低


常数阶


无论代码执行了多少行,只要是没有循环等复杂结构,那这个代码的时间复杂度就都是O(1)

int i = 2;
int j = 3;
++i;
j++;
int m = i+j;




  1. 它消耗的时候并不随着某个变量的增长而增长,那么无论这类代 码有多长,即使有几万几十万行,都可以用O(1)来表示它的时间复杂度
  2. 对数阶O(log2n)
int i = 1;
while(i<n){
  i = i*2;
}


在while循环里面,每次都将 i 乘以 2,乘完之后,i 距离 n 就越来越近了。假设循环 x次之后,i 就大于 2 了,此时这个循环就退出了,也就是说 2 的 x 次方等于 n,那么 x = log2n也就是说当循环 log2n 次以后,这个代码就结束了。因此这个代码的时间复杂度为: O(log2n) 。 O(log2n) 的这个2 时间上是根据代码变化的,i = i * 3 ,则是 O(log3n)


线性阶O(n)

for(i=1,i<=n,i++){
  j =i;
  j++;
}
  1. 这段代码,for循环里面的代码会执行n遍,因此它消耗的时间是随着n的变化而变 化的,因此这类代码都可以用O(n)来表示它的时间复杂度
  2. 线性对数阶O(nlogN)
for(i=1,i<=n,i++){
  i = 1;
  while(i<n){
    i = i*2;
  }
}
  1. 将时间复杂度为O(logn)的代码循环N遍的 话,那么它的时间复杂度就是 n * O(logN),也就是了O(nlogN)
  2. 平方阶O(n2)
for(i=1,i<=n,i++){
  for(j=1,j<=n,j++){
    j = i;
    j++;
  }
}



平方阶O(n2)

for(i=1,i<=n,i++){
  for(j=1,j<=n,j++){
    j = i;
    j++;
  }
}


  1. 如果把 O(n) 的代码再嵌套循环一遍,它的时间复杂 度就是 O(n²),这段代码其实就是嵌套了2层n循环,它的时间复杂度就是 O(n * n), 即 O(n²) 如果将其中一层循环的n改成m,那它的时间复杂度就变成了 O(m * n)
  2. 立方阶O(n³)、K次方阶O(nk)


平均时间复杂度和最坏时间复 杂度


平均时间复杂度是指所有可能的输 入实例均以等概率出现的情况下, 该算法的运行时间。

最坏情况下的时间复杂度称最坏时 间复杂度。一般讨论的时间复杂度 均是最坏情况下的时间复杂度。 原因是:最坏情况 下的时间复杂度是算法在任何输入实例上运行时间的界限,这就保证 了算法的运行时间不会比最坏情况更长。


808cd0803bb658a6e9dffd1cc96d6767.png

空间复杂度

一个算法的空间复杂度(Space Complexity)定义为该 算法所耗费的存储空间,它也是问题规模 n 的函数

空间复杂度(Space Complexity)是对一个算法在运行过程中临时占用存储空间大 小的量度。有的算法需要占用的临时工作单元数与解决问题的规模n有关,它 随着n的增大而增大,当n较大时,将占用较多的存储单元

一、冒泡排序 Bubble Sorting

基本思想


通过对待 排序序列从前向后(从下标较小的元素开始),依次比较 相邻元素的值,若发现逆序则交换,使值较大 的元素逐渐从前移向后部,就象水底下的气泡一样逐渐 向上冒。


优化

因为排序的过程中,各元素不断接近自己的位置,如果一趟比较下 来没有进行过交换,就说明序列有序,因此要在排序过程中设置 一个标志flag判断元素是否进行过交换。从而减少不必要的比较


4c303614e8ece9cb55498e8a997b28f6.png



分析

冒泡数组:3 ,9, -1, 10, -5

  • 使用冒泡排序法将其排成一个从小到大的有序数列
  • 相邻元素逆序就交换


结论

  1. 循环次数:数组长度 - 1 次
  2. 每一趟排序的次数不断减少
  3. 某一趟排序中,没有进行一次交换,则说明是有序排序(优化)
import java.text.SimpleDateFormat;
import java.util.Arrays;
import java.util.Date;
/**
 * @author Kcs 2022/9/2
 */
public class BubbleSorting {
    public static void main(String[] args) {
        //排序数组
        int[] array = {3, 9, -1, 10, 20};
        System.out.println("原始冒泡数组:" + Arrays.toString(array));
        bubbleSort(array);
        System.out.println("排序结果:" + Arrays.toString(array));
        // 测试冒泡排序的时间复杂度, 用一个较大的数据进行测试
        int[] test = new int[8000];
        for (int i = 0; i < 8000; i++) {
            test[i] = (int) (Math.random() * 80000);
        }
        Date beforeDate = new Date();
        SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
        String formatBefore = simpleDateFormat.format(beforeDate);
        System.out.println("排序前时间" + formatBefore);
        bubbleSort(test);
        Date afterDate = new Date();
        String afterDateformat = simpleDateFormat.format(afterDate);
        System.out.println("排序前时间" + afterDateformat);
    }
    //封装冒泡方法
    public static void bubbleSort(int[] arr) {
        //表示是否进行交换
        boolean flag = false;
        int temp;
        //第一趟排序
        for (int i = 0; i < arr.length - 1; i++) {
            for (int j = 0; j < arr.length - 1 - i; j++) {
                //前面的比后面的数大,就进行交换
                if (arr[j] > arr[j + 1]) {
                    flag = true;
                    temp = arr[j];
                    arr[j] = arr[j + 1];
                    arr[j + 1] = temp;
                }
            }
            // System.out.println("第" + (i + 1) + "次排序结果:" + Arrays.toString(arr));
            if (!flag) {
                //没有发生交换
                break;
            } else {
                //重置flag ,进行下次交换
                flag = false;
            }
        }
    }
}


二、选择排序 (Select Sorting)

选择式排序也属于内部排序法,是从欲排序的数据中,按指定的规则选出某一 元素,再依规定交换位置后达到排序的目的。


思路

第 一次从arr[0] ~ arr[n-1]中选取最小值,与arr[0]交换,第二次从 arr[1] ~ arr[n-1]中选取最小值,与arr[1]交换,第三次从arr[2]~ arr[n-1]中 选取最小值,与arr[2]交换,…,第i次从arr[i-1] ~ arr[n-1]中选取最小值, 与arr[i-1]交换,…, 第n-1次从arr[n-2]~arr[n-1]中选取最小值,与arr[n-2] 交换,总共通过n-1次,得到一个按排序码从小到大排列的有序序列。





f1b32b3dbf04586197ef1cc29369c914.png


分析

原始数组 : 101,34,119,1,要求:由小到大

每次执行的得到的结果:

  1. 1,34,119,101
  2. 1,34,119,101
  3. 1,34,101,119


结论


  1. 需要执行数组大小 -1轮排序
  2. 每一轮都是一个循环,循环的规则(代码)
  1. 先假定当前数为最小数,
  2. 然后和后面的数进行比较,若当前数更小,重新得到最小数,并得到下标
  3. 遍历到数组的最后时,就得到本轮的最小数和下标
  4. 交换
import java.text.SimpleDateFormat;
import java.util.Arrays;
import java.util.Date;
/**
 * @author Kcs 2022/9/2
 */
public class SelectSorting {
    public static void main(String[] args) {
        int[] arr = {101, 34, 119, 1};
        System.out.println("排序前:" + Arrays.toString(arr));
        selectSort(arr);
        System.out.println("排序后:" + Arrays.toString(arr));
        //测试时间
        int[] test = new int[8000];
        for (int i = 0; i < 8000; i++) {
            test[i] = (int) (Math.random() * 80000);
        }
        Date beforeDate = new Date();
        SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
        String formatBefore = simpleDateFormat.format(beforeDate);
        System.out.println("排序前时间" + formatBefore);
        selectSort(test);
        Date afterDate = new Date();
        String afterDateformat = simpleDateFormat.format(afterDate);
        System.out.println("排序前时间" + afterDateformat);
    }
    /**
     * 选择排序
     * 时间复杂度 O(n^2)
     * @param arr
     */
    public static void selectSort(int[] arr) {
        //原始数组 : 101,34,119,1,要求:由小到大
        for (int i = 0; i < arr.length - 1; i++) {
            //起始索引
            int minIndex = i;
            int min = arr[i];
            for (int j = i + 1; j < arr.length; j++) {
                //由大到小:>  由小到大:< ;当前值进行比较,找出索引
                if (min > arr[j]) {
                    min = arr[j];
                    // 得到最小值索引
                    minIndex = j;
                }
            }
            //最小值放在arr[0]
            if (minIndex != i) {
                arr[minIndex] = arr[i];
                arr[i] = min;
            }
            // System.out.println("第" + (i + 1) + "轮排序结果:" + Arrays.toString(arr));
        }
    }
}


三、插入排序

插入式排序属于内部排序法,是对于欲排序的元素以插入的方式找寻该元素的 适当位置,以达到排序的目的


基本思想

把n个待排序的元素看成为 一个有序表和一个无序表,开始时有序表中只包含一个元素,无序表中 包含有n-1个元素,排序过程中每次从无序表中取出第一个元素,把它 的排序码依次与有序表元素的排序码进行比较,将它插入到有序表中的 适当位置,使之成为新的有序表。



49103df6bf3a0a426ac8a6d33906b912.png


分析

原始数组:101, 34, 119, 1

import java.text.SimpleDateFormat;
import java.util.Arrays;
import java.util.Date;
/**
 * @author Kcs 2022/9/3
 */
public class InsertSort {
    public static void main(String[] args) {
        int[] arr = {101, 34, 119, 1, 45, 78};
        System.out.println("排序前结果:" + Arrays.toString(arr));
        insertSort(arr);
        System.out.println("排序后结果:" + Arrays.toString(arr));
        // 测试冒泡排序的时间复杂度, 用一个较大的数据进行测试
        int[] test = new int[8000];
        for (int i = 0; i < 8000; i++) {
            test[i] = (int) (Math.random() * 80000);
        }
        Date beforeDate = new Date();
        SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
        String formatBefore = simpleDateFormat.format(beforeDate);
        System.out.println("排序前时间" + formatBefore);
        insertSort(test);
        Date afterDate = new Date();
        String afterDateformat = simpleDateFormat.format(afterDate);
        System.out.println("排序后时间" + afterDateformat);
    }
    /**
     * 插入排序
     */
    public static void insertSort(int[] arr) {
        int insertValue = 0;
        int insertIndex = 0;
        for (int i = 1; i < arr.length; i++) {
            //定义待插入的数
            insertValue = arr[i];
            //arr[1] 前面这个数的索引
            insertIndex = i - 1;
            //insertValue 找到插入的位置  由小到大 < ;由大到小:<
            // insertIndex >= 0 放置越界
            // insertValue < arr[insertIndex] : 带插入的数字
            // arr[insertIndex] 后移
            while (insertIndex >= 0 && insertValue < arr[insertIndex]) {
                arr[insertIndex + 1] = arr[insertIndex];
                insertIndex--;
            }
            //找到插入的位置
            if (insertIndex + 1 == i) {
                arr[insertIndex + 1] = insertValue;
            }
            // System.out.println("第" + i + "轮排序结果:" + Arrays.toString(arr));
        }
    }
}


四、希尔排序

基本思想

希尔排序是把记录按下标的一定增量分组,对每组使用直接插入排序算法排序;随着增量逐渐减少,每组包含的关键词越来越多,当增量减至1时,整个文件恰 被分成一组,算法便终止


分析


初始数组 {8,9,1,7,2,3,5,4,6,0}

  1. 希尔排序时, 对有序序列在插入时采用交换法, 并测试排序速度.
  2. 希尔排序时, 对有序序列在插入时采用移动法, 并测试排序速度

252403e47cad9be185fae7a1f40339d0.png


交换法:

import java.util.Arrays;
/**
 * @author Kcs 2022/9/3
 */
public class ShellSort {
    public static void main(String[] args) {
        int[] arr = {8, 9, 1, 7, 2, 3, 5, 4, 6, 0};
        shellSort(arr);
    }
    /**
     * 希尔排序
     */
    public static void shellSort(int[] arr) {
        int temp = 0;
        int count = 0;
        // 循环处理 希尔排序
        for (int gap = arr.length / 2; gap > 0; gap /= 2) {
            for (int i = gap; i < arr.length; i++) {
                //遍历各组中所有的元素
                for (int j = i - gap; j >= 0; j -= gap) {
                    //当前元素大于加上步长后的元素,则进行交换
                    if (arr[j] > arr[j + gap]) {
                        temp = arr[j];
                        arr[j] = arr[j + gap];
                        arr[j + gap] = temp;
                    }
                }
            }
            System.out.println("第" + (++count) + "轮希尔排序结果:" + Arrays.toString(arr));
        }
    }
}



移步法

import java.util.Arrays;
/**
 * @author Kcs 2022/9/3
 */
public class ShellSort {
    public static void main(String[] args) {
        int[] arr = {8, 9, 1, 7, 2, 3, 5, 4, 6, 0};
        shellSort(arr);
        System.out.println("交换法:" + Arrays.toString(arr));
        shellSort2(arr);
        System.out.println("移位法:" + Arrays.toString(arr));
    }
    /**
     * 移位法
     * @param arr
     */
    public static void shellSort2(int[] arr) {
        for (int gap = arr.length / 2; gap > 0; gap /= 2) {
            //从gap个元素,逐个对其所在组进行之直接插入排序
            for (int i = gap; i < arr.length; i++) {
                int j = i;
                int temp = arr[j];
                if (arr[j] < arr[j - gap]) {
                    while (j - gap >= 0 && temp < arr[j - gap]) {
                        // 元素进行移动
                        arr[j] = arr[j - gap];
                        j -= gap;
                    }
                    //找到插入的位置
                    arr[j] = temp;
                }
            }
        }
    }
}



五、快速排序

基本思想


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



5d1a6cafa6c8ba0899ac55391d17a7ab.png


左右递归有顺序

import java.util.Arrays;
/**
 * @author Kcs 2022/9/3
 */
public class QuickSort {
    public static void main(String[] args) {
        int[] arr = {-9, 78, 0, 23, -567, 70};
        quickSort(arr, 0, arr.length - 1);
        System.out.println(Arrays.toString(arr));
    }
    /**
     * 快速排序
     * @param arr
     */
    public static void quickSort(int[] arr, int left, int right) {
        //左下标
        int l = left;
        //右下标
        int r = right;
        //中轴值
        int pivot = arr[(left + right) / 2];
        //作为交换
        int temp = 0;
        // 比 pivot 小的值 放到左边,大的值放到右边
        while (l < r) {
            //在pivot的左边找,比pivot大于等于的值
            while (arr[l] < pivot) {
                l += 1;
            }
            //在pivot的右边找,比pivot小于等于的值
            while (arr[r] > pivot) {
                r -= 1;
            }
            //左边是小于pivot的值, 右边大于等于pivot的值
            if (l == r) {
                break;
            }
            //进行交换
            temp = arr[l];
            arr[l] = arr[r];
            arr[r] = temp;
            //交换后,发现arr[l] == pivot 值 ,则前移
            if (arr[l] == pivot) {
                r -= 1;
            }
            //交换后,发现arr[r] == pivot 值 ,则后移
            if (arr[r] == pivot) {
                l += 1;
            }
        }
        // l==r 要 l++ r--
        if (l == r) {
            l += 1;
            r -= 1;
        }
        //左递归
        if (left < r) {
            quickSort(arr, left, r);
        }
        //右递归
        if (right > l) {
            quickSort(arr, l, right);
        }
    }
}


六、归并排序

采用分治策略。分治法将问题分 (divide) 成一些小的问 题然后递归求解,而治(conquer) 的阶段则将分的阶段得到的各答案"修补"在一 起,即分而治之。

df72ea7bbfb02500898f7fd3c30b83ff.png


以上结构很像一棵完全二叉树,采用递归去实现(也可采用迭代的方式去实现)。分阶段可以理解为就是递归 拆分子序列的过程

分析

上图合并了七次,下图为最后一次的合并详解:


b973dd646a4d1b9f439f045d37dcc93b.png

import java.util.Arrays;
/**
 * @author Kcs 2022/9/3
 */
public class MergeSort {
    public static void main(String[] args) {
        int[] arr = {8, 4, 5, 7, 1, 3, 6, 2};
        System.out.println("要排序的数组:" + Arrays.toString(arr));
        //归并需要一个额外的空间
        int[] temp = new int[arr.length];
        mergeSort(arr, 0, arr.length - 1, temp);
        System.out.println("归并排序结果:" + Arrays.toString(arr));
    }
    /**
     * 分解 + 合并 过程
     */
    public static void mergeSort(int[] arr, int left, int right, int[] temp) {
        if (left < right) {
            int mid = (left + right) / 2;
            // 向左递归进行分解
            mergeSort(arr, left, mid, temp);
            //向右递归分解
            mergeSort(arr, mid + 1, right, temp);
            //合并
            merge(arr, left, mid, right, temp);
        }
    }
    /**
     * 合并起来
     * @param arr 原始数组
     * @param left 左边有序序列的初始索引
     * @param mid 中间索引
     * @param right 右边索引
     * @param temp 做中转数组
     */
    public static void merge(int[] arr, int left, int mid, int right, int[] temp) {
        // 初始化i 左边有序序列的初始索引
        int i = left;
        // 初始化 j ,右边有序序列的初始索引
        int j = mid + 1;
        // 指向temp数组的当前索引
        int t = 0;
        // 1. 左右两边的数据,按照规则进行填充到 temp 数组,直到左右两边的有序序列,有一边完成为止
        while (i <= mid && j <= right) {
            //左边的有序序列的当前元素,小于等于右边有序序列的当前元素,即将左边的当前元素,拷贝到 temp 中
            if (arr[i] < arr[j]) {
                temp[t] = arr[i];
                t += 1;
                i += 1;
            } else {
                // 右边的比左边的元素大,填充到temp数据
                temp[t] = arr[j];
                t += 1;
                j += 1;
            }
        }
        // 2. 把剩余数据的一边的数据依次全部填充到 temp 中
        while (i <= mid) {
            //左边的 有序序列还有剩余元素,全部填充到 temp 中去
            temp[t] = arr[i];
            t += 1;
            i += 1;
        }
        while (j <= right) {
            //左边的 有序序列还有剩余元素,全部填充到 temp 中去
            temp[t] = arr[j];
            t += 1;
            j += 1;
        }
        // 3. 将temp 数据的元素拷贝到 arr,并不是每次拷贝所有
        t = 0;
        int tempLeft = left;
        //最后一次 tempLeft = 0 ,right = 7
        System.out.println("tempLeft=" + tempLeft + " " + "right=" + right);
        while (tempLeft <= right) {
            arr[tempLeft] = temp[t];
            t += 1;
            tempLeft += 1;
        }
    }
}

七、基数排序(桶排序)

基数排序(radix sort)属于“分配式排序”(distribution sort),又称“桶子法”(bucket sort)或bin sort

它是通过键值的各个位的值, 将要排序的元素分配至某些 “桶” 中,达到排序的作用。

基数排序法是属于稳定性的排序,基数排序法的是效率高的稳定性排序法(相同的数字会有先后顺序)


基本思想

  • 将所有待比较数值统一为同样的数位长度,数位较短的数前面补零。然后,从最低位开始,依次进行一次排序。这样从最低位排序一直到最高位排序完 成以后, 数列就变成一个有序序列

分析

将数组 {53, 3, 542, 748, 14, 214} 使用基数排序, 进行升序排序。有多少位数就要进行多少次桶排序


说明: 事先准备10个数组(10个桶), 0-9 分别对应位数的 0-9


第1轮排序


将各个数,按照个位大小放入到对应的各个数组中 (看每个元素的个位数,把这个多位数,放置在个位数对应的桶中(一个桶一个数组))


然后从 0-9 个数组 / 桶,依次按照加入元素的先后顺序取出


9ad5999d742459d232c8ec12ce9c011e.png



  1. 得到结果 :542 、53、3、14、214、748
  1. 第二轮排序 (后一轮是根据上一轮得到的结果进行排序)
  1. 将各个数,按照 十位 大小放入到对应的各个 数组 / 桶 中
  2. 然后从 0-9 个数组 / 桶,依次按照加入元素的先后顺序取出


ba4862bb11ece7dcbe83077c40694046.png


  1. 得到结果 :542 、53、3、14、214、748
  1. 第二轮排序 (后一轮是根据上一轮得到的结果进行排序)
  1. 将各个数,按照 十位 大小放入到对应的各个 数组 / 桶 中
  2. 然后从 0-9 个数组 / 桶,依次按照加入元素的先后顺序取出



82c8dbe7a7174098a7332d7b9282a309.png


import java.util.Arrays;
/**
 * @author Kcs 2022/9/3
 */
public class RadixSort {
    public static void main(String[] args) {
        int[] arr = {53, 3, 542, 748, 14, 214};
        radixSort(arr);
    }
    /**
     * 基数排序  时间换空间算法
     * @param arr
     */
    public static void radixSort(int[] arr) {
        //第一轮(每个元素的个位数进行处理)
        //二维数组表示十个桶,每个桶就是一个一维数组
        //防止数据溢出,将一维数组大小为arr.length
        int[][] bucket = new int[10][arr.length];
        //记录桶放入的有效数据中,实际存放的数据。存放bucket[0~10]的对应的数据
        int[] bucketElementCounts = new int[10];
        //得到数组中最大的数的位数
        int max = arr[0];
        for (int i = 0; i < arr.length; i++) {
            if (arr[i] > max) {
                max = arr[i];
            }
        }
        //得到最大的位数
        int maxLength = (max + "").length();
        for (int i = 0, n = 1; i < maxLength; i++, n *= 10) {
            //取出各位 个 十 百 千 位
            for (int j = 0; j < arr.length; j++) {
                //取出每个元素对应位数的值
                int digitOfElment = arr[j] / n % 10;
                //放入对应的桶中,前一个找到对应的桶,后一个为该个位对应的多位数
                bucket[digitOfElment][bucketElementCounts[digitOfElment]] = arr[j];
                //下一个桶
                bucketElementCounts[digitOfElment]++;
            }
            //依次桶的顺序,依次取出数据,放入原来的数组
            int index = 0;
            //遍历每一个桶,就将桶的数据,放入到原数组中,
            for (int k = 0; k < bucketElementCounts.length; k++) {
                //桶中有数据才放入原数组
                if (bucketElementCounts[k] != 0) {
                    //循环改桶的数据,放入原数组
                    for (int l = 0; l < bucketElementCounts[k]; l++) {
                        //取出元素放入 arr
                        arr[index++] = bucket[k][l];
                    }
                }
                //对桶进行清零
                bucketElementCounts[k] = 0;
            }
            System.out.println("第" + (i + 1) + "次排序结果:" + Arrays.toString(arr));
        }
        //以下为推导过程
        /*//第二轮:取出十位
        for (int j = 0; j < arr.length; j++) {
            //得到十位的数 每一轮只要修改这个取模的 个 十 百 千 位
            int digitOfElment = arr[j] / 10 % 10;
            //放入对应的桶中,前一个找到对应的桶,后一个为该个位对应的多位数
            bucket[digitOfElment][bucketElementCounts[digitOfElment]] = arr[j];
            //下一个桶
            bucketElementCounts[digitOfElment]++;
        }
        //依次桶的顺序,依次取出数据,放入原来的数组
        index = 0;
        //遍历每一个桶,就将桶的数据,放入到原数组中,
        for (int k = 0; k < bucketElementCounts.length; k++) {
            //桶中有数据才放入原数组
            if (bucketElementCounts[k] != 0) {
                //循环改桶的数据,放入原数组
                for (int l = 0; l < bucketElementCounts[k]; l++) {
                    //取出元素放入 arr
                    arr[index++] = bucket[k][l];
                }
            }
            //对桶进行清零
            bucketElementCounts[k] = 0;
        }
        System.out.println("第二轮结果:" + Arrays.toString(arr));
        //第三轮:取出白位
        for (int j = 0; j < arr.length; j++) {
            //得到十位的数 每一轮只要修改这个取模的 个 十 百 千 位
            int digitOfElment = arr[j] / 100;
            //放入对应的桶中,前一个找到对应的桶,后一个为该个位对应的多位数
            bucket[digitOfElment][bucketElementCounts[digitOfElment]] = arr[j];
            //下一个桶
            bucketElementCounts[digitOfElment]++;
        }
        //依次桶的顺序,依次取出数据,放入原来的数组
        index = 0;
        //遍历每一个桶,就将桶的数据,放入到原数组中,
        for (int k = 0; k < bucketElementCounts.length; k++) {
            //桶中有数据才放入原数组
            if (bucketElementCounts[k] != 0) {
                //循环改桶的数据,放入原数组
                for (int l = 0; l < bucketElementCounts[k]; l++) {
                    //取出元素放入 arr
                    arr[index++] = bucket[k][l];
                }
            }
            //对桶进行清零
            bucketElementCounts[k] = 0;
        }
        System.out.println("第三轮结果:" + Arrays.toString(arr));*/
    }
}


排序算法对比

解释:

稳定:如果 a原本在 b前面,而a=b , 排序之后 a仍然在 b的前面;

不稳定:如果 a原本在 b的前面,而 a=b,排序之后 a可能会出现在 b的后 面;

内排序:所有排序操作都在内存中完 成;

外排序:由于数据太大,因此把数据 放在磁盘中,而排序通过磁盘和内存 的数据传输才能进行;

时间复杂度: 一个算法执行所耗费 的时间。

空间复杂度:运行完一个程序所需内 存的大小。

n:数据规模

k:“桶”的个数

In -place:不占用额外内存

Out -place:占用额外内存

查找算法

顺序(线性查找)查找

  • 从头到尾一个一个进行对比查找,找到则返回下标
/**
 * @author Kcs 2022/9/5
 */
public class SequenceSearch {
    public static void main(String[] args) {
        //数组
        int[] arr = {1 ,-25, 45, 32, 3, 0, 12, -20};
        int index = seqSearch(arr, 0);
        if (index == -1) {
            System.out.println("查无次数据");
        } else {
            System.out.println("该数字位于:" + index);
        }
    }
    /**
     * 找到一个就返回
     * @param arr 匹配数组
     * @param value 要查找的数
     * @return index
     */
    public static int seqSearch(int[] arr, int value) {
        //逐一比对
        for (int i = 0; i < arr.length; i++) {
            if (arr[i] == value) {
                return i;
            }
        }
        return -1;
    }
}

二分查找 / 折半查找


必须是顺序的数组,若是无序的,则需要先排序(小到大 / 大到小)

思路

(以下是排序已为由小到大)


首先确定该数组的中间下标

mid = (left + right)/ 2

然后让需要查找的数 findValue 和 arr[mid] 进行比较

findValue > mid[arr] :说明要查找的数在 mid 的右边,应该向右查找(向右递归查找)

findValue < mid[arr] :说明要查找的数在 mid 的左边,应该向左查找(向左递归查找)

findValue = mid[arr] :说明已经找到,则返回

退出递归

找到目标退出递归

找不到退出递归 (left > right)

/**
 * @author Kcs 2022/9/5
 */
public class BinarySearch {
    public static void main(String[] args) {
        //有序数组
        int[] arr = {0, 11, 22, 33, 44, 45, 55, 77, 88, 88, 88, 88, 99};
        // int index = binarySearch(arr, 0, arr.length - 1, 12);
        // System.out.println("index=" + index);
        List<Integer> integers = binarySearch2(arr, 0, arr.length - 1, 99);
        System.out.println(integers);
    }
    /**
     * 二分查找算法
     * @param arr 查找数组
     * @param left 左边索引
     * @param right 右边索引
     * @param findValue 查找的值
     * @return index
     */
    public static int binarySearch(int[] arr, int left, int right, int findValue) {
        //没有找到
        if (left > right) {
            return -1;
        }
        //中间索引
        int mid = (left + right) / 2;
        //中间值
        int midValue = arr[mid];
        //判断递归的方向
        //向右
        if (findValue > midValue) {
            return binarySearch(arr, mid + 1, right, findValue);
        } else if (findValue < midValue) {
            return binarySearch(arr, left, mid - 1, findValue);
        } else {
            //查找到所有相同的值,并返回
            return mid;
        }
    }
    /**
     * 查找重复的值索引
     * @param arr
     * @param left
     * @param right
     * @param findValue
     * @return
     */
    public static ArrayList<Integer> binarySearch2(int[] arr, int left, int right, int findValue) {
        //没有找到
        if (left > right) {
            return new ArrayList<Integer>();
        }
        //中间索引
        int mid = (left + right) / 2;
        //中间值
        int midValue = arr[mid];
        //判断递归的方向
        //向右
        if (findValue > midValue) {
            return binarySearch2(arr, mid + 1, right, findValue);
        } else if (findValue < midValue) {
            return binarySearch2(arr, left, mid - 1, findValue);
        } else {
            ArrayList<Integer> resIndexlist = new ArrayList<>();
            int temp = mid - 1;
            //向mid左边开始继续搜索
            while (true) {
                //退出
                if (temp < 0 || arr[temp] != findValue) {
                    break;
                }
                resIndexlist.add(temp);
                temp -= 1;
            }
            resIndexlist.add(mid);
            //向mid右边继续搜索
            temp = mid + 1;
            while (true) {
                if (temp > (arr.length - 1) || arr[temp] != findValue) {
                    break;
                }
                resIndexlist.add(temp);
                temp += 1;
            }
            return resIndexlist;
        }
    }
}

插值查找

也是得用在有序数组上


插值查找每次从自适应mid处开始查找


将折半查找中的求mid 索引的公式 , low 表示左边索引left, high表示右边索引right. key 就是 findValue


0a991484f806dfa9edc7dd54cf8be7d8.png


int mid = low + (high - low) * (key - arr[low]) / (arr[high] - arr[low]) ;


  1. 对应前面的代码公式: int mid = left + (right – left) * (findVal – arr[left]) / (arr[right] – arr[left])

特点

  • 可以一次就能查找想要的结果
  • 与二分查找相比, 想过肯定的好很多的


使用场景

  • 对于数据量较大,关键字分布比较均匀的查找表来说,采用插值查找, 速度较快
  • 关键字分布不均匀的情况下,该方法不一定比折半查找要好
/**
 * @author Kcs 2022/9/6
 */
public class InsertValueSearch {
    public static void main(String[] args) {
        //1 ~ 100 数组
        int[] array = new int[100];
        for (int i = 0; i < 100; i++) {
            array[i] = i + 1;
        }
        int index = insertValueSearch(array, 0, array.length - 1, 50);
        System.out.println("index = " + index);
    }
    /**
     * 插值查找算法
     * @param array 查找的数组
     * @param left 左边 索引
     * @param right 右边索引
     * @param findValue 查找的值
     * @return index
     */
    public static int insertValueSearch(int[] array, int left, int right, int findValue) {
        System.out.println("查找了!!!!");
        //不符合,则退出 优化查找,防止mid越界。自适应的查找
        if (left > right || findValue < array[0] || findValue > array[array.length - 1]) {
            return -1;
        }
        //求出mid
        int mid = left + (right - left) * (findValue - array[left]) / (array[right] - array[left]);
        //中间的值
        int midValue = array[mid];
        if (findValue > midValue) {
            //向右递归查找
            return insertValueSearch(array, mid + 1, right, findValue);
        } else if (findValue < midValue) {
            //向左递归查找
            return insertValueSearch(array, left, mid - 1, findValue);
        } else {
            //相等
            return mid;
        }
    }
}


斐波那契查找

斐波那契数列 {1, 1, 2, 3, 5, 8, 13, 21, 34, 55 ···} 发现斐波那契数 列的两个相邻数 的比例,无限接近黄金分割值0.618



原理

mid不 再是中间或插值得到,而是位于黄金分 割点附近,即mid=low+F(k-1)-1 (F代表斐波那契数列)


由斐波那契数列 F[k]=F[k-1]+F[k-2] 的性质,可以得到 (F[k]-1)=(F[k-1]-1)+(F[k-2]-1)+1 。 该式说明:只要顺序表的长度为F[k]-1,则可以将该表分成长度为F[k-1]-1和F[k-2]-1的两段,即如 上图所示。从而中间位置为mid=low+F(k-1)-1


顺序表长度n不一定刚好等于F[k]-1,所以需要将原来的顺序表长度n增加至F[k]-1。这里的k值只 要能使得F[k]-1恰好大于或等于n即可,由以下代码得到,顺序表长度增加后,新增的位置(从n+1到 F[k]-1位置),都赋为n位置的值即可

分析

有序数组进行斐波那契查找 {1,8, 10, 89, 1000, 1234}

import java.util.Arrays;
/**
 * @author Kcs 2022/9/7
 */
public class FibonacciSearch {
    //斐波那契额数列的显示个数
    public static int maxSize = 20;
    public static void main(String[] args) {
        int[] arr = {1, 8, 10, 89, 1000, 1234};
        System.out.println(fibSearch(arr, 8));
    }
    /**
     * 20个斐波那契数列
     * @return f[]
     */
    public static int[] fib() {
        int[] f = new int[maxSize];
        f[0] = 1;
        f[1] = 1;
        for (int i = 2; i < maxSize; i++) {
            f[i] = f[i - 1] + f[i - 2];
        }
        return f;
    }
    /**
     * 斐波那契薯类
     * @param a 要在查找的数组
     * @param key 需要查找的关键值
     * @return index
     */
    public static int fibSearch(int[] a, int key) {
        //左边下标
        int low = 0;
        //右边下标
        int high = a.length - 1;
        //斐波那契分割数值的下标
        int k = 0;
        //mid
        int mid = 0;
        //获取斐波那契数列
        int[] f = fib();
        while (high > f[k] - 1) {
            k++;
        }
        //防止f[k] 大于 数组的长度,不足的时候使用 0 填充
        int[] temp = Arrays.copyOf(a, f[k]);
        //不使用0 进行填充,使用 arr数组的最后一位进行填充
        for (int i = high + 1; i < temp.length; i++) {
            temp[i] = a[high];
        }
        //查找目标值
        while (low <= high) {
            mid = low + f[k - 1] - 1;
            if (key < temp[mid]) {
                //查找前半部分(左边)
                high = mid - 1;
                k--;
            } else if (key > temp[mid]) {
                //向右边查找
                low = mid + 1;
                //f[k-1] = f[k -3]+f[k -4]
                k -= 2;
            } else {
                //查找到
                if (mid <= high) {
                    //左边mid
                    return mid;
                } else {
                    //右边的index
                    return high;
                }
            }
        }
        //没有找到
        return -1;
    }
}


哈希表

  • 哈希表属于数据结构

散列表(Hash table,也叫哈希表), 是根据关键码值(Key value)而直接进行访问的数据结构。它通过把关键码值映射到表中一个位置来访问记录,以加快查找的速度。这个映射函数叫做散列函数,存放记录的数组叫做散列表。


哈希表

思路

有一个公司,当有新的员工来报道时,要求将该员工的信息加入 (id,名字,···),当输入该员工的id时,要求查找到该员工的所有信息


要求

  • 不使用数据库,速度越快越好=>哈希表(散列)
  • 添加时,保证按照id从低到高插入

实现思路

  1. 使用链表来实现哈希表, 该链表不带表头 [即: 链表的第一个结点就存放雇员信息]

d09d90157054331ccabd8d4c0e3e13a0.png


  1. 代码实现[增删改查(显示所有员工,按id查询)]

散列函数

散列函数能使对一个数据序列的访问过程更加迅速有效,通过散列函数,数据元素将被更快地定位。

实际工作中需视不同的情况采用不同的哈希函数,通常考虑的因素有:


  1. 计算哈希函数所需时间
  2. 关键字的长度
  3. 哈希表的大小
  4. 关键字的分布情况
  5. 记录的查找频率


散列函数的几种方法


  1. 直接寻址法
  2. 数字分析法
  3. 平方取中法
  4. 折叠法
  5. 随机数法
  6. 除留余数法

创建三个类

  1. 雇员类:雇员的信息
  2. 雇员链表类:管理雇员
  3. 哈希表类:存放 n个 雇员,散列函数:使 id 对应的链表
import java.util.Scanner;
/**
 * @author Kcs 2022/9/9
 */
public class HashTabDemo {
    public static void main(String[] args) {
        HashTab hashTab = new HashTab(7);
        //测试菜单
        String key = "";
        Scanner scanner = new Scanner(System.in);
        while (true) {
            System.out.println("add:添加雇员");
            System.out.println("list:显示雇员");
            System.out.println("find:查找雇员");
            System.out.println("delete:删除雇员");
            System.out.println("exit:退出系统");
            System.out.println("请选择add/list/find/delete/exit");
            key = scanner.next();
            switch (key) {
                case "add":
                    System.out.println("请添加员工id");
                    int id = scanner.nextInt();
                    System.out.println("请添加员工姓名");
                    String name = scanner.next();
                    // 创建雇员
                    Emp emp = new Emp(id, name);
                    hashTab.add(emp);
                    System.out.println("添加成功!");
                    break;
                case "list":
                    hashTab.list();
                    break;
                case "find":
                    System.out.println("请输入要查找雇员的id");
                    id = scanner.nextInt();
                    hashTab.findEmpByID(id);
                    System.out.println("查找成功!");
                    break;
                case "delete":
                    System.out.println("请输入要删除雇员的id");
                    id = scanner.nextInt();
                    hashTab.delEmp(id);
                    System.out.println("删除成功!");
                    break;
                case "exit":
                    scanner.close();
                    System.exit(0);
                    System.out.println("成功退出!");
                    break;
                default:
                    break;
            }
        }
    }
}
/**
 * 雇员类
 */
class Emp {
    /**
     * id
     */
    public int id;
    /**
     * 姓名
     */
    public String name;
    /**
     * next : null
     */
    public Emp next;
    public Emp(int id, String name) {
        this.id = id;
        this.name = name;
    }
}
/**
 * 哈希表 管理雇员
 */
class HashTab {
    //数组存放雇员信息
    private EmpLinkedList[] empLinkedListsArray;
    //链表总数
    private int size;
    /**
     * 构造
     * @param size 多少链表
     */
    public HashTab(int size) {
        this.size = size;
        //初始化 empLinkedListsArray
        empLinkedListsArray = new EmpLinkedList[size];
        //初始化每个链表
        for (int i = 0; i < size; i++) {
            empLinkedListsArray[i] = new EmpLinkedList();
        }
    }
    /**
     * 添加雇员
     */
    public void add(Emp emp) {
        //根据id 获得该员工的应该添加到的那一条链表
        int empLinkedListNo = hashFun(emp.id);
        // 添加到对应的链表种
        empLinkedListsArray[empLinkedListNo].add(emp);
    }
    /**
     * 遍历所有链表
     */
    public void list() {
        for (int i = 0; i < size; i++) {
            empLinkedListsArray[i].list(i);
        }
    }
    /**
     * 根据id 查找雇员
     */
    public void findEmpByID(int id) {
        //散列函数确定查找的链表
        int empLinkedListNo = hashFun(id);
        Emp emp = empLinkedListsArray[empLinkedListNo].findEmpById(id);
        if (emp != null) {
            System.out.printf("在第%d 条链表中找到 雇员 id = %d\n", (empLinkedListNo + 1), id);
        } else {
            System.out.println("在哈希表中,没有找到该雇员!");
        }
    }
    /**
     * 删除雇员信息
     * @param id 雇员id
     */
    public void delEmp(int id) {
        int i = hashFun(id);
        empLinkedListsArray[i].delete(id);
    }
    /**
     * 散列函数 取模
     */
    public int hashFun(int id) {
        return id % size;
    }
}
/**
 * 创建EmpLinkedList,表示链表
 */
class EmpLinkedList {
    /**
     * 头指针,指向第一个雇员的Emp
     */
    private Emp head;
    /**
     * 添加雇员
     * 添加的雇员id 自增长,id 总是从小到大,所有将该雇员添加到本链表的最后
     */
    public void add(Emp emp) {
        //第一个雇员
        if (head == null) {
            head = emp;
            return;
        }
        //不是第一个雇员,添加到链表最后
        Emp curEmp = head;
        while (true) {
            //到链表最后
            if (curEmp.next == null) {
                break;
            }
            //后移
            curEmp = curEmp.next;
        }
        //退出时,加入链表
        curEmp.next = emp;
    }
    /**
     * 遍历链表
     */
    public void list(int num) {
        //链表为空
        if (head == null) {
            System.out.println("第" + (num + 1) + "个链表为空!");
            return;
        }
        System.out.print("第" + (num + 1) + "链表的信息为:");
        Emp curEmp = head;
        while (true) {
            System.out.printf("=> id = %d ,name=%s\t", curEmp.id, curEmp.name);
            //curEmp到最后结点
            if (curEmp.next == null) {
                break;
            }
            //指针后移
            curEmp = curEmp.next;
        }
        System.out.println();
    }
    /**
     * 通过id查找雇员信息
     * @param id 雇员id
     * @return 雇员信息
     */
    public Emp findEmpById(int id) {
        //链表是否为空
        if (head == null) {
            System.out.println("链表为空!");
            return null;
        }
        Emp curEmp = head;
        //id 不重复
        while (true) {
            if (curEmp.id == id) {
                //curEmp指向要查找的雇员
                break;
            }
            //退出
            if (curEmp.next == null) {
                //没有找到该雇员的信息
                curEmp = null;
            }
            //后移
            curEmp = curEmp.next;
        }
        return curEmp;
    }
    /**
     * 根据id删除雇员信息
     * @param id
     */
    public void delete(int id) {
        if (head == null) {
            System.out.println("链表为空,无须删除");
            return;
        }
        if (head.id == id) {
            //头节点为空
            if (head.next == null) {
                head = null;
            } else {
                //后移
                head = head.next;
            }
            return;
        }
        //辅助指针
        Emp curEmp = head;
        boolean flag = false;
        while (true) {
            if (head.id == id) {
                head = head.next;
                return;
            }
            if (curEmp.next.id == id) {
                flag = true;
                break;
            }
            if (flag) {
                curEmp.next = curEmp.next.next;
            } else {
                System.out.println("要删除的雇员不存在");
            }
            curEmp = curEmp.next;
        }
    }
}

w EmpLinkedList();

}

}

/**
 * 添加雇员
 */
public void add(Emp emp) {
    //根据id 获得该员工的应该添加到的那一条链表
    int empLinkedListNo = hashFun(emp.id);
    // 添加到对应的链表种
    empLinkedListsArray[empLinkedListNo].add(emp);
}
/**
 * 遍历所有链表
 */
public void list() {
    for (int i = 0; i < size; i++) {
        empLinkedListsArray[i].list(i);
    }
}
/**
 * 根据id 查找雇员
 */
public void findEmpByID(int id) {
    //散列函数确定查找的链表
    int empLinkedListNo = hashFun(id);
    Emp emp = empLinkedListsArray[empLinkedListNo].findEmpById(id);
    if (emp != null) {
        System.out.printf("在第%d 条链表中找到 雇员 id = %d\n", (empLinkedListNo + 1), id);
    } else {
        System.out.println("在哈希表中,没有找到该雇员!");
    }
}
/**
 * 删除雇员信息
 * @param id 雇员id
 */
public void delEmp(int id) {
    int i = hashFun(id);
    empLinkedListsArray[i].delete(id);
}
/**
 * 散列函数 取模
 */
public int hashFun(int id) {
    return id % size;
}


}
/**
创建EmpLinkedList,表示链表
/
class EmpLinkedList {
/*
头指针,指向第一个雇员的Emp
*/
private Emp head;
/**
添加雇员
添加的雇员id 自增长,id 总是从小到大,所有将该雇员添加到本链表的最后
*/
public void add(Emp emp) {
//第一个雇员
if (head == null) {
head = emp;
return;
}
//不是第一个雇员,添加到链表最后
Emp curEmp = head;
while (true) {
//到链表最后
if (curEmp.next == null) {
break;
}
//后移
curEmp = curEmp.next;
}
//退出时,加入链表
curEmp.next = emp;
}
/**
遍历链表
*/
public void list(int num) {
//链表为空
if (head == null) {
System.out.println(“第” + (num + 1) + “个链表为空!”);
return;
}
System.out.print(“第” + (num + 1) + “链表的信息为:”);
Emp curEmp = head;
while (true) {
System.out.printf(“=> id = %d ,name=%s\t”, curEmp.id, curEmp.name);
//curEmp到最后结点
if (curEmp.next == null) {
break;
}
//指针后移
curEmp = curEmp.next;
}
System.out.println();
}
/**
通过id查找雇员信息
@param id 雇员id
@return 雇员信息
*/
public Emp findEmpById(int id) {
//链表是否为空
if (head == null) {
System.out.println(“链表为空!”);
return null;
}
Emp curEmp = head;
//id 不重复
while (true) {
if (curEmp.id == id) {
//curEmp指向要查找的雇员
break;
}
//退出
if (curEmp.next == null) {
//没有找到该雇员的信息
curEmp = null;
}
//后移
curEmp = curEmp.next;
}
return curEmp;
}
/**
根据id删除雇员信息
@param id
*/
public void delete(int id) {
if (head == null) {
System.out.println(“链表为空,无须删除”);
return;
}
if (head.id == id) {
//头节点为空
if (head.next == null) {
head = null;
} else {
//后移
head = head.next;
}
return;
}
//辅助指针
Emp curEmp = head;
boolean flag = false;
while (true) {
if (head.id == id) {
head = head.next;
return;
}
if (curEmp.next.id == id) {
flag = true;
break;
}
if (flag) {
curEmp.next = curEmp.next.next;
} else {
System.out.println(“要删除的雇员不存在”);
}
curEmp = curEmp.next;
}
}
}


目录
相关文章
|
28天前
|
负载均衡 NoSQL 算法
一天五道Java面试题----第十天(简述Redis事务实现--------->负载均衡算法、类型)
这篇文章是关于Java面试中Redis相关问题的笔记,包括Redis事务实现、集群方案、主从复制原理、CAP和BASE理论以及负载均衡算法和类型。
一天五道Java面试题----第十天(简述Redis事务实现--------->负载均衡算法、类型)
|
22天前
|
Java
【Java集合类面试二十六】、介绍一下ArrayList的数据结构?
ArrayList是基于可动态扩展的数组实现的,支持快速随机访问,但在插入和删除操作时可能需要数组复制而性能较差。
|
22天前
|
搜索推荐 算法 Java
手写快排:教你用Java写出高效排序算法!
快速排序(QuickSort)是经典的排序算法之一,基于分治思想,平均时间复杂度为O(n log n),广泛应用于各种场合。在这篇文章中,我们将手写一个Java版本的快速排序,从基础实现到优化策略,并逐步解析代码背后的逻辑。
30 1
|
26天前
|
存储 设计模式 算法
JAVA中的常见数据结构
JAVA中的常见数据结构
|
13天前
|
设计模式 缓存 算法
揭秘策略模式:如何用Java设计模式轻松切换算法?
【8月更文挑战第30天】设计模式是解决软件开发中特定问题的可重用方案。其中,策略模式是一种常用的行为型模式,允许在运行时选择算法行为。它通过定义一系列可互换的算法来封装具体的实现,使算法的变化与客户端分离。例如,在电商系统中,可以通过定义 `DiscountStrategy` 接口和多种折扣策略类(如 `FidelityDiscount`、`BulkDiscount` 和 `NoDiscount`),在运行时动态切换不同的折扣逻辑。这样,`ShoppingCart` 类无需关心具体折扣计算细节,只需设置不同的策略即可实现灵活的价格计算,符合开闭原则并提高代码的可维护性和扩展性。
30 2
|
1月前
|
机器学习/深度学习 人工智能 算法
AI入门必读:Java实现常见AI算法及实际应用,有两下子!
本文全面介绍了人工智能(AI)的基础知识、操作教程、算法实现及其在实际项目中的应用。首先,从AI的概念出发,解释了AI如何使机器具备学习、思考、决策和交流的能力,并列举了日常生活中的常见应用场景,如手机助手、推荐系统、自动驾驶等。接着,详细介绍了AI在提高效率、增强用户体验、促进技术创新和解决复杂问题等方面的显著作用,同时展望了AI的未来发展趋势,包括自我学习能力的提升、人机协作的增强、伦理法规的完善以及行业垂直化应用的拓展等...
137 3
AI入门必读:Java实现常见AI算法及实际应用,有两下子!
|
21天前
|
安全 算法 Java
java系列之~~网络通信安全 非对称加密算法的介绍说明
这篇文章介绍了非对称加密算法,包括其定义、加密解密过程、数字签名功能,以及与对称加密算法的比较,并解释了非对称加密在网络安全中的应用,特别是在公钥基础设施和信任网络中的重要性。
|
1月前
|
算法 Java
LeetCode经典算法题:矩阵中省份数量经典题目+三角形最大周长java多种解法详解
LeetCode经典算法题:矩阵中省份数量经典题目+三角形最大周长java多种解法详解
34 6
|
1月前
|
存储 算法 Java
LeetCode经典算法题:打家劫舍java详解
LeetCode经典算法题:打家劫舍java详解
39 2
|
1月前
|
人工智能 算法 Java
LeetCode经典算法题:井字游戏+优势洗牌+Dota2参议院java解法
LeetCode经典算法题:井字游戏+优势洗牌+Dota2参议院java解法
35 1