Linear list, doubly linked list, static linked list, circular linked list (Joseph ring)

Table of contents

What is a linear table (linear storage structure)

Sequential storage structure and chained storage structure

predecessor and successor

Sequence table (sequential storage structure) and initialization details

Initialization of the sequence table

Doubly linked list and its creation (C language) detailed explanation

Creation of doubly linked list

Static linked list and its creation (C language implementation)

Nodes in a static linked list

standby list

Create a static linked list

Establishment of Circular Linked List (Joseph Ring) and Its Implementation in C Language

Circular linked list implements Joseph ring

Summarize


What is a linear table (linear storage structure)

Through the previous study, we know that the best way to store data with a "one-to-one" logical relationship is to use a linear table . So, what is a linear table?

Linear table, the full name is linear storage structure . The way to store data using a linear table can be understood in this way, that is, "string all the data with a line and store them in physical space".

 

"One-to-one" logical relationship data


Figure 1 Data of "one-to-one" logical relationship


As shown in Figure 1, this is a set of data with a "one-to-one" relationship, and we then use a linear table to store it in physical space.

First, use "a thread" to "string" them in sequence, as shown in Figure 2:

 


Figure 2 "Linear" structure of data


In Figure 2, the left side is the "string" data, and the right side is the free physical space. To place this "string" of data in physical space, we can choose the following two methods, as shown in Figure 3.

 

Two Linear Storage Structures


Figure 3 Two linear storage structures


Figure 3a) is the storage method that most people think of, while Figure 3b) is rarely thought of. We know that the success of data storage depends on whether the data can be completely restored to its original form. If you pull up one end of the line in Figure 3a) and Figure 3b), you will find that the position of the data has not changed (same as Figure 1). Therefore, it can be concluded that both storage methods are correct.

The data with a "one-to-one" relationship is stored "linearly" in the physical space, and this storage structure is called a linear storage structure (referred to as a linear table) .

The data stored in the linear table requires the same data type as the data stored in the array, that is to say, the data stored in the linear table is either all integers or all strings. A set of data whose half is an integer and the other half is a string cannot be stored in a linear table.

Sequential storage structure and chained storage structure

From Figure 3, we can see that linear table storage data can be subdivided into the following two types:

  1. As shown in Figure 3a), the data is stored sequentially in a continuous block of physical space. This storage structure is called a sequential storage structure ( sequence table for short );
  2. As shown in Figure 3b), the data is stored scattered in the physical space, and the logical relationship between them is saved through a line. This storage structure is called a chained storage structure (referred to as a linked list );


That is to say, the linear table storage structure can be subdivided into a sequential storage structure and a linked storage structure.

predecessor and successor

In data structures , each individual in a set of data is called a " data element " (or " element " for short). For example, the set of data shown in Figure 1, where 1, 2, 3, 4, and 5 are all elements of the set of data.

In addition, for data with a "one-to-one" logical relationship, we have been using unprofessional words such as "the left side (front) or right side (back) of an element". In fact, there are more accurate terms in the linear table :

  • The left adjacent element of an element is called "direct predecessor", and all elements located on the left side of this element are collectively called " predecessor elements ";
  • The right adjacent element of an element is called "immediate successor", and all elements to the right of this element are collectively called " successor elements ";


Taking element 3 in the data in Figure 1 as an example, its direct predecessor is 2, and there are 2 precursor elements of this element, namely 1 and 2; similarly, the direct successor of this element is 4, and there are also 2 successor elements. 4 and 5 respectively. As shown in Figure 4:

 

predecessor and successor


Figure 4 Predecessor and successor

Sequence table (sequential storage structure) and initialization details

Sequence table, full name sequential storage structure, is a kind of linear table . Through the study of the section " What is a linear table ", we know that a linear table is used to store data with a logical relationship of "one-to-one", and the sequential table is no exception.

Not only that, but the sequence table also has requirements on the physical storage structure of the data. When the sequential table stores data, it will apply for a whole piece of physical space of sufficient size in advance, and then store the data in sequence, so that there is no gap between the data elements during storage.

For example, using a sequential table to store collections  {1,2,3,4,5}, the final storage state of the data is shown in Figure 1:


 


Figure 1 Schematic diagram of sequential storage structure


From this, we can conclude that the storage structure that "stores data with a 'one-to-one' logical relationship sequentially in a whole piece of physical space" is a sequential storage structure.

By observing the storage state of the data in Figure 1, we can find that the data stored in the sequential table is very close to the array. In fact, the sequence table uses arrays to store data.

Initialization of the sequence table

Before using the sequence table to store data, in addition to applying for a sufficient size of physical space, in order to facilitate the later use of the data in the table, the sequence table also needs to record the following two items of data in real time:

  1. The storage capacity requested by the sequence table;
  2. The length of the sequence table, that is, the number of data elements stored in the table;

Tip: Under normal conditions, the storage capacity requested by the sequence table is greater than the length of the sequence table.

Therefore, we need to customize the sequence table, and the C language implementation code is as follows:

 
 
  1. typedef struct Table{
  2. int * head;//declare an array of indeterminate length named head, also called "dynamic array"
  3. int length;//Record the length of the current sequence table
  4. int size;//The storage capacity allocated by the record sequence table
  5. }table;

Note that head is an uninitialized dynamic array we declared, don't just treat it as an ordinary pointer.

Next, start to learn the initialization of the sequence table, that is, initially establish a sequence table. To create a sequence table, you need to do the following:

  • Apply for sufficient physical space for head dynamic data;
  • Assign initial values ​​to size and length;


Therefore, the C language implementation code is as follows:

 
 
  1. #define Size 5 //Make a macro definition for Size, indicating the size of the application space for the sequence table
  2. table initTable(){
  3. table t;
  4. t.head=(int*)malloc(Size*sizeof(int));//Construct an empty sequence table and dynamically apply for storage space
  5. if (!t.head) //If the application fails, give a prompt and exit the program directly
  6. {
  7. printf("Initialization failed");
  8. exit(0);
  9. }
  10. t.length=0;//The length of the empty table is initialized to 0
  11. t.size=Size;//The initial storage space of the empty table is Size
  12. return t;
  13. }

We can see that the entire sequence table initialization process is encapsulated into a function, and the return value of this function is an initialized sequence table. The advantage of this is that it increases the usability of the code and is more beautiful. At the same time, during the initialization process of the sequence table, it is necessary to pay attention to the judgment of the application of the physical space, and to deal with the failure of the application. Here, only the operation of "output prompt information and forced exit" is performed, and you can modify it according to your own needs. The if statement in the code was improved.

By calling the initTable statement in the main function, an empty sequence table can be successfully created. At the same time, we can also try to add some elements to the sequence table. The C language implementation code is as follows:

  1. #include <stdio.h>
  2. #include <stdlib.h>
  3. #define Size 5
  4. typedef struct Table{
  5. int * head;
  6. int length;
  7. int size;
  8. }table;
  9. table initTable(){
  10. table t;
  11. t.head=(int*)malloc(Size*sizeof(int));
  12. if (!t.head)
  13. {
  14. printf("Initialization failed");
  15. exit(0);
  16. }
  17. t.length=0;
  18. t.size=Size;
  19. return t;
  20. }
  21. //Function to output the elements in the sequence table
  22. void displayTable(table t){
  23. for (int i=0;i<t.length;i++) {
  24. printf("%d ",t.head[i]);
  25. }
  26. printf("\n");
  27. }
  28. int main(){
  29. table t=initTable();
  30. //Add elements to the list
  31. for (int i=1; i<=Size; i++) {
  32. t.head[i-1]=i;
  33. t.length++;
  34. }
  35. printf("The elements stored in the sequence table are:\n");
  36. displayTable(t);
  37. return 0;
  38. }

The result of the program running is as follows:

The elements stored in the sequence table are:
1 2 3 4 5

As you can see, the sequence table is initialized successfully.

 

Doubly linked list and its creation (C language) detailed explanation

The linked list we have learned so far , whether it is a dynamic linked list or a static linked list , each node in the table contains only one pointer (cursor), and they all point to the direct successor node uniformly. This kind of linked list is usually called a one-way linked list (or single linked list ).

Although the use of singly linked list can 100% solve the storage problem of "one-to-one" data logical relationship, but when solving some special problems, singly linked list is not the most efficient storage structure. For example, if the algorithm needs to find a large number of predecessor nodes of a specified node, using a singly linked list is undoubtedly disastrous , because a singly linked list is more suitable for "from front to back" search, and "from back to front" search and Not its forte.

In order to efficiently solve similar problems, this section will learn about doubly linked lists (referred to as double linked lists).

Understand the doubly linked list from the name, that is, the linked list is "two-way", as shown in Figure 1:


 

Schematic diagram of doubly linked list structure


Figure 1 Schematic diagram of the structure of the doubly linked list

Bidirectional means that the logical relationship between nodes is bidirectional, but usually only one head pointer is set, unless the actual situation requires it .

As can be seen from Figure 1, each node in the doubly linked list contains the following three parts of information (as shown in Figure 2):

  1. Pointer field: used to point to the direct predecessor node of the current node;
  2. Data field: used to store data elements.
  3. Pointer field: used to point to the immediate successor node of the current node;


 

Node composition of doubly linked list


Figure 2 Node composition of the doubly linked list


Therefore, the node structure of the doubly linked list is implemented in C language as:

  1. typedef struct line{
  2. struct line * prior; // point to the immediate predecessor
  3. int data;
  4. struct line * next; // point to the immediate successor
  5. }line;

Creation of doubly linked list

Compared with the single-linked list, the double-linked list only has one more pointer field for each node to point to the direct predecessor. Therefore, we can easily create a double-linked list on the basis of a single-linked list.

It should be noted that, unlike the single-linked list, in the process of creating a double-linked list, every time a new node is created, two connections must be established with its predecessor node, namely:

  • Point the prior pointer of the new node to the direct predecessor node;
  • Point the next pointer of the direct predecessor node to the new node;


Here is the C language implementation code for creating a doubly linked list:

  1. line* initLine(line * head){
  2. head=(line*)malloc(sizeof(line));//Create the first node of the linked list (head node)
  3. head->prior=NULL;
  4. head->next=NULL;
  5. head->data=1;
  6. line * list=head;
  7. for (int i=2; i<=3; i++) {
  8. //Create and initialize a new node
  9. line * body=(line*)malloc(sizeof(line));
  10. body->prior=NULL;
  11. body->next=NULL;
  12. body->data=i;
  13. list->next=body;//The next pointer of the direct predecessor node points to the new node
  14. body->prior=list;//The new node points to the direct predecessor node
  15. list=list->next;
  16. }
  17. return head;
  18. }


We can try to output the created double linked list in the main function, the C language code is as follows:

  1. #include <stdio.h>
  2. #include <stdlib.h>
  3. //node structure
  4. typedef struct line{
  5. struct line * prior;
  6. int data;
  7. struct line * next;
  8. }line;
  9. //Create function of double linked list
  10. line* initLine(line * head);
  11. //Function to output double linked list
  12. void display(line * head);
  13. int main() {
  14. //Create a head pointer
  15. line * head=NULL;
  16. //Call the linked list creation function
  17. head=initLine(head);
  18. //Output the created linked list
  19. display(head);
  20. //Display the advantages of double linked list
  21. printf("The immediate predecessor of the fourth node in the linked list is: %d", head->next->next->next->prior->data);
  22. return 0;
  23. }
  24. line* initLine(line * head){
  25. //Create a head node, the head pointer of the linked list is head
  26. head=(line*)malloc(sizeof(line));
  27. //Initialize the node
  28. head->prior=NULL;
  29. head->next=NULL;
  30. head->data=1;
  31. //Declare a pointer to the head node to facilitate adding newly created nodes to the linked list later
  32. line * list=head;
  33. for (int i=2; i<=5; i++) {
  34. //Create a new node and initialize it
  35. line * body=(line*)malloc(sizeof(line));
  36. body->prior=NULL;
  37. body->next=NULL;
  38. body->data=i;
  39. //The new node establishes a relationship with the last node of the linked list
  40. list->next=body;
  41. body->prior=list;
  42. //list always points to the last node in the linked list
  43. list=list->next;
  44. }
  45. //return the newly created linked list
  46. return head;
  47. }
  48. void display(line * head){
  49. line * temp=head;
  50. while (temp) {
  51. //If this node has no successor node, it means this node is the last node of the linked list
  52. if (temp->next==NULL) {
  53. printf("%d\n",temp->data);
  54. }else{
  55. printf("%d <-> ",temp->data);
  56. }
  57. temp=temp->next;
  58. }
  59. }

Program running result:

1 <-> 2 <-> 3 <-> 4 <-> 5
The immediate predecessor of the fourth node in the linked list is: 3

 

Static linked list and its creation (C language implementation)

In the section " Advantages and Disadvantages of Sequence List and Linked List ", we have learned about the respective characteristics of the two storage structures. Then, is there a storage structure that can combine respective advantages of the sequence list and the linked list , so that elements can be accessed quickly and Can quickly add or delete data elements .

Static linked list is also a kind of linear storage structure . It takes into account the advantages of both sequential list and linked list. It can be regarded as an upgraded version of sequential list and linked list.

Use a static linked list to store data, all data is stored in an array (same as a sequential table), but the storage location is random, and the "one-to-one" logical relationship between data is passed through an integer variable (called "cursor", and pointer Similar in function) to maintain (similar to linked list).

For example, the process of using static linked list storage  {1,2,3} is as follows:

create a sufficiently large array, assuming the size is 6, as shown in Figure 1:


 

empty array


Figure 1 Empty array


Then, when storing the data in the array, each data element is equipped with an integer variable, which is used to indicate the subscript of the position in the array where the direct successor element of each element is located, as shown in Figure 2:


 

Static linked list stores data


Figure 2 Static linked list stores data

Usually, the static linked list will place the first data element in the position of the array subscript 1 (a[1]).

In Figure 2,

Starting from the data element 1 stored in a[1], through the stored cursor variable 3, the direct successor element 2 of element 1 can be found in a[3];

Through the cursor variable 5 stored in the element a[3], the direct successor element 3 of the element 2 can be found in a[5], and this loop process will end until the cursor variable of an element is 0 (because a[0] does not store by default data element) .

Similar to Figure 2, the storage structure that stores data with a linear relationship in the way of "array + cursor" is a static linked list.

Nodes in a static linked list

Through the above study, we know that storing data elements in a static linked list also requires a custom data type, which must contain at least the following two parts of information:

  • Data field: used to store the value of the data element;
  • Cursor: In fact, it is an array subscript, indicating the position in the array where the immediate successor element is located;


Therefore, the composition of the nodes in the static linked list is implemented in C language as:

 
 
  1. typedef struct {
  2. int data;//data field
  3. int cur;//cursor
  4. }component;

standby list

The static linked list shown in Figure 2 is not complete enough. In the static linked list, in addition to the linked list formed by the data itself through the cursor, there needs to be a linked list connecting each free position, which is called a standby linked list.

The role of the standby linked list is to reclaim unused or previously used (currently unused) storage space in the array for later use. That is to say, there are two linked lists in the physical space applied by the array for the static linked list, one linking the data, and the other linking the unused space in the array.

Usually, the head of the standby linked list is located at the position of the array subscript 0 (a[0]), while the head of the data linked list is located at the position of the array subscript 1 (a[1]).

The advantage of setting a standby linked list in the static linked list is that it can clearly know whether there is a free position in the array, so that the data linked list can be used when adding new data. For example, if there is data stored in the position where the subscript of the array is 0 in the static linked list, it proves that the array is full.

For example, using static linked list storage {1,2,3}, assuming an array a with a length of 6 is used, the storage state may be as shown in Figure 3:


 

Standby Linked List and Data Linked List


Figure 3 standby linked list and data linked list


In Fig. 3, a[0], a[2] and a[4] are connected in sequence on the standby linked list, and a[1], a[3] and a[5] are connected in sequence on the data linked list.

Create a static linked list

Assuming a static linked list (array length of 6) is used for storage  {1,2,3}, the following stages are required:

  1. Before the data linked list is initialized, all positions in the array are free, so they should be linked on the standby linked list, as shown in Figure 4:


     

    The state of the static linked list before storing data


    Figure 4 The state of the static linked list before storing data


    When adding data to the static linked list, it is necessary to remove nodes from the standby linked list in advance for the use of new data.

    The easiest way to remove a node from the standby linked list is to remove the direct successor node of a[0]; similarly, adding an idle node to the standby linked list is to add a new direct successor node of a[0]. Because a[0] is the first node of the backup list, we know its position, and it is relatively easy to operate its direct successor nodes without traversing the backup list, and the time complexity is  O(1).

  2. On the basis of Figure 4, the process of adding element 1 to the static linked list is shown in Figure 5:


     

    Add element 1 to the static linked list


    Figure 5 Adding element 1 to the static linked list

  3. On the basis of Figure 5, the process of adding element 2 is shown in Figure 6:


     

    Continue adding elements to the static linked list 2


    Figure 6 Continue to add elements in the static linked list 2

  4. On the basis of Figure 6, continue to add element 3, the process is shown in Figure 7:


     

    Continue to add elements in the static linked list 3


    Figure 7 Continue to add elements in the static linked list 3

Thus, the static linked list is created.

The C language implementation code for creating a static linked list is given below:

  1. #include <stdio.h>
  2. #define maxSize 6
  3. typedef struct {
  4. int data;
  5. int cur;
  6. }component;
  7. //Link all components in the structure array to the standby list
  8. void reserveArr(component *array);
  9. //Initialize the static linked list
  10. int initArr(component *array);
  11. // output function
  12. void displayArr(component * array,int body);
  13. //The function to remove the free node from the standby list
  14. int mallocArr(component * array);
  15. int main() {
  16. component array[maxSize];
  17. int body=initArr(array);
  18. printf("Static linked list is:\n");
  19. displayArr(array, body);
  20. return 0;
  21. }
  22. //Create backup list
  23. void reserveArr(component *array){
  24. for (int i=0; i<maxSize; i++) {
  25. array[i].cur=i+1;//Link each array component together
  26. array[i].data=-1;
  27. }
  28. array[maxSize-1].cur=0;//The cursor value of the last node of the linked list is 0
  29. }
  30. // extract allocated space
  31. int mallocArr(component * array){
  32. //If the standby linked list is not empty, return the allocated node subscript, otherwise return 0 (when the last node is allocated, the cursor value of the node is 0)
  33. int i=array[0].cur;
  34. if (array[0].cur) {
  35. array[0].cur=array[i].cur;
  36. }
  37. return i;
  38. }
  39. //Initialize the static linked list
  40. int initArr(component *array){
  41. reserveArr(array);
  42. int body=mallocArr(array);
  43. //Declare a variable, use it as a pointer, point to the last node of the linked list, because the linked list is empty, so it coincides with the head node
  44. int tempBody=body;
  45. for (int i=1; i<4; i++) {
  46. int j=mallocArr(array);//Take out free components from the standby list
  47. array[tempBody].cur=j;//Link the free component of the application behind the last node of the linked list
  48. array[j].data=i;//Initialize the data field of the newly applied component
  49. tempBody=j;//Move the pointer to the last node of the linked list backward
  50. }
  51. array[tempBody].cur=0;//The pointer of the last node of the new linked list is set to 0
  52. return body;
  53. }
  54. void displayArr(component * array,int body){
  55. int tempBody=body;//tempBody is ready for traversal
  56. while (array[tempBody].cur) {
  57. printf("%d,%d ",array[tempBody].data,array[tempBody].cur);
  58. tempBody=array[tempBody].cur;
  59. }
  60. printf("%d,%d\n",array[tempBody].data,array[tempBody].cur);
  61. }

The code output is:

The static linked list is:
-1,2 1,3 2,4 3,0

Tip, this code creates a static linked list with a head node, so the first output "-1,2" represents the head node (-1 means no data is stored here), and its head node (storage element 1 nodes) in the array array[2].

Establishment of Circular Linked List (Joseph Ring) and Its Implementation in C Language

Whether it is a static linked list or a dynamic linked list , sometimes when solving specific problems, we need to slightly adjust its structure. For example, the two ends of the linked list can be connected to make it a circular linked list, usually called a circular linked list.

As its name implies, just point the pointer of the last node in the list to the head node, and the linked list can form a ring, as shown in Figure 1.

Figure 1 circular linked list

It should be noted that although the circular linked list is circular, it is still a linked list in essence, so in the circular linked list, the head pointer and head node can still be found. Compared with ordinary linked lists, the only difference between circular linked lists and ordinary linked lists is that circular linked lists are connected , and everything else is exactly the same.

Circular linked list implements Joseph ring

The Joseph ring problem is a classic circular linked list problem

The meaning of the title is: Known n people (represented by numbers 1, 2, 3, ..., n) sit around a round table, start counting clockwise from the person numbered k, and the person who counts to m goes out. row; his next person starts from 1 again, and starts counting clockwise, and the person who counts to m goes out again; repeat in turn until there is only one person left on the round table .

As shown in Figure 2, assuming that there are 5 people around the circle at this time, it is required to count clockwise from the person numbered 3, and the person who counts to 2 is out:
 

Circular linked list implements Joseph ring


Figure 2 Circular linked list realizes Joseph ring


The listing order is as follows:

  • The person numbered 3 starts counting 1, then 4 counts 2, so 4 goes out first;
  • After 4 is dequeued, count 1 from 5, and 1 counts 2, so 1 is dequeued;
  • After 1 is dequeued, count 1 from 2, and count 2 from 3, so 3 is dequeued;
  • After 3 is dequeued, count 1 from 5, and count 2 from 2, so 2 is dequeued;
  • In the end only 5 of us are left, so 5 wins.

There are many variants of the Joseph ring problem, such as turning from clockwise to counterclockwise , etc. Although the details of the problem have many variables, the central idea of ​​​​solving the problem is the same, that is, using a circular linked list.

Through the above analysis, we can try to write C language code, the complete code is as follows:

 
 
  1. #include <stdio.h>
  2. #include <stdlib.h>
  3. typedef struct node{
  4. int number;
  5. struct node * next;
  6. }person;
  7. person * initLink(int n){
  8. person * head=(person*)malloc(sizeof(person));
  9. head->number=1;
  10. head->next=NULL;
  11. person * cyclic=head;
  12. for (int i=2; i<=n; i++) {
  13. person * body=(person*)malloc(sizeof(person));
  14. body->number=i;
  15. body->next=NULL;
  16. cyclic->next=body;
  17. cyclic=cyclic->next;
  18. }
  19. cyclic->next=head;//end to end connection
  20. return head;
  21. }
  22. void findAndKillK(person * head,int k,int m){
  23. person * tail=head;
  24. //Find the previous node of the first node of the linked list to prepare for the delete operation
  25. while (tail->next!=head) {
  26. tail=tail->next;
  27. }
  28. person * p=head;
  29. // find the person with number k
  30. while (p->number!=k) {
  31. tail=p;
  32. p=p->next;
  33. }
  34. //Starting from the person numbered k, only when p->next==p is met, it means that all the numbers in the linked list are listed except for the p node.
  35. while (p->next!=p) {
  36. //Find the person who reported m from p to number 1, and also know the position tail of the number of m-1de people, so that it is convenient to delete.
  37. for (int i=1; i<m; i++) {
  38. tail=p;
  39. p=p->next;
  40. }
  41. tail->next=p->next;//Pick off the p node from the linked list
  42. printf("The number of the listed person is: %d\n", p->number);
  43. free(p);
  44. p=tail->next;//Continue to use the p pointer to point to the next number of the dequeued number, and the game continues
  45. }
  46. printf("The number of the listed person is: %d\n", p->number);
  47. free(p);
  48. }
  49. int main() {
  50. printf("Enter the number of people on the round table n:");
  51. int n;
  52. scanf("%d",&n);
  53. person * head=initLink(n);
  54. printf("Start counting from the kth person (k>1 and k<%d):",n);
  55. int k;
  56. scanf("%d",&k);
  57. printf("The person who counted to m: ");
  58. int m;
  59. scanf("%d",&m);
  60. findAndKillK(head, k, m);
  61. return 0;
  62. }

Output result:

Enter the number of people on the round table n: 5
and start counting from the kth person (k>1 and k<5): 3
People counting to m are listed: 2 The number of
the person listed is: 4
The number of the person listed is: 1
The number of the listed person is: 3
The number of the listed person is: 2
The number of the listed person is: 5

The person standing last is the winner. Of course, you can also improve the program so that when the last person is found, output information about the person's victory.

Summarize

The only difference between a circular linked list and a dynamic linked list is its end-to-end connection, which is also destined to traverse the linked list when the circular linked list is used.

In the process of traversal, it is especially important to note that although the circular linked list is connected end to end, it does not mean that the linked list does not have the first node and the last node. Therefore, do not change the pointing of the head pointer at will.

Guess you like

Origin blog.csdn.net/qq_38998213/article/details/132306491