The doubly linked list operations keep the ring and return the right result full
This proof shows: correctness.
Setting. A list L has a sentinel L.nil and a count L.n. Write n=L.n, and let v0,…,vn−1 be its item nodes from the front, with v−1=vn=L.nil.
Invariant (I). The nodes L.nil,v0,…,vn−1 are distinct, vi.next=vi+1 for −1≤i≤n−1, and vi.prev=vi−1 for 0≤i≤n. The list held is (v0.key,…,vn−1.key).
Since v−1 and vn are both L.nil, (I) says L.nil.next=v0 and L.nil.prev=vn−1, and for n=0 both are L.nil. Write ℓi=vi.key. For each operation we assume (I) and the precondition, and show (I) for the list that the list type asks for.
INSERT-AFTER(L,p,x), p=vj with −1≤j≤n−1. Line L1 makes a node v. It is new, so it is distinct from the nodes of the ring. By (I), p.next=vj+1, which is L.nil when j=n−1. Line L2 sets v.next=vj+1 and v.prev=vj. Line L3 writes vj+1.prev=v, and line L4 writes vj.next=v and sets the count to n+1. When j=n−1, the node vj+1 is the sentinel, and when j=−1, the node vj is the sentinel; the lines are the same. Define the new sequence w−1,…,wn by wi=vi for i≤j, wj+1=v, and wi=vi−1 for i≥j+2, with wn+1=L.nil. Its n+2 nodes are distinct. Check the next fields: for i<j and for i≥j+2 the field was not written and keeps its old value, which is the old successor, and that is wi+1; wj.next=v=wj+1 by line L4; wj+1.next=vj+1=wj+2 by line L2. Check the prev fields: wj+1.prev=vj=wj by line L2; wj+2.prev=v by line L3; the others were not written. So (I) holds for the list (ℓ0,…,ℓj,x,ℓj+1,…,ℓn−1).
The order of the lines matters only in one place: line L2 reads p.next before line L4 writes it, and line L3 reads p.next (still vj+1) before line L4. Both reads see the old value.
DELETE(L,v), v=vj with 0≤j≤n−1. By (I), v.prev=vj−1 and v.next=vj+1, where either can be the sentinel. Line L5 writes vj−1.next=vj+1, and line L6 writes vj+1.prev=vj−1 and sets the count to n−1. Line L5 does not change v.next or v.prev, so line L6 reads the old values. The new sequence v−1,…,vj−1,vj+1,…,vn has n+1 distinct nodes. The only changed fields are the next field of the predecessor of vj and the prev field of its successor, and both now skip vj. All other fields keep their old values, which already match the new sequence. So (I) holds for (ℓ0,…,ℓj−1,ℓj+1,…,ℓn−1), and line L7 returns ℓj.
SEARCH(L,k). Claim: each time line S2 tests the loop condition, x=vi for some 0≤i≤n and ℓt=k for all t<i. At the first test, x=L.nil.next=v0 and the claim holds with i=0. If the loop body runs, then x=vi=L.nil, so i<n, and ℓi=k; line S3 sets x=vi+1 by (I), and the claim holds for i+1. Each pass increases i, and i≤n, so the loop stops after at most n passes. When it stops, either x=L.nil, so i=n and no item equals k, and line S4 returns NIL; or x=vi with ℓi=k and no earlier item equal to k, and line S4 returns the first node with key k. No field is written, so (I) still holds.
End operations. push_front, push_back and insert_before are INSERT-AFTER with p=L.nil=v−1, p=L.nil.prev=vn−1 and p=q.prev for a node q=vj, which is vj−1. In each case p is in the allowed range, and the new item lands at position 0, n and j. pop_front and pop_back are DELETE of v0 and vn−1, which are item nodes when n≥1. So all of them are correct by the two cases above. ■