算法很美02递归和排序

转载

c++:https://blog.csdn.net/qq_38265137/article/details/85628925

习题:https://blog.csdn.net/qq_44391957/article/category/8657987

java:https://blog.csdn.net/qq_38265137/article/details/85628925

目录

02递归和排序

练习:

1.求最大公约数

2.插入法排序递归

3.汉诺塔问题

4.二分查找的递归解法

5.希尔排序

解题实战:

【1】小白上楼梯 

【2】旋转数组的最小数字

【3】在有空字符串的有序字符串数组中查找

【4】最长连续递增子序列

【5】高效求a的n次幂算法


02递归和排序

递归的要点

找变化,找重复,找出口,子问题要与原问题有同样的结构。

练习:

求阶乘

打印i--j

数组求和

翻转字符串

1.求最大公约数

辗转相除法, 又名欧几里德算法(Euclidean algorithm),是求最大公约数的一种方法。它的具体做法是:用较大数除以较小数,再用出现的余数(第一余数)去除除数,再用出现的余数(第二余数)去除第一余数,如此反复,直到最后余数是0为止。如果是求两个数的最大公约数,那么最后的除数就是这两个数的最大公约数。(采自百度词条)

假设有两个数x和y,存在一个最大公约数z=(x,y),即x和y都有公因数z,
那么x一定能被z整除,y也一定能被z整除,所以x和y的线性组合mx±ny也一定能被z整除。(m和n可取任意整数)

对于辗转相除法来说,思路就是:
若x>y,设x/y=n余c,
则x能表示成x=ny+c的形式,
将ny移到左边就是x-ny=c,由于一般形式的mx±ny能被z整除,
所以等号左边的x-ny(作为mx±ny的一个特例)就能被z整除,
即x除y的余数c也能被z整除。
原文链接:https://blog.csdn.net/hello_woo/article/details/79293235

/* 辗转相除法求最大公约数 */
#include <iostream>
using namespace std;
/*m是较大数,n是较小数*/    //最大公约数greatest common divisor
int gcd(int m, int n){
    if(n == 0){
        return m;
    }
    else{
        return gcd(n, m%n);
    }
}
int main() {
    cout << gcd(36,24) <<endl;
}

性能分析: 

m%n < (m/2)

所以 n%(m%n)< (n/2),每两次进行折半计算。

2*lgn,O(lgn)

2.插入法排序递归

/*插入排序改递归*/
#include <iostream>
using namespace std;

void insertSort(int* arr , int Index)
{
    if(Index == 0)   //递归结束的条件
        return ;
    insertSort(arr , Index - 1);

    int num = arr[Index]; //保留当前的数
    int index = Index -1 ;
    
    while(num < arr[index])
    {    
        /*向后移一位*/
        arr[index + 1] = arr[index];
        index --;
    }
    /*元素k应在的位置*/
    arr[index + 1] = num;

}
int main()
{
    int arr[4] = {1,2,3,4};
    insertSort(arr , 3);
    for(auto i : arr)
         cout<< i << endl;
    return 0;
}

3.汉诺塔问题

1~N从A移动到B,C作为辅助等价于:

1、1~(N-1)从A移动到C,B为辅助

2、把N从A移动到B

3、1~(N-1)从C移动到B,A为辅助(最终结果都是移动到B上,所以子问题形式与原问题相同)

#include <iostream>
using namespace std;
#define MAX 100
/*汉诺塔*/
void Hanoi(int N, string from, string to, string help){
    if(N == 1){
        cout << "move " << N << " from " << from << " to " << to << endl;
        return;
     }
    else{
        Hanoi(N-1, from, help, to);
        cout << "move " << N << " from " << from << " to " << to << endl;
        Hanoi(N-1, help, to, from);
    }
}
 
int main() {
    Hanoi(5, "A", "B", "C");
}

算法性能分析:

递归关系:T(n) = 2T(b-1) + O(1)

结果:O(2^n)

4.二分查找的递归解法

三个子问题:

左边找

中间比

右边找

/*二分查找*/
int binarysearch(int arr[], int low, int high, int key){
    if(low > high)
        return -1;//未找到
    int mid = low + ( (high - low) >> 1 );
    int val = arr[mid];
    if(key > val){
        binarysearch(arr, mid+1, high, key);
    }
    else if(key < val){
        binarysearch(arr, low, mid-1, key);
    }
    else{
        return 1;//找到
    }
}

5.希尔排序

一趟一个增量,每个增量对应于N/inerval组,每组内采用插入排序

/*希尔排序*/
void ShellSort(int arr[], int K){
    int interval;//增量
    //不断缩小增量
    for(interval=K/2; interval>0; interval=interval/2){
    //增量为interval的插入排序
        int i;//i表示组
        for(i=interval; i<K; i++){//遍历每一组
            int x = arr[i];
            int index = i - interval;
            while(index>-1 && x < arr[index]){
            /*将比当前元素大的数后挪interval位*/
                arr[index + interval] = arr[index];
                index -= interval;
            }
            /*元素k应在的位置*/
            arr[index + interval] = x;
        }
    }
}

测试代码:便于观察规律。

//希尔排序
#include <iostream>
using namespace std;
void shellSort(int *arr, int k)
{
    int interval; //增量
    //不断缩小增量

    for(interval = k/2 ;interval>0; interval = interval /2 )
    {
        //增量为interval的插入排序
        int i; //i表示组
        for(i = interval ; i < k; i++) {//遍历一组
            int x = arr[i];   //保存当前的数据
            int index = i - interval; //保存需要对比元素的下标
            while(interval > -1 && x <arr[index] )
            {
                //将比当前元素大的数后移动interval位
                arr[index + interval ] = arr[index] ;
                index -= interval;
            }

           arr[index + interval] = x;
          cout << "i组为:"<< i << endl;
           for (int i = 0; i < k; i++)
            cout << arr[i] << " ";
           cout << endl;

        }
           cout << "增量为:"<< interval << "********"<<endl;
           for (int i = 0; i < k; i++)
            cout << arr[i] << " ";
           cout << endl;
    }
}
int main()
{
    int arr[10] = { 6, 4, 8, 9, 2, 3, 1, 15 ,18 ,85} ;
    shellSort(arr, 10);
    return 0;
}

解题实战:

【1】小白上楼梯 

小白正在上楼梯,楼梯有n阶台阶,小白一次可以上1阶, 2阶或3阶,实现-个方法,计算小白有多少种走完楼梯的方式。

思路:就是到达最后一个台阶之前,到n-1个台阶的方式,n-2,n-3台阶之和

/*小白上楼梯*/
int smallwhite(int n){//n表示台阶数
    if(n==0) return 1;   //注意这里的情况传入的是3,对应的就是直接3个台阶,所以就是1
    if(n==1) return 1;
    if(n==2) return 2;
    return smallwhite(n-1)+smallwhite(n-2)+smallwhite(n-3);//这里是之和
}

【2】旋转数组的最小数字

把一个数组最开始的若干个元素搬到数组的末尾,我们称之为数组的旋转。输入一个递增排序的数组的一个旋转,输出旋转数组的最小元素。例如数组{3,4,5,1,2}为{1,2,3,4,5}的一个旋转,该数组的最小值为1.

#include <iostream>
using namespace std;
/*旋转数组的最小数字(二分法)*/
int arrmin(int* arr, int begin, int end){
/*最小的那个数一定在无序的一边*/
    if(arr[begin] < arr[end]) return arr[begin];//没有旋转的情况

    while(begin + 1 < end){  //说明还没有结束
        int mid = begin + ( (end - begin) >> 1);
        //要么左侧有序,要么右侧有序
        //下面这行代码有个bug 假如数组为 1 1 1 0 1 这种数组应该在程序的入口检测一下
        //假如左侧和中间相等了  那么就应该用顺序查找最小值了。
       if(  arr[mid] >=   arr[begin]){//右边无序
                begin = mid;
       }
       else{//左边无序
               end = mid;
       }
     }
     return arr[end];
}
int main() {
    int arr[5] = {3,4,5,1,2};
    cout << arrmin(arr,0,4)<< endl;
    int arr2[6] = {1,1,1,0,0,1};
    cout << arrmin(arr2,0,5)<< endl;
    return 0;
}

【3】在有空字符串的有序字符串数组中查找

有个排序后的字符串数组,其中散布着一 些空字符串,编写一个方法,找出给定字符串(肯定不是空字符串)的索引。

#include <iostream>
#include <string>

using namespace std;
/*在有空字符串的有序字符串数组中查找*/
int indexOf(string str[], string p, int length){
    int begin = 0;                      //因为是二分变体,所以先是开始
    int end = length - 1;               //终点,都写出来
    while(begin <= end){
        int indexOfmid = begin + ( (end - begin) >> 1 ); //求中间值
        while(str[indexOfmid] == ""){  //如果找到了"" 就向右移动一个,直到遇到不是空的
            indexOfmid ++;
            if(indexOfmid > end)  //如果越界了,就返回-1。错误了
                return -1;
        }
        if(str[indexOfmid].compare(p) > 0) //和目标做对比
            end = indexOfmid - 1;
        else if(str[indexOfmid].compare(p) < 0)
            begin = indexOfmid + 1;
        else
            return indexOfmid;
    }
    return -1;
}
int main() {
    string str[8] = {"a", "", "ac", "", "ad", "b", "", "ba"} ;
    cout << indexOf(str, "ad", 8);
}

【4】最长连续递增子序列

 (1,9,2,5,7,3,4,6,8,0)中最长的递增子序列为(3,4,6,8)。
思路:设置一前一后两个指针,前指针先不动,后指针向后扫描,如果是递增的则接着扫描,直到扫到第一个非递增元素,记录此时前后指针之间的距离,记为length,并保留前指针的位置信息。然后重置前后指针,使得前指针指向刚才扫描到的那个非递增元素,重复上面工作直到结束,找到length最长的那个前指针位置信息并输出。

#include <iostream>
using namespace std;
int maxSizeInArrr(int *arr , int n )
{
    int front = 0 , end = 1;
    int length =1 , max_len = 0 , max_index = 0;
    int i;
    for(i = 0; i < n; ++i)
    {

       if(end < n && arr[end]  > arr[i])
       {
           end++;
           length++;
       }else{
          if(length > max_len )
          {
              max_len = length;
              max_index = front;
          }
          front = end;
          end++;
          length = 1;
       }
    }
    for(i = 0; i < max_len; ++i)
        cout << arr[max_index + i] <<" ";
    cout << endl;
    return max_len;
}
int main()
{
    int arr[15] = {7,8,9,4,5,6,7,1,2,3,4,5,6,7,8};
    cout<<"maxsize:"<< maxSizeInArrr(arr, 15)<< endl;

    return 0;
}

【5】高效求a的n次幂算法

/*快速设计一个高效的求a的n次幂的算法*/
int pow(int a, int n){
    if(n == 0) return 1;
    int res = a;
    int ex = 1;
    //能翻
    while( (ex << 1) <= n ){
        //翻
        res  = res * res;
        //指数*2
        ex <<= 1;
    }
    //不能翻
    //差n-ex方没有乘到结果里面
    return res * pow(a, n-ex);
}

 

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值