第4章入门篇(2)——算法初步(6)
4.6 two pointers
4.6.1 什么是 two pointers
two pointers是算法编程中一种编程技巧。two pointers的思想十分简洁,但却提供了非常高的算法效率。
第一个例子:给定一个递增的正整数序列和一个正整数M,求序列中的两个不同位置的数a和b,使它们的和恰好为M,输出所有满足条件的方案。
本题最直接的想法就是使用二重循环:
for(int i=0;i<n;i++){
for(int j=i+1;j<n;j++){
if(a[i]+a[j]==M){
printf("%d %d\n",a[i],a[j]);
}
}
}
使用two pointers思想解答:
令下标i的初值为0,下标j的初值为n-1,即令i、j分别指向序列的第一个元素和最后一个元素,接下来根据a[i]+a[j]与M的大小来进行下面三种选择,使i不断向右移动、使j不断向左移动,直到i>=j成立。
①如果满足a[i]+a[j]==M说明找到了其中一组方案。令i=i+1、j=j-1(即令i向右移动,j向左移动)
②如果满足a[i]+a[j]>M。令j=j-1(令j向左移动)
③如果满足a[i]+a[j]<M。令i=i+1(令i向右移动)
while(i<j){
if(a[i]+a[j]==m){
printf("%d %d\n",i,j);
i++;
j++;
}else if(a[i]+a[j]<m){
i++;
}else{
j--;
}
}
two pointers的思想充分利用了序列递增的性质,以很浅显的思想降低了复杂度。
第二个问题: 序列合并问题:假设有两个递增序列A与B,要求将它们合并为一个递增序列C。
思路:①若A[i]<B[j],说明A[i]是当前序列A与序列B的剩余元素中最小的那个,因此把A[i]加入序列C中,并让i加1
②若A[i]>B[j],说明B[j]是当前序列A与序列B的剩余元素中最小的那个,因此把B[j]加入序列C中,并让j加1
③若A[i]=B[j],则任意选一个加入到序列C中,并让对应下标加1
int merge(int A[],int B[],int C[],int n,int m){
int i=0,j-0,index=0;
while(i<n&&j<m){
if(A[i]<=B[j]{
C[index++]=A[i++];
}else{
C[index++]=B[j++];
}
}
while(i<n) C[index++]=A[i++];
while(j<n) C[index++]=A[j++];
return index;
}
two pointers是利用问题本身与序列的特性,使用两个下标i、j对序列进行扫描,以较低的复杂度解决问题。
4.6.2 归并排序
原理:将序列两两分组,将序列归并为[n/2]个组,组内单独排序;然后将这些组再两两归并,生成[n/4]个组,组内再单独排序。以此类推,直到剩下一个组为止。
核心:如何将两个有序序列合并为一个有序序列
(1)递归实现
只需要反复将当前区间[left,right]分为两半,对两个子区间[left,mid]与[mid+1,right]分别递归进行归并排序,然后将两个已经有序的子区间合并为有序序列即可。
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); //将左子区间[left,mid]归并排序
mergeSort(A,mid+1,right); //将右子区间[mid+1,right]归并排序
merge(A,left,mid,mid+1,right); //将左子区间和右子区间合并
}
}
(2)非递归实现
思路:令步长step的初值为2,然后将数组中每step个元素作为一组,将其内部进行排序(即把左step/2个元素与右step/2个元素合并,而若元素个数不超过step/2,则不操作);再令step乘2,重复上面的操作,直到step/2超过元素个数n
//非递归实现一
void mergeSort(int A[]){
for(int step=2;step/2<=n;step*=2){
for(int i=1;i<=n;i+=step){
int mid=i+step/2-1;
if(mid+1<=n){
merge(A,i,mid,mid+1,min(i+step-1,n));
}
}
}
}
//非递归实现二
void mergeSort(int A[]){
for(int step=2;step/2<=n;step*=2){
for(int i=1;i<=n;i+=step){
sort(A+i,A+min(i+step,n+1));
}
}
}
4.6.3 快速排序
首先引入一个问题:对一个序列A[1]、A[2]、……A[n],调整序列中元素的位置,使得A[1]的左侧所有元素都不超过A[1]、右侧所有元素都大于A[1]。
思想就是two pointers
int Partition(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;
}
快速排序思路:①调整序列中的元素,使当前序列最左端的元素在调整后满足左侧所有元素均不超过该元素、右侧所有元素均大于该元素
②对该元素的左侧和右侧分别递归进行①的调整,直到当前调整区间的长度不超过1.
void quickSort(int A[],int left,int right){
if(left<right){
int pos=Partition(A,left,right);
quickSort(A,left,pos-1); //对左子区间递归进行快速排序
quickSort(A,pos+1,right); //对右子区间递归进行快速排序
}
}
随机选择主元的方法
int randPartition(int A[],int left,int right){
int p=(round(1.0*rand()/RAND_MAX*(right-left)+left));
swap(A[p],A[left]);
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;
}
备注:随机函数的使用
随机数据的函数,需要添加stdlib.h头文件与time.h头文件。首先再main函数开头加上srand((unsigned)time(NULL));,这个语句将生成随机数种子。然后,在需要使用随机数的地方使用rand()函数。
#include <iostream>
#include <stdlib.h>
#include <time.h>
using namespace std;
int main(){
srand((unsigned)time(NULL));
for(int i=0;i<10;i++){
printf("%d ",rand());
}
return 0;
}
rand()函数只能生成[0,RAND_MAX]范围内的整数,如果想要输出给定范围[a,b]内的随机数,需要使用rand()%(b-a+1)+a。
#include <iostream>
#include <stdlib.h>
#include <time.h>
using namespace std;
int main(){
srand((unsigned)time(NULL));
for(int i=0;i<10;i++){
printf("%d ",rand()%2); //[0,1]
}
printf("\n");
for(int i=0;i<10;i++){
printf("%d ",rand()%5+3); //[3,7]
}
return 0;
}
#include <iostream>
#include <stdlib.h>
#include <time.h>
using namespace std;
int main(){
srand((unsigned)time(NULL));
for(int i=0;i<10;i++){
printf("%d ",(int)(round(1.0*rand()/RAND_MAX*50000+10000)); //[10000,60000]
}
return 0;
}