1. 程式人生 > >《演算法設計與分析》迴圈右移合併陣列

《演算法設計與分析》迴圈右移合併陣列

題目:有已經排好序的陣列arr[0:k-1]和arr[k:n-1],現在將兩個數組合併成一個大的有序陣列。要求:時間複雜度最壞的情況下為0(n),空間複雜的度為o(1)。

之前看過《演算法筆記》上面將遞迴時,有個two pointers思想,講的正好是怎麼將兩個有序陣列在時間複雜度為O(n)的情況下合併成一個大的有序陣列。但是這種思想所用到的空間複雜度為o(n),本題中就不適用。

筆者的想法是選擇arr[i:k-1]和arr[j:n-1](初始時i=0,j=k)中最小的數,如果該數在arr[j:n-1]中,那麼該數必定時arr[j],交換arr[i]與arr[j],則(如果)該數在arr[i:k-1]中,直接I++即可。這種想法,由於還要將arr[j:n-1]在進行排序,最壞情況下的時間複雜度相比於o(n)增加到o(nlogn)。筆者想不出了什麼高效的演算法,於是偷偷看解答書。?

《演算法設計與分析習題解答》中提供了一種思想----->迴圈右移陣列方法。書中講的這個方法的大概思想如下:在arr[0:k-1]中找出arr[i](i範圍是0~k-1)在arr[k:n-1]中的位置(二分查詢法),記為pos,在計算pos到k的距離,則從i到pos一直往復迴圈,次數為距離。

程式碼如下可能不好懂,但一定要認真理解。

#include <bits/stdc++.h>

using namespace std;
//演算法實現:用o(1)的空間複雜度,時間複雜度最壞為o(n)的情況下
//合併同一個陣列的兩個排好序的部分,將其變成一個大的有序陣列
const int maxn = 1e5+10;
int arr[maxn];
int binarySearch(int x,int left,int right)
{
    int mid;
    while(left <= right)
    {
        mid = (right-left)/2+left;
        if(arr[mid] == x) return mid;
        if(arr[mid] > x) right = mid-1;
        else
            left = mid+1;
    }
    if(arr[mid] == x)
        return mid;
    else
        return mid-1;
}
void shiftRight(int k,int s,int e)  //k是移動次數,s是開始迴圈移動的地方,e是結束迴圈移動的地方
{
    for(int i=0; i<k; i++)
    {
        int tmp = arr[e];
        for(int j=e; j>s; j--)
            arr[j] = arr[j-1];
        arr[s] = tmp;
    }
}
void mer(int k,int n)
{
    int i=0,j=k;
    while(i<j && j<n)
    {
        int pos = binarySearch(arr[i],j,n-1);
        shiftRight(pos-j+1,i,pos);
        j = pos+1;
        i = i + pos - j +2;
    }
}
int main()
{
    int n,i,k;
    cin>>n;
    for(i=0; i<n; i++)
    {
        cin>>arr[i];
        if(arr[i] < arr[i-1])
        {
            k = i;
        }
    }
    mer(k,n);
    for(i=0; i<n; i++)
        cout<<arr[i]<<" ";
    return 0;
}