目录
一、插入排序
(1)定义
直接插入排序是最简单的排序方法,每次将一个待排序的记录,插入到已经排好序的数据序列中,得到一个新的长度增1的有序表。
插入到有序序列时,可以用第0个空间暂存这个数据(作为哨兵,免去数组越界的判断),也可以用一个temp变量保存
(2)算法步骤
①待排序记录存储在数组r[1...n]中,可以把第1个记录r[1]看作一个有序序列
②依次将r[i](i=2,……,n)插入到已经排好序的序列r[1...i-1]中,并保持有序性
例如,利用直接插入排序算法对序列{12,2,16,30,28,10,16*,20,6,18}进行非递减排序。
初始状态,把r[1]看作一个有序序列
将2插入到有序序列中
将16插入到有序序列中
将30插入到有序序列中
将28插入到有序序列中
将10插入到有序序列中
将16插入到有序序列中
将20插入到有序序列中
将 6插入到有序序列中
最后再把18插入到有序序列中
(3)算法分析
①时间复杂度
- 最好情况下,待排序序列本身是正序的(如待排序序列是非递减的,题目要求是非递减的),每个记录只需和前一个记录比较一次,不小于前一个记录,则什么都不用做,总的比较次数为:n-1。最好情况下,直接插入排序的时间复杂度为O(n)
- 最坏情况下,待排序序列本身是逆序的(如待排序序列是非递增的,题目要求是非递减的),每个记录都需要比较i次(和前i-1记录比较+和哨兵r[0]比较),总的比较次数为:。最坏情况下,直接插入排序的时间复杂度为O(n^2)
- 平均情况下,若待排序序列出现各种情况的概率均等,则可取最好情况和最坏情况的平均值。平均情况下,直接插入排序的时间复杂度也为O(n^2)
②空间复杂度
直接插入排序使用了一个辅助空间r[0],空间复杂度为O(1)
③稳定性
直接插入排序时,已经有序的序列中的记录比待排序记录大时才向后移动,和待排序记录相等时不向后移动,因此两个相等的记录在排序前后的位置顺序不是变的。
在有序序列中查找待排序记录的插入位置时,折半查找的方法比顺序查找效率更高,但元素移动效率是不变的,时间复杂度还是O(n^2)
(4)代码
#define INF 0x3f3f3f3f
#define ll long long
#include<iostream>
#include <stdio.h>
using namespace std;
const int Maxsize=100+5;
void StraightInsertSort(int r[],int n){
int j;
for(int i=2;i<=n;i++){
if(r[i-1]>r[i]){
r[0]=r[i];
r[i]=r[i-1];
for(j=i-2;r[j]>r[0];j--)
r[j+1]=r[j];
r[j+1]=r[0];
}
}
}
int main(){
int i,n,r[Maxsize+1];
printf("请输入数列中的元素个数n\n");
scanf("%d",&n);
printf("请依次输入数列中的元素:\n");
for(int i=1;i<=n;i++){
scanf("%d",&r[i]);
}
StraightInsertSort(r,n);
printf("直接插入排序结果\n");
for(int i=1;i<=n;i++)
printf("%d ",r[i]);
return 0;
}
折半查找插入排序
#define INF 0x3f3f3f3f
#define ll long long
#include<iostream>
#include <stdio.h>
using namespace std;
const int Maxsize=100+5;
void InsertSort(int r[],int n){
int j,mid;
for(int i=2;i<=n;i++){
if(r[i-1]>r[i]){
r[0]=r[i];
int low=1,high=i-1;
while(low<=high){
mid=(low+high)/2;
if(r[mid]>r[0]){
high=mid-1;
}
else{
low=mid+1;
}
}
// 将大于 temp 的元素向后移动
for (j = i; j > high; j--) {
r[j] = r[j - 1];
}
r[high + 1] = r[0]; // 插入 temp 到正确的位置
}
}
}
int main(){
int i,n,r[Maxsize+1];
printf("请输入数列中的元素个数n\n");
scanf("%d",&n);
printf("请依次输入数列中的元素:\n");
for(int i=1;i<=n;i++){
scanf("%d",&r[i]);
}
InsertSort(r,n);
printf("直接插入排序结果\n");
for(int i=1;i<=n;i++)
printf("%d ",r[i]);
return 0;
}
二、冒泡排序
(1)定义
冒泡排序是一种最简单的交换排序算法,通过两两比较关键字,如果逆序就交换,使关键字大的记录像泡泡一样冒出来放在尾部。重复执行若干次冒泡排序,最终得到有序序列。
(2)算法步骤
①设待排序的记录存储在数组r[1...n]中,首先第一个记录和第二个记录关键字比较,若逆序则交换;然后第二个记录和第三个记录关键字比较,……,以此类推,直到第n-1个记录和第n个记录关键字比较完毕为止。第一趟排序结束,关键字最大的记录在最后一个位置。
②第二趟排序,对前n-1个元素进行冒泡排序,关键字次大的记录在n-1位置。
③重复上述过程,直到某一趟排序中没有进行交换记录为止,说明序列已经有序。
(3)算法分析
①时间复杂度
- 最好情况下,待排序序列本身是正序的(如待排序序列是非递减的,题目要求也是非递减排序),只需要一趟排序,n-1次比较,无交换记录。最好情况下,冒泡排序时间复杂度为O(n)。
- 最坏情况下,待排序序列本身是逆序的(如待排序序列是非递增的,题目要求是非递减排序),需要n-1趟排序,每趟排序i-1次比较,总的比较次数为:,还有交换时间,最坏情况下,冒泡排序的时间复杂度为O(n^2)
- 平均情况下,若待排序序列出现各种情况的概率均等,则可取最好情况和最坏情况的平均值。平均情况下,冒泡排序的时间复杂度也为O(n^2)。
②空间复杂度
冒泡排序使用了一些辅助空间i,j,temp,flag,空间复杂度为O(1)。
③稳定性
冒泡排序是稳定的排序方法
冒泡排序比插入排序差,因为有大量的交换
(4)代码
#define INF 0x3f3f3f3f
#define ll long long
#include<iostream>
#include <stdio.h>
using namespace std;
const int Maxsize=100+5;
void BubbleSort(int r[],int n){
int i=n-1,temp;
bool flag=true;
while(i>=2 && flag){
flag=false;
for(int j=0;j<=i-1;j++){//进行一趟排序
if(r[j]>r[j+1]){//交换两个记录
flag=true;
temp=r[j];
r[j]=r[j+1];
r[j+1]=temp;
}
}
for(int k=0;k<=i;k++)//测试每趟排序结果
printf("%d ",r[k]);
printf("\n");
i--;
}
}
int main(){
int i,n,r[Maxsize+1];
printf("请输入数列中的元素个数n\n");
scanf("%d",&n);
printf("请依次输入数列中的元素:\n");
for(int i=0;i<n;i++){
scanf("%d",&r[i]);
}
BubbleSort(r,n);
printf("冒泡排序结果\n");
for(int i=0;i<n;i++)
printf("%d ",r[i]);
return 0;
}
三、斐波那契logn解法
斐波那契数列以如下被以递推的方法定义:F(1)=1,F(2)=1,F(n)=F(n-1)+F(n-2)(n≥3)
斐波那契数列有多种解法:
- 通项公式 O(1)
- 递归 O(1.618^n)
- 动态规划 O(n)
- 矩阵快速幂 O(logn)
本节就是讲用矩阵快速来求解斐波那契数列
(1)推导
①矩阵公式
F(n+1)=1*F(n)+1*F(n-1)
F(n)=1*F(n)+0*F(n-1)
F(n)=1*F(n-1)+1*F(n-2)
F(n-1)=1*F(n-1)+0*F(n-2)
通过这4个公式得到以下公式
n~2要乘n-2个
所以,只要求出 ,结果的第一行第二列和第二行第一列都是F(n)的解
②快速幂
快速幂公式如下:
的值可能会很大,会溢出,所以a先求余再求b次方(b次方后可能会很大,所以还需求余)。求余是因为有些题不需要这么大的数,只需要后几位就行
可以按照下面公式处理快速求
b是奇数的话,b/2会向下取整所以还需乘a
例如求2^n,初始时,ans=1,a=2
Ⅰ如果n=4,要求则先求
a=a*a=2*2,n=n/2=2【】
a=a*a=(2*2)*(2*2),n=n/2=1【】
ans=ans*a=1* (2*2)*(2*2),n=n/2=0
Ⅱ如果n=5,要求则先求
ans=ans*a=2,a=a*a=2*2,n=n/2=2【】
a=a*a=(2*2)*(2*2),n=n/2=1【】
ans=ans*a=2*(2*2)*(2*2),n=n/2=0
如果不用快速幂,a*a*a*a需要计算3次,使用快速幂只需要计算log4=2次,先计算2*2,再计算(2*2)*(2*2) ,如上所示求到=1,即x=logn
int Pow(int n,int p){
int ans=1;
while(n){
if(n&1)//奇数,最后n=1时也是奇数
ans=ans*a%p;
a=a*a%p;
n=n/2;
}
}
③矩阵快速幂
矩阵乘法运算是第一个矩阵的第i行元素分别乘以第二个矩阵的第j列元素,然后求和得到结果矩阵的元素,如图所示
单位矩阵(类似单位1)
矩阵快速幂和普通的快速幂类似,只是执行的是矩阵乘法而已。
求解矩阵c的n次幂,首先初始化c矩阵,初始化ans为单位矩阵,然后判断n如果为奇数,则res与c执行矩阵乘法,c与c执行矩阵乘法,n=n/2,循环,直到n=0时停止。
#define INF 0x3f3f3f3f
#define ll long long
#include<iostream>
#include <stdio.h>
#include <cstring>
using namespace std;
const int MOD=10000;
struct mat{
ll a[2][2];
};
mat mat_mul(mat x,mat y){
mat res;
memset(res.a,0,sizeof(res.a));
for(int i=0;i<2;i++){
for(int j=0;j<2;j++){
for(int k=0;k<2;k++){
res.a[i][j]=(res.a[i][j]+x.a[i][k]*y.a[k][j])%MOD;
}
}
}
return res;
}
void mat_pow(int n){
mat c,ans;
c.a[0][0]=1;c.a[0][1]=1;c.a[1][0]=1;c.a[1][1]=0;
memset(ans.a,0,sizeof(ans.a));
for(int i=0;i<2;i++)
ans.a[i][i]=1;//单位矩阵
while(n){
if(n&1)
ans=mat_mul(ans,c);
c=mat_mul(c,c);
n=n>>1;
}
printf("%I64d\n",ans.a[0][1]);
}
int main(){
int n;
printf("输入指数\n");
scanf("%d",&n);
printf("F(%d)斐波那契数列值为:\n",n);
mat_pow(n);
}