舍伍德算法之線性時間選擇問題


一.概念引入

        設A是一個確定性算法,當它的輸入實例為x時所需的計算時間記為tA(x)。設Xn是算法A的輸入規模為n的實例的全體,則當問題的輸入規模為n時,算法A所需的平均時間為1。這顯然不能排除存在x∈Xn使得的2可能性。希望獲得一個隨機化算法B,使得對問題的輸入規模為n的每一個實例均有3。這就是舍伍德算法設計的基本思想。當s(n)與tA(n)相比可忽略時,舍伍德算法可獲得很好的平均性能。

        概率算法的一個特點是對同一實例多次運用同一概率算法結果可能同。舍伍德算法(O(sqrt(n)),綜合了線性表和線性鏈表的優點)總能求的問題的一個正確解,當一個確定性算法在最壞情況和平均情況下差別較大時可在這個確定性算法中引入隨機性將之改造成一個舍伍德算法引入隨機性不是為了消除最壞,而是為了減少最壞和特定實例的關聯性。比如線性表a的查找若是找10(獨一無二),如果在a[0]則為O(1),若是最后一個則O(n),可見時間與輸入實例有關,此時可引入隨機性將之改造成一個舍伍德算法。

        有時候無法直接把確定性算法改造為舍伍德算法,這時候對輸入洗牌。

        下面是洗牌算法源代碼:

import java.util.Random;

public class Main{

    public static void main(String[] args) {
        int a[] = new int[]{1,2,4,5,8};
        /*
         * Collections.shuffle(list)參數只能是list
         */
        myShuffle(a);
        for(int i:a) {
            //犯了個低級錯誤,輸出了a[i],結果數組下標越界異常
            System.out.print(i+" ");
        }
        System.out.println();
    }

    private static void myShuffle(int[] a) {
        
        int len = a.length;
        for(int i=0; i<len; i++) {
            Random r = new Random();
            //直接Random.nextInt(len)提示靜態方法里無法引用
            int j = r.nextInt(len);
            //Collections.swap(list,i,j)必須是list類型
            if(i!=j) {//原來沒加這個條件
                int temp = a[i];
                a[i] = a[j];
                a[j] = temp;
            }
        }
    }
}
View Code

實現如下:

  

二.舍伍德思想解決迅雷2010年校招--發牌

        問題描述:52張撲克牌分發給4人,每人13張,要求保證隨機性。已有隨機整數生成函數rand(),但開銷較大。請編寫函數實現void deal(int a[],int b[],int c[],int d[]),撲克牌用序號0-51表示,分別存在大小為13的a,b,c,d四個數組中,要求盡可能高效。

以下是源代碼:

import java.util.Random;

public class Poker {

    /*
     * 這道題確實不怎么明白,直接初始化后洗牌算法不得了
     * 不過解答只是替換nextInt,直接線性同余了,交換次數也減少了
     * 交換次數是隨機產生的
     */
    //為方便swap和deal函數使用
    static int array[] = new int[52];
    
    public static void main(String[] args) {
        
        for(int i=0; i<array.length; i++) {
            array[i] = i;
        }
        int a[] = new int[13];
        int b[] = new int[13];
        int c[] = new int[13];
        int d[] = new int[13];
        deal(a,b,c,d);
        //這樣輸出方便
        for(int i=0; i<13; i++) {
            System.out.println(a[i]+" "+b[i]+" "+c[i] + " "+d[i]);
        }
        
    }

    private static void deal(int[] a, int[] b, int[] c, int[] d) {
        
        int m = 10;
        int p = 31;//需要素數
        int q = 10;
        Random r = new Random();
        int num = r.nextInt(52);//循環次數
        
        for(int i=0; i<num; i++) {
            m = m*p + q;
            m = m%(51-i);
            int j = 51 - m;
            if(i!=j) {
                swap(array,i,j);
            }
        }
        
        for(int i=0; i<13; i++) {
            a[i] = array[i];
            b[i] = array[i+13];
            c[i] = array[i+26];
            d[i] = array[i+39];
        }
    }
    
    private static void swap(int[] a, int i, int j) {
        //交換是正確的
        int temp = a[i];
        a[i] = a[j];
        a[j] = temp;
    }
}
View Code

代碼實現結果:

三.線性時間選擇算法

     1)隨機划分選擇基准

     對於選擇問題而言,用擬中位數作為划分基准可以保證在最壞的情況下用線性時間完成選擇。如果只簡單地用待划分數組的第一個元素作為划分基准,則算法的平均性能較好,而在最壞的情況下需要O(n^2)計算時間。舍伍德選擇算法則隨機地選擇一個數組元素作為划分基准,這樣既保證算法的線性時間平均性能,又避免了計算擬中位數的麻煩。非遞歸的舍伍德型選擇算法如下:

參考源代碼:

//隨機化算法 線性時間選擇 隨機划分選擇基准
//#include "stdafx.h"
#include "RandomNumber.h"
#include <iostream>
using namespace std;
 
template<class Type>
Type select(Type a[],int l,int r,int k);
 
template<class Type>
Type select(Type a[],int n,int k);
 
template <class Type>
inline void Swap(Type &a,Type &b);
 
int main()
{
    int a[] = {5,7,3,4,8,6,9,1,2};  
    cout<<"原數組為:"<<endl;
    for(int i=0; i<9; i++)  
    {  
        cout<<a[i]<<" ";  
    }  
    cout<<endl;  
    cout<<"所給數組第7小元素為:"<<select(a,9,7)<<endl;  
    return 0;
}
 
//計算a[0:n-1]中第k小元素
//假設a[n]是一個鍵值無窮大的元素
template<class Type>
Type select(Type a[],int n,int k)
{
    if(k<1 || k>n)
    {
        cout<<"請輸入正確的k!"<<endl;
        return 0;
    }
    return select(a,0,n-1,k);
}
 
//計算a[l:r]中第k小元素
template<class Type>
Type select(Type a[],int l,int r,int k)
{
    static RandomNumber rnd;
    while(true)
    {
        if(l>=r)
        {
            return a[l];
        }
 
        int i = l,
            j = l + rnd.Random(r-l+1);//隨機選擇划分基准
 
        Swap(a[i],a[j]);
 
        j = r+1;
        Type pivot = a[l];
 
        //以划分基准為軸做元素交換
        while(true)
        {
            while(a[++i]<pivot);
            while(a[--j]>pivot);
            if(i>=j)
            {
                break;
            }
            Swap(a[i],a[j]);
        }
 
        if(j-l+1 == k)//第k小
        {
            return pivot;
        }
 
        //a[j]必然小於pivot,做最后一次交換,滿足左側比pivot小,右側比pivot大
        a[l] = a[j];
        a[j] = pivot;
 
        //對子數組重復划分過程
        if(j-l+1<k)
        {
            k = k-j+l-1;//右側:k-(j-l+1)=k-j+l-1
            l = j + 1;
        }
        else
        {
            r = j - 1;
        }
    }
}
 
template <class Type>
inline void Swap(Type &a,Type &b)
{
    Type temp = a;
    a = b;
    b = temp;
}
View Code
#include"time.h"
//隨機數類
const unsigned long maxshort = 65536L;
const unsigned long multiplier = 1194211693L;
const unsigned long adder = 12345L;
 
class RandomNumber
{
    private:
        //當前種子
        unsigned long randSeed;
    public:
        RandomNumber(unsigned long s = 0);//構造函數,默認值0表示由系統自動產生種子
        unsigned short Random(unsigned long n);//產生0:n-1之間的隨機整數
        double fRandom(void);//產生[0,1)之間的隨機實數
};
 
RandomNumber::RandomNumber(unsigned long s)//產生種子
{
    if(s == 0)
    {
        randSeed = time(0);//用系統時間產生種子
    }
    else
    {
        randSeed = s;//由用戶提供種子
    }
}
 
unsigned short RandomNumber::Random(unsigned long n)//產生0:n-1之間的隨機整數
{
    randSeed = multiplier * randSeed + adder;//線性同余式
    return (unsigned short)((randSeed>>16)%n);
}
 
double RandomNumber::fRandom(void)//產生[0,1)之間的隨機實數
{
    return Random(maxshort)/double(maxshort);
}
View Code

代碼實現結果:

      2)隨機洗牌預處理

      有時也會遇到這樣的情況,即所給的確定性算法無法直接改造成舍伍德型算法。此時可借助於隨機預處理技術,不改變原有的確定性算法,僅對其輸入進行隨機洗牌,同樣可收到舍伍德算法的效果。例如,對於確定性選擇算法,可以用下面的洗牌算法shuffle將數組a中元素隨機排列,然后用確定性選擇算法求解。這樣做所收到的效果與舍伍德型算法的效果是一樣的。

參考源代碼:

//隨機化算法 線性時間選擇 輸入預處理,洗牌
#include "stdafx.h"
#include "RandomNumber.h"
#include <iostream>
using namespace std;
 
template<class Type>
Type select(Type a[],int l,int r,int k);
 
template<class Type>
Type select(Type a[],int n,int k);
 
template<class Type>
void Shuffle(Type a[],int n);
 
template <class Type>
inline void Swap(Type &a,Type &b);
 
int main()
{
    int a[] = {5,7,3,4,8,6,9,1,2};  
    cout<<"原數組為:"<<endl;
    for(int i=0; i<9; i++)  
    {  
        cout<<a[i]<<" ";  
    }  
    cout<<endl; 
    Shuffle(a,9);//洗牌
    cout<<"洗牌后數組為:"<<endl;
    for(int i=0; i<9; i++)  
    {  
        cout<<a[i]<<" ";  
    }  
    cout<<endl;  
    cout<<"所給數組第7小元素為:"<<select(a,9,7)<<endl;  
    return 0;
}
 
//計算a[0:n-1]中第k小元素
//假設a[n]是一個鍵值無窮大的元素
template<class Type>
Type select(Type a[],int n,int k)
{
    if(k<1 || k>n)
    {
        cout<<"請輸入正確的k!"<<endl;
        return 0;
    }
    return select(a,0,n-1,k);
}
 
//計算a[l:r]中第k小元素
template<class Type>
Type select(Type a[],int l,int r,int k)
{
    while(true)
    {
        if(l>=r)
        {
            return a[l];
        }
        int i = l;
        int j = r+1;
        Type pivot = a[l];
 
        //以划分基准為軸做元素交換
        while(true)
        {
            while(a[++i]<pivot);
            while(a[--j]>pivot);
            if(i>=j)
            {
                break;
            }
            Swap(a[i],a[j]);
        }
 
        if(j-l+1 == k)//第k小
        {
            return pivot;
        }
 
        //a[j]必然小於pivot,做最后一次交換,滿足左側比pivot小,右側比pivot大
        a[l] = a[j];
        a[j] = pivot;
 
        //對子數組重復划分過程
        if(j-l+1<k)
        {
            k = k-j+l-1;//右側:k-(j-l+1)=k-j+l-1
            l = j + 1;
        }
        else
        {
            r = j - 1;
        }
    }
}
 
template <class Type>
inline void Swap(Type &a,Type &b)
{
    Type temp = a;
    a = b;
    b = temp;
}
 
//隨機洗牌算法
template<class Type>
void Shuffle(Type a[],int n)
{
    static RandomNumber rnd;
    for(int i=0; i<n; i++)
    {
        int j = rnd.Random(n-i)+i;
        Swap(a[i],a[j]);
    }
}
View Code
#include"time.h"
//隨機數類
const unsigned long maxshort = 65536L;
const unsigned long multiplier = 1194211693L;
const unsigned long adder = 12345L;
 
class RandomNumber
{
    private:
        //當前種子
        unsigned long randSeed;
    public:
        RandomNumber(unsigned long s = 0);//構造函數,默認值0表示由系統自動產生種子
        unsigned short Random(unsigned long n);//產生0:n-1之間的隨機整數
        double fRandom(void);//產生[0,1)之間的隨機實數
};
 
RandomNumber::RandomNumber(unsigned long s)//產生種子
{
    if(s == 0)
    {
        randSeed = time(0);//用系統時間產生種子
    }
    else
    {
        randSeed = s;//由用戶提供種子
    }
}
 
unsigned short RandomNumber::Random(unsigned long n)//產生0:n-1之間的隨機整數
{
    randSeed = multiplier * randSeed + adder;//線性同余式
    return (unsigned short)((randSeed>>16)%n);
}
 
double RandomNumber::fRandom(void)//產生[0,1)之間的隨機實數
{
    return Random(maxshort)/double(maxshort);
}
View Code

代碼實現結果:

 

 參考文獻:王曉東《算法設計與分析》

        https://blog.csdn.net/liufeng_king/article/details/9038771

                  https://www.cnblogs.com/hxsyl/p/3219621.html


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM