Я пытаюсь реализовать список пропуска, который работает так же хорошо, как BST, используя минимальные дополнительные накладные расходы памяти, на данный момент даже не рассматривая ограничение памяти, производительность моей реализации SkipList далека от даже очень наивного Сбалансированного Внедрение BST - так сказать, ручной BTS:) -
В качестве ссылки я использую оригинальную бумагу от William Pugh PUG89 и реализацию, которую я нашел в Алгоритмах в C от Sedgewick -13.5-, Мой код является рекурсивной реализацией, вот ключ операции вставки и поиска:
sl_node* create_node()
{
short lvl {1};
while((dist2(en)<p)&&(lvl<max_level))
++lvl;
return new sl_node(lvl);
}
void insert_impl(sl_node* cur_node,
sl_node* new_node,
short lvl)
{
if(cur_node->next_node[lvl]==nullptr || cur_node->next_node[lvl]->value > new_node->value){
if(lvl<new_node->lvl){
new_node->next_node[lvl] = cur_node->next_node[lvl];
cur_node->next_node[lvl] = new_node;
}
if(lvl==0) return;
insert_impl(cur_node,new_node,lvl-1);
return;
}
insert_impl(cur_node->next_node[lvl],new_node,lvl);
}
sl_node* insert(long p_val)
{
sl_node* new_node = create_node();
new_node->value = p_val;
insert_impl(head, new_node,max_level-1);
return new_node;
}
И это код для операции поиска:
sl_node* find_impl(sl_node* cur_node,
long p_val,
int lvl)
{
if(cur_node==nullptr) return nullptr;
if(cur_node->value==p_val) return cur_node;
if(cur_node->next_node[lvl] == nullptr || cur_node->next_node[lvl]->value>p_val){
if(lvl==0) return nullptr;
return find_impl(cur_node,p_val,lvl-1);
}
return find_impl(cur_node->next_node[lvl],p_val,lvl);
}
sl_node* find(long p_val)
{
return find_impl(head,p_val,max_level-1);
}
Список sl_node -skip node - выглядит так:
struct sl_node
{
long value;
short lvl;
sl_node** next_node;
sl_node(int l) : lvl(l)
{
next_node = new sl_node*[l];
for(short i{0};i<l;i++)
next_node[i]=nullptr;
}
~sl_node()
{
delete[] next_node;
}
};
Как вы видите, это реализация не имеет ничего особенного и не продвинута, если сравнивать с реализацией книги, я не буду использовать код Balaced BTS, так как я не думаю, что это необходимо здесь, но это очень простая BTS с функцией балансировки запускается во время вставки, когда новая высота node больше 16 * lg (n), где n - количество узлов.
Так сказать, я перебалансирую три только в том случае, если максимальная высота в 16 раз выше, чем самая лучшая теоретическая, как я уже сказал, этот самодельный BST с прямым ударом выполняет намного лучше, чем самодельный Пропустить Список.
Но сначала рассмотрим некоторые данные, используя p =.5 и n = 262144, уровень узлов в SkipList имеет следующее распределение:
1:141439, 53.9547%
2:65153, 24.8539%
3:30119, 11.4895%
4:13703, 5.22728%
5:6363, 2.42729%
6:2895, 1.10435%
7:1374, 0.524139%
8:581, 0.221634%
9:283, 0.107956%
10:117, 0.044632%
11:64, 0.0244141%
12:31, 0.0118256%
13:11, 0.00419617%
14:5, 0.00190735%
15:1, 0.00038147%
16:5, 0.00190735%
Которая почти идеально -ох, это большая! - сопоставьте теорию с этой статьей, то есть: 50% уровня 1, 25% уровня 2 и т.д. и т.д. Входные данные поступали от моего наилучшего доступного генератора псевдослучайных чисел aka std:: random_device с std:: default_random_engine и равномерного распределения int. Вход выглядит довольно случайным для меня:)!
Время, необходимое для поиска "всех" элементов 262144 в случайном порядке SkipList - в 315ms на моей машине, тогда как для той же операции поиска на наивной BTS требуемое время составляет 134 мс, поэтому BTS почти в два раза быстрее, чем SkipList. Это не совсем то, чего я ожидал от "алгоритмов пропущенных списков, которые имеют те же асимптотические ожидаемые временные рамки, что и сбалансированные деревья, и просты, быстрее и используют меньше места" PUG89.
Время, необходимое для "вставки" узлов, составляет 387 мс для SkipList и 143ms для BTS, а более эффективный BST работает лучше.
Все становится немного интереснее, если вместо использования случайной последовательности входного номера я использую отсортированную последовательность, здесь мой бедный домашний BST становится медленным, а вставка 262144 отсортированного int занимает 2866 мс, тогда как СкипЛисту требуется только 168 мс.
Но, придя в поисковое время, BST все еще быстрее! для отсортированного ввода мы имеем 234 мс против 77 мс, этот BST в 3 раза быстрее.
При разных значениях p-фактора я результаты несколько отличаются:
И последнее, но не менее важное: график использования памяти, который, как вы можете ожидать, подтверждает, что если мы увеличим количество уровней на node, мы значительно повлияем на отпечаток памяти. Использование памяти вычисляется как сумма пространства, необходимого для хранения дополнительных указателей для всех node.
После всего этого кто-нибудь из вас может предоставить мне комментарий о том, как реализовать SkipList так же хорошо, как BTS, но не подсчитывая дополнительные служебные данные памяти?
Я знаю о статье от DrDobbs LINK о SkipList, и я прошел через всю бумагу, код для операции поиска и вставки точно соответствует исходной реализации из PUG89, так что должно быть так же хорошо, как и мое, и статья не дает никакого анализа производительности в любом случае, я не нашел любой другой источник. Можете ли вы мне помочь?
Любые комментарии очень ценятся!
Спасибо!:)