文章目录

  • 二、 常用技巧
    • 1. 散列
    • 2. 递归
      • 2.1 全排列问题
      • 2.2 n皇后问题
      • 2.3 回溯法优化n皇后问题
    • 3. 贪心
      • 3.1 简单贪心
      • 3.2 区间贪心
    • 4. 二分
      • 4.1 二分查找
      • 4.2 二分法拓展(以求根号2近似值为例)
      • 4.3 快速幂
    • 5. two pointers(双指针)
      • 5.1 求递增数列中的A+B=M
      • 5.2 序列合并问题
      • 5.3 [归并排序](#4. 归并排序)
      • 5.4 [快速排序](#5. 快速排序)
    • 6. 打表
    • 7. 活用递推
    • 8. 随机选择算法
  • 三、 排序算法
    • 1. 冒泡排序
    • 2. 选择排序
    • 3. 插入排序
    • 4. 归并排序
      • 4.1 二路归并(递归)
      • 4.2 二路归并(非递归)
      • 4.3 用sort函数替代merge实现
    • 5. 快速排序
      • 5.1 总是以A[left]为主元
      • 5.2 随机主元

二、 常用技巧

1. 散列

原理是把一个元素key通过散列函数H转换为H(key)。

最常见的散列法:把key作为数组下标。

散列函数:直接定址法,平方取中法,除留余数法。

解决冲突的方法:线性探测法,平方探测法,链地址法。

字符串hash:把26个字母看做26进制(0~25)的数,将26进制转换为10进制,如BCD看做731。


2. 递归

  • 递归边界:返回最简单底层的结果。
  • 递归式:减少数据规模并向下一次递归。

2.1 全排列问题

#include <cstdio>const int maxn = 11;
//输出1~n的全排列,P为当前排列,hash记录整数x是否已经在P中
int n, P[maxn], hash[maxn] = {false};void fullP(int index) { //当前处理排列的第index号位if (index == n + 1) { //递归边界,已经处理完排列的1~n位for (int i = 1; i <= n; i++) {printf("%d", P[i]); //输出当前排列}printf("\n");return;}for (int x = 1; x <= n; x++) {if (hash[x] == false) {P[index] = x;hash[x] = true;fullP(index + 1); //递归式hash[x] = false;}}
}int main() {n = 3; //输出1~3的全排列fullP(1); //从P[1]开始填return 0;
}

2.2 n皇后问题

#include <cstdio>
#include <cmath>const int maxn = 11;
// n皇后问题,P为当前排列,hash记录整数x是否已经在P中,count记录合法排列个数
int n, P[maxn], hash[maxn] = {false}, count = 0;void fullP(int index) { //当前处理排列的第index号位if (index == n + 1) { //递归边界,已经处理完排列的1~n位bool flag = true;for (int i = 1; i <= n; i++) { //遍历任意两个皇后for (int j = i + 1; j <= n; j++) {if (abs(i - j) == abs(P[i] - P[j])) { //在一条对角线上flag = false;}}}if (flag) count++;return;}for (int x = 1; x <= n; x++) {if (hash[x] == false) {P[index] = x;hash[x] = true;fullP(index + 1); //递归式hash[x] = false;}}
}int main() {n = 8; //八皇后问题fullP(1); //从P[1]开始填printf("%d", count);return 0;
}

2.3 回溯法优化n皇后问题

#include <cstdio>
#include <cmath>const int maxn = 11;
// n皇后问题,P为当前排列,hash记录整数x是否已经在P中,ans数组记录每种情况,cnt记录合法排列个数
int n, P[maxn], hash[maxn] = {false}, ans[1000] = {0}, cnt = 0;void fullP(int index) { //当前处理排列的第index号位if (index == n + 1) { //递归边界,能到达这里的一定是合法的for (int i = 1; i <= n; i++) {ans[cnt] = ans[cnt] * 10 + P[i]; //将结果数组转化为整数}cnt++;return;}for (int x = 1; x <= n; x++) {if (hash[x] == false) {bool flag = true;for (int pre = 1; pre < index; pre++) { //遍历之前的皇后//第index列皇后的行号为x,第pre列皇后的行号为P[pre]if (abs(index - pre) == abs(x - P[pre])) {flag = false;break;}}if (flag) {P[index] = x;hash[x] = true;fullP(index + 1); //递归式hash[x] = false;}}}
}int main() {n = 8; //八皇后问题fullP(1); //从P[1]开始填printf("%d\n", cnt);for (int i = 0; i < cnt; i++) {printf("%d\n", ans[i]);}return 0;
}

3. 贪心

局部最优推全局最优。

3.1 简单贪心

3.2 区间贪心

N个开区间,选择尽可能多的开区间,使这些开区间两两没有交集。

  • 若开区间1被开区间2包含,则选开区间2。
  • 去除开区间包含的情况,先选所有开区间左端点最大的区间。
#include <cstdio>
#include <algorithm>
using namespace std;const int maxn = 110;
struct Inteval {int x, y; //开区间左右段点
} I[maxn];
bool cmp(Inteval a, Inteval b) {if (a.x != b.x)return a.x > b.x; //先按左端点从大到小排序elsereturn a.y < b.y; //左端点相同的按右端点从小到大
}int main() {int n;while (scanf("%d", &n), n != 0) {for (int i = 0; i < n; i++) {scanf("%d%d", &I[i].x, &I[i].y);}sort(I, I + n, cmp);// ans记录不相交区间个数,lastX记录上一个被选中区间的左端点int ans = 1, lastX = I[0].x;for (int i = 1; i < n; i++) {if (I[i].y <= lastX) { //如果该区间右端点在lastX左边lastX = I[i].x; //以I[i]作为新选中的区间ans++;}}printf("%d\n", ans);}return 0;
}

4. 二分

4.1 二分查找

严格单调递增数列中查找元素

int binarySearch(int arr[], int left, int right, int key) {while (left <= right) {//下列句子可以优化为mid = left + (right - left) / 2,防止溢出int mid = (left + right) / 2; //取中点if (arr[mid] == key){ //找到return mid;} else if (arr[mid] > key) { //中间的数大于keyright = mid - 1;} else { //中间的数小于keyleft = mid + 1;}}return -1; //查找失败,返回-1
}

查找第一个满足条件C的元素

int binarySearch(int arr[], int left, int right, int key) {while (left < right) { //对[left,right]来说,left=right意味着到唯一位置int mid = left + (right - left) / 2;//下面if的括号内填入找的条件,如下是找第一个大于等于key的元素if (arr[mid] >= key) { //条件成立,第一个满足条件的元素的位置<=midright = mid;} else { //条件不成立,则第一个满足条件的元素的位置>midleft = mid + 1;}}return left; //返回夹出的位置
}

查找最后一个满足条件C的元素

先求第一个满足**条件"!C"**的元素,然后将该位置减1。

4.2 二分法拓展(以求根号2近似值为例)

#include <cstdio>const double eps = 1e-5;
double f(double x) {return x * x;
}
double calSqrt() {double left = 1, right = 2, mid;while (right - left > eps) {mid = (left + right) / 2;if (f(mid) > 2) {right = mid;} elseleft = mid;}return mid;
}int main() {printf("%f", calSqrt());return 0;
}

4.3 快速幂

给定三个正整数 a 、 b 、 m ( a < 1 0 9 , b < 1 0 6 , 1 < m < 1 0 9 ) a、b、m(a<10^9,b<10^6,1<m<10^9) a、b、m(a<109,b<106,1<m<109),求解 a b % m a^b\%m ab%m

递归写法

  • 若 b b b是奇数,则 a b = a ∗ a b − 1 a^b =a*a^{b-1} ab=a∗ab−1
  • 若 b b b是偶数,则 a b = a b / 2 ∗ a b / 2 a^b=a^{b/2}*a^{b/2} ab=ab/2∗ab/2
typedef long long LL;
//求a^b%m,递归写法
//若初始时a大于m,则在进入函数前先让a对m取模
//若m=1,则直接判定为0
LL binaryPow(LL a, LL b, LL m) {if (b == 0) return 1;// b为奇数,转换为b-1if (b % 2 == 1) { //可用if (b & 1)替代return a * binaryPow(a, b - 1, m) % m;} else { // b为偶数,转换为b/2LL mul = binaryPow(a, b / 2, m);return mul * mul % m;}
}

迭代写法

可以证明,任意的 a b a^b ab都可以表示为 a 2 k 、 … a 4 、 a 2 、 a 1 a^{2k}、…a^4、a^2、a^1 a2k、…a4、a2、a1中若干项的乘积。若 b b b的二进制的 i i i位为1,则 a 2 i a^{2i} a2i就选中,注意到 a 2 k 、 … a 4 、 a 2 、 a 1 a^{2k}、…a^4、a^2、a^1 a2k、…a4、a2、a1序列中的前一项总是等于后一项的平方。

typedef long long LL;
//求a^b%m,递归写法
LL binaryPow(LL a, LL b, LL m) {LL ans = 1;while (b > 0) {if (b & 1) { //如果b的二进制末尾为1,相当于b为奇数ans = ans * a % m; //令ans累积上a}a = a * a % m; //令a平方,相当于a^2i序列b >>= 1; // b二进制右移一位,相当于b/=2}return ans;
}

5. two pointers(双指针)

5.1 求递增数列中的A+B=M

void add(int arr[], int n, int m) {int i = 0, j = n - 1;while (i < j) {if (arr[i] + arr[j] == m) { //找到满足的情况printf("%d+%d=%d\n", arr[i], arr[j], m);i++;j--;} else if (arr[i] + arr[j] < m) { // i往右移动i++;} else // j往左移动j--;}
}

5.2 序列合并问题

将两个递增序列A与B合并为一个递增序列C。

int merge(int A[], int B[], int C[], int n, int m) {int i = 0, j = 0, index = 0; // i指向A[0],j指向B[0]while (i < n && j < m) {if (A[i] <= B[j]) { //选择序列A和B中最小的元素加入序列CC[index++] = A[i++];} else {C[index++] = B[j++];}}while (i < n) C[index++] = A[i++]; //将序列A中剩余元素直接加入序列Cwhile (j < m) C[index++] = B[j++]; //将序列B中剩余元素直接加入序列Creturn index; //返回序列C的长度
}

5.3 [归并排序](#4. 归并排序)

5.4 [快速排序](#5. 快速排序)


6. 打表

  • 在程序中一次性计算出所有需要的结果,之后的查询直接取这些结果,如素数表。
  • 在程序B中分一次或多次计算出所有需要的结果,手工把结果写在程序A的数组中,然后在程序A中就可以直接使用这些结果,如提前计算出n皇后问题的方案数。
  • 对没有思路的题,先用暴力程序计算小范围内的结果,然后找规律。

7. 活用递推

找到F(n)与F(n-1)的递推关系,简化计算。


8. 随机选择算法

找到一个无序数组中第K大的数(数组中元素各不相同)

int randPartition(int A[], int left, int right) {//生成[left,right]内的随机数int p = (int)(round(1.0 * rand() / RAND_MAX * (right - left) + left));swap(A[p], A[left]); //交换A[p]和A[left]int temp = A[left];while (left < right) {while (left < right && A[right] > temp) right--; //反复左移rightA[left] = A[right]; //将A[right]移到A[left]while (left < right && A[left] < temp) left++; //反复右移leftA[right] = A[left]; //将A[left]移到A[right]}A[left] = temp; //把temp放到left和right相遇的地方return left; //返回相遇的下标
}int randSlect(int A[], int left, int right, int K) {if (left == right) return A[left]; //递归边界int p = randPartition(A, left, right); //划分后主元的最终位置为pint M = p - left + 1; // A[p]是A[left,right]中的第M大if (K == M) return A[p]; //找到第K大数if (K < M) { //第K大数在主元左侧return randSlect(A, left, p - 1, K);} else //第K大数在主元右侧return randSlect(A, p + 1, right, K - M);
}

应用举例

给定一个由整数组成的集合,集合中的整数各不相同,现在要将它分为两个子集合,使得这两个子集合的并为原交集、交为空集,同时在两个子集合的元素个数 n 1 n_1 n1​与 n 2 n_2 n2​之差的绝对值 ∣ n 1 − n 2 ∣ \lvert n_1-n_2 \rvert ∣n1​−n2​∣尽可能小的前提下,要求它们各自的元素之和 S 1 S_1 S1​与 S 2 S_2 S2​之差的绝对值 ∣ S 1 − S 2 ∣ \lvert S_1-S_2 \rvert ∣S1​−S2​∣尽可能大,并求 ∣ S 1 − S 2 ∣ \lvert S_1-S_2 \rvert ∣S1​−S2​∣的最大值。

该问题等价于求原集合中第n/2大元素,同时根据这个元素把集合划分为两部分,使得其中一个子集合中的元素都不小于这个数,而另一个子集合中的元素都大于这个数。因此使用随机选择算法较为简便。

#include <cstdio>
#include <ctime>
#include <cstdlib>
#include <cmath>
#include <algorithm>
using namespace std;const int maxn = 100010;
int A[maxn], n;int randPartition(int A[], int left, int right) {//生成[left,right]内的随机数int p = (int)(round(1.0 * rand() / RAND_MAX * (right - left) + left));swap(A[p], A[left]); //交换A[p]和A[left]int temp = A[left];while (left < right) {while (left < right && A[right] > temp) right--; //反复左移rightA[left] = A[right]; //将A[right]移到A[left]while (left < right && A[left] < temp) left++; //反复右移leftA[right] = A[left]; //将A[left]移到A[right]}A[left] = temp; //把temp放到left和right相遇的地方return left; //返回相遇的下标
}int randSlect(int A[], int left, int right, int K) {if (left == right) return A[left]; //递归边界int p = randPartition(A, left, right); //划分后主元的最终位置为pint M = p - left + 1; // A[p]是A[left,right]中的第M大if (K == M) return A[p]; //找到第K大数if (K < M) { //第K大数在主元左侧return randSlect(A, left, p - 1, K);} else //第K大数在主元右侧return randSlect(A, p + 1, right, K - M);
}int main() {//初始化随机数种子srand((unsigned)time(NULL));// sum和sum1记录所有整数之和与切分后前n/2个元素之和int sum = 0, sum1 = 0;scanf("%d", &n); //整数个数for (int i = 0; i < n; i++) {scanf("%d", &A[i]);sum += A[i];}randSlect(A, 0, n - 1, n / 2);for (int i = 0; i < n / 2; i++) {sum1 += A[i];}printf("%d", (sum - sum1) - sum1); //求两个子集合的元素和之差return 0;
}

三、 排序算法

1. 冒泡排序

void bubbleSort(int arr[], int n) {int flag = 0; //标记本轮是否发生元素交换for (int i = 1; i <= n - 1; i++) {for (int j = 0; j < n - i; j++) {if (arr[j] > arr[j + 1]) {int temp = arr[j];arr[j] = arr[j + 1];arr[j + 1] = temp;flag = 1;}}if (!flag) break;}
}

2. 选择排序

void selectSort(int arr[], int n) {for (int i = 0; i < n; i++) {int min = i; //标记最小元素的下标for (int j = i + 1; j < n; j++) {if (arr[j] < arr[min]) {min = j;}}int temp = arr[i];arr[i] = arr[min];arr[min] = temp;}
}

3. 插入排序

void insertSort(int arr[], int n) {for (int i = 1; i < n; i++) {int temp = arr[i], j = i;while (j > 0 && temp < arr[j - 1]) { //元素后移arr[j] = arr[j - 1];j--;}arr[j] = temp; //插入到对应的位置}
}

4. 归并排序

4.1 二路归并(递归)

const int maxn = 100;
void merge(int A[], int L1, int R1, int L2, int R2) {int i = L1, j = L2;        // i指向A[L1],j指向A[L2]int temp[maxn], index = 0; // temp临时存放合并后的序列,index为其元素个数while (i <= R1 && j <= R2) {if (A[i] <= A[j]) {temp[index++] = A[i++];} else {temp[index++] = A[j++];}}while (i <= R1) temp[index++] = A[i++]; //将[L1,R1]中剩余元素直接加入序列Cwhile (j <= R2) temp[index++] = A[j++]; //将[L2,R2]中剩余元素直接加入序列Cfor (i = 0; i < index; i++) {A[L1 + i] = temp[i]; //将合并后的序列赋值返回序列A}
}void mergeSort(int A[], int left, int right) {if (left < right) {int mid = (left + right) / 2; //取[left,right]中点mergeSort(A, left, mid); //递归,对左子区间[left,mid]归并排序mergeSort(A, mid + 1, right); //递归,对右子区间[mid+1,right]归并排序merge(A, left, mid, mid + 1, right); //将左子区间和右子区间合并}
}

4.2 二路归并(非递归)

const int maxn = 100;
void merge(int A[], int L1, int R1, int L2, int R2) {int i = L1, j = L2;        // i指向A[L1],j指向A[L2]int temp[maxn], index = 0; // temp临时存放合并后的序列,index为其元素个数while (i <= R1 && j <= R2) {if (A[i] <= A[j]) {temp[index++] = A[i++];} else {temp[index++] = A[j++];}}while (i <= R1) temp[index++] = A[i++]; //将[L1,R1]中剩余元素直接加入序列Cwhile (j <= R2) temp[index++] = A[j++]; //将[L2,R2]中剩余元素直接加入序列Cfor (i = 0; i < index; i++) {A[L1 + i] = temp[i]; //将合并后的序列赋值返回序列A}
}void mergeSort(int A[], int n) {// step为组内元素个数,step/2为左子区间元素个数for (int step = 2; step / 2 <= n; step *= 2) {//每step个元素一组,组内前step/2和后step/2个元素进行合并for (int i = 0; i < n; i += step) { //对每一组int mid = i + step / 2 - 1;if (mid + 1 <= n) { //右子区间存在元素则合并,注意最后一个右子区间//左子区间为[i,mid],右子区间为[mid+1,min(i+step-1,n)]merge(A, i, mid, mid + 1, min(i + step - 1, n));}}}
}

4.3 用sort函数替代merge实现

void mergeSort(int A[], int n) {// step为组内元素个数,step/2为左子区间元素个数for (int step = 2; step / 2 <= n; step *= 2) {//每step个元素一组,组内前step/2和后step/2个元素进行合并for (int i = 0; i < n; i += step) { //对每一组sort(A + i, A + min(i + step, n + 1));}}
}

5. 快速排序

5.1 总是以A[left]为主元

//区间划分
int partition(int A[], int left, int right) {int temp = A[left]; //以A[left]为主元while (left < right) {while (left < right && A[right] > temp) right--; //反复左移rightA[left] = A[right]; //将A[right]移到A[left]while (left < right && A[left] < temp) left++; //反复右移leftA[right] = A[left]; //将A[left]移到A[right]}A[left] = temp; //把temp放到left和right相遇的地方return left; //返回相遇的下标
}
//快速排序
void quickSort(int A[], int left, int right) {if (left < right) { //当前区间的长度大于1int pos = partition(A, left, right); //划分左右子区间quickSort(A, left, pos - 1); //对左子区间递归进行快速排序quickSort(A, pos + 1, right); //对右子区间递归进行快速排序}
}

5.2 随机主元

int randPartition(int A[], int left, int right) {//生成[left,right]内的随机数int p = (int)(round(1.0 * rand() / RAND_MAX * (right - left) + left));swap(A[p], A[left]); //交换A[p]和A[left]int temp = A[left];while (left < right) {while (left < right && A[right] > temp) right--; //反复左移rightA[left] = A[right]; //将A[right]移到A[left]while (left < right && A[left] < temp) left++; //反复右移leftA[right] = A[left]; //将A[left]移到A[right]}A[left] = temp; //把temp放到left和right相遇的地方return left; //返回相遇的下标
}void quickSort(int A[], int left, int right) {if (left < right) { //当前区间的长度大于1int pos = randPartition(A, left, right); //划分左右子区间quickSort(A, left, pos - 1); //对左子区间递归进行快速排序quickSort(A, pos + 1, right); //对右子区间递归进行快速排序}
}

《算法笔记》第4章常用技巧及排序算法相关推荐

  1. 对以下6种常用的内部排序算法进行比较:起泡排序、直接插入排序、简单选择排序、快速排序、希尔排序、堆排序。

    题目要求: (1)对以下6种常用的内部排序算法进行比较:起泡排序.直接插入排序.简单选择排序.快速排序.希尔排序.堆排序. (2)待排序表的表长不小于100:其中的数据要用伪随机数产生程序产生:至少要 ...

  2. <<算法很美>>——(三)十大排序算法(上)

    目录 前言 冒泡排序 图解冒泡 代码实现 冒泡优化 选择排序 图解选排​ 代码实现 插入排序 图解插入 ​代码实现 希尔排序 图解希尔 ​代码实现: 归并排序 图解归并 ​代码实现 快速排序 图解快排 ...

  3. 《算法不好玩》专题二:基础排序算法

    视频链接:<算法不好玩>专题二 2-1选择排序 无序数组→有序数组 基于排序算法可以学习的话题:「时间复杂度」.「递归」.「循环不变量」 排序算法可以分为:「基于比较的排序算法」.「非比较 ...

  4. 《算法笔记》第4章常用技巧

    文章目录 常用技巧 1. 散列 2. 递归 2.1 全排列问题 2.2 n皇后问题 2.3 回溯法优化n皇后问题 3. 贪心 3.1 简单贪心 3.2 区间贪心 4. 二分 4.1 二分查找 4.2 ...

  5. 【Java数据结构与算法】第七章 冒泡排序、选择排序、插入排序和希尔排序

    第七章 冒泡排序.选择排序.插入排序和希尔排序 文章目录 第七章 冒泡排序.选择排序.插入排序和希尔排序 一.冒泡排序 1.基本介绍 2.代码实现 二.选择排序 1.基本介绍 2.代码实现 三.插入排 ...

  6. 常用的比较排序算法总结

    写在前面 一直很惧怕算法,总是感觉特别伤脑子,因此至今为止,几种基本的排序算法一直都不是很清楚,更别说时间复杂度.空间复杂度什么的了. 今天抽空理了一下,其实感觉还好,并没有那么可怕,虽然代码写出来还 ...

  7. 强化学习经典算法笔记(十九):无监督策略学习算法Diversity Is All You Need

    强化学习经典算法笔记19:无监督策略学习算法Diversity Is All You Need DIAYN核心要点 模型定义 目标函数的构造 DIAYN算法细节 目标函数的优化 SAC的训练 判别器的 ...

  8. 常用七大经典排序算法总结(C语言描述)

    目录 一.交换排序 1.冒泡排序 2.快速排序 二.插入排序 1.直接插入排序 2.希尔(shell)排序 三.选择排序 1.直接选择排序 2.堆(Heap)排序 四.归并排序 正文 简介 其中排序算 ...

  9. php常用算法的时间复杂度,php的几个经典排序算法及时间复杂度和耗时​

    $arr = []; for ($i=0; $i 测试结果:排序用时(秒):5.2821290493011 php排序里的经典算法首先想到的就是冒泡排序法, 排序思想:两两交换小数上浮大数下沉每轮浮出 ...

最新文章

  1. VirtualBox uuid冲突问题
  2. 最强去马赛克AI来了,分分钟回到无马世界,连像素风《我的世界》人物都能复原...
  3. python-sendcmd被动模式访问ftp
  4. flink state ttl
  5. 【转】oracle number与java中long、int的对应
  6. JS异步操作新体验之 async函数
  7. java长连接socket【转】http://jiewo.iteye.com/blog/1562168
  8. Java内存模型–快速概述和注意事项
  9. sql怎么撤回update_零基础快速自学SQL,2天足矣!
  10. 关于微型计算机的原理 叙述正确的是,微型计算机原理练习附答案概念.doc
  11. jenkins简介及docker部署
  12. Pannellum:实例之为全景图添加指南针
  13. mysql b树深度_为什么Mysql用B+树做索引而不用B树
  14. 多媒体文件格式全解说
  15. 创建一个Scrapy爬虫项目并运行
  16. 3DMax制作下雨场景
  17. pve万兆网卡驱动_PVE+lede+DSM网卡硬盘直通+win10
  18. 怎样降低硕士论文重复率
  19. 东南大学计算机学院程茜雅,满满的黑科技,这份录取通知书火了!
  20. 网络DNS域名转换成IP地址

热门文章

  1. STM32开发笔记103: 24位模数转换芯片ADS1258使用方法(概述)
  2. Orin 调试GMSL camera 96712手册重点
  3. samba服务器的功能
  4. PMI-ACP(Agile Certified Practitioner)敏捷管理专业人士资格认证考试知识点汇总
  5. 虚拟机给openwrt添加硬盘并且配置smb
  6. leetcode: 70. Climbing Stairs
  7. C语言:到底是不是太胖了
  8. 归并排序(JAVA版)
  9. 大数据发展必备三个条件
  10. 3GPP TS 23501-g51 中英文对照 | 4.2.10 Architecture Reference Model for ATSSS Support