斯坦福 算法1 第一周作业

斯坦福 Algorithms: Design and Analysis 1 第一周作业

来自斯坦福网站的Algorithms: Design and Analysis,与目前coursera上的版本内容没有变化,不过时间安排略有不同。

1. Problem Set 1

在这里插入图片描述
三路归并排序的复杂度。和两路没有啥区别,依然要递归 l o g n logn logn层,只不过log的底数从2变成3。每一层的计算复杂度依然是 O ( n ) O(n) O(n)
在这里插入图片描述
根据大O表示法的定义写一下即可。

在这里插入图片描述
这个也可以先用大O表示法的定义写一下。发现给的条件与目标并不能直接得到。于是思考其目标成立的条件。

先看选项1,如果 f ( n ) ≤ g ( n ) f(n) \leq g(n) f(n)g(n),因此 2 f ( n ) ≤ 2 g ( n ) 2^{f(n)} \leq 2^{g(n)} 2f(n)2g(n),也就是 2 f ( n ) = O ( 2 g ( n ) ) 2^{f(n)}=O(2^{g(n)}) 2f(n)=O(2g(n))。于是选项1正确。另外可以举一反例,比如 f ( n ) = 2 n , g ( n ) = n f(n) = 2n, g(n) = n f(n)=2n,g(n)=n,此时无法得到 2 f ( n ) = O ( 2 g ( n ) ) 2^{f(n)}=O(2^{g(n)}) 2f(n)=O(2g(n))

在这里插入图片描述
一共需要比较k-1次,每一次比较的次数为2n,3n,…,kn,因此其总数量级为 n k 2 nk^{2} nk2

在这里插入图片描述
比较大小。指数比较多的可以取log之后比较。

2 Optional Theory Problems

在这里插入图片描述
问题1,在限定比较次数内得到第二大的数。由于其比较次数只比n大一点,所以遍历两次的做法显然不行。于是考虑本周内容中的归并方式。

step1,把每次归并数组的操作改为比较两个小数组的最大值,并保留较大的一个。因此比较次数为n/2+n/4+…+2,相加得到n-1。此时得到数组最大值。

step2,回溯与最大值比较过的数字中的最大值,它就是数组的第二大的数。(显然第二大的数一定与最大值比较过,不然无法得出最大数。)回溯的比较次数与递归函数的层数相同,即 l o g n − 1 logn-1 logn1。于是满足比较次数的要求。

C++ 实现:

int get_second(vector<int> v) {
    int n = v.size();
    vector<int> record(n); //记录最大值在当前位置是否经过转换
    
    for(int gap = 1; gap < n; gap*=2) { //非递归形式的归并,每次将最大值放在当前小数组最前方
        for(int i = 0; i < n/gap/2; i++){ //比如最后一层比较即每两个数相比,0,1|2,3|4,5|6,7,比较后每两个数较大值置换到0,2,4,6位置
            if(v[i*gap*2] < v[i*gap*2+gap]) {//0,1,2,3|4,5,6,7 上一层得到每4个数的最大值,也放在0和4的位置
                swap(v[i*gap*2],v[i*gap*2+gap]);//最终得到的全局最大值在位置0
                record[i*gap*2] = 1;  //record数组表示当前位置的数是否经过位置变化而来
            }
        }
    }
    int a = v[n/2];
    int index = 0;
    for(int gap = n/2; gap >= 1; gap/=2) { //根据record数组的记录回溯与最大比较过的数
        a = max(v[index+gap],a);//可以证明这么做的正确性
        if(record[index])//假如最大值所在位置的record是0,说明其一直在当前位置
            index += gap;//假如最大值位置的record是1,那么一定有最大值造成的部分(其他值的变换可能事先让其为1,但既然目前其值为max,说明最大值一定是变换过来的)。
        //而最大值根据record回溯回上一次比较的位置之后不会再与当前位置的record再有联系。
    }
    return a;
}

实现的代码虽然不复杂但貌似被我搞得过程很复杂。仔细想了一下应该能证明这么做的正确性。

问题2,相对简单。要求用 O ( l o g n ) O(logn) O(logn)的复杂度找到驼峰型数组中的最大值。用二分查找,每次判断mid位置与左右两个数之间的关系来判断边界的变换。相对简单,C++实现如下:

int get_max(vector<int> v) {
    int n = v.size();
    int left = 0, right = n-1;
    int mid;
    while(left <= right) {
        mid = (left+right);
        if(v[mid] > v[mid-1]) {
            if(v[mid] > v[mid+1])
                return v[mid];
            else
                left = mid+1;
        }else
            right = mid-1;
    }
    return 0;
}

问题3,在有序数组中判断是否存在一个下标i令A[i]=i。依然是用二分查找,因为是有序数组,因此边界变化也好判断。

bool get_i(vector<int> v) {
    int n = v.size();
    int left = 0, right = n-1;
    int mid;
    while(left <= right) {
        mid = (left+right);
        if(v[mid] == mid) {
            return true;
        }else if (v[mid] > mid)
            right = mid-1;
        else
            left = mid+1;
    }
    return false;
}

3 Programming Question 1

在这里插入图片描述
实现课程中的逆序数计算。代码如下:

#include <iostream>
#include <vector>
#include <fstream>
#include <sstream>
#include <string>

using namespace std;

long long count(vector<int>& v1,vector<int>&v2, int begin, int end, bool flag) {
    // cout << begin << ' '<< end << endl;
    long long res = 0;
    if(begin == end-1) {
        if(flag)
            v2[begin] = v1[begin];
        else
            v1[begin] = v2[begin];
        return res;
    }
    else {
        int mid = begin+(end-begin)/2;
        res += count(v1,v2,begin,mid,!flag);
        res += count(v1,v2,mid,end,!flag);
        int i = begin, j = mid, i2 = begin;
        while(i < mid && j < end) {
            if(flag) {
                if(v1[i] <= v1[j]) {
                    v2[i2++] = v1[i++];
                }else {
                    v2[i2++] = v1[j++];
                    res += mid-i;
                }
            }else {
                if(v2[i] <= v2[j]) {
                    v1[i2++] = v2[i++];
                }else {
                    v1[i2++] = v2[j++];
                    res += mid-i;
                }
            }
        }
        if(i < mid)
            while(i < mid) {
                if(flag) v2[i2++] = v1[i++];
                else v1[i2++] = v2[i++];
            }
        if(j < end)
            while(j < end) {
                if(flag) v2[i2++] = v1[j++];
                else v1[i2++]  = v2[j++];
            }
        return res;
    }
}

vector<int> readData(string fileName) {
    cout << "read data from "<< fileName <<endl;
    ifstream input(fileName);
    string line;
    vector<int> res;
    int tmp;
    if(input) {
        while(getline(input,line)) {
            istringstream ist(line);
            ist >> tmp;
            // cout << tmp << endl;
            res.push_back(tmp);
        }
    }
    return res;
}
int main() {
    vector<int> test = readData("IntegerArray.txt");
    // vector<int> test = {8,7,6,5,4,3,2,1,1,2,3};
    vector<int> v2(test);
    long long res = count(test,v2,0,test.size(),true);
    cout << res <<endl;
    return 0;
}

归并排序的一大问题是归并数组的处理。因为不可能只用一个数组做inplace的归并,因此至少需要两个数组。比较naive的做法是每次递归调用都生成一个新的小数组返回,这样做比较废空间与开辟数组的时间,因此可以使用两个数组将其来回利用,减少损耗。具体可以看代码,使用了一个布尔值来表示当前函数是将v2中的小数组归并到v1还是将v1中的数归并到v2中。

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值