算法设计与分析(期末复习版3)
一、分治法概论
1.设计思想
对规模较大的问题,将其分解为规模较小的子问题,这些子问题相互独立并且与原问题形式相同,递归解决子问题再将各子问题解合并得到原问题的解
2.求解过程
1)分解成若干子问题
2)求解子问题
3)合并子问题
二、分解排序问题
1.快速排序
快速排序基本思想是在待排序的n个元素中任取一个元素作为基准,把这个元素放入最终位置后,整个数据序列被基准一分为二,然后再分别对前后两个序列进行重复操作直到每个子序列都只有一个元素
下面我们给出快速排序的源代码
# include <stdio.h>
void disp(int n, int a[]){ //打印序列
int i;
for(i=0;i<n;i++) //通过n次循环打印出数组在经过排序前和后的序列,从而验证快排的正确与否
printf("%d",a[i]);
printf("\n");
}
int Partition(int a[],int s,int t) //快排算法中的划分序列算法
{
int i=s,j=t;//i指向数组第一个元素,j指向数组最后一个元素,一前一后
int temp=a[s]; //设置一个新的变量系统生成一块新的存储空间存储数组第一个元素作为分割序列的基准
while(i!=j) //设置循环结束条件 i!=j时退出循环
{
while(a[j]>=temp && j>i) // 寻找小于基准的元素
j--; //一直往回走
a[i]=a[j]; //找到比基准小的元素后交换位置,目的是为基准找到自己的位置便于分割序列
while(i<j && a[i]<=temp)
i++;
a[j]=a[i];
}
a[i]=temp; //找打基准的正确位置后将基准从系统开辟的存储空间中取出放在找到的位置后记住其位置用i来记录
return i;
}
void QuickSort(int a[],int s,int t){ //对a[s...t]中的序列进行递增排序
if(s<t)
{
int i=Partition(a,s,t); //进行分割序列
QuickSort(a,s,i-1); //递归数组a被分割后基准前一段序列的元素
QuickSort(a,i+1,t); //递归数组a被分割后基准后一段序列元素
}
}
int main()
{
int n=10;
int a[]={2,5,1,7,10,6,9,4,3,8};
printf("排序前:");
disp(n,a);
QuickSort(a,0,n-1);
printf("排序后:");
disp(n,a);
}
2.归并排序
归并算法基本思想是首先将a[0…n-1]数组看成n个长度为1的子序列,然后将相邻的两个子序列成对归并,再将得到的这些子序列对两两归并,最终得到一个长度为n的有序表。
下面我们给出归并排序的源代码
#include <stdio.h>
#include <stdlib.h> // 使用stdlib.h代替malloc.h
void disp(int n, int a[]) {
int i;
for (i = 0; i < n; i++)
printf("%d ", a[i]);
printf("\n");
}
void Merge(int a[], int low, int mid, int high) {
int *temp;
int i = low, j = mid + 1, k = 0;
temp = (int *)malloc((high - low + 1) * sizeof(int));
if (temp == NULL) {
printf("Memory allocation failed.\n");
exit(-1); // 如果内存分配失败,退出程序
}
while (i <= mid && j <= high) {
if (a[i] < a[j]) {
temp[k++] = a[i++];
} else {
temp[k++] = a[j++];
}
}
while (i <= mid) {
temp[k++] = a[i++];
}
while (j <= high) {
temp[k++] = a[j++];
}
for (k = 0, i = low; i <= high; k++, i++) {
a[i] = temp[k];
}
free(temp); // 释放内存
}
void MergeSort2(int a[], int low, int high) {
if (low < high) {
int mid = low + (high - low) / 2;
MergeSort2(a, low, mid);
MergeSort2(a, mid + 1, high);
Merge(a, low, mid, high);
}
}
int main() {
int n = 10;
int a[] = {2, 5, 1, 7, 10, 6, 9, 4, 3, 8};
printf("排序前:");
disp(n, a);
MergeSort2(a, 0, n - 1);
printf("排序后:");
disp(n, a);
return 0;
}
三、求解查找问题
1.查找最大和次大元素
问题描述:对于给定的含有n个元素的无序序列,求这个序列中的最大元素和次大元素
解决想法:
若这个序列a[low…high]只有一个元素,则最大元素max1=a[low],max2=(-无穷)
若这个序列a[low…high]有两个元素,max1=MAX(a[low],a[high]),max2=MIN(a[low],a[high])
若这个序列a[low…high]有两个元素以上的元素,按中间元素mid将序列一分为二,前一个序列最大元素lmax1,次大元素lmax2,后一个序列最大,次大分别为rmax1,rmax2,若lmax1>rmax1,max1=lmax1,max2=MAX(lmax1,rmax1),否则max1=rmax1,max2=MAX(lmax2,rmax2)
下面我们给出算法代码以供理解
void solve(int a[],int low,int high,int *max1,int *max2)
{
if(low==high) //只有一个元素
{
max1=a[low];
max2=-INF;
}
else if(low==high-1)
{
max1=MAX(a[low],a[high]); //两个元素中最大的那个
max2=MIN(a[low],a[high]); //两个元素中最小的那个
}
else
{
int mid=(low+high)/2;
int lamx1,lmax2;
solve(a,low,mid,lmax1,lmax2);
int rmax1,rmax2;
solve(a,mid+1,high,rmax1,rmax2);
if(lamx1>rmax1)
{
max1=lmax1;
max2=MAX(lmax2,rmax2);
}
else
{
max1=rmax1;
max2=MAX(lmax2,rmax2);
}
}
}
2.折半查找
折半查找又称为二分查找,是一种效率较高的查找方法,但是折半查找要求元素是有序的,这里我们假设是递增有序的。
折半查找思路:
设a[low,high]为查找区间,k为需要查找的元素,首先确定中间元素a[mid]
如果k==a[mid],一次查找完毕
如果k>a[mid],说明k在序列后半部分,此时查找a[mid+1,high],重复以上步骤
如果k<a[mid],说明k在序列前半部分,此时查找a[low,mid-1],重复以上步骤
下面我们给出折半查找的代码
# include<stdio.h>
int BinSearch(int a[],int low,int high,int k)
{
int mid;
if(low<high)
{
mid=(low+high)/2;
if(a[mid]==k)
return mid;
else if(a[mid]>k)
return BinSearch(a,low,mid-1,k);
else
return BinSearch(a,mid+1,high,k);
}
else return -1;
}
int main()
{
int n=10,i;
int a[]={1,2,3,4,5,6,7,8,9,10};
int k=6;
i=BinSearch(a,0,n-1,k);
if(i>0) printf("a[%d]=%d\n",i,k);
else printf("未找到%d元素\n",k);
}
3.查找一个序列中第k小的元素
# include <stdio.h>
int QuickSelect(int a[],int s,int t,int k)
{
int i=s,j=t;
int tmp;
if(s<t)
{
tmp=a[s];
while(i!=j)
{
while(i<j && a[j]>=tmp)
j--;
a[i]=a[j];
while(i<j &&a[i]<=tmp)
i++;
a[j]=a[i];
}
a[i]=tmp;
if(k-1==i) return a[i];
else if(k-1<i) return QuickSelect(a,s,i-1,k);
else return QuickSelect(a,i+1,t,k);
}
else if(s==t && s==k-1)
return a[k-1];
}
int main()
{
int n=10,k;
int e;
int a[]={2,5,1,7,10,6,9,4,3,8};
for(k=1;k<=n;k++)
{
e=QuickSelect(a,0,n-1,k);
printf("第%d小的元素:%d\n",k,e);
}
}
四、求解组合问题
求解最大连续子序列和问题
给定一个有n个元素的序列,求出其中最大连续子序列的和
解决思想:通过中间元素a[mid]将序列一分为二,最大连续子序列和只有三种情况
第一种,该子序列完全落在左半部,即a[0…mid],只要递归求出最大连续子序列和maxleftSum即可
第二种 该子序列完全落在右半部分,即a[mid+1…right],递归求出右边最大连续子序列和即可
第三种,该子序列横跨左右两个部分,这种情况需要在左半部分(包含a[mid])求出最大子序列和,右边一样,最后相加 然后减去a[mid]
求出以上三种情况,然后求其中最大的
代码如下:
# include <stdio.h>
long max3(long a,long b,long c) //求出三个元素中最大值
{
if(a<b)
a=b;
if(a>c)
return a;
else
return c;
}
long maxSubSum(int a[],int left,int rigth) //求最大连续子序列和
{
long maxleftSum,maxrightSum;
long maxleftBorderSum,leftBorderSum;
long maxrightBorderSum,rightBorderSum;
int i,j;
if(left==rigth)
{
if(a[left]>0) return a[left];
else return 0;
}
int mid=(left+rigth)/2;
maxleftSum=maxSubSum(a,left,mid); //求左边最大连续子序列和
maxrightSum=maxSubSum(a,mid+1,rigth); //求右边最大连续子序列和
maxleftBorderSum,leftBorderSum=0;
for(i=mid;i>=left;i--) //求左边加上a[mid]元素构成的序列构成的最大和
{
leftBorderSum+=a[i];
if(leftBorderSum>maxleftBorderSum)
maxleftBorderSum=leftBorderSum;
}
maxrightBorderSum,rightBorderSum=0;
for(j=mid+1;j<=rigth;j++) //求出a[mid]右边元素构成的序列构成的最大和
{
rightBorderSum+=a[j];
if(rightBorderSum>maxrightBorderSum)
maxrightBorderSum=rightBorderSum;
}
return max3(maxleftSum,maxrightSum,maxleftBorderSum+maxrightBorderSum);//返回三个值中的最大值
}
int main()
{
int a[]={-2,11,-4,13,-5,-2},n=6;
int b[]={-6,2,4,-7,5,3,2,-1,6,-9,10,-2},m=10;
printf("a序列的最大连续子序列和为:%d\n",maxSubSum(a,0,n-1));
printf("b序列的最大连续子序列和为:%d\n",maxSubSum(b,0,m-1));
}