1. 程式人生 > >『嗨威說』演算法設計與分析 - 演算法第二章上機實踐報告(二分查詢 / 改寫二分搜尋演算法 / 兩個有序序列的中位數)

『嗨威說』演算法設計與分析 - 演算法第二章上機實踐報告(二分查詢 / 改寫二分搜尋演算法 / 兩個有序序列的中位數)

本文索引目錄:

一、PTA實驗報告題1 : 二分查詢

  1.1  實踐題目

  1.2  問題描述

  1.3  演算法描述

  1.4  演算法時間及空間複雜度分析

二、PTA實驗報告題2 : 改寫二分搜尋演算法

  2.1  實踐題目

  2.2  問題描述

  2.3  演算法描述

  2.4  演算法時間及空間複雜度分析

三、PTA實驗報告題3 : 兩個有序序列的中位數

  3.1  實踐題目

  3.2  問題描述

  3.3  演算法描述

  3.4  演算法時間及空間複雜度分析

四、實驗心得體會(實踐收穫及疑惑)

 

 

一、PTA實驗報告題1 : 二分查詢

  1.1  實踐題目:

  1.2  問題描述:

      這道題主要闡述,給你一段有序的數字序列(已經排好序了),並給出需要查詢的數Value,利用二分查詢發法找出Value所在的下標,以及查詢過程中所比較的次數。

  1.3  演算法描述:

    二分查詢的定義:

      二分查詢也稱折半查詢(Binary Search),它是一種效率較高的查詢方法。

      折半查詢要求線性表必須採用順序儲存結構,而且表中元素按關鍵字有序排列。

    二分查詢的操作:

      假如以本例的樣例來說,具體操作流程如下:

      首先得到了一段數字序列,存入空間Temp:

 

      將這段Temp陣列送入遞迴中,賦值左右指標為0和3(下標)

 

 

 

 

       求得left與right的中間下標mid = ( l + r) >> 1,mid結果是向下取整的,得注意一下!

 

 

 

      得到中間下表mid之後,對mid所在的數,跟value值,進行比較,如果小了,那麼就送入遞迴(mid +1 ,right)

      如果比value值大了,那麼就送入遞迴(left,mid)區間。

      在此處,temp【mid】 = 2,大於題目給的value,所以,我們送入遞迴(0,1)中,剩下的數字就不管了。

 

      此時發現mid = (0 + 1 )/ 2 = 0, 所在的值與題乾的value值 相等,從此return返回,到此總共比較2次。

      下面是程式碼展示:

#include<bits/stdc++.h>
using namespace std;

int temp,x,ans,cnt,mark[99999999];
int getAns(int l,int r)
{
    cnt++;
    if(l >= r) 
     return l; int mid = (l + r) / 2; if(mark[mid] == x)
     return mid; if(x<=mark[mid]) return getAns(l,mid-1); else
     return getAns(mid+1,r); } int main() { cin>>temp; for(int i = 0;i<temp;i++) cin>>mark[i]; cin>>x; ans = getAns(0,temp-1); if(mark[ans] != x) ans = -1; cout<<ans<<endl; cout<<cnt<<endl; }

  1.4  演算法時間及空間複雜度分析:

    整體演算法上看,二分演算法是不斷折半查詢,不斷折半,所以時間複雜度是以2為底的係數,也就是O(log2 n)的複雜度。

    再加上特判的一些處理,以及輸出的處理,都是在O(1)的複雜度,所以綜合起來,時間複雜度是O(logn)。

    空間複雜度上,使用一個與問題規模一致的Temp陣列空間,並且使用了三個臨時變數,整體來說沒有開闢新的輔助空間。

    所以空間複雜度是O(1)。

 

 

二、PTA實驗報告題2 : 改寫二分搜尋演算法:

  2.1  實踐題目:

  2.2  問題描述:

    第二題是二分演算法的一個小小改進,不過做了一個變化,就是不存在指定數value時,就輸出小於x的最大元素位置i和大於x的最小元素位置j,如果存在這個數,就直接輸出i,j,且i == j。

  2.3  演算法描述:

    首先,讀題:就是不存在指定數value時,就輸出小於x的最大元素位置i和大於x的最小元素位置j,其實在這裡可以發現,當二分遞迴出來的結果l,即使找不到,也是小於x的最大元素位置i,那麼求大於x的最小元素位置j只需要加1即可,對於特別的點,只需要進行一些簡單的特判,就可以過了。

#include<bits/stdc++.h>
using namespace std;

int temp,x,ans,cnt,mark[99999999];
int getAns(int l,int r)
{
    cnt++;
    if(l >= r) 
    return l; int mid = (l + r) / 2; if(mark[mid] == x)
    return mid; if(x<=mark[mid])
   return getAns(l,mid-1); else
return getAns(mid+1,r); } int main() { cin>>temp>>x; for(int i = 0;i<temp;i++) cin>>mark[i]; ans = getAns(0,temp-1); if(mark[ans] != x) { if(ans == 0) cout<<"-1 0"; else cout<<ans<<" "<<ans+1; return 0; } cout<<ans<<" "<<ans<<endl; return 0; }

  2.4  演算法時間及空間複雜度分析:

    演算法複雜度依舊和第一題一樣,本質都是二分搜尋,時間複雜度為O(log n)

    空間複雜度上,依舊用了四個臨時變數並使用一個與問題規模同大的Temp陣列,整體來說沒用使用額外的輔助空間,空間複雜度為O(1)。

 

 

三、PTA實驗報告題3 : 兩個有序序列的中位數:

  3.1  實踐題目:

  3.2  問題描述:

    該題目為:題幹給你兩段有序的數字序列,想辦法使用logn的演算法實現找出兩段合併後的序列內的中位數。

  3.3  演算法描述:

    這道題我一上來的想法思路就是用排序然後取值,但是這樣的時間複雜度就會到O(nlogn),超出了題目所限制的時間複雜度,於是我們需要另外思考一個新的辦法,那就是使用二分搜尋,對不同的兩段數學分別求解中位數:

    ①如果兩段序列的中位數,都是相等的話,那麼中位數即為該數。

    ②如果當第一段的中位數大於第二段的時候,那麼兩端閤中位數一定在第一段中位數前或第二段中位數後,這時只取這兩部分,再繼續進行二分比較

    ③如果當第一段的中位數小於第二段的時候,那麼兩端閤中位數一定在第一段中位數後面或第二段中位數前面,這時只取這兩部分,再繼續進行二分比較

    這裡我需要用一下我同伴做的一張圖,我覺得做的還不錯,特地分享一下:

     AC程式碼:

#include<iostream>
using namespace std;
int n,a[100000],b[100000];
int getAns(int a[],int b[],int n) {
    int l1 = 0, l2 = 0, r1 = n - 1, r2 = n - 1;
    while(l1 < r1 && l2 < r2) {
        int mid1 = (l1 + r1) / 2;
        int mid2 = (l2 + r2) / 2;
        if (a[mid1] == b[mid2])
            return a[mid1];
        if (a[mid1] < b[mid2]) {
            if ((l1 + r1) % 2 == 0) {
                l1 = mid1;
                r2 = mid2;
            }
            else {
                l1 = mid1 + 1;
                r2 = mid2 ;
            }
        }
        else {
            if ((l1 + r1) % 2 == 0) {
                r1 = mid1;
                l2 = mid2;
            }
            else {
                r1 = mid1;
                l2 = mid2 +1;
            }
        }
    };
    if (a[l1] < b[l2])
        cout << a[l1] << endl;
    else
        cout << b[l2] << endl;
}

int main() {
    cin >> n;
    for (int i = 0; i < n; i++) cin >> a[i];
    for (int i = 0; i < n; i++) cin >> b[i];
    getAns(a, b, n);
    return 0;
}

  3.4  演算法時間及空間複雜度分析:

    因為採用二分查詢演算法來尋找中位數而不是排序,所以時間複雜度為O(logn)。

    依舊用了四個臨時變數並使用兩個與問題規模同大的陣列,沒用使用其他的輔助空間,所以空間複雜度為O(1)。

 

 

四、實驗心得體會(實踐收穫及疑惑):

    二分搜尋看起來思路挺簡單的,但是在執行過程中,總會有一些細節上的小錯誤,在這次的實驗過程種也感受到了:

    ① 邊界點的等號是否取到,中間點的位置是否可取

    ② 二分的物件該如何妥當處理

    等等的細節問題,在我日常ACM打題時也有遇到像double浮點數,處理上可能會更麻煩一點點,除此之外,二分還只是最基礎的演算法,更多的還有三分,尺取法等。最核心的思想也就是,分而治之:

    也在一些書籍找到一些關於分治演算法的解釋:

    分治演算法是遞迴的解決問題的一般步驟為:

      (1)找出基線條件,這種條件必須儘可能簡單

      (2)不斷將問題分解(或者說縮小規模),直到符合基線條件。

      (3)按原問題的要求,判斷子問題的解是否就是原問題的解,或是需要將子問題的解逐層合併構成原問題的解。

    分治法的設計思想是,將一個難以直接解決的大問題,分割成一些規模較小的相同問題,以便各個擊破,分而治之。

 

 

如有錯誤不當之處,煩請指