算法入门
概述
什么是算法?
算法简单来说就是解决问题的步骤。
设计原则:正确性、可读性、健壮性 bug、高效率与低存储。内存+CPU内存占用最小,CPU占用最小,运算速度最快。
评价算法的两个重要指标:
-
时间复杂度:运行一个程序所花费的时间。去掉常数,保留最高阶
一个 while 一个 log(n),一个 for 一个 n
- 常数性时间复杂度 O(1)
- 线性性 O(n) 单层for
- 对数性 nlog(n) 快速排序
- 平方性 O(n^2) 两层for
-
空间复杂度:运行程序所需要的内存,一般就是找数组,容器等
排序算法
- 冒泡排序:其核心就是两两比较,选出大的继续和别人比
- 选择排序:拿出来最高的这个 和所有人去比较,得出位置。
- 插入排序:对一个有序的序列插入,插入进去后保证还是有序的。
- 希尔排序:对插入排序的改进。步长/2,分组,减少比较次数。
- 快速排序:使用递归。根据基准数分组,分成两组,左边的都比基准数小,右边都比之大。
- 归并排序:先分再合。
- 堆排序:是一种选择排序,不稳定排序。堆是一种完全二叉树。参考
- 基数排序:将整数按位数切割成不同的数字,然后按每个位数分别比较。参考
交换两个数,并不一定需要通过第三变量,可以使用加减法
a=2,b=3
a = a + b; //a=5
b = a - b; //b=2
a = a - b; //a=3
冒泡排序
public class BubbleSort {
// 4 2 5 1
// 4
// 4 2 => 2 4
// 2 4 5 =
// 2 4 5 1=> 1 2 4 5
public static void main(String[] args) {
Scanner cin = new Scanner(System.in);
int n = cin.nextInt();
int data[] = new int[n];
for (int i = 0; i < n; i++) {
data[i] = cin.nextInt();
}
// Map size() list.size();
// n = 5
//data 0 1 2 3 4
for (int i = 0, len = data.length; i < len - 1; i++) {
for (int j = 0; j < len - 1 - i; j++) {
if (data[j] > data[j + 1]) {
// 交换 不让你引入第三个变量 交换 a b的值
int temp = data[j];
data[j] = data[j + 1];
data[j + 1] = temp;
}
}
}
for(int i = 0 ; i < n; i++) {
System.out.print(data[i] + " ");
}
System.out.println();
}
}
希尔排序
public class ShellSort {
public static void main(String[] args) {
// 4 1 2 3 5
// 第一步
//len = 5 表示有5个数字
// step = len /2 => 5/2 =2;
// 4 2 =>分出来的还是进行一个插入排序 2 4
// 1 3 => 1 3
// 5=> 5
// 2 4 1 3 5
//100 / 2 = 50
// 50 / 2 => 25
//25 /2
//step = 1排完
// 第二步 step = step / 2 =>1
// 第四步: 1 2 3 4
Scanner cin = new Scanner(System.in);
int n = cin.nextInt();
int data[] = new int[n + 1];
for (int i = 0; i < n; i++) {
data[i] = cin.nextInt();
}
int step = n;
while(step >= 1) {
step = step / 2;
for(int i = step ; i < n; i ++) {
for(int j = i ;j - step >= 0; j-= step) {
if(data[j] < data[j - step]) {
int temp = data[j];
data[j] = data[j - step];
data[j - step] = temp;
}else {
break;
}
}
}
}
for(int i = 0 ; i < n; i++) {
System.out.print(data[i] + " ");
}
System.out.println();
}
}
快速排序
public class QSort {
public static void qSort(int data[], int left, int right) {
int ll = left;// 从左边找的位置
int rr = right; // 从右边找的位置
int base = data[left]; // 取第一个作为基准数
//Sort
// 1 2 3 4 5
while (ll < rr) {
// 从后面往前找到比基准数小的数进行对换
while (ll < rr && data[rr] >= base) {
rr--;
}
if (ll < rr) { // 为了怕是 没找到
int temp = data[rr];
data[rr] = data[ll];
data[ll] = temp;
ll++;
}
// 从前面往后面找比基准数大的进行对换
while (ll < rr && data[ll] <= base) {
ll++;
}
if (ll < rr) {
int temp = data[rr];
data[rr] = data[ll];
data[ll] = temp;
rr--;
}
}
System.out.println("以Base=" +base+ "的排序结果");
print(data);
// 以基准数分为3部分,左边的比之小,右边比之大 我们要做的就是一把这左边和右边分别进行快速排序
if (ll > left) {
qSort(data, left, ll - 1);
}
if (rr < right) {
qSort(data, ll+1, right);
}
}
归并排序
public class MergSort {
public static void main(String[] args) {
int data[] = {9,5,6,8,0,3,7,1,20,1};
mergeSort(data, 0, data.length -1);
System.out.println(Arrays.toString(data));
//String a = "1";
//String b = "2";
//a.compareTo(b);
}
public static void mergeSort(int data[],int left,int right) {
if(left < right) {
int mid = (left + right) / 2;
mergeSort(data, left, mid);
mergeSort(data, mid+1, right);
//以上就分完了
merge(data, left, mid, right);
}
}
public static void merge(int data[],int left,int mid,int right) {
int temp[] = new int[data.length]; //就是用来保存合并后的序列,辅助我们排序
int point1 = left; //表示左边的第一个数的位置
int point2 = mid + 1; //表示后边的第一个数的位置
int loc = left; //这个就是用来保存我们当前填了那个数字到temp里面去
while(point1 <= mid && point2 <= right) {
if(data[point1] <= data[point2]) {
temp[loc] = data[point1];
loc ++ ;
point1 ++ ;
}else {
temp[loc] = data[point2];
loc ++ ;
point2 ++ ;
}
}
while(point1 <= mid) {
temp[loc++] = data[point1++];
}
while(point2 <= right) {
temp[loc ++] = data[point2 ++];
}
for(int i = left ;i <= right ; i++) {
data[i] = temp[i];
}
}
}
二叉树
使用中序遍历来排序
public class BinarySearchTree {
int data;
BinarySearchTree left;
BinarySearchTree right;
public BinarySearchTree(int data) {
this.data = data;
left = null;
right = null;
}
public void insert(BinarySearchTree root,int data) { //构建树
if(root.data < data) { //根结点小于 data 插入右子树
if(root.right == null) {
root.right = new BinarySearchTree(data);
}else { //表示子树不为空 ,那么还要继续往下找,要找到叶子结点才能插入
insert(root.right, data);
}
//insert(root, data);
}else {
if(root.left == null) {
root.left = new BinarySearchTree(data);
}else {
insert(root.left, data);
}
}
}
public void in(BinarySearchTree root) { //中序遍历来排序
if(root != null) {
in(root.left);
System.out.print(root.data + " ");
in(root.right);
}
}
public static void main(String[] args) {
//快速排序,归并排序,二叉树排序
int data[] = {0,5,9,1,2,3,10};
BinarySearchTree root = new BinarySearchTree(data[0]); //第一个点作为跟结点
for(int i = 1 ; i < data.length ; i ++) {
root.insert(root, data[i]);
}
System.out.println("中序遍历:");
root.in(root);
}
}
贪心
概念:贪心算法又叫做贪婪算法,它在求解某个问题是,总是做出眼前最大利益。也就是说只顾眼前不顾大局,所以它是局部最优解。
- 贪心策略(涉及到排序)
- 通过局部最优解能够得到全局最优解
给定一个数组,它的第 i 个元素是一支给定股票第 i 天的价格。
设计一个算法来计算你所能获取的最大利润。你可以尽可能地完成更多的交易(多次买卖一支股票)。
**注意:**你不能同时参与多笔交易(你必须在再次购买前出售掉之前的股票)。
输入: [7,1,5,3,6,4]
输出: 7
解释: 在第 2 天(股票价格 = 1)的时候买入,在第 3 天(股票价格 = 5)的时候卖出, 这笔交易所能获得利润 = 5-1 = 4 。
随后,在第 4 天(股票价格 = 3)的时候买入,在第 5 天(股票价格 = 6)的时候卖出, 这笔交易所能获得利润 = 6-3 = 3 。
class Solution {
public int maxProfit(int[] prices) {
//贪心法
if(prices==null || prices.length==0) return 0;
int profit = 0;
for (int i =1;i < prices.length;i++){
if(prices[i]>prices[i-1]) profit+=prices[i]-prices[i-1];
}
return profit;
}
}
动态规划
分解子问题,通过局部最大值得到全局最大。
思路:构建一个矩阵。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Nc5FrVWa-1589633967686)(C:\Users\ccw\Desktop\背包问题.png)]
public class DP {
public static void main(String[] args) {
int value[] = {60,100,120}; //每个物品的钱
int weight[] = {10,20,40}; //每个物品的重量 和上面的一一对应
int w = 50; //袋子的容积
int n = 3; //物品的个数
int dp[][] = new int[n+1][w+1]; //表示分割,成一个 小的表格
for(int i = 1; i<=n ;i++) { //表示物品往里面加
for(int cw = 1; cw <= w; cw ++) { //袋子在每一个容积下所装的最大的钱
if(weight[i - 1] <= cw) { //表示这个物品可以装
dp[i][cw] = Math.max(
value[i-1]+dp[i-1][cw-weight[i-1]], //我装新加的物品
dp[i-1][cw] //我不装这个新加的这个物品
);
}else {
dp[i][cw] = dp[i-1][cw]; //新加的这个装不下 ,那么就取前一个物品装值
}
}
}
System.out.println("袋子能装的最大价值:" + dp[n][w]);
}
}
一只青蛙一次可以跳上1级台阶,也可以跳上2级……它也可以跳上n级。求该青蛙跳上一个n级的台阶总共有多少种跳法。
public class Solution {
public int JumpFloorII(int target) {
//有 n 中跳的方式,1阶、2阶...n阶,首次为跳1~n,再把下次跳n-n的可能相加
//如f(3) = f(3-1)+f(3-2)+f(3-3),
//所以f(n) = f(n-1)+f(n-2)+...+f(n-(n-1)) + f(n-n)
//即f(n) = f(0) + f(1) + f(2) + f(3) + ... + f(n-2)+ f(n-1)
//所以 f(n-1) = f(0) + f(1) + f(2) + f(3) + ... + f(n-2)
//通过f(n)-f(n-1)简化得 f(n) = 2*f(n-1)
if (target <= 0) return 0;
if (target <= 1) return 1;
return 2 * JumpFloorII(target - 1);
//解法二:最后一块木板是青蛙到达的位子, 必须存在
//剩下n-1块板,每个木板有存在和不存在两种选择,(n-1) 块木板 就有 [2^(n-1)] 种跳法
//if (target <= 0) return 0;
//return (int) Math.pow(2, target - 1);
}
}
贪心算法&动归的主要使用场景
贪心效率优于动态规划。
贪心是一种特殊的动态规划,只不过他的子问题只会被计算一次。动归则多次计算得到全局最优。
- 求解最值问题:可以贪心,查公交最短路线。
- 短字符相似性匹配:动态规划里面的编辑距离,求字符串变换几次可以相互转换
- 策略问题:动归,使用矩阵
Abc
Acb= >我们把 c变成b b变成c 转换2不可以转换成abc。
这时候我们可以设置阈值,若果转换小于n我们可以认为短语相似。
须存在
//剩下n-1块板,每个木板有存在和不存在两种选择,(n-1) 块木板 就有 [2^(n-1)] 种跳法
//if (target <= 0) return 0;
//return (int) Math.pow(2, target - 1);
}
}
> 贪心算法&动归的主要使用场景
贪心效率优于动态规划。
贪心是一种特殊的动态规划,只不过他的子问题只会被**计算一次**。动归则多次计算得到全局最优。
1. 求解最值问题:可以贪心,查公交最短路线。
2. 短字符相似性匹配:动态规划里面的编辑距离,求字符串变换几次可以相互转换
3. 策略问题:动归,使用矩阵
Abc
Acb= >我们把 c变成b b变成c 转换2不可以转换成abc。
这时候我们可以设置阈值,若果转换小于n我们可以认为短语相似。
> 未完待补充
这篇博客介绍了算法入门的基础知识,重点关注排序算法和贪心策略。文章讲解了算法设计的原则,包括时间复杂度和空间复杂度的概念,并详细阐述了几种排序算法:冒泡排序、快速排序、归并排序和希尔排序。此外,还讨论了贪心算法的基本思想,给出了动态规划解决最值问题的应用示例。

2711

被折叠的 条评论
为什么被折叠?



