1、快速排序
第一行输入n为数字个数: 5
第二行输入n个数字为其排序:5 1 3 2 4
输入:
5
3 1 2 4 5
快速排序是在先找定中间数a[m]的基础上,从左找比a[m]大的数,从右找比a[m]小的数,交换这两个数:
经过第一次寻找后的状态: 2 1 3 5 4
此时 i=0,j=3
在(i<j)的情况下继续进行上一步直到跳出;
跳出时i=3;
这是为了保证在中间数的左右侧分别是比中间数小和大的数;
注意:a[m]的值是可以变的,所以最后 j 的位置才是分割两部分的位置;
此时进行下一步:
quick_sort(l,j);
quick_sort(j+1,r);
这是为了单独对中间数左和右侧进行上述操作:
经过一层递归后的状态:1 2 3 4 5
此时排序完成。
#include<bits/stdc++.h>
using namespace std;
const int N=1e5+5;
int a[N],n;
void quick_sort(int l,int r) {
if(l>=r) return;
int i=l-1,j=r+1,m=(l+r)>>1;
while(i<j) {
do i++;
while(a[i]<a[m]);
do j--;
while(a[j]>a[m]);
if(i<j) {
swap(a[i],a[j]);
}
}
quick_sort(l,j);
quick_sort(j+1,r);
}
int main() {
cin>>n;
for(int i=0; i<n; i++) {
cin>>a[i];
}
quick_sort(0,n-1);
for(int i=0; i<n; i++) {
cout<<a[i]<<" ";
}
return 0;
}
2、归并排序
第一行输入n为数字个数: 5
第二行输入n个数字为其排序:5 1 3 2 4
输入:
5
3 1 2 4 5
//排序之外多加一个:输出逆序数(注意当排列中有相同的数字时,就不能用这个方法求逆序数)
归并排序用的就是一种分而治之和递归的思想;
可以用一个gif动画来理解:
对原数组先对半分,分到只有两个开始进行对比和排序,再递归。以中点为界,左边从 L 开始,右边从 mid+1 开始,用一个新的数组从小到大存储 L 到 R 中的数,再用一个for循环更新原来的数组。
另外可以用归并排序来计算排列中的逆序数:
因为 i 始终在 j 的左侧,所以当 a[j]<a[i] 时,mid-i+1就是 a[j] 此时前移的位置,存储在ans中,最后的值就是排列的逆序数;
mid-i+1:此时 i 指向的是前半部分已经取出的下一个数,因为当ans增加时,a[j] 所移动到的位置必然在前面被取出的 j之后,所以 a[j] 前移的长度只与 i 有关;
#include<bits/stdc++.h>
using namespace std;
const int N=1e5+5;
int a[N],t[N],n,ans;//ans用来存总逆序数
void merge_sort(int l,int r) {
if(l>=r) return;
int mid=(l+r)>>1;
merge_sort(l,mid),merge_sort(mid+1,r);
int k=0,i=l,j=mid+1;
while(i<=mid&&j<=r) {
if(a[i]<a[j]) t[k++]=a[i++];
else {
t[k++]=a[j++];
ans+=mid-i+1;
}
}
while(i<=mid) t[k++]=a[i++];
while(j<=r) t[k++]=a[j++];
for(i=l,j=0; i<=r; i++,j++) {
a[i]=t[j];
}
return;
}
int main() {
cin>>n;
for(int i=0; i<n; i++) {
cin>>a[i];
}
merge_sort(0,n-1);
for(int i=0; i<n; i++) {
cout<<a[i]<<" ";
}
cout<<"ans: "<<ans<<endl;
return 0;
}
3、二分
第一行输入 n,m : 6 3
第二行输入 n 个升序的数字:1 2 2 3 3 4
后 m 输入每一个要查询的数字,输出他的起始位置和终止位置:3 4 5
若数字不存在输出:-1 -1
输入:
6 3
1 2 2 3 3 4
3
4
5
二分查找缩短时间复杂度,暴力复杂度为O(n),二分复杂度为O(logn);
整一个查找分为左偏和右偏两个部分
左偏:
while(l<r){
int mid=(l+r)>>1;
if(q[mid]>=x) r=mid;
else l=mid+1;
}
此时 l 寻找的是这个数的起始位置,所以当 q[mid]>=x 时 r 指向 mid ,当 q[mid]<x 时,l 指向 mid+1 ,而 l 最后指向的应该是第一个大于或等于 x 的值;
右偏:
while(l<r){
int mid=(l+r+1)>>1;
if(q[mid]<=x) l=mid;
else r=mid-1;
}
此时 l 寻找的是这个数的终止位置,所以当 q[mid]>x 时,r 指向 mid-1,当 q[mid]<=x 时 l 指向 mid ,而 l 最后指向的应该是最后一个等于 x 的值;
注意此时 mid=(l+r+1)>>1,这也是右偏特色,如果不加1会导致死循环,例如:查找3时,当 l 指向4,r 指向5时,对应的数字分别是 3,4,mid=4+5>>1=4,q[mid]=3<=3,l=3,陷入死循环…
#include<bits/stdc++.h>
using namespace std;
const int N=1e5+5;
int main(){
int n,m;
int q[N];
cin>>n>>m;
for(int i=0;i<n;i++){
cin>>q[i];
}
while(m--){
int x;
cin>>x;
int l=0,r=n-1;
while(l<r){
int mid=(l+r)>>1;
if(q[mid]>=x) r=mid;
else l=mid+1;
}
if(q[l]!=x) cout<<"-1 -1"<<endl;
else {
cout<<l<<" ";
int l=0,r=n-1;
while(l<r){
int mid=(l+r+1)>>1;
if(q[mid]<=x) l=mid;
else r=mid-1;
}
cout<<l<<endl;
}
}
return 0;
}
3.1、数的三次方根
输入浮点数 n:
1000.00
求出他的三次方根并保存六位小数输出;
这题就是用的二分思想查找三次方根;
while(r-l>1e-8)
这里是为了保证 l 与 r 之间相差足够小时跳出,这样精度更高;
注意:浮点数不能使用位运算,还是老老实实 /2 吧;
#include<iostream>
using namespace std;
int main(){
double x;
cin>>x;
double l=-100,r=100;
while(r-l>1e-8){
double mid=(l+r)/2;
if(mid*mid*mid>=x) r=mid;
else l=mid;
}
printf("%.6lf\n",l);
return 0;
}
4、高精度
接触STL,利用vector来做高精度:
4.1 大加
因为 int 最多存九位,所以将字符串分成每九位存进 vector 容器中,在计算时直接进行+运算,同时用1000000000取模后存入容器 c 中,最后输出 c ;
#include<bits/stdc++.h>
using namespace std;
const int N=1000000000;
vector<int> add(vector<int> &a,vector<int> &b) {
if(a.size()<b.size()) return add(b,a);
vector<int> c;
int t=0;
for(int i=0;i<a.size();i++){
t=a[i];
if(i<b.size()) t+=b[i];
c.push_back(t%N);
t/=N;
}
if(t) c.push_backe(t);
return c;
}
int main() {
string n,m;
cin>>n>>m;
vector<int> a,b;
for(int i=n.size()-1,s=0,j=0,t=1; i>=0; i--) {
s+=(n[i]-'0')*t;
j++,t*=10;
if(j==9||i==0) {
a.push_back(s);
s=j=0;
t=1;
}
}
for(int i=m.size()-1,s=0,j=0,t=1; i>=0; i--) {
s+=(m[i]-'0')*t;
j++,t*=10;
if(j==9||i==0) {
b.push_back(s);
s=j=0;
t=1;
}
}
auto c=add(a,b);
cout<<c.back();
for(int i=c.size()-2;i>=0;i--){
cout<<c[i];
}
return 0;
}
4.2 大减
大减就是先将字符串转变成数字倒序存入 vector 中,用 cmp 判定减的顺序,保证大的数减小的数,如果相减顺序与输入顺序相反,则输出 “-” ;
t 的作用是存储借位,如果前一位是小的数减大的数,t 存 1 ,t=a[i]-t 就是实现借位的过程;
#include<bits/stdc++.h>
using namespace std;
bool cmp(vector<int> &a,vector<int> &b) {
if(a.size()!=b.size()) return a.size()>b.size();
for(int i=0; i<a.size(); i++) {
if(a[i]!=b[i]){
return a[i]>b[i];
}
}
}
vector<int> sub(vector<int> &a,vector<int> &b) {
vector<int> c;
for(int i=0,t=0;i<a.size();i++){
t=a[i]-t;
if(i<b.size()) t-=b[i];
c.push_back((t+10)%10);
if(t<0) t=1;
else t=0;
}
while(c.size()>1&&c.back()==0) c.pop_back();
return c;
}
int main() {
string n,m;
cin>>n>>m;
vector<int> a,b;
for(int i=n.size()-1;i>=0;i--){
a.push_back(n[i]-'0');
}
for(int i=m.size()-1;i>=0;i--){
b.push_back(m[i]-'0');
}
vector<int> c;
if(cmp(a,b)) c=sub(a,b);
else c=sub(b,a),cout<<"-";
for(int i=c.size()-1;i>=0;i--){
cout<<c[i];
}
return 0;
}
4.3 大乘
因为一般大乘都是一个超大数和一个整型数相乘,所以读入一个 string 和一个 int 来进行乘法运算;
#include<bits/stdc++.h>
using namespace std;
vector<int> mul(vector<int> &a,int b) {
vector<int> c;
int t=0;
for(int i=0; i<a.size()||t; i++) {
if(i<a.size()) t+=a[i]*b;
c.push_back(t%10);
t/=10;
}
while(c.size()>1&&c.back()==0) c.pop_back();
return c;
}
int main() {
string n;
int b;
cin>>n>>b;
vector<int> a;
for(int i=n.size()-1; i>=0; i--) {
a.push_back(n[i]-'0');
}
auto c=mul(a,b);
for(int i=c.size()-1; i>=0; i--) {
cout<<c[i];
}
return 0;
}
4.4 大除
正向输入,正向相除;
因为最后需要去掉前置0,所以需要加一个 reverse 函数使 vector 变量翻转;
#include<bits/stdc++.h>
using namespace std;
vector<int> div(vector<int> &a,int b,int &r) {
vector<int> c;
r=0;
for(int i=0;i<a.size();i++) {
r=r*10+a[i];
c.push_back(r/b);
r%=b;
}
reverse(c.begin(),c.end());
while(c.size()>1&&c.back()==0) c.pop_back();
return c;
}
int main() {
string n;
int b;
cin>>n>>b;
vector<int> a;
for(int i=0;i<n.size();i++) {
a.push_back(n[i]-'0');
}
int r;
auto c=div(a,b,r);
for(int i=c.size()-1; i>=0; i--) cout<<c[i];
cout<<endl<<r<<endl;
return 0;
}