Sequence table (data structure) --- line up!

Table of contents

Foreword:

1. Properties of linear tables

2. Static array or dynamic array

2.1 Static array

2.2 Dynamic array

3. The creation of the structure

4* Detailed explanation of interface functions

4.1 Initialize the structure

4.2 tail plug

4.3 Print data

4.4 Destroy the created heap space after use up

4.5 Tail deletion

4.6 plug

4.7 header deletion

4.8 Search

4.9 Insert at any position

4.10 Delete anywhere 


❤ Blogger CSDN: Ah Su wants to learn

    ▶Column Category: Data Structure

  Learning data structure is an interesting thing. I hope that readers can really feel the relationship between data in my blog post. When operating data elements, they can have a clear idea and a picture in their mind! 


Foreword:

  In this section, we will learn and implement the simplest sequence table in the linear structure. Since the blogger uses C language to implement it, readers must understand and master the knowledge of the three parts of C language structure, pointers, and memory management . Look at the code to understand, understand, and believe in your own strength!

1. Properties of linear tables

  Data is stored in memory in two structures, logical structure and physical structure . The sequence table is the linear structure in the logical structure + the sequential structure in the physical structure .

  Properties of linear structures: Linear means that the relationship between data in memory appears as a line .

  Sequential structure requirements: The sequential structure requires that the data be in order, and the data elements are next to each other, just like queuing .

  The essence of the sequence table: After reading the linear structure and the sequential structure, is there any inspiration in your mind? Yes, the essence of the sequence table is an array . This array is different from ordinary arrays. Our ordinary arrays can perform the following operations :

#include <stdio.h>

int main()
{
    int arr[10] = {0};
    arr[0] = 1;
    arr[5] = 10;
    arr[9] = 20;
    return 0;
}

 If we want to put 3 integer values ​​into an array, ordinary arrays can put these 3 integer values ​​anywhere in the array ; for example, the first element, the sixth element, and the last element of the array The elements are assigned the values ​​1, 10, and 20 respectively . However, to store these three values ​​in the sequence table, they can only be stored in the element positions with subscripts 0, 1, and 2 in order .

2. Static array or dynamic array

2.1 Static array

  A static array means that we determine the size of the array from the beginning, and we cannot change how much the array stores at runtime. So here's the question:

  • When we used a static array to open up space, we gave space for 100 elements, but the actual situation needs to store 101 elements, but we can't save them, and the array is full
  • So I decided to give a larger array and create an array that can store 1000 elements, but in reality, only 200 elements need to be stored, which caused unnecessary waste.

  Summary: The disadvantage of static arrays is that if the array space is too small, it is not enough, and if it is too large, it cannot be used up .

2.2 Dynamic array

  Therefore, we choose to open up an array of appropriate size according to the specific storage situation, and dynamic arrays can be realized .

  The dynamically opened space is opened on the heap area, and the realloc function will be used to open up the space on the heap area. Let's describe the function of this function here. The original intention of realloc is to add space, and malloc is a pure function to realize the opening of space, but realloc is more powerful .

The function of realloc
situation: Function:
The pointer to the receiving address is a null pointer Equivalent to malloc to open up heap space
The heap space pointed to by the pointer is enough to append In-place expansion, no need to change the memory location
The heap space pointed to by the pointer is not enough to append Move the original memory space data to the expanded memory

    I believe that students who have never learned memory management look at a loss, with question marks above their heads. Look at the picture to understand:

  • When the pointer of the receiving address is not a null pointer :

  • When the heap space pointed to by the pointer is sufficient for appending :

  • When the heap space pointed to by the pointer is not enough to add :

  In this way, the array is given a smaller space at the beginning, but the space is not enough, and the dynamic array is realized by increasing the capacity with the help of realloc. Generally, each time the capacity is increased, the new capacity is twice the original capacity .

3. The creation of the structure

  The progress is a bit slow, let's look at the code directly:

  1. Static sequence table

  Use the macro (N) to represent the number of array elements. After that, if you need to change the number of arrays, you don’t need to change them one by one, you can replace them all by changing N; the same is true for changing the array element type of the sequence table to SLDataType ;

  2. Dynamic sequence table

  In fact, the creation of dynamic arrays is still the same as static arrays, such as changing the name of struct SeqList to SL, and using SLDataType as the element type . The main difference is that pointers are used to manage the created array space, and an additional capacity variable indicates the maximum capacity of the array .

  Supplement: The importance of sub-module writing

  1. The test.c file is a file used to test the function of SeqList.
  2. SeqList.h is used to declare interface functions, as well as some macros, library function header files used by programs, etc.
  3. SeqList.c is used to implement interface functions.

  Supplement: Using the macro #pragma once can prevent the header file from being included multiple times ; when we lead the header file, the compiler will copy the contents of the header file and put it in our program. If we quote it multiple times, there will be A lot of repetitive code, this macro is good enough to prevent header files from being referenced multiple times .

  Okay, the premise is over, let's get to the point. Learn ideas and implement interface functions.

4* Detailed explanation of interface functions

4.1 Initialize the structure

  Remember that there are three structure members in the structure , a is used to point to the heap array, size is used to indicate how many data are in the array, and capacity is used to indicate the number of data that the array can store .

  Next, we will no longer write what the structure is, but just write the interface function, so as not to take up too much space. 

//初始化结构体
void SeqListInit(SL* ps)
{
	ps->a = NULL;
	ps->size = ps->capacity = 0;
}

int main()
{
    SL s1;
    SeqListInit(&s1)
    
}

  Seeing the picture below, I believe that readers who don't understand pointers and structures very well can try to understand it. 

4.2 tail plug

void SeqListPushBack(SL* ps, SLDataType x)
{
    //检查容量
    if(ps->size == ps->capacity)
    {
        //为了防止初始化capacity为0,每次乘二也为0,所以判断capacity是否为0,为零先赋值成4。
        int newcapacity = ps->capacity == 0 ? 4: capacity*2;
        //注意是SLDataType*,返回这块空间的首元素地址,是SLDataType类型。
        SLDataType* tmp = (SLDataType*)realloc(ps->a, newcapacity*sizeof(SLDataType));
        //判断realloc成不成功,不成功realloc返回NULL
        if(tmp == NULL)
        {
            printf(realloc 失败\n);
            //系统函数exit,执行到exit整个程序直接结束,-1是一个错误码而已。
            exit(-1);
        }
        //将新空间的地址交给a管理
        ps->a = tmp;
        //更新capacity的容量
        ps->capacity = newcapacity;
    }
    //尾部加元素,size是数组最后一个数据下一个位置的下标
    ps->a[ps->size] = x;
    size++;
}

  It is not difficult to plug in the tail: take a look at the picture below

   One thing that needs extra attention is: every time data is inserted, the number of data increases. We need to consider whether the size will be equal to the capacity. Once it is equal, the capacity will be expanded. Our previous capacity check is to do this .

4.3 Print data

  Now that some data has been inserted through tail insertion, we need to see if it is successful, and print out the contents of the array to see.

void SeqListPrint(SL* ps)
{
    for(int i = 0; i < ps->size; i++)
    {
        printf("%d ", ps->a[i]);
    }
    printf("\n");
}

  Printing is also very simple~, use the VS compiler to test it out and see!

4.4 Destroy the created heap space after use up

void SeqListDestory(SL* ps)
{
    free(ps->a);
    ps->a = NULL;
    //可以连续赋值
    ps->size = ps->capacity = 0;
}

  Supplement : If a heap space is not released, it will cause a memory leak, resulting in less and less memory space for the program, because the unreleased heap space can neither be used by us (the pointer to it has been destroyed) , the operating system can not recycle . Therefore, as long as the space is dynamically opened up, it must be released in the end .

4.5 Tail deletion

void SeqListPopBack(SL* ps)
{
    //不动于声的方式,当size不符合条件是,不执行就是了,
    //assert是只要有触碰的倾向,直接报错,可以看下面另一种方式的解释。
    if(ps->size > 0)
    {
         ps->size--;    
    }
   
}

  When deleting the tail, let size-- be fine . Because the size identifies the number of valid data in the sequence table, when the size is reduced by one, the last data in the sequence table is no longer valid data, and the sequence table cannot be accessed .

  When there is no data, do not continue to decrease the size, because the size represents the subscript outside the array. At this time, it is easy to cause illegal access errors, so add the restriction of size>0 . For example, when the size is 1, delete 1 at the end. At this time, the size meets the conditions and enters if and successfully reduces the size to 0. When calling again, it does not meet the conditions and prevents the size from being reduced.

  Here's another way :

#include <assert.h>
void SeqListPopBcak(SL* ps)
{
    //断言,如果条件为真则相安无事,如果条件为假,直接挂断程序,并报出错误。
    assert(ps->size > 0);
    ps->size--;
}

4.6 plug

  Insert an element at the first position of the array. In order to achieve this, we need to move all the original data back one position .

  Regardless of whether it is head insertion or tail insertion, as long as it is insertion, it must be ensured that the capacity will not be exceeded after inserting data, that is, the code for checking the capacity should also be used in the implementation of the head insertion, so we can encapsulate the code for checking the capacity into a function , just call it.

  Next, see the implementation part of the plug-in :

void SeqListPushFront(SL* ps, SLDataType x)
{
    SeqListCheckCapacity(ps);//检查容量的接口函数
    int end = ps->size-1;
    while(end>=0)
    {
        ps->a[end+1] = ps->a[end];
        end--;
    }
    ps->a[0] = x;
    ps->size++;
}

//用for循环实现移动数据
int end = ps->size-1;
for(int i = end; i >= 0; i--)
{
    ps->a[i+1] = ps->[i];
}
ps->a[0] = x;
ps->size++;

  It is very convenient to pack the code blocks that check the capacity and decide whether to expand the capacity into functions. 

4.7 header deletion

  Head deletion is actually to move all elements after the first data forward by one data position .

void SeqListPopFront(SL* ps)
{
    assert(ps->size > 0);
    int begin = 1;
    while(begin < ps->size)
    {
        ps->a[begin-1] = ps->a[begin];
        ++begin;
    }
    ps->size--;
}

//用for循环实现移动数据
int begin = 1;
for(int i = begin; i < ps->size; i++)
{
    ps->a[i-1] = ps->a[i];
}
ps->size--;

//也可以是
int begin = 0;
for(int i = begin; i < ps->size-1; i++)
{
    ps->a[i] = ps->a[i+1];
}

4.8 Search

  Search is to find a data in the array, find the subscript of the returned data, and return -1 if not found .

int SeqList(SL* ps, SLDataType x)
{
    for(int i = 0; i < ps->size; i++)
    {
        if(ps->a[i] == x)
        {
            return i;
        }
    }
    return -1;
}

4.9 Insert at any position

 For inserting at any position, there is a certain limit on the insertion range ; one is that it cannot be inserted to a place exceeding the size subscript, which can be equal to. Insert at the place where size is the subscript, which is equivalent to tail insertion; the other is that data cannot be inserted at the subscript less than 0 ;

void SeqListInsert(SL* ps, int pos, SLDataType x)//pos是要插入的下标
{
    //温柔的方式
    /*
    if(pos > ps->size || pos < 0)
    {
        printf("下标pos不能插入数据\n");
        return;
    }
    */
    //暴力的方式
    assert(pos <= ps->size && pos>=0);
    SeqListCheckCapacity(ps);
    int end = ps->size-1;
    //包括要插入的位置的数据也给往后挪,也就是end=pos进入循环
    while(end>=pos)
    {
        ps->a[end+1] = ps->a[end];
        --end;
    }
    ps->a[pos] = x;
    ps->size++;
}

//使用for循环挪动数据
int end = ps->size-1;
for(int i = end; i >= pos; i--)
{
    ps->a[i+1] = ps->a[i];
}

  If we want to insert a new data at a certain data position, we can use SeqListFind to calculate the corresponding subscript, and bring it into any inserted interface function .

  Change other functions :

  change plug

 Now that we have realized that we can insert at any position, head insertion is a special case of arbitrary insertion, we can directly lock the insertion position pos to 0, and call the function SeqListInsert(&s1, 0, data); the same is true for tail insertion ;

void SeqListPushFront(SL* ps, SLDataType x)
{
    //一句代码搞定头插
    SeqListInsert(ps, 0, x);

  The tail plug is as follows :

void SeqListPushBack(SL* ps, SLDataType x)
{
    SeqListInsert(ps, ps->size, x);
}

4.10 Delete anywhere 

  The range limit of any deleted position is: the position of pos cannot be less than subscript 0, and the position of pos cannot be greater than size . Here, unlike inserting data, it can be equal to size, because there is no data at the subscript of size .

void SeqListErase(SL* ps, int pos)
{
    assert(ps->size > 0);//保证有数字可以删除
    assert(pos >= 0 && pos < ps->size);
    
    int begin = pos + 1;
    while(begin < ps->size)
    {
        ps->a[begin-1] = ps->a[begin];
        ++begin;
    }
    ps->size--;
}

//用for循环移动数据
int begin = pos;
for(int i = begin; i < ps->size-1; i++)
{
    ps->a[i] = ps->a[i+1];
}
ps->size--

  Same as above: After learning to delete elements at any position, we can modify the head deletion and tail deletion, which are special cases of deletion at any position .

  Header delete :

void SeqListPopFront(SL* ps)
{
    SeqListEras(ps, 0);
}

  Tail delete :

void SeqListPopBack(SL* ps)
{
    SeqLisErase(ps, ps->size-1);
}

  The tail deletion is the last data, and the subscript is naturally size-1 .

  Alright, here we are, the content of the sequence table is finished, it is not easy to update, please give me a thumbs up~


Conclusion: I hope readers can gain something after reading it! Learn more about data structures!

  Readers who do not understand this article, or find errors in the content of the article, please leave a comment below to tell the blogger ~, and you can also make some suggestions for article improvement to the blogger, thank you very much! last of the last!

  ❤Please like, please pay attention, your likes are the driving force for my update, let's make progress together.

Guess you like

Origin blog.csdn.net/muwithxi/article/details/130498641