算法通识与部分基础算法
算法的定义:解题方案的准确而完整的表述,一系列解决问题的清晰表述 (解决问题的方法,优劣取决于时间复杂度和空间复杂度,即运行时间和内存限制)
时间复杂度:一种衡量算法运行时间长短的指标,是时间随输入规模增长而增长的量度(斜率) *空间复杂度即内存随输入规模增长的量度(斜率)
大O表示法:算法的渐进时间复杂度函数或基本操作重复执行次数T(n),设T(n)=time(1+2n),当n足够大1可以忽略不计,倍数较小时,对于时间复杂度的影响不是很大,通常将常数简化,即T(2n)=O(n),表示一个阶级的时间复杂度
常数阶 O(1) 常量级别
线性阶 O(n) 循环
二次方阶O(n^2) 2层循环
对数阶 O(logN) for(int i=1 ; i<=n ; i*2) 即自增呈指数型增长时为对数阶if( j % i ==0 ) 这种判断也是对数阶
指数阶 O(2^n) for (int j = 1; j <= i * i; ++j) 即判断呈指数型增长时为指数阶
总时间复杂度,例如:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 for (int i = 1 ; i <= n; ++i){ for (int j = 1 ; j <= i * i; ++j) { for (int k = 1 ; k <= j; ++k) { ++sum; } } } for (int i = 1 ; i <= n; ++i){ for (int j = 1 ; j <= i * i; ++j) { if (j % i == 0 ) { for (int k = 1 ; k <= j; ++k) { ++sum } } } }
部分基础算法
贪心:问题分为若干步骤,每次选取最优的选择,以保证总体最优(走一步看一步)
这要求我们在每一步时使用尽可能少的嵌套递归等,以下是一个抽象的实例:1.最优情况肯定是增加数字总位数,每增加一位相当于原数字乘10 2.而需要线段数量最少的就是’1’,所以能添‘1’就尽量添‘1’(2条线段) 3.如果线段总条数为奇数会剩余一条线段,而’7’刚好需要三条线段,把第一位数字改为’7’即可 4.这样我们就用最小的线段数组成了最大的数字
前缀和(即S,前缀到前缀之和):时间复杂度高达n*a*q ,我们是否可以用其他方式减少时间复杂度呢
举例:求第i项到第r项的和
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 while (m个询问,每个询问对应一对l和r){ for (i=l;i<r;i++){ sum+=a[i]; } printf ("%d" ,sum) } while (m个询问){ sum[0 ] = 0 ; for (i=l;i<r;i++){ sum[i]=sum[i-1 ]+a[i] } printf ("%d" ,sum[r]-sum[i-1 ]) }
原数组变为二维数组怎么解决?:每次询问变为了(x1, y1) 到 (x2, y2)这一范围内数组的和
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 #define min(x,y) ((x)<(y)?(x):(y)) #define max(x,y) ((x)>(y)?(x):(y)) int i1 = min(x1, x2) - 1 ; int j1 = min(y1, y2) - 1 ;int i2 = max(x1, x2) - 1 ;int j2 = max(y1, y2) - 1 ;for (int i = 0 ; i < 3 ; i++) { for (int j = 0 ; j < 3 ; j++) { sum[i][j] = a[i][j]; if (i > 0 ) sum[i][j] += sum[i - 1 ][j]; if (j > 0 ) sum[i][j] += sum[i][j - 1 ]; if (i > 0 && j > 0 ) sum[i][j] -= sum[i - 1 ][j - 1 ]; } } printf ("%d" ,sum[i2][j2] - sum[i1-1 ][j2] - sum[i2][j1-1 ] + sum[i1-1 ][j1-1 ])
*sum[x1][y1] = sum[x2][y2] - sum[x1][y2] - sum[x2][y1] + sum\[x1][y1]*
处理前缀和即两个小区间的和减去重叠区间,再加上a[i][j]
:
即***sum[i][j] = sum[i][j - 1] + sum[i - 1][j] - sum[i - 1][j - 1] + a[i][j]***
(写在循环体内)
二分
二分查找;二分答案;浮点二分 即另外一半不符合条件的舍去
最简易的二分:
1 2 3 4 5 6 7 8 9 10 11 12 #include <stdio.h> int main () {int N;scanf ("%d" , &N);for (int a = 1 ; a <= N; a++) { int c = 0 ; for (int b = 1 ; b <= a / 2 ; b++) if (a % b == 0 ) c += b; if (a == c) printf ("%d " , a); return 0 ; }
二分查找:*每次舍去一半区间,达到 log n的复杂度(要防止死循环)
1 2 3 4 5 6 7 8 int l=1 , r=n;while (l<r){ int mid=(l+r)/2 ; if (mid>num) r=mid; else l=mid+1 ; }
二分查找也称折半查找,就是每次查找去掉不符合条件的一半区间,直到找到答案 1.区间必须有单调性(不能来回跳,不然二分查找就无效了) 2.查找第一次出现的位置,如果查到一个值大于等于目标值,就把右半边放弃,因为右半边肯定也大于等于目标值;如果查到值比目标值小,那就放弃左半边 ![[Pasted image 20231109201446.png]] 由长度 l+=1 直到算到最长的原木长度思路更改为 原木长度不断二分,直到二分小段数量等于需求数量 (大于还能更长,小于段数不够要缩短)
1 2 3 4 5 6 7 8 9 int l=1 , r=n;while (l<r){ int mid=(l+r)/2 ; if (mid小段数<需求数) r=mid; else if (mid小段数>需求数) l=mid+1 ; else length=mid; }
二分答案:1.答案在一个区间里 2.能够判断答案是否正确 3.具有单调性
浮点二分1.同样是每次判断答案是否正确 2.如果小于的话就将l赋为mid,即为舍弃左区间;如果大于就将r赋为mid,即为舍弃右区间 3.最后当 r - l < 1E-n时,结束循环( n可以自己设定,取决于精确度)
排序 冒泡排序 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 #include <stdio.h> #define N 10 int main (void ) { int arr[N] = { 0 ,3 ,2 ,5 ,8 ,4 ,7 ,6 ,9 ,1 }; int i = 0 ; int j = 0 ; int temp = 0 ; for (i = 0 ; i < N - 1 ; i++) { for (j = 0 ; j < N - 1 - i; j++) { if (arr[j] < arr[j + 1 ]) { temp = arr[j]; arr[j] = arr[j + 1 ]; arr[j + 1 ] = temp; } } } for (i = 0 ; i < N; i++) { printf ("%d " , arr[i]); } return 0 ; }
快速排序
快速排序的每一轮处理其实就是将这一轮的基准数归位,直到所有的数都归位为止,排序就结束了。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 #include <stdio.h> int a[101 ],n;void quicksort (int left,int right) { int i,j,t,temp; if (left>right) return ; temp=a[left]; i=left; j=right; while (i!=j) { while (a[j]>=temp && i<j) j--; while (a[i]<=temp && i<j) i++; if (i<j) { t=a[i]; a[i]=a[j]; a[j]=t; } } a[left]=a[i]; a[i]=temp; quicksort(left,i-1 ); quicksort(i+1 ,right); } int main () { int i,j,t; scanf ("%d" ,&n); for (i=1 ;i<=n;i++) scanf ("%d" ,&a[i]); quicksort(1 ,n); for (i=1 ;i<=n;i++) printf ("%d " ,a[i]); getchar(); getchar(); return 0 ; }
归并排序
具体的我们以一组无序数列{14,12,15,13,11,16}为例分解说明,如下图所示:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 int min (int x, int y) { return x < y ? x : y; } void merge_sort (int arr[], int len) { int * a = arr; int * b = (int *) malloc (len * sizeof (int )); int seg, start; for (seg = 1 ; seg < len; seg += seg) { for (start = 0 ; start < len; start += seg + seg) { int low = start, mid = min(start + seg, len), high = min(start + seg + seg, len); int k = low; int start1 = low, end1 = mid; int start2 = mid, end2 = high; while (start1 < end1 && start2 < end2) b[k++] = a[start1] < a[start2] ? a[start1++] : a[start2++]; while (start1 < end1) b[k++] = a[start1++]; while (start2 < end2) b[k++] = a[start2++]; } int * temp = a; a = b; b = temp; } if (a != arr) { int i; for (i = 0 ; i < len; i++) b[i] = a[i]; b = a; } free (b); }
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 void merge_sort_recursive (int arr[], int reg[], int start, int end) { if (start >= end) return ; int len = end - start, mid = (len >> 1 ) + start; int start1 = start, end1 = mid; int start2 = mid + 1 , end2 = end; merge_sort_recursive(arr, reg, start1, end1); merge_sort_recursive(arr, reg, start2, end2); int k = start; while (start1 <= end1 && start2 <= end2) reg[k++] = arr[start1] < arr[start2] ? arr[start1++] : arr[start2++]; while (start1 <= end1) reg[k++] = arr[start1++]; while (start2 <= end2) reg[k++] = arr[start2++]; for (k = start; k <= end; k++) arr[k] = reg[k]; } void merge_sort (int arr[], const int len) { int reg[len]; merge_sort_recursive(arr, reg, 0 , len - 1 );
计数排序
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 #include <stdio.h> #include <stdlib.h> #include <time.h> void print_arr (int *arr, int n) { int i; printf ("%d" , arr[0 ]); for (i = 1 ; i < n; i++) printf (" %d" , arr[i]); printf ("\n" ); } void counting_sort (int *ini_arr, int *sorted_arr, int n) { int *count_arr = (int *) malloc (sizeof (int ) * 100 ); int i, j, k; for (k = 0 ; k < 100 ; k++) count_arr[k] = 0 ; for (i = 0 ; i < n; i++) count_arr[ini_arr[i]]++; for (k = 1 ; k < 100 ; k++) count_arr[k] += count_arr[k - 1 ]; for (j = n; j > 0 ; j--) sorted_arr[--count_arr[ini_arr[j - 1 ]]] = ini_arr[j - 1 ]; free (count_arr); } int main (int argc, char **argv) { int n = 10 ; int i; int *arr = (int *) malloc (sizeof (int ) * n); int *sorted_arr = (int *) malloc (sizeof (int ) * n); srand(time(0 )); for (i = 0 ; i < n; i++) arr[i] = rand() % 100 ; printf ("ini_array: " ); print_arr(arr, n); counting_sort(arr, sorted_arr, n); printf ("sorted_array: " ); print_arr(sorted_arr, n); free (arr); free (sorted_arr); return 0 ; }