算法笔记-第四章-第六章

4.1排序

1.选择排序

思路:总共需要进行N趟操作,每次从i-n中选出最小的元素并与第I个元素交换。算法的时间复杂度为O(n2 ).
假设有数组A[i] (0<=i<=n-1), 如下:

void selectSort() {
	for(int i=0; i<n; i++) {
		int k=i;
		for(int j=i; j<n; j++) {
			if(A[j]<A[k]) {
				k=j;
			}
		}
		int temp=A[k];
		A[k]=A[i];
		A[i]=temp;
	}
}

2.直接插入排序

思路:对序列A[1]到A[n]中的n个元素,令i从2到n进行一个个地顺序枚举,并令j=i往前枚举,与A[i]比较,如果A[i]<A[j] && j>1则继续循环,直至退出循环,退出了要么是到了A[i]>=A[j]的地方或者是j==1,对于前者,直接插在j对应的元素的后面一个即可,如果是后者,则直接插在第一个位置。在循环过程中如果满足循环条件就得把A[j-1]移动到A[j]的位置上,以此来达到插入的目的。

int A[maxn], n;
void insertSort() {
	for(int i=2; i<=n; i++) {
		int temp=A[i], j=i;
		while(j>1 && A[i]<A[j-1]) {
			A[j]=A[j-1];
			j--;
		}
		A[j]=temp;
	}
} 

3.关于sort函数

使用C语言的库函数qsort函数、C++的sort函数
对于C++中的sort函数,需要添加头文件 #include < algorithm >,并在头文件下加一行using namespace std;
sort()
1)sort(首元素地址(必填),尾元素地址的下一个地址(必填),比较函数(非必填));
若不写比较函数则默认对前面给出的区间进行递增排序。
对于数据类型为int, double, char(默认是字典序)
2)对于比较函数cmp

①基本数据类型数组的排序
若不填比较函数,则默认按照从小到大的顺序排序。
若想从大到小排序则需要在程序开头写一个cmp函数

bool cmp(int a, int b) {		//若数据类型为int
	return a>b;
}
//若为double
bool cmp(double a, double b){
	return a>b;
}
//若为char 类型
bool cmp(char a, char b) {
	return a>b;
}

记忆:从小到大用<,从大到小用>

②结构体数组
如果定义如下结构体:

struct node{
	int x, y;
}ssd[10];

1)若仅按照x从大到小排序,(一级排序)则cmp函数如下:

bool cmp(node a, node b) {
	return a.x>b.x;
} 

2)若想先按照x从大到小排序,如果x相等的话,按照y从小到大来排序。

bool cmp(node a, node b) {
	if(a.x!=b.x) return a.x>b.x;
	else return a.y<b.y;
}

③STL容器中的vector和string排序

4.2 散列(hash)

是一种常用的算法思想。
bool hashTable[100010]={fasle};
bool hashTable[100010]={0];
通过建立类似这样的hashTable来以空间换时间降低算时间复杂度.
散列,将元素通过一个函数转化为整数, 使该数可以尽量唯一地代表这个元素。
常用的散列函数(以H表示)有:
1.直接定址法
即H(key)=key, 直接把key作为数组下标,是最常用的散列应用
2.除留取余法
H(key)=key%mod;
通过这个函数可以把很大的数转化为不超过mod的整数,这样就可以将它作为可行的数组下标。其中要注意表长Tsize必须不小于mod, 否则会产生越界。当mod是个素数的时候,H(key)能覆盖的范围是[0, mod),一般而言,通常把==Tsize取为素数, mod直接取成与Tsize相等。
但是我们会发现通过除留取余可能会导致两个不同的key1,key2生成相同的数,导致冲突,为了解决这个问题,主要有以下几种方式:
①线性探查法:
当得到key的hash值H(key)时,若检查发现下标为H(key)的位置已经被占据,则检查下一个位置(即令hash++)看是否被占,若没有则放在这个位置上,否则继续检查下一个位置,。。。但是这样容易导致元素扎堆
②平方探查法:
为了避免元素扎堆,当下标为H(key)的位置被占时,按下面的顺序检查表的位置:H(key)+12 ,H(key)-12 , H(key)+22 , H(key)-22
③链地址法:
把所有H(key)相同的key连接成单链表,可以设定一个数组Link, 范围是Link[0], Link[mod-1], 其中LInk[h]存放H(key)=h,

一般来说可以通过使用标准模板库中的map来直接使用hash的功能

关于map用法

map-映射,常见的STL容器, 可以将任何基本类型映射到任何基本类型(包括STL容器)。
使用map需要添加头文件#include< map >,下面还要加上using namespace std;

1.定义
map<typename1, typename2> mp;
map<string, int> mp;
map<set<int>, string> mp;

注意如果是字符串到整型的映射,必须要使用string而不是char数组
且需要添加头文件==#include < string >==

2.map容器内元素的访问

①通过下标访问:

map<char, int> mp;
mp['c']=20;
mp['c']=30;

注意map中的键是唯一的,因此后面再赋值的话会把前面的20覆盖。
②通过迭代器访问(可以近似理解为指针)

map<typename1, typename2>::iterator it;

由于it有两个typename,因此要是访问的话有it->first访问键,it->second访问值,

#include <cstdio>
#include <map>
using namespace std;
int main() {
	map<char, int> mp;
	mp['m']=20;
	mp['r']=30;
	mp['a']=40;
	for(map<char, int>::iterator it=mp.begin(); it!=mp.end(); it++){	//begin()用来取mp首元素地址,
		printf("%c %d\n", it->first, it->second);
	}
	return 0;
}

注意:map会以键从小到大的顺序自动排序

3.map常用函数

①find():
find(key)返回键为key的映射的迭代器,时间复杂度为O(logN),N为map中映射的个数。

#include <cstdio>
#include <map>
using namespace std;
int main() {
	map<char, int> mp;
	mp['m']=20;
	mp['r']=30;
	mp['a']=40;
	map<char, int>::iterator it=mp.find('a');
	printf("%c %d\n", it-.first, it->second);
	return 0;
}

②erase()
两种用法:删除单个元素、删除一个区间内的元素
-删除单个元素
有两种方法:
mp.erase(it), it为迭代器,时间复杂度为O(1)
mp.erase(key), key为键,时间复杂度为O(logN)
-删除一个区间内的元素
mp.erase(first, last),其中fisrt为需要删除区间的起始迭代器, last为需要删除的区间的末尾的下一位迭代器
③size()
mp.size(),用来获得map中映射的对数。
④clear()
mp.clear(),用来清空map中的所有元素。
begin() 返回指向map头部的迭代器
clear() 删除所有元素
count() 返回指定元素出现的次数
empty() 如果map为空则返回true
end() 返回指向map末尾的迭代器

4.3递归

关于全排列的递归

问题:输出1-n这n个整数的全排列
思路:可以把问题分解成为–输出以1开头的全排列,以2开头的全排列,可以设定一个数组p[]用来保存当前的排列, 再设定一个hashtable[], hashtable[x]=true表示整数x已经在数组p[]中。假定p[]中已经填好了0-indx-1那么下一个要填的是index,则需要从1到N进行枚举,直到hahstable[x]为false,将其填入p[],并置hashtable[x]=true,接下来就可以递归调用,填写index+1处的数字。注意,递归结束返回后,要把hashtable[x]=false。
以下为代码:

#include <cstdio>
const int maxn=11;
int n, p[maxn], hashtable[maxn]={false};

void generateP(int index) {
	if(index==n+1) {			//这是递归边界 
		for(int i=1; i<=n; i++) {
			printf("%d ", p[i]);
		}
		printf("\n");
		return;
	}
	for(int x=1; x<=n; x++) {	//对p[index]进行枚举1-n 
		if(hashtable[x]==false) {
			p[index] =x;
			hashtable[x]=true;
			generateP(index+1);
			hashtable[x]=false;		//注意这里必须将其还原,便于以后递归调用 
		}
	}
} 

int main() {
	n=3;
	generateP(1);
	return 0;
}

输出的即1-3的全排列。

关于N皇后问题

问题:在n*n的矩阵中放置n个皇后,要求这些皇后不能在同一行,同一列,同一对角线。
思路:如果直接利用组合数枚举,很不合算,考虑对n列的皇后的行号进行枚举,这样只需要枚举n!次,就可以得到答案。
这样的话代码和前面的枚举全排列差不多,只是在输出排列数之前要判断任意两个皇后是否在同一对角线上。

int count=0;		//全局变量
....
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 ++;
....

更加聪明的方法–回溯法
如下:

void generateP(int index) {
	if(index==n+1) {
		count++;
		return;
	}
	for(int i=1; i<=n; i++) {
		if(hashtable[x]==false) {
			bool flag=true;
			for(int  pre=1; pre<index; pre++) {
				if(abs(index-pre)==abs(x-p[pre])) {
					flag=false;
					break;
				}
			}
			if(flag==true) {
				p[index]=x;
				hashtable[x]=true;
				generateP(index+1);
				hashtable[x]=false;
				
			}
		}
	}
} 

4.4贪心

区间贪心

思路:两种方向:
1.每次寻找左端点最大的区间
2.每次寻找右端点最小的区间。
1的代码如下:

#include <cstdio>
#include <algorithm>
using namespace std;
const int maxsize=110;
struct Range{
	int x,y;
}info[maxsize];
bool cmp(Range a, Range b) {
	if(a.x!=b.x) return a.x>b.x;
	else return a.y<b.y; 
}

int main() {		
	int n;
	scanf("%d", &n);
	while(n!=0) {
		for(int i=0; i<n; i++) {
			scanf("%d%d", &info[i].x, &info[i].y);
		}
		sort(info, info+n, cmp);
		int left=info[0].x, count=1;		//注意!初始count为1 
		for(int i=1; i<n; i++) {
			if(info[i].y<=left) {
				left=info[i].x;
				count++;
			}
		}
		printf("%d\n", count);
		scanf("%d", &n);
	}
	return 0;
}

2的代码如下:

#include <cstdio>
#include <algorithm>
using namespace std;
const int maxsize=110;
struct Range{
	int x, y;
}info[maxsize]; 
bool cmp(Range a, Range b) {
	if(a.y!=b.y) return a.y<b.y;
	else return a.x<b.x;
}

int main() {
	int n;
	scanf("%d", &n);
	while(n!=0) {
		for(int i=0; i<n; i++) {
			scanf("%d%d", &info[i].x, &info[i].y);
		}
		sort(info, info+n, cmp);
		int count=1, right=info[0].y;
		for(int i=1; i<n; i++) {
			if(info[i].x>=right) {
				right=info[i].y;
				count++;
			}
		}
		printf("%d\n", count);
		scanf("%d", &n);
	}

	return 0;
}

关于string容器的基本概念及各种函数使用

1.定义

#include <string>			//注意不是cstring/string.h
using namespace std;
string str;//像普通数据类型那样直接定义string变量即可!

2.string的访问
①像char数组那样直接根据下标访问

string str="abcd";
for(int i=0; i<str.length(); i++) {
	printf("%c", str[i]);
}

注意:输入、输出string只能用cin,cout
②根据迭代器访问

for(string::iterator it=str.begin(); it!=str.end(); it++) {
	printf("%c", *it);
}

3.string常用函数
1) +=

string str1="abcd", str2="efg";
string str3 = str1+str2;
cout << str3 << endl;
//输出应为abcdefg
str1 += str2;
cout << str1 << endl;
//输出应为abcdefg

2)compare operator
<,>, >=, <= …比较原则是字典序
3)length()/size()
4)insert()
–1)str.insert(pos, string2); //将string2插入到str的第pos个位置上及之后,
–2)str.insert(it, it2, it3);
4)erase()
str.erase(it);
str.erase(first, last);
str.erase(pos, length);
5)clear() //清空string中的数据
6)substr()
substr(pos, len); //返回从pos开始的长度为len的子串
7)string::npos
为一个常数,可作为find()找不到时的返回值
8)find()
str.find(str2), 当str2是str的子串时,返回str2在str中第一次出现的位置,若不是子串,则返回string::npos
str.find(str2, pos), 从str的pos位开始匹配str2,返回值与上面相同。
9)replace()
str.replace(pos, len, str2); //把str从pos位开始、长度为len的子串替换为str2
str.replace(it1, it2, str2); //把str的迭代器[it1,it2)范围内的子串替换为str2.

4.5 二分

1.二分查找

#include <cstdio>
int binarySearch(int a[], int left, int right, int x) {
	int mid;
	while(left<=right) {
		mid=(left+right)/2;
		if(a[mid]>x) {			//a[]严格递增,若递减则改为小于号即可 
			right=mid-1;
		} else if(a[mid]==x) {
			return mid;
		} else {
			left=mid+1;
		}
	}
	return -1; 
}

注意:若left大于int数据类型的一半时,mid=(left+right)/2会溢出,因此,可以写成:mid=left+(right-left)/2来避免溢出
以下为解决寻找有序序列第一个不满足某个条件的元素的位置的模板

//二分区间为[left, right], 传入的初值应为[0, n];
int solve (int left, int right) {
	int mid;
	while(left<right) {
		mid=(left+right)/2;
		if(条件成立) {
			right=mid;
		} else {
			left=mid;
		}
	}
	return left;
}

另外,如果题目要求寻找最后一个满足某条件的元素的位置,可以先找到第一个不满足该条件的元素的位置,然后减一即可。

关于lower_bound()和upper_bound()函数

1.lower_bound(int a[], int left, int right, int x):
返回第一个大于等于x的元素的位置
2.upper_bound(int a[], int left, int right, int x):
返回第一个大于X的元素的位置

快速幂

1.也叫二分幂
如果b是奇数,ab =a*ab-1
如果b是偶数,ab =ab/2 *ab/2
因此时间复杂度可以从原来的循环O(n)降低到O(logN)级别。算法如下:递归

typedef long long ll;
ll binaryPow(ll a, ll b, ll m) {
	if(b==0) return 1;
	else if(b%2==1) {			//这个条件写成b&1可以加快算法速度!
		return a*binary(a, b-1, m)%m;
	else {
		ll mul=binaryPow(a, b/2, m);
		return mul*mul%m;
	}
}

4.6 two pointers

1.归并排序

二路归并排序://核心就是把两个递增序列合并

const int maxn=100;
void merge(int a[], int l1, int r1, int l2, int r2) {		
	int i=l1, j=l2;
	int temp[maxn], index=0;
	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++];
	while(j<=r2) temp[index++]=a[j++];
	for(i=0; i<index; i++) {
		a[l1+i]=temp[i];
	}
}
//递归实现
void mergeSort(int a[], int left, int right) {
	if(left<right) {
		int mid=(left+right)/2;
		mergeSort(a, left, mid);
		mergeSort(a, mid+1, right);
		merge(a, left, mid, mid+1, right);
	}
}		
//非递归实现
void mergeSort(int a[]) {
	for(int step=2; step/2<=n; step*=2) {
		for(int i=1; i<=n; i++) {
			int mid=i+step/2+1;
			if(mid+1<n) {
				merge(a,i mid, mid+1, min(i+mid-1, n));
			}
		}
		//输出序列
	}
}
//若题目要求输出每一趟的序列,可以考虑使用sort函数
//当然上面的也可以解决这个问题
void mergeSort(int a[]) {
	for(int step=2; step/2<=n; step*=2) {
		for(int i=1; i<=n; i++) {
			sort(a+i, a+min(i+step/2-1, n));
		}
		//输出
	}
}

快速排序

时间复杂度达到了O(NlogN)
步骤:1.two pinters操作,使主元前的元素小于主元,主元后的元素大于主元

int Patition(int a[], int left, int right) {
	int temp=a[left];
	while(left<right) {
		while(left<right && a[right]>temp) right--;
		a[left]=a[right];
		while(left<right && a[left]<=temp) left++;
		a[right]=a[left];
	}
	a[left]=temp;
	return left;
}

2.对序列递归操作,直至当前调整的序列长度不超过1

void quickSort(int a, int left, int right) {
	if(left<right){		//当前序列长度不超过1
		int pos=Partition(a, left, right);
		quickSort(a, left, pos-1);
		quickSort(a, pos+1, right);
	}
} 

注意:快速排序算法在序列的排列比较随机时效率最高,当序列元素接近有序排列时,会达到最坏时间复杂度O(n2 ),要解决这个问题,主要在于如何选择主元,如果随机选择主元,则对于任意输入的排列的期望时间复杂度为O(NlogN)。

#include <stdio.h>
#include <stdlib.h>			//要添加这个头文件及下面的time.h才可以生成随机数
#include <time.h>			
int main() {
	srand((unsigned)time(NULL));	//srand是初始化随机种子,然后在需要使用随机数的地方使用rand()函数
	for(int i=0; i<10; i++) {
		printf("%d", rand());
	}
	return 0;
}

注意:rand()只能生成[0, RAND_MAX]范围内的数,不同的系统环境该常数不同,若要生成[a,b],
则需要rand()%(b-a+1)+a;
但是这种生成的数只在RAND_MAX范围内,若要生成比这个大的数,则:
思路:先用rand()生成[0,1]范围内的浮点数,然后用该数乘以b-a,然后再加a
(int) (round(1.0rand()%RAND_MAX50000+10000; //生成[10000,60000]范围内的数

//选取随即主元
int randomPartition(int a[], int left, int right) {
	int p= round(rand()%RAND_MAX*(right-left)+left);
	swap(a[p], a[left]);
	//下面的代码与前面一致
	nt temp=a[left];
	while(left<right) {
		while(left<right && a[right]>temp) right--;
		a[left]=a[right];
		while(left<right && a[left]<=temp) left++;
		a[right]=a[left];
	}
	a[left]=temp;
	return left;
}

关于用辗转相除法求最大公约数—gcd(a, b)

书中给出了迭代代码求最大公约数,注意;要求a>b

int gcd(int a, int b) {
	if(b==0) return a;
	else return gcd(b, a%b);
}
//或者
int gcd(int a, int b) {
	return !b ? a: gcd(b, a%b);
}

最小公倍数—lcm(a, b)

计算出a和b的最大公约数后,a/d*b即得到最小公倍数

有gcd(a, b)lcm(a, b)=ab;
注意,在c++的algorithm库里可以直接调用,gcd(a, b),

5.3 关于分数的处理

1.表示及化简

书中代码,如下:

struct Fraction{			//结构定义
	int up, down;
}
Fraction reduction(Fraction result) {	//化简
	if(result.down<0) {
		result.down*=-1;
		result.up*=-1;
	}
	if(result.up==0) {
		result.down=1;
	} else {
		int temp=gcd(abs(result.up), abs(result.down));	
		//书中这里没有考虑二者的大小问题,这么考虑的话应该是在gcd()函数里面进行了交换,or not
		result.up/=temp;
		result.down/=temp;
	}
}

对于加法和减法,及乘法和除法,记得在做完后调用reduction来化简!
另外,如果除数输入为0,则需要输出error
一般分子和分母的数据类型是long long, 避免溢出

5.4 关于素数

1.判断素数
书中代码:

#include <cstdio>
#include <cmath>
bool isPrime(int n) {
	if(n<=1) return false;
	int sqr=(int) sqrt(1.0*n);
	for(int i=2;; i<=sqr; i++) {
		if(n%i==0) {
			return false;
		}
	}
	return true;
}

打印素数表

1.暴力枚举:时间复杂度O(N*N1/2), n在105 范围内是可以承受的
2.埃氏筛选

质因子分解

思路:枚举1-sqrt(n)范围内的所有质因子,判断其是否为n的因子,如果 是的话, 就令fac数组中增加质因子p,并初始化个数为0,然后只要p还是n的因子,就一直让n/p,直到p不是N的因子。

if(n%prime[i]==0) {
	fac[num].x=prime[i];
	fac.count=0;
	while(n%prime[i]==0) {
		fac[num].cnt++;
		n/=prime[i];
	}
}

然后如果这样的步骤之后n仍然不是1,则说明==n有且仅有一个大于sqrt(n)的质因子(有可能是n本身),这时把这个质因子加入数组,令其个数为1。
时间复杂度为O(sqrt(n));

高精度数的加减

1.加法
书中代码如下:

bign add(bign a, bign b) {
	

扩展欧几里得算法

1.扩展欧几里得算法
2.计算质因子个数

计算n!中质因子p的个数:
1.暴力计算,时间复杂度达到了O(NlogN),
2.有一个公式:
n!中有n/p2 +n/p3 +…个质因子p,

int cal(int n, int p) {
	int ans=0;
	while(n) {
		ans += n/p;
		n/=p;
	}
}
//递归写法
int cal(int n, int p){
	if(n<p) return 0;
	return n/p + cal(n/p, p);
}

计算组合数

1.C(n, m)
①公式法:这样容易溢出
②递归计算

long long C(long long n, long long m) {
	if(m==0 || m==n) return 1;
	else {
		return C(n-1, m)+C(n-1, m-1);
	}
}
//
long long res[67][67]={0};
long long C(long long n, long long m) {
	if(m==0 || m==n) return 1;
	if(res[n][m]!=0)return res[n][m];
	return res[n][m]=C(n-1, m)+C(n-1, m-1);
}
//

2.计算C(n, m)%p

int res[1010][1010]={0};
int C(int n, int m, int p) {
	if(m==0 || m==n) return 1;
	if(res[n][m]!=0) return res[n][m];
	return res[n][m]=(C(n-1, m)+C(n-1, m-1))%p;
}

关于map的详细用法:见以下链接LINK?

map 用法补充

1.map<string, set< int > >:
一个字符串对应多个整型,注意,键值是唯一的,一对多

关于istream

std::istream: input stream. Input stream objects can read and interpret input from sequences of characters. The standard object cin is an object of this type.
详细见链接?

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值