数据结构与算法基础篇--常用数据结构、插排、快排、希尔、贪心算法

154 阅读8分钟

这是我参与更文挑战的第5天,活动详情查看: 更文挑战

一、程序员了解数据结构和算法的好处:

1.对算法的理解的越多,就越能减少时间复杂度

	例如:像248163264128256,如何判断一个数是2的幂次方?
	思考:传统的做法是不是拿这个数不断的对2取模,看最后余数是否为0?
	会算法就快很多:
	2转为二进制是:0010
	2的前一个数1的二进制:0001
	4--->0100
	3--->0011
	8--->1000
	7--->0111
	...
	得出 n & (n-1)=0 则n符合条件(二进制的&运算,在对应的二进制位上不同会为0)
	//测试
	 public static void main(String[] args) {
        System.out.println(1024 & 1023);
    }
	//结果
	0

Process finished with exit code 0

2.对算法理解越多,就越能减少空间复杂度,节省服务器资源

例如: 统计中国16亿人口的年龄,并按从小到大排列,服务器cpu 1核 内存512M。 思考:如果初始化一个16亿的数组,再把这个超大的数组按一定的算法排序,服务器早蹦了。 用算法的思维:人的年龄一般在0-200岁之间,我们初始化一个200的数组,数组的角标代表年龄,数组的value代表在这个年龄内有多少人,简化代码如下

public static void main(String[] args) {
        int[] peopleArr = new int[200];
        //模拟统计各年龄段的人口
        Random random = new Random();
        for (int i = 0; i < peopleArr.length; i++) {
           peopleArr[i] = random.nextInt(100) * 100;
        }
        //人口按年龄从小到大输出
        System.out.println(Arrays.toString(peopleArr));
        for (int i = 0; i < peopleArr.length; i++) {
            System.out.println("人口是"+i+"岁的:");
            for (int j = 0; j < peopleArr[i]; j++) {
                System.out.print(i+" ");
            }
            System.out.println();
        }
    }

3、对数据结构理解的越多,就越能应付不同的业务场景。

	例如:在不使用Redis的情况下,如何实现VIP用户的优先排队。
	其实,JDK自带了队列的数据结构,使用优先队列的类就能解决此问题:
//value属性是排序的权重,value越大,task对象排在越前面
class  Task implements Comparable<Task>{
    private int value =1;

    public int getValue() {
        return value;
    }

    public void setValue(int value) {
        this.value = value;
    }

    @Override
    public int compareTo(Task o) {
        return this.value>o.value?-1:1;
    }

    @Override
    public String toString() {
        return "Task{" +
                "value=" + value +
                '}';
    }
}

//运行
public static void main(String[] args) {
        PriorityBlockingQueue<Task> priorityBlockingQueue = new PriorityBlockingQueue<>();
        Task task1 = new Task();
        task1.setValue(1);
        Task task2 = new Task();
        task2.setValue(2);
        priorityBlockingQueue.add(task1);
        priorityBlockingQueue.add(task1);
        priorityBlockingQueue.add(task2);
        priorityBlockingQueue.add(task1);
        Iterator<Task> iterator = priorityBlockingQueue.iterator();
      while (iterator.hasNext()) {
          System.out.println(iterator.next());
      }
    }

//结果如下
Task{value=2}
Task{value=1}
Task{value=1}
Task{value=1}

Process finished with exit code 0

二、常用的数据结构简析:

1.List

在这里插入图片描述

		//底层数组,有下标,适合做查询多的容器
        List<Integer>  arrayList = new ArrayList<Integer>();
        //底层链表,有指针,适合做增删多的容器
        List<Integer> linkedList = new LinkedList<Integer>();
        //线程安全,但效率慢
        Vector<Integer> vector =new Vector<Integer>();

2.set

相比于list,有去重的功能 在这里插入图片描述

		//乱序去重,与插入的顺序不同
        Set<Integer> hashSet = new HashSet<Integer>();
        //排序去重,与插入的顺序不同
        Set<Integer> treeSet = new TreeSet<Integer>();
        //去重,与插入的顺序相同
        Set<Integer> linkedHashSet = new LinkedHashSet<Integer>();

3.map

在这里插入图片描述

HashMap 使用哈希表(hash table)实现, 在 keys 和/或 values 之中,都是无序的. TreeMap 基于红黑树(red-black tree)数据结构实现, 按 key 排序. LinkedHashMap 保持者插入顺序. Hashtable 与HashMap实现方式一样,但Hashtable属于同步(synchronized)的.

4.queue

在这里插入图片描述 ArrayBlockingQueue :一个由数组支持的有界队列。 LinkedBlockingQueue :一个由链接节点支持的可选有界队列。 PriorityBlockingQueue :一个由优先级堆支持的无界优先级队列。 DelayQueue :一个由优先级堆支持的、基于时间的调度队列。 SynchronousQueue :一个利用 BlockingQueue 接口的简单聚集(rendezvous)机制。

三、常用的算法原理及代码实现:

1.算法的前置知识

计算代码的时间复杂度

		//一、常数时间复杂度为O(1)		
 		int a =1; //O(1)
        int b =a+1; //O(1)
        
        for (int i = 0; i < 100; i++) {  //O(1)
		//循环次数确实为100次,因为100是常数,所以时间复杂度还是O(1)
        }

		//二、线性的时间复杂度为O(n)
		int n;
       	for (int i = 0; i < n; i++) { //O(n)
        }

		//三、对数的时间复杂度
		 while (c<n){
               c=c*2;
       }
       //推导:c^2=n ===>c=log2n(以2为底)==>c=logn,时间复杂度为O(logn)
       //再加一层循环
        for (int i = 0; i < n; i++) {
          while (c<n){
               c=c*2;
      	}
       }
    //时间复杂度为O(nlogn)
	
	//平方的时间复杂度O(n^2)
	     for (int i = 0; i < n; i++) {
            for (int i = 0; i < n; i++) {

            }
        }
    //变型
      for (int i = 0; i < =n; i++) {
          for (int j = i; j <= n; j++) {

           }
        }   
	//推导: (n-1)+(n-2)+(n-3)+......+1==>n(n-1)/2//常熟不算,时间复杂度还是为O(n^2)

2.两个数交换位置的另外一种思路

平常做法的引入第三个数temp。其实两个数利用加减法就可以完成位置互换,举个栗子:

int a=3,b=2;
a=a+b;
b=a-b;
a=a-b;

3.快速排序

在这里插入图片描述 之后对基准数3之前的一组和3之后的一组进行递归操作,就可以实现从小到大的排列顺序

public class QuicklySort {

    public static void qSort(int[] arr,int left,int right){
        int ll = left;
        int rr = right;
        int base = arr[left];

        //从后往前找,比基准数小的就交换位置
        while (ll<rr && base<=arr[rr]){
            rr--;
        }

        if (ll<rr){
            int temp = base;
            arr[ll] = arr[rr];
            arr[rr]=temp;
            //刚换过一次位置,可以少一次计算
            ll++;
        }

        //再从前面往后面找,比基准数大的就交换位置
        while (ll<rr && base>=arr[ll]){
            ll++;
        }

        if (ll<rr){
            int temp = base;
            arr[rr] =arr[ll];
            arr[ll]=temp;
            rr--;
        }

        //递归,剩余分组快排
        if (ll>left){
            qSort(arr,left,ll-1);
        }

        if (rr<right){
            qSort(arr,ll+1,right);
        }
    }


    public static void main(String[] args) {
        int[] arr = new int[]{1,5,3};
        qSort(arr,0,arr.length-1);
        System.out.println(Arrays.toString(arr));
    }
}

//运行结果
[1, 3, 5, 6, 2, 4, 7, 8, 9]
Process finished with exit code 0

4.插入排序

在这里插入图片描述 如上图所示,一个数组的元素,一个个的插入进来,如果新进来的元素比前一个大,直接放在前一个元素的后面就好了,如果比前一个元素小,则依次与前一个元素做比较,不断的交换. 代码:

public class InsertSort {
    public static void main(String[] args) {
        //实现插入排序
        System.out.println("请您输入您将要输入几个数:");
        Scanner sc = new Scanner(System.in);
        int length = sc.nextInt();
        int[] arrInput = new int[length];
        for (int i = 0; i < length; i++) {
            System.out.println("请您输入第"+(i+1)+"个数:");
            arrInput[i] = sc.nextInt();
        }

        for (int i = 1; i < arrInput.length; i++) {
            for (int j = i; j >0 ; j--) {
                if (arrInput[j]<arrInput[j-1]){
                    arrInput[j] = arrInput[j]+arrInput[j-1];
                    arrInput[j-1]=arrInput[j]-arrInput[j-1];
                    arrInput[j]=arrInput[j]-arrInput[j-1];
                }else {
                    break;
                }
            }
        }

        System.out.println(Arrays.toString(arrInput));
    }
}

5.希尔排序

希尔排序与插入排序的原理类似,不过添加了一个step,步长和2分的概念

public class ShellSort {
    public static void main(String[] args) {
        //实现希尔排序
        System.out.println("请您输入您将要输入几个数:");
        Scanner sc = new Scanner(System.in);
        int length = sc.nextInt();
        int[] arrInput = new int[length];
        for (int i = 0; i < length; i++) {
            arrInput[i] = sc.nextInt();
        }

        int step = length/2;
        while (step >= 1) {
            for (int i = step; i < length; i++) {
                for (int j = i; j-step >=0; j -= step) {
                    if (arrInput[j] < arrInput[j - step]) {
                        arrInput[j] = arrInput[j] + arrInput[j - step];
                        arrInput[j - step] = arrInput[j] - arrInput[j - step];
                        arrInput[j] = arrInput[j] - arrInput[j - step];
                    } else {
                        break;
                    }
                }
            }
            step = step / 2;
        }

        System.out.println(Arrays.toString(arrInput));
    }
}

6.冒泡排序

经典排序,大家都懂

public class BubbleSort {

    public static void main(String[] args) {
        //实现冒泡排序
        System.out.println("请您输入您将要输入几个数:");
        Scanner sc = new Scanner(System.in);
        int length = sc.nextInt();
        int[] arrInput = new int[length];
        for (int i = 0; i < length; i++) {
            System.out.println("请您输入第"+(i+1)+"个数:");
            arrInput[i] = sc.nextInt();
        }
        System.out.println("============");

        for (int i = 0; i < arrInput.length; i++) {
            for (int j = 0; j < arrInput.length-1-i; j++) {
                if (arrInput[i]>arrInput[i+1]){
                    arrInput[i]=arrInput[i]+arrInput[i+1];
                    arrInput[i+1]=arrInput[i]-arrInput[i+1];
                    arrInput[i]=arrInput[i]-arrInput[i+1];
                }
            }
        }

        System.out.println(Arrays.toString(arrInput));
    }
}

四、贪心算法和动态规划在现实生活场景的运用

1.贪心算法的简单理解

贪心算法指在当前情况下的最优解,不考虑以后的影响,也叫做局部最优解。例如:老王在A公司可以拿1W,在B公司可以拿2w。老王就去了B公司,不考虑在AB公司的长远发展。

2.贪心算法的场景:

在一天内的一家酒馆有N个同档次免费的饭局,已知所有饭局的开始和结束时间,如何在一天内蹭最多次饭? 分析:a.一天有24小时 b.把所有饭局的结束时间排序,结束时间早的优先去,好赶下一场饭局 c.下一场饭局的开始时间要大于本场饭局的结束时间 代码实现

public class FeastSort implements Comparable<FeastSort>{

    private int startTime;

    private int endTime;

    private int feastNum;

    public FeastSort(int startTime, int endTime, int feastNum) {
        this.startTime = startTime;
        this.endTime = endTime;
        this.feastNum = feastNum;
    }

    //结束时间早的饭局排在前面
    @Override
    public int compareTo(FeastSort o) {
        return this.endTime>o.endTime?1:-1;
    }

    public int getStartTime() {
        return startTime;
    }

    public void setStartTime(int startTime) {
        this.startTime = startTime;
    }

    public int getEndTime() {
        return endTime;
    }

    public void setEndTime(int endTime) {
        this.endTime = endTime;
    }

    public int getfeastNum() {
        return feastNum;
    }

    public void setfeastNum(int feastNum) {
        this.feastNum = feastNum;
    }

    @Override
    public String toString() {
        return "FeastSort{" +
                "startTime=" + startTime +
                ", endTime=" + endTime +
                ", feastNum=" + feastNum +
                '}';
    }

    public static void main(String[] args) {
        Scanner sc = new Scanner(System.in);
        System.out.println("请输入总饭局数");
        int totalFeastCount = sc.nextInt();
        List<FeastSort> feastSortList = new ArrayList<>();
        for (int i = 0; i < totalFeastCount; i++) {
            System.out.println("请输入第"+(i+1)+"场饭局开始和结束时间");
            FeastSort feastSort = new FeastSort(sc.nextInt(),sc.nextInt(),i+1);
            feastSortList.add(feastSort);
        }
        //使用FeastSort中重写的compareTo方法排序
        feastSortList.sort(null);

        //过滤饭局,本场饭局的开始时间要小于上一场饭局的结束时间,结束时间不能超过24:00
        int currentTime = 0;
        for (FeastSort feastSort : feastSortList) {
            if (feastSort.getStartTime()>=currentTime && feastSort.getEndTime()<=24){
                System.out.println(feastSort);
                currentTime = feastSort.getEndTime();
            }
        }
    }
}

//结果
请输入总饭局数
6
请输入第1场饭局开始和结束时间
1
3
请输入第2场饭局开始和结束时间
4
5
请输入第3场饭局开始和结束时间
2
5
请输入第4场饭局开始和结束时间
8
16
请输入第5场饭局开始和结束时间
8
10
请输入第6场饭局开始和结束时间
19
21
FeastSort{startTime=1, endTime=3, feastNum=1}
FeastSort{startTime=4, endTime=5, feastNum=2}
FeastSort{startTime=8, endTime=10, feastNum=5}
FeastSort{startTime=19, endTime=21, feastNum=6}

Process finished with exit code 0


3.动态规划的场景

已知商场内所有商品的重量和价格,购物车载重50kg,现在老王被选为幸运客户,可以免费用购物车带走商品,请问老王如何拿才最值? 思路一:用商品的价格除以重量,性价比高的先选? 例: 商品价格:80 50 120 商品重量:30kg 20kg 40kg 性价比: 2.6 2.5 3 反证:选商品1和商品2的商品价格是大于只选商品3的,思路一不正确 思路二:利用动态规划矩阵 商品价格:8元 5 12 商品重量:3kg 2kg 4kg 把购物车以一1kg为单位划分做矩阵横坐标,物品按个数做纵坐标 在这里插入图片描述 在只有一个物品且该物品可以放入购物车的时候,就选该物品;如果有多个物品,那么看新物品的价值大还是,之前物品组合的价值大。 代码实现:

public class DynamicProgrammingAlgorithm {
    public static void main(String[] args) {
        //商品的重量和价格
        int[] value = {50,60,100};
        int[] weight = {30,20,40};

        //商品的总数量和背包可以承受的最大重量
        int goodsCount = weight.length;
        int bagWeight = 50;
        //商品总数和背包承受重量的矩阵
        int[][] gb = new int[goodsCount+1][bagWeight+1];

        //循环每件商品
        for (int i = 1; i <= goodsCount; i++) {
            //动态规划书包承重,单位为1
            for (int j = 1; j <= bagWeight; j++) {
                //判断当前商品的重量小于动态规划的背包承重
                if (weight[i-1]<=j){
                    //gb矩阵,从当前值和上一个值中取最大
                    gb[i][j]=Math.max(value[i-1]+gb[i-1][bagWeight-weight[i-1]],gb[i][j]);
                }else {
                    //证明背包放不下该商品的重量,取矩阵上一行的值
                    gb[i][j]=gb[i-1][j];
                }
            }
        }


        System.out.println("背包在受重范围内能带走的最大商品价值为:"+gb[goodsCount][bagWeight]+"元");
    }

}

public class DynamicProgrammingAlgorithm {
    public static void main(String[] args) {
        //商品的重量和价格
        int[] value = {70,60,120};
        int[] weight = {30,20,40};

        //商品的总数量和背包可以承受的最大重量
        int goodsCount = weight.length;
        int bagWeight = 50;
        //商品总数和背包承受重量的矩阵
        int[][] gb = new int[goodsCount+1][bagWeight+1];

        //循环每件商品
        for (int i = 1; i <= goodsCount; i++) {
            //动态规划书包承重,单位为1
            for (int j = 1; j <= bagWeight; j++) {
                //判断当前商品的重量小于动态规划的背包承重
                if (weight[i-1]<=j){
                    //gb矩阵,从当前值和上一个值中取最大
                    gb[i][j]=Math.max(value[i-1]+gb[i-1][j-weight[i-1]]//新装物品的价值+当前矩阵剩余空间和上一个物品定位矩阵的值
                            ,gb[i-1][j]//装不下就取上一个物品
                    );
                }else {
                    //证明背包放不下该商品的重量,取矩阵上一行的值
                    gb[i][j]=gb[i-1][j];
                }
            }
        }


        System.out.println("背包在受重范围内能带走的最大商品价值为:"+gb[goodsCount][bagWeight]+"元");
    }

}
//运行结果
购物车在受重范围内能带走的最大商品价值为:130元

Process finished with exit code 0