算法复习2


第五周第一节跳过了

递归的概念

递归算法的执行过程分递推回归两个阶段
在【栈区】

long f(int n)
{
	if(n==0) return 1;
	return n*f(n-1);
}

Fibonacci数列

int f(int n)
{
   if (n <= 1) return 1; //0 和 1 都是 1
   return f(n-1)+f(n-2);
}

去递归:
1、前2例中的函数都可以找到相应的非递归方式定义,一个公式
(Ackerman函数却无法找到非递归的定义,结论:并非一切递归函数都能用非递归方式定义)

📕例题1:(全排列)

//按字典序排列
#include<iostream>
#include<algorithm>
using namespace std;

int A[11];

void perm(int list[],int k,int m) //从k到 m 全排列 
{
	if(k==m) 
	{
		for(int i=0;i<=m;i++)  cout<<list[i];
		cout<<endl;
	}
	else
	{
		for(int i=k;i<=m;i++)
		{
			sort(list+k,list+m+1); //加一个sort就可以字典序了 
			swap(list[k],list[i]);
			perm(list,k+1,m);
			swap(list[k],list[i]);
		}
	}
}

void swap(int &a,int &b)
{
	int temp=a;a=b;b=temp;
 } 

int main()
{
	int n;cin>>n;
	for(int i=0;i<n;i++){
		A[i]=i+1;
	}
	perm(A,0,n-1);
}

非字典序

#include<iostream>
using namespace std;

int A[11];

void perm(int list[],int k,int m) //从k到 m 全排列 
{
	if(k==m) 
	{
		for(int i=0;i<=m;i++)  cout<<list[i];
		cout<<endl;
	}
	else
	{
		for(int i=k;i<=m;i++)
		{
			swap(list[k],list[i]);
			perm(list,k+1,m);
			swap(list[k],list[i]);
		}
	}
}

void swap(int &a,int &b)
{
	int temp=a;a=b;b=temp;
 } 

int main()
{
	int n;cin>>n;
	for(int i=0;i<n;i++){
		A[i]=i+1;
	}
	perm(A,0,n-1);
}

分治法的基本思想

对这k个子问题分别求解。如果子问题的规模仍然不够小,则再划分为k个子问题,如此递归的进行下去,直到问题规模足够小,很容易求出其解为止

将求出的小规模的问题的解合并为一个更大规模的问题的解,【自底向上】逐步求出原来问题的解

分治法所能解决的问题一般具有以下几个特征:
1、该问题的规模缩小到一定的程度就可以容易地解决;
2、问题的最优解包含其子问题的最优解,即最优子结构性质
3、利用问题分解出的子问题的解可以合并为原问题的解;
4、该问题所分解出的各个子问题是【相互独立】的,即子问题之间不包含公共的子问题。

特征4涉及到分治法的效率,如果各子问题是不独立的,则分治法要做许多不必要的重复工作,重复地解公共的子问题,此时虽然也可用分治法,但一般用动态规划较好

人们从大量实践中发现,在用分治法设计算法时,最好【使子问题的规模大致相同】。
即将一个问题分成大小相等的k个子问题的处理方法是行之有效的。-- 平衡子问题

一个分治法将规模为n的问题分成规模为n/m的子问题,其中k个子问题需要求解(k>=1, m>1) 。设分解阀值n0=1,且adhoc解规模为1的问题耗费1个单位时间。再设将原问题分解为k个子问题以及用merge将k个子问题的解合并为原问题的解需用f(n)个单位时间。用T(n)表示该分治法解规模为|P|=n的问题所需的计算时间,则有:

通过迭代法求得方程的解:
看这里的博文

主定理(求分治法的时间复杂度)

在这里插入图片描述
T(n)=kT(n / m)+nd
练习:T(n)=16T(n/4)+n
k=16,m=4,d=1,有 k>md , T(n)=θ(n2)
练习:T(n)=T(3n/7)+1
k=1,m=7/3,d=0,有 k=md , T(n)=θ(logn)
并非所有递推式都可用主定理求解
如:T(n)=2T(n/2)+nlogn
T(n)=T(n-1)+1

📕例题2:(二分搜索)

最坏情况下时间复杂度:O(logn) 以二为底

#include<iostream>
using namespace std;

int BinarySearch(int arr[],const int& x,int left,int right)
{
	while(left<=right)
	{
		int m=(left+right)/2;
		if(x==arr[m]) return m;
		if(x<arr[m]) right=m-1;
		else left=m+1;
	}
	return -1;
 } 

int main()
{
	int A[11];
	int n;cin>>n;
	for(int i=0;i<n;i++){
		A[i]=i+1;  //1 2 3
	}
	cout<<BinarySearch(A,2,0,n-1); //返回数字2的下标 1 
}
#include<iostream>
using namespace std;
//递归法 
int BinarySearch(int arr[],const int& x,int left,int right)
{
	if(left>right) return -1;
	int m=(left+right)/2;
	if(x==arr[m]) return m;
	if(x<arr[m]) return BinarySearch(arr,x,left,m-1);
	else return BinarySearch(arr,x,m+1,right);
 } 

int main()
{
	int A[11];
	int n;cin>>n;
	for(int i=0;i<n;i++){
		A[i]=i+1;  //1 2 3
	}
	cout<<BinarySearch(A,2,0,n-1); //返回数字2的下标 1 
}

大整数的乘法

设计有效的算法,进行两个n位二进制大整数的乘法运算
二进制乘法 : 0×0=0  1×0=0  0×1=0 1×1=1 
传统的方法:O(n^2^) 效率太低
【例】求 (1110)2 乘(101)2 之积

解:   1 1 1 0 
         × 1 0 1   
     -----------------
         1 1 1 0 
      0 0 0 0  
     1 1 1 0   
  ------------------  
   1 0 0 0 1 1 0  
     


问题规模缩小了一半

ac、ad、bc、bd 四个子问题

Strassen(斯特拉森)矩阵乘法


几种排序方法的时间复杂度比较

排序方法平均时间最坏情况
简单排序O(n2)O(n2)
快速排序O(nlogn)O(n2)
堆排序O(nlogn)O(nlogn)
合并排序O(nlogn)O(nlogn)

合并排序

基本思想:当n=1时终止排序,否则将待排序元素分为大小大致相同的2个子集合,分别对2个子集合进行排序,最终将拍好序的子集合合并称为所要求的排好序的集合。 O(n)

void MergeSort(int a[],int left,int right)
{
	if(left<right) //至少有 2个元素
	{ 
		int mid=(left+right)/2; //取中点
		mergeSort(a,left,mid);  //递归调用自身 
		mergeSort(a,mid+1,right);
		merge(a,b,left,mid,right);//合并到数组 b
		copy(a,b,left,right); //复制回数组 a
	} 
 } 

■从分治的策略入手,容易消除算法中的递归。
■事实上,算法mergesort的递归过程只是将【待排序集合一分为二,直至集合中只剩下一个元素为止】。按此机制,将数组a中相邻元素两两配对,用合并算法排序,构成n/2组长度为2的排好序的子数组段,然后再将他们排序成n/4组长度为4的排好序的子数组段,如此继续下去直到整个数组排好序


MergePass

Merge

另一个方法:自然合并排序

📕合并排序代码

#include<iostream>
using namespace std;
void MergeSort(int a[],int n);
void MergePass(int a[],int b[],int s,int n);
void Merge(int c[],int d[],int l,int m,int r);


//自顶向下逐步细分
//消去递归后的合并排序算法
void MergeSort(int a[],int n)//n个元素 
{
	int *b=new int[n];
	int s=1;
	while(s<n)
	{
		MergePass(a,b,s,n); //每s一小段合并到数组 b
		s+=s;  //s要翻倍 
		if(s>=n){
			for(int i=0;i<n;i++) a[i]=b[i];
			break;
		}
		MergePass(b,a,s,n); //合并到数组 a
		s+=s; 
	}
 } 
//MergePass函数用于合并排好序的相邻数组段
void MergePass(int a[],int b[],int s,int n)
{
	int i=0;
	while(i+2*s-1<=n-1)  //最后一个值还在数组下标最大值内,没有越界 
	{
		Merge(a,b,i,i+s-1,i+2*s-1);//合并到数组 b
		i=i+2*s; 
	} ///合并大小为 s 的相邻 2 段子数组
	//剩下元素小于 2s
	if(i+s-1<n-1)Merge(a,b,i,i+s-1,n-1); //处理零头 1-3个元素 
	else for(int j=i;j<=n-1;j++) b[j]=a[j]; //只有一段时1-2个元素 
 } 

void Merge(int c[],int d[],int l,int m,int r)
{
	//合并 c[l:m] 和 c[m+1:r] 到 d[l:r] 
	int i=l,j=m+1,k=l;
	while(i<=m&&j<=r)
	{
		if(c[i]<=c[j]) d[k++]=c[i++];
		else d[k++]=c[j++];
	}  
	 if(i>m) for(int q=j;q<=r;q++) d[k++]=c[q];  //左边这一半填完了,就把右边全放进去就行了 
	 else for(int q=i;q<=m;q++) d[k++]=c[q];
	
}

int main()
{
	int A[11]={0};
	int n;cin>>n; 
	for(int i=0;i<n;i++){
		cin>>A[i];
	}
	MergeSort(A,n);
	for(int i=0;i<n;i++){
		cout<<A[i]<<" ";
	}
}

📕快速排序(代码)

劈成两半,左边比右边的小

在快速排序中,元素的 比较和交换是从两端向 中间进行的,值较大的 元素一次就能交换到后 面单元,值较小的记录 一次就能交换到前面单
元,元素每次移动的距 离较大,而总的比较和 移动次数较少

若左边特别大,是9,则最后i 和 j 都指向 8
快速排序的运行时间与划分是否对称有关

#include<iostream>
using namespace std;
void QuickSort(int a[],int p,int r);
int Partition(int a[],int p,int r);

void QuickSort(int a[],int p,int r) //p左边 r右边 
{ 
	if(p<r)   //至少有两个数待排序 
	{
		int q=Partition(a,p,r);  //把p到r分割成两半  q是中点 
		QuickSort(a,p,q-1);	 //对左半段排序 
		QuickSort(a,q+1,r);	  //对右半段排序 
	}	
}

int Partition(int a[],int p,int r)
{
	int i=p,j=r+1;   
	int x=a[p];                   //6 
	//将 <x 的元素交换到左边区域 
	//将 >x	的元素交换到右边区域
	while(true)
	{ //i一上来就要加 j一上来就要减 
		while(a[++i]<x && i<r); //++i就是 7  a[i]=7 比 6 小的话 继续循环 
		while(a[--j]>x);     //上面的找到 7 这个找到 5 
		if(i >= j) break;   //遇到 2 7 不需要交换 ,且 退出 
		swap(a[i],a[j]);     //交换 7 和 5 
	} 
	a[p]=a[j];  //2 和 6 交换  
	a[j]=x;
	return j;
 } 

int main()
{
	//6 7 5 2 5 8
	int arr[]={6,7,5,2,5,8};
	QuickSort(arr,0,5);
	for(int i=0;i<6;i++)
	{
		cout<<arr[i]<<" ";
	}
}

最坏情况下的快速排序

条件1:输入是有序的(升序或降序) :
条件2:总以最大值或最小值为划分基准。
后果:划分的结果总是有一-边没有元素。
如:432 1, 以4为基准划分结果: {1 3 2} 4,
4的右方为空,则时间复杂度为T(0),左方为T(n-1)
在这里插入图片描述

最好情况下的快速排序

■每次以中值为基准划分数组
■T(n)= 2T(n/2) +日(n) =日(r^log n)
■这种情况非常理想化,难以实现。

若随机数
在这里插入图片描述

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
Python算法复习可以从以下几个方面入手: 1.数据结构:Python中常用的数据结构有列表、元组、字典、集合等,需要掌握它们的基本操作,如增删改查等。 2.排序算法:常见的排序算法有冒泡排序、选择排序、插入排序、快速排序、归并排序等,需要了解它们的原理和实现方法。 3.查找算法:常见的查找算法有顺序查找、二分查找、哈希查找等,需要了解它们的原理和实现方法。 4.递归算法:递归是一种常见的算法思想,需要了解递归的原理和实现方法,并能够熟练地运用递归解决问题。 5.动态规划算法:动态规划是一种常见的算法思想,需要了解动态规划的原理和实现方法,并能够熟练地运用动态规划解决问题。 6.贪心算法:贪心算法是一种常见的算法思想,需要了解贪心算法的原理和实现方法,并能够熟练地运用贪心算法解决问题。 7.图论算法:图论是一种常见的算法思想,需要了解图论的基本概念和算法,如最短路径算法、最小生成树算法等。 8.字符串算法:字符串算法是一种常见的算法思想,需要了解字符串匹配算法、编辑距离算法等。 以下是一个例子,演示如何使用Python实现冒泡排序算法: ```python def bubble_sort(arr): n = len(arr) for i in range(n): for j in range(0, n-i-1): if arr[j] > arr[j+1]: arr[j], arr[j+1] = arr[j+1], arr[j] arr = [64, 34, 25, 12, 22, 11, 90] bubble_sort(arr) print("排序后的数组:") for i in range(len(arr)): print("%d" % arr[i]) ```

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

DDouble-

你的鼓励是我最大的动力~

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值