Skip lists are a hidden gem! I first learned about them from a GitHub developer at OSCon a few years ago, and at time they seemed like black magic. It's a shame that universities (or at least mine) don't teach more interesting algorithms like this... it would make the algorithms/data structures coursework so much more interesting.
Georgia Tech definitely teaches it in the intro data structures and algorithms course. I don't remember if we had to implement it, but it definitely came up in lecture.
Skip Lists Done Right
71–80 of 87 posts
Re: Skip Lists Done Right
#72Might anyone familiar with both data structures be able to briefly explain how this actually differs from a B-tree?
A skip list node contains one key and has a random fanout; a B-tree node contains 1-N keys and has fanout equal to - 1.
A B-tree is guaranteed amortized log(N) lookup/insert/delete time, a skip-list has a high probability of a log(N) lookup/insert/delete time, but does not guarantee it.
The advantage of skip lists over b-trees is that they can have a smaller constant-factor, and are more cache friendly under many workloads.
Re: Skip Lists Done Right
#73Skip Lists. Seem handy for increasing access times on ordered lists... Any Lispers care to comment?
The funny thing is how rarely you need that sort of thing if you have an unordered associative container, and a sorting function.
It's possible to combine a hash table with a list. E.g. the hash table references not the items directly, but the conses of a list in which those items are stored as the cdr. Then you have keyed random access to the list, as well as a particular order. Finding a spot where to insert is linear, of course.
A container which keeps items in order is useful primarily for real-time programming, for implementing a priority queue: the situation is that a dynamic set of items is in constant churn, and needs to be in order all the time (where the order is different from the order in which those items come to life or arrive or whatever). For performance, we don't want to just add an item to the end of the queue and then call sort.
If we don't care about this real time aspect, we can just accumulate items and then sort them. Even if we sort a list several times, that may not be of consequence if it is bounded to a small length. Unix treats directories like this: the "dents" are not ordered in any way: globbing expansion and the "ls" utility accumulate names and sort them. Unix could keep directories in sorted order (and there are, I think, some file systems which actually do).
There is an anti-pattern of abusing sorting when people don't have an associative container. An example of this is shell scripts which use sorting to help de-duplicate items, instead of an obvious awk job with an associative array. Another anti-pattern occurred in C++: for long time C++ had only std::map and std::set: ordered containers. These were used all the time, even when order was not required (Now there is std::unordered_map).
In the late 1990's, I wrote a pretty nice red-black tree data structure in C, which ended up used in various places, such as in ext2fs tools. one company I worked for used it for the basis of a "real-time database" for software running on a switch node for real time packet inspection and processing. The implementation had to be modified to allow for shared memory.
I don't miss that kind of container in everyday Lisp programming somehow. I have a several years old branch where that code is half-added to my Lisp dialect. There isn't much motivation to finish the job; the need for that just somehow doesn't arise. Most of the time, either you're dealing with dynamic sets which are unordered (by definition of "set", practically), or else with lists which are ordered, but don't have to be constructed by out-of-order insertions. A lot of the time, the order of a list doesn't actually follow any predicate: the only specification of order is that list instance itself and its actual order. (The order came from somewhere, but its reason is not necessarily known, only that it should be preserved.)
ANSI CL doesn't have any optimized ordered list container, only hashes. Why? It seems like nothing notable of that sort originated in any of the base dialects from which CL was formed.
Suppose you needed some real-time database for some service application: it would make a lot of sense to just pick one and make Lisp bindings to it.
Re: Skip Lists Done Right
#74Maybe off-topic, but thanks for putting what tools you used to do your illustrations with, always happy to see what people are using for that.
Re: Skip Lists Done Right
#75I've read this page before, and again today, and I still don't understand how these unrolled lists are supposed to work in practice. Based on the author's example at https://i.imgur.com/FYpPQPh.png , how do you take an unrolled skiplist that has a bottom row like this: [1,2,3] -> [4,5,_] -> [7,8,9] And insert 2.5? An inevitable tree restructuring would have to occur, which vastly complicates the insertion logic.
Re: Skip Lists Done Right
#76I've read this page before, and again today, and I still don't understand how these unrolled lists are supposed to work in practice. Based on the author's example at https://i.imgur.com/FYpPQPh.png , how do you take an unrolled skiplist that has a bottom row like this: [1,2,3] -> [4,5,_] -> [7,8,9] And insert 2.5? An inevitable tree restructuring would have to occur, which vastly complicates the insertion logic.
That first chunk will get split, 3 will get copied to new chunk, and list is restructured. I think the selling point here is you paid this price but get dividends on reduced cache misses in the future.
Re: Skip Lists Done Right
#77Earlier quoted context omitted.
Skip lists allow for that, sort of. In Lisp, most people would just, you know, not use a list for that. You can do that, you know... The only reason I asked about lispers is that lispers really like lisps, and I was curious if any of them had implemented skip lists for some reason, and if so, how.
> ... just, you know ... you know ... Yes, I know. Do you? You made the suggestion that skip lists are inferior to ordered lists, not me. (good for "increasing access times on ordered lists") I'm still perplexed as to your rationale for that statement.
Re: Skip Lists Done Right
#78Skip Lists. Seem handy for increasing access times on ordered lists... Any Lispers care to comment?
Skip lists are container data structures for keeping items in order, an alternative to various balanced trees like red-black. The funny thing is how rarely you need that sort of thing if you have an unordered associative container, and a sorting function. It's possible to combine a hash table with a list. E.g. the hash table references not the items directly, but the conses of a list in which those items are stored a…
Re: Skip Lists Done Right
#79Skip lists are probably the easiest way of getting O(log(n)) lookups on ordered lists. Recently I got rid of huge bottleneck on an oldish piece of software by moving from a vanilla linked list to a skip list. And I did do many things suggested in the article (e.g. having a vector of fixed length for the pointers, and thus a fixed tallness). Funnily enough, for my case, I managed to do without a RNG just fine. I just…
> Skip lists are probably the easiest way of getting O(log(n)) lookups on ordered lists. I wouldn't say that. The algorithms for AVL/red-black/splay trees are not especially complex, and they contain much less subtlety than skip lists. If you just look them up on wikipedia and see the list of operations, it's not particularly hard to implement them. Skip lists, on the other hand, are very easy to get wrong (this is t…
and can be coded once, then used in millions of programs. That whole point is largely moot.
A red-black tree gives you certain guarantees without any probabilistic arguments. Wikipedia: A skip list does not provide the same absolute worst-case performance guarantees as more traditional balanced tree data structures, because it is always possible (though with very low probability) that the coin-flips used to build the skip list will produce a badly balanced structure.
A skip list algorithm which guards against the worst case will necessarily have to do some rebalancing that requires nodes to move among levels according to some heuristic.
Skip lists are not storage efficient unless the nodes are variably sized. If each node includes enough pointers to participate in any level, then you waste space. Variable size means we cannot draw nodes from a single, compact pool array of nodes for a skip list. Variable size also creates difficulties if we want to use this as an "intrusive container". (A container which works by inserting the necessary node links and other fields into the client's data structure, so that the client data structures directly link into the graph as nodes, rather than being separately allocated and referenced from it.)
I'm looking at a C implementation pointed at by DADS. http://epaperpress.com/sortsearch/txt/skl.txt
This uses the C struct hack for variable node sizing, which makes it rely on malloc for the nodes. A client cannot pass in a pre-allocated node.
A mistake in this type of code can lead to a buffer overflow: accessing the n-th level of a node which only has k If logic were added which moves a node from one skip level to another, that would require the entire node to be realloc'ed, if its number of links needs to increase. Realloc-ing can change its address; all existing pointers to the node must be rewritten.
Re: Skip Lists Done Right
#80Earlier quoted context omitted.
> Skip lists are probably the easiest way of getting O(log(n)) lookups on ordered lists. I wouldn't say that. The algorithms for AVL/red-black/splay trees are not especially complex, and they contain much less subtlety than skip lists. If you just look them up on wikipedia and see the list of operations, it's not particularly hard to implement them. Skip lists, on the other hand, are very easy to get wrong (this is t…
I did a brief survey before deciding to go with skip lists, and found them to be easier to grasp and reason about. (e.g. there is no need to balance trees; a skip list is very similar to a linked list, and linked lists are very simple). I remember somebody saying that "in a sane world skip lists would always have been discovered before red-black trees". This historical accident (skip lists were only discovered 1989;…
Look at the basic diagram here.
https://en.wikipedia.org/wiki/B%2B_tree
See how there is an "express lane" containing just (3 5), and the lower layer contains the entire (1 2 3 4 5 6 7) list?
The main difference is that it's a combination of arrays and links rather than parallel lists.