List
Data Structure
List
Definition
A list is a finite sequence of N elements
$write\ as\newline$ $L = (a_1, a_2, a_3,…a_n)\newline$
$The\ size\ of\ this\ list\ is\ n\newline$
$n=0 \rightarrow empty\ list\newline$
element
- The first element(No predecessor),called “head”
- The ith element(has only one predecessor and only one successor)
- The last element(No successor),called “tail”
ADT
ADT List {
Data object:D = {ai | ai∈Elements, (i=1,2,…,n, n≥0)}
Data relationship : R1 = { < ai-1,ai > |ai-1,ai ∈ D,
(i=2,3,…,n) }
Operations:
• InitList(&L);
• DestroyList(&L);
• ListInsert(&L,i,e);
• ListDelete(&L,i,&e);
……and so on
} ADT List
Operation
{
InitList(&L):Create a empty list
DestroyList(&L):Delete a list(L has been initialized)
ClearList(&L):make L be an empty list(L has been initialized)
ListLength(L):return the size of elements in the list(L has been initialized)
ListEmpty(L):return if the list is an empty list(L has been initialized)
GetElem(L, i, &e):
make e equaled to the ith element in the list(L has been initialized and 1 <= i <= ListLength(L))
LocateElem(L, e, compare()):
return the rank that the rank of the element is the first element which compare(element[rank], e) is true(L has been initialized),
if no element satisfy it, return 0
PriorElem(L, cur_e, &pre_e):
make the pre_e equal the predecessor of the element which is the first which equal to cur_e, or return FAIL(L has been initialized)
NextElem(L, cur_e, &next_e):
make the next_e equal the successor of the element which is the first which equal to cur_e, or return FAIL(L has been initialized)
ListInsert(&L, i, e):
insert the element in the ith rank of the list(L has been initialized)
ListDelete(&L, i, &e):
delete the ith element in the list(L has been initialized and not be empty)
ListTraverse(&L, visit()):
for (begin, end)
visit(list[rank]_data)
(L has been initialized)
}
Example: make $ListA \bigcup ListB$
The code
|
|
Merge List
|
|
Implementation of Lists
Array implementation of lists
Using consecutive storage units to store elements of a list,the elements are stored in the logic order
k—- the memory space size for one element
The address of the (i+1)th element->Loc(ai+1) = Loc(ai) + k
Loc(a1)—-base address of the list
The address of the ith element:->Loc(ai) = Loc(a1) + (i-1)*k
$logic\ Element: [a_0, a_1, …, a_n]\newline$ $Physical\ Element:{[locate(a_0)]\ [locate(a_1)]\ …\ [locate(a_n)]}\newline$
$!!!\ Storage\ structure: random\ storage$
about data
|
|
supplement expend and shrink
|
|
T(n) = O(n)
insert
|
|
!!!The number of elements moved depends on the length of the order table and the location of the inserted element.
!!!Average:
$T(n) = \frac{\sum_{i=0}^n i}{n + 1} = \frac{\frac{n(n + 1)}{2}}{n + 1} = \frac {n}{2} = O(n)$
delete
|
|
!!!The number of elements moved depends on the length of the order table and the location of the deleted element
!!!Average:
$T(n) = \frac{\sum_{i = 0}^{n - 1} i}{n} = \frac{\frac{n(n - 1)}{2}}{n} = \frac{n - 1}{2} = O(n)$
Advantage
- Logic is Contiguous, and physical is Contiguous
- Random access any element
- The address can be calculated by base address
Disadvantage
- Insertion and deletion must move lot of elements
- Must assign memory with maximum-size
- It is not easy to extend array size
Linked list implement of lists
- Connected by pointer links
- Accessed via a pointer to the first node of the list
- Subsequent nodes are accessed via the link-pointer member of the current node
- Link pointer in the last node is set to null to mark the list’s end
Use a linked list instead of an array when
- You have an unpredictable number of data elements
- Your list needs to be sorted quickly
Types of linked list
-
Singly linked list
Only traversed in one direction
-
Circular, singly linked list
Pointer in the last node points back to the first node
-
Doubly linked list
Allows traversals both forwards and backwards
-
Circular, doubly linked list
Forward pointer of the last node points to the first node and backward pointer of the first node points to the last node
Singly Linked list
without head node
$head(pointer) \rightarrow [a_1] [pointer]\rightarrow [a_2] [pointer]\rightarrow ……\rightarrow [a_n] [pointer]\rightarrow null$
empty list
$head\rightarrow null$
with head node
$head(pointer)\rightarrow [head] [pointer]\rightarrow [a_1] [pointer]\rightarrow [a_2] [pointer]\rightarrow ……\rightarrow[a_n] [pointer]\rightarrow null$
empty list
$head\rightarrow [head] [pointer]\rightarrow null$
code
|
|
address data
code
|
|
insert node
|
|
delete
|
|
Circular, singly linked list
A Feature of the circulated linked lists—the next pointer of the last node points to the head, so the linked list becomes a circle
Any node can be found from any given node
structure
without head node
$head(pointer) \rightarrow [a_1] [pointer]\rightarrow [a_2] [pointer]\rightarrow ……\rightarrow [a_n] [pointer]\rightarrow head, tail \rightarrow [a_n]$
empty list
$head\rightarrow head, tail\rightarrow head$
with head node
$head(pointer)\rightarrow [head] [pointer]\rightarrow [a_1] [pointer]\rightarrow [a_2] [pointer]\rightarrow ……\rightarrow[a_n] [pointer]\rightarrow [a_0]$
empty list
$head\rightarrow [head] [pointer]\rightarrow [head]$
code
|
|
Doubly linked list
The feature of double linked lists —- There are two pointer fields in one node, one points to the predecessor, another to the successor
Any node in the list can be found by tracing back and forth from any given node.
structure
$[a_{i-1}]^\rightarrow_\leftarrow[prior] [data] [next]^\rightarrow_\leftarrow[a_{i + 1}]$
no empty list
$L\rightarrow[head]\rightarrow[a_1]^\rightarrow_\leftarrow[a_2]^\rightarrow_\leftarrow……^\rightarrow_\leftarrow[a_n]\rightarrow null\newline$ $head.prior\rightarrow[a_n]\newline$
empty list
$L\rightarrow prior = L\rightarrow next = null$
delete
|
|
insert
|
|
Circular, doubly linked list
structure
without head
$head\rightarrow prior = [a_n]\ a_n\rightarrow next = head$
empty
$head\rightarrow prior = head\rightarrow next = head$
Summary
Advantage
- store items “sequentially” without restrictions on location
- insert new item without shifting
- delete existing item without shifting
- size can expand/contract throughout use
Disadvantage
- overhead of links: used only internally, pure overhead
- no longer have direct access to each element of the list. O(1) access becomes O(n) access since we must go through first element, and then second, and then third, etc.
Select ArrayedList or LinkedList?
Array-Based List:
- Insertion and deletion are O(n).
- Direct access are O(1).
- Array must be allocated in advance.
- No overhead if all array positions are full.
Linked List:
- Insertion and deletion are O(1).
- Direct access are O(n).
- Space grows with number of elements.
- Every element requires overhead.
Contiguous storage is generally preferable:
- when the entries are individually very small;
- when the size of the list is known when the program is written;
- when few insertions or deletions need to be made except at the end of the list;
- when random access is important.
Linked storage proves superior
- when the entries are large;
- when the size of the list is not known in advance
- when flexibility is needed in inserting, deleting, and rearranging the entries.
Other:Cursor Implementation of Linked List
implementation in a consecutive storage, and the next is not a pointer but a Rank to search next node
- You still need to allocate a large space beforehand;
- But in the linear table insert and delete operations do not need to move elements, only need to modify the cursor (pointer), so it still has the main advantages of chain storage structure;
- A linked list described by an array is called a static linked list.
Application of Linked list:
Example: Polynomials
$P(x) = a_0 + a_1x + a_2x^2 + … + a_nx^n$
Array-based implementation:Disadvantage: for sparse polynomials ->waste memory!
Linked list implementation can solve the problem
About Homework
you can use Singly linked list like a stack (push and pop)