查找鏈表中點

據說查找中點能夠達到O(0.5n)

該方法的實現是:ios

  1. 2個指針從鏈表的頭開始。
  2. 一個指針每步+1,一個指針每步+2
  3. 而後跑的快的指針到鏈表尾部的時候,那個慢一點的指針就是中點了

通常的算法是第一次遍歷獲得長度,第二次遍歷取出中點。
而後就有人以爲這個方法比傳統的遍歷2次更優。 ????
首先O(0.5n)和O(1.5n)的這個說法就有問題。
但今天我只討論2個方法到底孰優孰劣。
廢話不說先上結果:c++

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。而是在前一個指針遍歷的時候保存下來給另外一個指針。windows

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_2spa

我估算須要(1,1.25)n個get_next()操做。指針

相關文章
相關標籤/搜索