这是我参与更文挑战的第5天,活动详情查看: 更文挑战
一、程序员了解数据结构和算法的好处:
1.对算法的理解的越多,就越能减少时间复杂度
例如:像2、4、8、16、32、64、128、256,如何判断一个数是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公司,不考虑在A,B公司的长远发展。
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