Time：2021-9-8

List: This paper introduces the linear list of linked storage structure – single linked list from zero. It includes the following contents:

• What is a chained storage structure?
• Structure of single linked list
• Discrimination of confusing concepts such as head node and head pointer
• Comparison between single linked list and sequential list

# Chain storage structure of linear list

stay[detailed explanation of sequence table]In this paper, we introduce a linear table “connected by curves”. Curve is a visual language. In fact, there will be no such thing as “curve”.

The so-called “curve connection” is chain storage. What is chain storage?

stay[detailed explanation of sequence table]An example is given when introducing the sequential storage structure in the article:

For example, a group of children stand side by side in a row, occupying a certain continuous land. The group of children is very neat and uniform.

This example is reflected in memory, which is a sequential storage structure.

Now the children feel that standing side by side is too restrictive, so they disperse and stand wherever they want, but the team can’t be interrupted. So they held hands to keep the team moving.

Now children occupy not continuous land, but an arbitrary piece of land.

This example is reflected in memory, that is, data elements appear arbitrarily and occupy a certain block of memory.

By comparing the above two figures, we can see the problem:

• Straight line vs curve
• Uniformity vs disorder
• Continuous memory space vs arbitrary memory space
• Sequential storage vs chain storage

stay[detailed explanation of sequence table]As mentioned in the article, one of the characteristics of linear tables isThere is order between elements。 Sequential storage structure realizes this “order” by continuous memory space, but the elements of chain storage structure are “arbitrary”. How to realize “order”?

“Arbitrary” and “order” look like antonyms at first glance, but they are not contradictory. In order not to interrupt the team, children “hand in hand”. If we want to achieve “order”, we rely on the “curve like a chain” to link the elements, so there is order.

This is chain storage.

stay[detailed explanation of sequence table]The article mentioned that “sequential storage structure is a data structure that uses a continuous memory unit to store the data in the linear table respectively”. If we follow the cat and draw the tiger, we can get it“Chain storage structure is a data structure that uses a group of arbitrary and discontinuous memory units to store the data in the linear table respectively”Conclusion.

Then I prefer to use continuous memory units to store data, can I? Certainly.

The following figure intuitively shows that the elements of a linear table are stored in memory in a chained manner.

For the convenience of drawing, we will represent the curve of orderArtificiallyStraighten any stored elementsArtificiallyArrange neatly to form the logical relationship in the figure below:

The linear table of this chain storage structure is calledLinked list

The linked lists in the figure above are connected by a one-way arrow from one to the other, so that the whole linked list has a direction, which we callSingle linked list

Summarize the following features:

1. Use a set of arbitrary storage units to store the data elements of the linear table, which can be continuous (1 and 3) or discontinuous;
2. This group of arbitrary storage units is connected by a “chain”, so although it is out of order in memory, it is still linear in logic;
3. The direction of the single linked list is one-way, only from front to back, not from back to front;

# Implementation of single linked list

Because the single linked list is an arbitrary memory location, the array must not be used.

Because you want to store a value, just use variables.

Because the single linked list is stored arbitrarily in physical memory, the arrow representing the order (the child’s arm) must be implemented in code.

The arrow means that you can find 2 through 1.

And because we use variables to store values, in other words, we need to find another variable through one variable.

Variable find variable? How?

C language just has this mechanism – pointer. If you are not clear about the pointer, you can move to the article[how to master a sharp tool of C language – pointer?]

Now everything is ready, only a combination.

A variable is used to store a value, and a pointer is used to store its direct successor address. Bind the variable and pointer together to form a data element. Click.

Since the address of its direct successor is stored in the pointer, it is equivalent to pointing to its direct successor with a directed arrow.

First clarify a few nouns:

• The variables used to store data are calledData domain
• The pointer used to store the “address of the direct successor element” is calledPointer field
• The data elements composed of data field and pointer field are callednode (node)

To sum up, the node of a single linked list consists of the following parts:

• The data field used to store data——data
• The pointer field used to store the address of the direct successor node——next

# Implementation of node

The structure of C language can be used to implement nodes:

In order to explain the problem simply, the nodes here only store integers.

//Node
typedef struct Node {
int data; // Data fields: storing data
struct Node *next; // Pointer field: stores the address of the direct successor node
} Node;

Such a structure can perfectly represent a node, and many nodes can form a linked list.

# Structure of single linked list

A single linked list consists of a number of one-way links of nodes. A single linked list must have a head and a tail, because the computer is “stupid”, unlike people who know where the head is and where the tail is at a glance. So we need to use code to clearly represent all the structures of a single linked list.

Please pay attention first“Head”This concept, when we take the rope in our daily life, we always like to find the “rope head”, and this “head” is the other “head”.

Then understandPointerThis concept (if you don’t know the pointer, please move to, please move to the article[how to master a sharp tool of C language – pointer?]）, the pointer stores the address.

So,The head pointer is the pointer that stores the memory address of the first node of the linked listThat is, the header pointer points to the first node of the linked list.

The following figure shows a single linked list composed of three nodes:

(in order to understand the structure of the linked list, I will give the address of each node and the value of the pointer field)

Value is3The node of is the “tail” of the linked list, so the value saved in its pointer field isNULLUsed to indicate that the whole linked list ends here

We use the head pointer to represent the beginning of the linked listNULLIndicates the end of the linked list

In the above linked list, we can1Add another node before the node of, which is calledHead node。 See the figure below:

Generally, data is not stored in the data field of the header node (for example, the number of nodes can be stored)be of no great importanceFrom this point of view, the head node is different from other nodes“False node”。

At this time, the header pointer points to the header node becauseNow the head node is the first node

Why set a header node? This can facilitate our operation of the linked list. You will realize this later.

of course,The head node is not one of the necessary structures of the linked list. It is optional only according to your preference

Since the head node is not a necessary structure of a linked list, this means that there can be two types of linked lists:

Don’t faint. Look at the following two pictures:

Remember the following:

• The head pointer is important,Not without it
• Although the head node can facilitate some of our operations, butYou can have it or not
• Whether or not,Head pointers point to the first node of the linked list

(in the following operations, we will discuss the two cases of no leading node and leading node)

The following figure shows an empty linked list of the leading node:

At this time, the address of the first node – the head node is stored in the head pointer, and the address of the first node is stored in the pointer field of the head nodeNULL

(the following figure will no longer give the memory address)

# Create node

So far, we have understood the basic concept, implementation idea and specific implementation of nodes of single linked list, but these still stay in our mind. The next thing to do is to move the things in our minds into memory in the form of memory.

Because the linked list is composed of nodes, let’s create nodes first.

/**
*Create a node and return a pointer to the node
*Elem: the value of the data field of the node
*Return: pointer to the node (address of the node)
*/
Node *create_node(int elem)
{
Node *node = (Node *) malloc(sizeof(Node));
node->data = elem;
node->next = NULL;
return node;
}

Note: we want to usemallocThe function applies for a piece of memory for the node, and then the data field of the node can be assigned. Because the node is an independent node and has no direct successor node, its pointer field isNULL

To initialize a linked list without leading nodes, we directly create oneA null pointer that can point to a nodeThen, the null pointer isHead pointer

The single linked list of the leading node is characterized by an additional head node that does not store data, so we should create it when initializing the linked list.

However, before creating, let’s clarify three questions:

2. Pointer to [pointer to node]
3. Value passing and address passing of function parameters

Simple explanation:

1. The head pointer is a necessary part of the linked list. Only when you find the head pointer can you find the whole linked list, otherwise the whole linked list will disappear in the “vast memory”. Therefore, no matter what operation is carried out, the head pointer must be “held in our hands” as we hold the head of the rope.
3. The value transfer of a function changes the formal parameter (a copy of the argument) and does not affect the argument. So in some cases, we need to pass the address to the function. The function uses the pointer to directly operate the memory pointed to by the pointer.

If the above content is not clear, it means that you are not proficient in the pointer. Please move to the article[how to master a sharp tool of C language – pointer?]

Draw a more vivid picture below:

In the picture above, does the head pointer and the linked list look like a whip with a handle?

For example, the following game I used to play when I was a child

/**
*/
{
Node *node = (Node *) malloc(sizeof(Node));
node->next = NULL;
}

# Traversal operation

The so-called traversal is to start from the head of the chain and traverse one node at the end of the chain. We usually use oneAuxiliary pointerTo traverse.

The linked list of non leading nodes traverses from the pointer, so the initial position of the auxiliary pointer is the head pointer. Here we take the length of the linked list as an example:

{
int length = 0;
while (p != NULL) {
length++;
p = p->next;
}
return length;
}

useforLoops make the code look leaner.

The linked list of the leading node needs to be traversed from the first node, so the initial position of the auxiliary pointer is the successor node of the leading node:

{
int length = 0;
while (p != NULL) {
length++;
p = p->next;
}
return length;
}

# Insert operation

## basic thought

We took the example of “children holding hands” to describe the single linked list.

Children a and B are linked hand in hand. Now there is a child C who wants to plug in between them. What should I do?

C hand pulling on B = > a hand loosening B (dotted line indicates loosening) = > a hand pulling on C

A release B’s hand = > a pull C’s hand = > C pull B’s hand

Similarly, in the linked list, we also perform similar operations:

The code is:

new->next = current;
previous->next = new;

Or change the order:

previous->next = new;
new->next = current;

These two sentences are the core code of the insertion operation and the invariance of the insertion operation in various cases. If you understand these two sentences, you can respond to changes with invariance.

amongpreviouscurrentandnewAre three pointers to nodes,newPoint to the node to be inserted,previouscurrentBefore and after the insertion operation, the relationship is:

current = previous->next;

in fact,currentPointer is not required, only onepreviousYou can also insert becausecurrent = previous->next, the core code in this case becomes:

new->next = previous->next;
previous->next = new;

But please note, in this case, the two sentences are in order, and you can’t write:

//Error code
previous->next = new;
new->next = previous->next;

We can understand why these two sentences are in order from two perspectives:

[angle I]

becausecurrentPointer is used to savepreviousThe address of the direct successor node, so we disconnectpreviousandcurrentAfter contacting, we can still find itcurrentAnd subsequent nodes“ Even if the “chain” is temporarily disconnected, we can connect it because there are marks on both sides of the disconnection..

But not nowcurrentThen, once disconnected,currentAnd later nodes disappear in the vast memory, which is the key moment of the chain.

So we have to putnewandprevious->nextpreviousThe direct successor node) of, so that the pointernewIt saves the node it points to and subsequent nodes,

[angle II]

Look directly at the code,previous->next = newAfter executionnew->next = previous->nextIt’s equivalent tonew->next = new, I mean myself, which is obviously incorrect.

In short, understanding the core code in place, the rest is how to find it accuratelypreviousandcurrentThe location of the.

## Insert at specified location

We need to consider two situations:

1. Insert before first element
2. Insert before other elements
/**
*Specify the insertion location
*Position: specify position (1 < = position < = length + 1)
*Elem: data of new node
*/
void insert(Node **p_head, int position, int elem)
{
Node *new = create_node(elem);
Node *previous = NULL;
if (position < 1 || position > length + 1) {
Printf ("illegal insertion position \ n");
return;
}
for (int i = 0; current != NULL && i < position - 1; i++) {
previous = current;
current = current->next;
}
new->next = current;
if (previous == NULL)
else
previous->next = new;
}

Because it has a header node, the operation of inserting before the first element is the same as that of inserting before other elements.

/**
*Specify the insertion location
*Position: specify position (1 < = position < = length + 1)
*Elem: data of new node
*/
void insert(Node **p_head, int position, int elem)
{
Node *new = create_node(elem);
Node *current = previous->next;
if (position < 1 || position > length + 1) {
Printf ("illegal insertion position \ n");
return;
}
for (int i = 0; current != NULL && i < position - 1; i++) {
previous = current;
current = current->next;
}
new->next = current;
previous->next = new;
}

The header insertion method of the leading node is not, that is, the newly inserted node is always pointed by the header pointer.

/**
*Header insertion: the newly inserted node is always pointed by the header pointer
*Elem: data of new node
*/
{
Node *new = create_node(elem);
}

The head insertion method of the leading node, that is, the newly inserted node is always the direct successor of the head node.

/**
*The new node is the direct successor of the head node
*Elem: data of new node
*/
{
Node *new = create_node(elem);
}

Note: there is one more header node, so the code changes.

## Tail interpolation

Tail interpolation requires us to find the last node of the linked list first, so the focus is on how to traverse to the last node.

/**
*Tail insertion method: the newly inserted node is always at the end of the linked list
*Elem: data of new node
*/
{
Node *new = create_node(elem);
while (p->next !=  Null) // traverse from the beginning to the end of the linked list
p = p->next;
p->next = new;
}

/**
*Tail insertion method: the newly inserted node is always at the end of the linked list
*Elem: data of new node
*/
{
Node *new = create_node(elem);
while (p->next != NULL)
p = p->next;
p->next = new;
}

# Delete operation

## basic thought

The delete operation is to remove the node to be deleted from the linked list, which is similar to the insert operation.

previousandcurrentFor the pointer to the node, now we want to delete the nodecurrent, the process is as follows:

The core code is:

previous->next = current->next;
free(current);

free()The operation releases the node to be deleted.

currentThe pointer is not required. It can be used without it. The code is written as follows:

previous->next = previous->next->next;

But at this time, we can’t release the node to be deleted, because we don’t have a pointer to it, and it has disappeared into the vast memory.

## Delete at specified location

Knowing the core code, the rest of the work lies with usHow can I correctly traverse to the node to be deleted

As you can see,previousThe pointer is required and must be the direct precursor of the node to be deleted, sopreviousThe pointer traverses to its direct predecessor node.

/**
*Delete the node at the specified location
*Position: specify position (1 < = position < = length + 1)
*Elem: use the variable pointed to by this pointer to receive the deleted value
*/
void delete(Node **p_head, int position, int *elem)
{
Node *previous = NULL;
if (length == 0) {
Printf ("empty linked list \ n");
return;
}
if (position < 1 || position > length) {
Printf ("illegal deletion location \ n");
return;
}
for (int i = 0; current->next != NULL && i < position - 1; i++) {
previous = current;
current = current->next;
}
*elem = current->data;
if (previous == NULL)
else
previous->next = current->next;
free(current);
}

/**
*Delete the node at the specified location
*Position: specify position (1 < = position < = length + 1)
*Elem: use the variable pointed to by this pointer to receive the deleted value
*/
void delete(Node **p_head, int position, int *elem)
{
Node *current = previous->next;
if (length == 0) {
Printf ("empty linked list \ n");
return;
}
if (position < 1 || position > length) {
Printf ("illegal deletion location \ n");
return;
}
for (int i = 0; current->next != NULL && i < position - 1; i++) {
previous = current;
current = current->next;
}
*elem = current->data;
previous->next = current->next;
free(current);
}

adoptinsertanddeleteFunction, we can realize the difference between the non leading node and the leading node. For insertion and deletion operations, the non leading node needs to consider the special situation of inserting and deleting the first element before the first element, while the linked list of the leading node unifies the operations on all elements.

There are also special header deletion and tail deletion methods. The code is no longer given here

# Find and modify operations

The essence of search is to traverse the linked list. Use an auxiliary pointer to traverse the pointer to the specified position correctly, and you can get the node.

Modification is to modify the value of the target node on the basis of finding it.

The code is simple and will not be listed here. The detailed code is obtained at the end of the text.

Through the above code, you can realize:

• When you insert and delete an element, you don’t have to move a large number of elements like a sequential table.
• The length of the linked list is not as fixed as the sequential list. It is created when needed and deleted when not needed. It is extremely convenient.

• The search and modification of the single linked list need to traverse the linked list. If the element to be searched happens to be the last of the linked list, it needs to traverse the whole single linked list, which can be accessed directly unlike the sequential list.

If insert and delete operations are frequent, select single linked list; If the search and modification operations are frequent, select the sequence table; If the number of elements changes greatly and is difficult to estimate, a single linked list can be used; If the number of elements changes little and can be estimated, the sequence table can be used.

In short, single chain table and linear table have their own advantages and disadvantages. There is no need to step on one hand.The ultimate goal of learning data structures and algorithms is to flexibly select data structures according to the actual situation and better solve problems