Linked Lists
A linked list is a data structure that uses a
"chain" of node objects, connected by pointers,
Minimal Linked List
Interface
A linked list implementation will
typically provide at least:
-
initialization function to set up basic structure for an
empty list
-
insert functions to add new element to the list; at front,
at rear, at user-selected position, ordered insertion
-
remove function to remove element from the list
-
find function to determine whether a given element occurs in
the list
-
clear function to restore the list to an empty state
In
C we would organize this as a pair of struct types (list and node) and a
collection of associated functions.
Generic Node and List
|
#ifndef DLIST_H
#define DLIST_H
// List node: struct
_DNode
{
struct _DNode
*prev; //
Previous list element struct _DNode *next; // Next list element
} ;
// List object: struct
_DList
{
struct _DNode
head; //
List head struct _DNode tail; //
List tail
} ;
typedef struct _DNode
DNode; typedef struct _DList DList;
#endif
|
DList Initialization
This eliminates special cases,
because every data node will always be between two other nodes.
We
could also make head.prev
point
to tail and tail.next point to head, which
would eliminate NULL
pointers
and allow the list to be used in a circular fashion.
Wrapping the Node in the
Payload
We may use a single DList of DNode objects
with any user data type, without sacrificing type-checking.
We merely have to create a
"duct tape" object to attach a data object to a node:
|
#ifndef INTEGERDT_H
#define INTEGERDT_H #include
"DList.h"
struct _IntegerDT { // "duct tape" attaches data
object to DNode int payload; DNode node;
} ;
typedef struct _IntegerDT IntegerDT;
void IntegerDT_Init(IntegerDT* const pLE, const
int* const
I);
#endif
|
Example of "duct-taped"
List Structure
The DList only "knows about" two DNode objects.
Each DNode object only "knows about"
one or two other DNode
objects.
The
DList and Dnode objects
"know" nothing of IntegerDT
objects.
Inserting a DNode
We want to insert the node on the bottom between the other
two nodes:
elem->next =
before; // 2
before->prev->next = elem; // 3 before->prev = elem;
// 4
Inserting a DNode
The DList only "knows about" two DNode objects.
|
/* Inserts
elem as the predecessor of before, which may be either an interior element or
a tail.
*/ void DList_Insert (DNode* const
before, DNode* const elem) {
assert (is_interior (before) ||
is_tail ( before)); assert (elem !=
NULL);
elem->prev = before->prev; elem->next = before;
before->prev->next = elem; before->prev = elem;
}
|
Searching
Clearly, we need to be able to search a list for a data
value that matches some
But we must follow the list pointers, which tie the DNode objects
together…
… so how are we going to access the user data objects?
Accessing the "duct
tape"
Suppose the IntegerDT object is laid out in memory as shown:
IntegerDT
We want a
pointer q
that
points to the IntegerDT
object
that contains the Dnode
that
p points to.
Then it appears we can set the value for q by
subtracting 4 from p…
… but that logic depends on the specific memory layout shown
above.
offsetof() to the Rescue!
The Standard Library includes a relevant C macro:
offsetof(type, member-designator)
expands to an integer constant expression that has type size_t, the
value of which is the offset in bytes, to the structure member (designated by member-designator), from the beginning
of its structure (designated by type).
/*
Converts pointer to a DNode NODE into a pointer to the structure that DNode is
embedded inside.
Supply
the name of the outer structure STRUCT and the member name MEMBER of the DNode.
*/
#define
DList_Entry(NODE, STRUCT, MEMBER) \
((STRUCT *)
((uint8_t *) (NODE) – \
offsetof
( STRUCT, MEMBER )))
Traversing the DList
|
void
traverseList(DList* pL) { DNode* e = DList_Head(pL);
while ( (e = DList_Next(e)) != DList_End(pL)) {
// Get pointer to the
"duct-tape" object from //
the pointer to the DList element:
IntegerDT *p
= DList_Entry(e, IntegerDT, node);
// Get value of payload within
"duct-tape" object: int userData =
p->payload;
// do stuff with current user data
element }
}
|
Here are some ideas for DList interface functions:
|
// Set up an empty list: void
DList_Init(DList* pList);
// Insert node elem in front of node
before: void DList_Insert(DNode* pBefore, DNode* pElem);
// Remove node elem:
DNode* DList_Remove(DNode* pElem);
// Is list empty? bool
DList_Empty(DList* pList);
// Restore list to empty state: void
Dlist_Clear(Dlist* pList);
.
. .
|
|
. . .
// Get pointer to first/last data node
in list:
DNode* DList_Begin(DList* pList);
DNode* DList_End(DList* pList);
// Get pointer to
successor/predecessor of node:
DNode* DList_Next(DNode* pElem);
DNode* DList_Prev(DNode* pElem);
// Get pointer to head/tail of list:
DNode* DList_Head(DList* pList);
DNode* DList_Tail(DList* pList);
.
. .
|
|
. . .
// Insert elem at front/rear of list:
void DList_PushFront(DList* pList,
DNode* pElem); void DList_PushBack(DList* pList, DNode* pElem);
// Remove elem from front/rear of
list:
DNode* DList_PopFront(DList* pList);
DNode*
DList_PopBack(DList* pList);
|
No comments:
Post a Comment