辟谣!单向链表查找中点【附正确方法】

来源:互联网 发布:ubuntu安装pychom 编辑:程序博客网 时间:2024/05/21 22:54

听说查找中点可以达到O(0.5n)

该方法的实现是:

  1. 2个指针从链表的头开始。
  2. 一个指针每步+1,一个指针每步+2
  3. 然后跑的快的指针到链表尾部的时候,那个慢一点的指针就是中点了

一般的算法是第一次遍历得到长度,第二次遍历取出中点。
然后就有人觉得这个方法比传统的遍历2次更优。 ????
首先O(0.5n)和O(1.5n)的这个说法就有问题。
但今天我只讨论2个方法到底孰优孰劣。
废话不说先上结果:

0.00027776 s : find_mid 传统方法
0.000276798 s : find_mid_1
2.81981 s : find_mid 提高指针取得下一个元素的代价
2.81391 s : find_mid_1

#include <iostream>#include <thread>#include <sys/timeb.h>#include <time.h>#include <windows.h>using namespace std;class list {private:    list *next = nullptr;public:    int m_d;    list* add(list* p) { next = p; return p;  }    list* get_next() { Sleep(delay); return next; }    static int delay;};int list::delay = 0;list* find_mid(list* ptr){    int len = 0;    list *p_start = ptr;    while (true)    {        if (!p_start)            break;        len++;        p_start = p_start->get_next();    }    len /= 2; p_start = ptr;    while (len--)    {        p_start = p_start->get_next();    }    return p_start;}list* find_mid_1(list* ptr){    list *p_s1 = ptr, *p_s2 = ptr;    while (p_s2=p_s2->get_next())    {        p_s2 = p_s2->get_next();        if (!p_s2)            break;        p_s1 = p_s1->get_next();     }    return p_s1;}#define cal_time(fun,ptr)   {\    QueryPerformanceCounter(&startCount);\    fun(ptr);\    QueryPerformanceCounter(&endCount);\    double elapsed = (double)(endCount.QuadPart - startCount.QuadPart) / freq.QuadPart;\    cout << elapsed<<" s : "<< #fun   << endl;}int main(){    int c = 1*1000;    list * start = new list,*p;    p = start;    while (c--)    {        p = p->add(new list);        p->m_d = c;    }    LARGE_INTEGER startCount;    LARGE_INTEGER endCount;    LARGE_INTEGER freq;    QueryPerformanceFrequency(&freq);    _ASSERT(find_mid_1(start) == find_mid(start));    cal_time(find_mid, start);    cal_time(find_mid_1, start);    list::delay = 1;    cal_time(find_mid, start);    cal_time(find_mid_1, start);    return 0;}

分析

其实这2种方法都是O(n),使用大O表示法不够精确,这种简单的算法直接数一下next的调用次数就可以知道都是1.5*len个get_next().find_mid_1 唯一的优势就是少用了一个 int len。所以性能提升实在少的可怜。

说了这么多不给出个好点的方案说不过去啊

思路:其实也是用到了2个指针,其中一个走过的路程是另一个的1/2。但千万不要使用一个+1另一个+2。而是在前一个指针遍历的时候保存下来给另一个指针。

list* find_mid_2(list* ptr){    list *p_s1 = ptr, *p_s2 = ptr, *tmp= ptr;    int d = 1,t=1;    int i = 0;    while (true)    {        tmp = p_s2;        for (int c=0; i < d; i++,c++)        {            p_s2 = p_s2->get_next();            if (!p_s2)            {                int len = c / 2;                while (len--)                    {                        p_s1 = p_s1->get_next();                    }                return p_s1;            }        }        d *= 2;        p_s1 = tmp;    }    return p_s1;}

来看效果吧

最好情况
0.608294 s : find_mid
0.612391 s : find_mid_1
0.404288 s : find_mid_2

最差情况
0.604105 s : find_mid
0.600299 s : find_mid_1
0.502341 s : find_mid_2

我估算需要(1,1.25)n个get_next()操作。

0 0
原创粉丝点击