约瑟夫问题(小小算法,真不可笑)

数据结构课的一道作业题如下:

设有n个人围坐在圆桌周围(圆桌会议?),现从第s个人开始报数,数到第m的人出列。(哈,不就是约瑟夫问题吗,这人一定死了!)然后从出列的下一个人开始重新报数,数到m的人又出列,以此类推…按照出列人的顺序,输出他们的序号。

所以约瑟夫站在哪个位置才能不死呢?这是后话。先来看看约瑟夫问题的由来。

这是以夫拉维·约瑟夫(他是公元一世纪时著名历史学家,如果不是他的数学天赋,他是不会活到那一天的)命名的问题。在犹太罗马战争期间,他们41名犹太反抗者困在了罗马人包围的洞穴中。这些反抗者宁愿自杀也不愿被活捉,于是决定围成一个圆圈,并沿着圆圈每隔两个人杀死一个人,直到剩下两个人为止。但是,约瑟夫和一个未被告发的同谋者不希望无谓的自杀,于是他迅速计算出他和其朋友在这个险恶的圆圈中应该站的位置。

约瑟夫的情况可谓是到了危急存亡的关头,如何迅速算出“生还位置”才是关键。

下面,先对于上面那道作业题给出相应的几种解法。
不进行删除操作的数组解法:
#include <iostream>
#include <vector>
#include <ostream>
using namespace std;

vector<int>  JosephusArray(int n, int m, int s) {
    vector<int> ans;
    int* p = new int[n];
    memset(p,0,n);
    int countM = 0;
    int countN = n;
    for(int i = s-1;countN != 0;i = (i+1) % n) {
        if(p[i] == -1) {
            continue;
        }
        countM++;
        if(countM == m) {
            countM = 0;
            p[i] = -1; //出列的位置标记为-1
            ans.push_back(i+1);
            countN--;
        }
    }
    return ans;
} // 数组标记方法,不删除数组元素

ostream & operator << (ostream & o, const vector<int> & arr) {
    for(vector<int>::const_iterator i = arr.begin(); i != arr.end(); ++i) {
        o << *i << " ";
    }
    return o;
}

int main() {
    cout << JosephusArray(4, 2, 1);
    //(n, m, s)
    return 0;
}

数组二:由于上面方法在最后数组的-1元素很多,必须一个一个判断,丧失了数组随机访问速度,所以如果删除掉出列元素,可省去与-1比较的时间。
#include <iostream>
#include <vector>
#include <ostream>

using namespace std;

class Array {
private:
    int* p;
    int size;
public:
    Array(int n):p(new int[n]), size(n) {
        for(int i = 0; i < n; ++i)
            p[i] = i+1;
    }
    ~Array() {
        if(p != NULL) {
            delete [] p;
            size = 0;
        }
    }
    bool deleteElement(const int & i);
    friend vector<int> JosephusArray(int n, int m, int s);
};

bool Array::deleteElement(const int & i) { //i为下标,即第i+1个元素
    if(size < i || i < 0) {
        cout << "下标越界" << endl;
        return false;
    }
    if(i == size-1) {
        size--;
        return true;
    }
    
    for(int j = i; j < size-1; ++j)
        p[j] = p[j+1];
    size--;
    return true;
}

vector<int> JosephusArray(int n, int m, int s) {
    vector<int> ans;
    Array array(n);
    for(int i = (s-1+m-1) % array.size; array.size != 0; i = (i+m-1) % array.size) {
        ans.push_back(array.p[i]);
        array.deleteElement(i);
    }
    return ans;
}

ostream & operator << (ostream & o, const vector<int> & arr) {
    for(vector<int>::const_iterator i = arr.begin(); i != arr.end(); ++i) {
        o << *i << " ";
    }
    return o;
}


int main() {
    cout << JosephusArray(4, 2, 1);
    return 0;
}

有一个小问题:这个代码报浮点异常的错误,网上说可能是a%0的缘故或者gcc链接库版本不匹配。但是,检查了一下,应该没有逻辑问题,array.size==0时,就跳出了。有点玄乎>_<

链表解法:优势:删除的操作更少。劣势,不能随机访问,必须遍历。

这里要自行实现链表的数据结构和成员函数。
Link是节点类
LnkLink是链表
其中,makeCircular()是把单链表建成循环链表的函数。这样,不用取模,自己成环,环环相扣,题目可解。

#include <iostream>
#include <ostream>
#include <vector>
using namespace std;

template <class T>
class Link {
public:
    T data;
    Link<T> *next;
    Link(const T info = 0) {
        data = info;
        next = NULL;
    }
    ~Link(){next = NULL;}
};

template <class T>
class LnkList {
private:
    Link<T> *head, *tail;
    int size;
public:
    LnkList();
    ~LnkList();
    bool isEmpty();
    void clear();
    //int length();
    bool append(const T value);
    Link<T>* getPosPtr(const int p);
    Link<T>* getPosPtr(Link<T>* start, const int p);
    void makeCircular() {
        tail->next = head -> next;
    }
    bool delNode(Link<T> *l);
    void show();
};

template <class T>
LnkList<T>::LnkList() {
    head = tail = new Link<T>; //头结点
    size = 0;
}

template <class T>
LnkList<T>::~LnkList() {
    if(size == 0) {
        delete head;
    }
    else {
        Link<T>* p = head->next;
        while(p != tail) {
            Link<T>* tmp = p;
            p = p -> next;
            delete tmp;
        }
        delete tail;
        delete head;
    }
    
}

template <class T>
bool LnkList<T>::append(const T value) {
    tail->next = new Link<T>(value);
    tail = tail -> next;
    ++size;
    return true;
}

template <class T>
bool LnkList<T>::isEmpty() {
    return size == 0 ? true: false;
}

template <class T>
void LnkList<T>::clear() {
    Link<T>* p = head->next;
    while(p != tail) {
        Link<T>* tmp = p;
        p = p->next;
        delete tmp;
    }
    delete tail;
    head->next = NULL;
    size = 0;
}

template <class T>
Link<T>* LnkList<T>::getPosPtr(const int p) {
    Link<T>* h = head;
    for(int i = 0;i < p; ++i) {
        h = h->next;
    }
    return h;
}

template <class T>
Link<T>* LnkList<T>::getPosPtr(Link<T>* start, const int p) {
    Link<T>* s = start;
    for(int i = 0;i < p; ++i) {
        s = s -> next;
    }
    return s;
}

template <class T>
bool LnkList<T>::delNode(Link<T> *l) {
    Link<T>* t = head->next;
    while(t != tail && t->next != l) {
        t = t -> next;
    }
    t->next = l -> next;
    if(l == head -> next) {
        head -> next = l -> next;
    }
    delete l;
    --size;
    return true;
}

template <class T>
void LnkList<T>::show() {
    Link<T>* p = head->next;
    cout << p->data << " ";
    p = p->next;
    while(p != head->next) {
        cout << p->data << " ";
        p = p ->next;
    }
}

vector<int> JosephusLink(int n, int m, int s) {
    LnkList<int> ll;
    vector<int> res;
    for(int i = 0; i < n; ++i) {
        ll.append(i+1);
    }
    ll.makeCircular();
    Link<int>* p = ll.getPosPtr(s);
    //cout << p -> data;
    //ll.show();
    int i = 0;
    while( !ll.isEmpty()) {
        i++;
        Link<int>* tmp = ll.getPosPtr(p,m-1);
        //cout << tmp -> data <<" " << i <<  endl;
        p = tmp -> next;
        res.push_back(tmp->data);
        ll.delNode(tmp);
    }
    return res;
}

ostream & operator << (ostream & o, const vector<int> & v) {
    for(vector<int>::const_iterator it = v.begin(); it != v.end(); ++it)
        o << *it << " ";
    o << endl;
    return o;
}

int main() {
    cout << JosephusLink(4,5,1);
    
    return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值