Linear Table--Sequential Table C Language Realization

Explore the charm of data structure from the sequential table

As a type of linear table, sequence table can be said to be the simplest and most basic structure in data structure. Many books use this as the beginning to describe the charm of data structures. As the beginning of the data structure, it is necessary to study this content carefully, so as to develop a good data structure thinking habit . In essence, a data structure is to define a property and maintain this property in subsequent operations to achieve a specific purpose.

Array Talk

The sequence table can actually be regarded as an "advanced" array , because the nature of the array just meets the definition of the sequence table, so before discussing the sequence table, let's take a look at the use of the array and its shortcomings.

array name and pointer to array head

Array names and pointers to array headers can often be confused, but strictly speaking, the two are different.
Let's look at a code:

#include <stdio.h>

int main() {
    
    
    int array[] = {
    
    1,2,3,4,5};
    int *p = array;

    printf("sizeof(array) = %d\n",sizeof(array));
    printf("sizeof(p) = %d\n",sizeof(p));

    return 0;
}

The output result under the 64-bit machine is:

sizeof(array) = 20
sizeof(p)= 8

It can be seen that the array name refers to the entire array, which contains how much memory an array occupies. We can use the array name to get how many elements are in the array. The pointer to the head of the array is essentially a pointer variable, and the number of bytes it occupies is fixed.
insert image description here

iterate over the entire array

If it is only in the scope of the array definition, traversing the array is very simple, and only needs an array name.

#include <stdio.h>

int main() {
    
    
    int array[] = {
    
    1,2,3,4,5};
	int size = sizeof(array) / sizeof(int);
    for (int i = 0; i < size;i++) {
    
    
        printf("%d ",array[i]);
    }
    printf("\n");

    return 0;
}

But if you want to achieve the same function across scopes , you often use functions and pass parameters to achieve it. But in the C language, what we pass is an array name, but the compiler will turn it into an address and pass it, thus losing the length information of the array . So the following approach is obviously wrong:

#include <stdio.h>

void order(int array[]) {
    
    
	int size = sizeof(array) / sizeof(int);
    for (int i = 0; i < size;i++) {
    
    
        printf("%d ",array[i]);
    }
    printf("\n");
}

int main() {
    
    
    int array[] = {
    
    1,2,3,4,5};
    order(array);

    return 0;
}

In order to avoid such errors, a common practice is to pass the length of the array as a parameter to the function. as follows:

#include <stdio.h>

void order(int array[],int size) {
    
    
    for (int i = 0; i < size;i++) {
    
    
        printf("%d ",array[i]);
    }
    printf("\n");
}

int main() {
    
    
    int array[] = {
    
    1,2,3,4,5};
    int size = sizeof(array) / sizeof(int);
    order(array,size);

    return 0;
}

From here we can already find the first shortcoming of the array: the length information of the array will be lost in the parameter passing

Array subscript out of bounds access

Array subscript out of bounds is a very common error. In the C language specification, this is an undefined behavior . Each compiler handles it differently. But one thing is clear, such an operation is very dangerous .

#include <stdio.h>

#define SIZE 5
int main() {
    
    
    int array[SIZE] = {
    
    1,2,3,4,5};

    for (int i = 0;i < SIZE + 1;i++) {
    
    
        printf("%d ",array[i]);
    }
    printf("\n");

    return 0;
}

From the above code, it can be easily seen that when the traversal is executed for the last time, the accessed location is array[5], which obviously means that the subscript is out of bounds, and we don't know the specific value in this piece of memory. But this problem will not be discovered during compilation, and it is even difficult to detect at runtime, so the second deficiency of the array is also coming out: the out-of-bounds access of the array subscript is an undefined behavior, and we cannot effectively protect it .

Invalid data in array

Many times, in order to save effort, we open up a large array in one go, but in actual operation, only a part of it is used (we assume that it must be used from front to back when used), so accessing unused array space is also a dangerous thing. This is actually a hidden danger brought by programming skills.
For example: Suppose we want to record this year’s income by day, we will take the year as a unit and take out a large book to record it directly. Suppose today is April 1. At this time, I want to check the income on May 1. At this time, we can find the bill corresponding to this day on the bill, but we have not recorded this day. All inquiries are possible, but the information in it is invalid.

Problems with arrays

Go through the previous three examples. We have already concluded the defects of ordinary arrays, there are two main points:

  • The length information of the array will be lost in the parameter passing
  • Out-of-bounds access to array subscripts is an undefined behavior, and we cannot effectively protect it

There is another hidden danger brought by programming skills:

  • Some data in the array is invalid

Solve the shortcomings of the array

Now that we clearly know the shortcomings of the array, we can prescribe the right medicine and solve them one by one.
As mentioned above, the solution to the first two problems is to add a size field, so that not only the length of the array can be known, but also the subscript can be avoided.
The hidden dangers caused by programming skills can be solved by adding a length field. This field records the first length data of the array is valid, and the part exceeding the length is invalid data for us.
According to the above analysis, we can start to write code:

#include <stdio.h>

#define SIZE 100

int main() {
    
    
    int array[SIZE] = {
    
    0};
    int size = sizeof(array) / sizeof(int);   //记录array的长度
    int length = 0;                           //记录array中有效数据的个数


    return 0;
}

By using the size and length fields, we can make the operation of the array safer and make up for the inherent shortcomings of the array.
So far, we've actually doneStructural definition of sequence table. The sequence table is actually adding two fields through the array, which achieves the purpose of being safer and more reliable than the array.

Linear Table – Sequential Table

Here it is necessary to clarify the relationship between the linear table and the sequential table .
A linear table is a concept at the logical structure level, which means that the relationship between elements is one-to-one .
The sequence table is a concept at the physical structure level, which means that elements are stored in a continuous memory space , and arrays just have such properties.

Definition of a linear table:

A linear list is also called an ordered list, and each instance of it is an ordered collection of elements. Each instance is of the form ( e 0 , e 1 , e 2 , . . . en − 1 ) (e_0,e_1,e_2,...e_{n-1})(e0,e1,e2,...en1)
where n is a finite natural number,ei e_ieiIs the element of the linear table , i is the element ei e_ieiindex , n is the length of the linear table .
It can be seen that the index is from0start ton-1Finish.

Structural definition of sequence table

The sequential table is actually a linear table implemented with a continuous memory space, and the array is the best choice. In fact, when analyzing the array, we have already described the structure definition of the sequence table, and we encapsulate it into a structure to facilitate subsequent operations.

typedef struct SequentialList{
    
    
    int *array;      //数组
    int size;        //记录数组的长度
    int length;      //记录array中有效数据的个数
}SequentialList; 

Operations related to the sequence table

The basic operations of the sequence table are:

  • Create sequence table
  • Destruction Sequence Table
  • null operation
  • Find an element by a given index
  • Insert an element at a given index
  • delete an element by a given index
  • Iterate over all elements in the output sequence table

etc. Next, we will implement them one by one.
Create and destroy sequence tables
Because the C language does not have a GC mechanism, these two operations often appear in pairs, and we implement them together.
Create a sequence table
Before creating, we need to know clearly which quantities we need to initialize and what their values ​​are.
It is not difficult to imagine that the length of the array can be customized, we need to control it by passing parameters, and need to open up corresponding memory space for it. The size field records the length of the array, which can also be determined. In the initialized sequence table, the effective number of elements is 0, and the value of length is determined.

SequentialList *createSequentialList(int _size) {
    
    
    SequentialList *sl = (SequentialList*)malloc(sizeof(SequentialList));
    sl->array = (int*)malloc(sizeof(int) * _size);
    sl->size = _size;
    sl->length = 0;
    return sl;
}

Destruction Sequence Table
The operation of destruction is very simple, which is to reclaim the memory on the heap to prevent memory leaks.

void destroySequentialList(SequentialList *sl) {
    
    
    if (sl == NULL) return;

    free(sl->array);   //回收数组所占内存
    free(sl);          //回收顺序表对象的内存

    return;
}

null operation
The null judgment operation is actually an auxiliary operation. We only need to know whether the current number of valid data is 0 or not.

int is_empty(SequentialList *sl) {
    
    
    if (sl == NULL) return -1;
    return sl->length == 0;
}

Find an element by a given index
It is enough to return the elements according to the subscript, but it should be noted that the valid range of the data index should be from 0 to length - 1 .

int getElement(SequentialList *sl,int index) {
    
    
    if (sl == NULL) return -1;
    if (index < 0 || index > sl->length) return -1;   

    return sl->array[index];
}

Insert an element at a given index
It is also necessary to pay attention to the legal value of the index, which should be 0 to length (because it is an insertion, it is reasonable to insert it at the end). At the same time, the insertion operation needs to make all the values ​​behind the index one bit backward in order, so that there are empty positions for insertion. And the order of the next bit should be the last bit to start moving .
insert image description here

int insert(SequentialList *sl,int index,int element) {
    
    
    if (sl == NULL) return 0;
    if (index < 0 || index > sl->length) return 0;
    if (sl->length == sl->size) return 0;

    for (int i = sl->length;i > index; i--) {
    
    
        sl->array[i] = sl->array[i - 1];
    }
    sl->array[index] = element;
    sl->length++;

    return 1;
}

delete an element by a given index
The technique is similar to inserting, the difference is that this time the moving direction of the element starts from the last element of the index , and moves forward one bit uniformly.
insert image description here

int erase(SequentialList *sl,int index) {
    
    
    if (sl == NULL) return 0;
    if (index < 0 || index > sl->length) return 0;

    for (int i = index + 1; i < sl->length; i++) {
    
    
        sl->array[i - 1] = sl->array[i]; 
    }
    sl->length--;

    return 1;
}

Iterate over all elements in the output sequence table

void display(SequentialList *sl) {
    
    
    if (sl == NULL) return;
    if (is_empty(sl)) return;

    for(int i = 0; i < sl->length; i++) {
    
    
        printf("%d ",sl->array[i]);
    }
    printf("\n");

    return;
}

The overall implementation of the sequence table

#include <stdio.h>
#include <stdlib.h>

//顺序表结构定义
typedef struct SequentialList{
    
    
    int *array;      //数组
    int size;        //记录数组的长度
    int length;      //记录array中有效数据的个数
}SequentialList; 

//创建顺序表   
SequentialList *createSequentialList(int _size) {
    
    
    SequentialList *sl = (SequentialList*)malloc(sizeof(SequentialList));
    sl->array = (int*)malloc(sizeof(int) * _size);
    sl->size = _size;
    sl->length = 0;
    return sl;
}

//销毁顺序表
void destroySequentialList(SequentialList *sl) {
    
    
    if (sl == NULL) return;

    free(sl->array);   //回收数组所占内存
    free(sl);          //回收顺序表对象的内存

    return;
}

//判空操作
int is_empty(SequentialList *sl) {
    
    
    if (sl == NULL) return -1;
    return sl->length == 0;
}

//按一个给定索引查找一个元素
int getElement(SequentialList *sl,int index) {
    
    
    if (sl == NULL) return -1;
    if (index < 0 || index > sl->length) return -1;   

    return sl->array[index];
}

//按一个给定索引插入一个元素
int insert(SequentialList *sl,int index,int element) {
    
    
    if (sl == NULL) return 0;
    if (index < 0 || index > sl->length) return 0;
    if (sl->length == sl->size) return 0;

    for (int i = sl->length;i > index; i--) {
    
    
        sl->array[i] = sl->array[i - 1];
    }
    sl->array[index] = element;
    sl->length++;

    return 1;
}

//按一个给定索引删除一个元素
int erase(SequentialList *sl,int index) {
    
    
    if (sl == NULL) return 0;
    if (index < 0 || index > sl->length) return 0;

    for (int i = index + 1; i < sl->length; i++) {
    
    
        sl->array[i - 1] = sl->array[i]; 
    }
    sl->length--;

    return 1;
}

//遍历输出顺序表中的全部元素
void display(SequentialList *sl) {
    
    
    if (sl == NULL) return;
    if (is_empty(sl)) return;

    for(int i = 0; i < sl->length; i++) {
    
    
        printf("%d ",sl->array[i]);
    }
    printf("\n");

    return;
}

int main() {
    
    
    SequentialList *sl = createSequentialList(100);           //创建顺序表
    
    for (int i = 0; i < 10; i++) {
    
      
        if (!insert(sl,i,i))                                  //按一个给定索引插入一个元素
            return -1;                                        //插入失败
    }
    display(sl);                                              //遍历输出顺序表中的全部元素

    for (int i = 0;i < 5; i++) {
    
    
        if (!erase(sl,0))                                     //按一个给定索引删除一个元素
            return -1;                                        //删除失败
    }
    display(sl);                                              //遍历输出顺序表中的全部元素

    for (int i = 0; i < sl->length; i++) {
    
    
        printf("索引%d对应的数据为:%d\n",i,getElement(sl,i)); //按一个给定索引查找一个元素 
    }

    destroySequentialList(sl);                                //销毁顺序表
    return 0;
}

Advantages and disadvantages of sequence table

Because the sequential table is implemented by continuous memory space, its advantages and disadvantages are obvious.
Advantages : fast search operation, subscript index between, time complexity O(1)
Disadvantages : slow insertion and deletion operations, need to traverse the entire array, time complexity O(n)

postscript

The defects of arrays in C language have actually been eliminated in many languages. Arrays in languages ​​such as Java and C# have length and other information. But this does not affect our sequence table learning. In the final analysis, data structure is a kind of thinking logic . Once we understand the meaning of a certain structure and its practical use, we can use it in the right place and play its real role.

Guess you like

Origin blog.csdn.net/weixin_43003108/article/details/113732423