Data structure - linear table concept and basic operation

Keywords: C C++ data structure linked list

data structure

Basic concept and operation of linear table (I)

Mind map


2.1 definition and basic operation of linear table

2.1.1 definitions

A linear table is a finite sequence of n (n ≥ 0) data elements with the same data type, where n is the table length. When n = 0, the linear table is an empty table. If L is used to name the linear table, it is generally expressed as

L = ( a 1 , a 2 , ... , a i , a i + 1 , ... , a n ) L = (a_1, a_2, ... , a_i, a_i+1, ... , a_n) L=(a1​,a2​,...,ai​,ai​+1,...,an​)

Several concepts:
a i a_i ai , is the "ith" element in the linear table and the bit order in the linear table
a 1 a_1 a1 is the header element; a n a_n an is the footer element.
Except for the first element, each element has and has only one direct precursor; Except for the last element, each element has and has only one direct successor.

2.1.2 basic operation

Initlist: initialize table. Construct an empty linear table L and allocate memory space.

Destroylist: destroy operation. Destroy the linear table and free the memory space occupied by linear table L.

Listinsert (& L, i, e): insert operation. Insert the specified element E at position i in table L.

ListDelete & L, i, & e: delete operation. Delete the element at position i in Table L and return the value of the deleted element with e.

Locaterelem (L, e): find operation by value. Finds an element with a given keyword value in table L.

GetElem(L,i): bitwise lookup operation. Gets the value of the element at position i in table L.

Other common operations:

Length(L) find the table length. Returns the length of linear table L, that is, the number of data elements in L.

PrintList(L) output operation. Output all element values of linear table L in sequence.

Empty(L) empty judgment. If l is an empty table, it returns true; otherwise, it returns false.

2.2 sequential representation of linear table

2.2.1 definitions

Definition: sequential table – realize the sequential storage of linear table by sequential storage. Logically adjacent elements are stored in storage units that are also adjacent in physical locations, and the relationship between elements is reflected by the adjacency relationship of storage units.

1. Static allocation

Allocate continuous storage space for each data element, with the size of MaxSize * sizeof(ElemType)

#include <stdio.h>
#define MaxSize 10 / / define the maximum length
typedef struct SqList
{
    /* data */
    int data[MaxSize]; //Use a static "array" to store data elements
    int length;        //Current length of sequence table
};

void InitList(SqList &L)
{
    L.length = 0; //The initial length of the sequence table is 0;
}

int main()
{
    SqList L; //Declare a sequence table
    InitList(L); //initialization
    for (int i = 0; i < L.length; i++)
    {
        /* code */
        printf("data[%d]=%d\n", i, L.data[i]);
    }
    return 0;
}

2. Dynamic allocation

C language knowledge

1. Malloc is a system function, which is the abbreviation of memory allocate. Where memory means "memory" and allocate means "allocate". As the name suggests, malloc functions

Allocate memory. To call it, you must include the header file < stdlib. H >.

The return value of malloc function is an address, which is the starting address of dynamically allocated memory space. If the function fails to execute successfully, such as insufficient memory space, a null pointer is returned

NULL.

int *p = (int *)malloc(4);

The system is requested to allocate 4 bytes of memory space, return the address of the first byte, and then assign it to the pointer variable P. When malloc is used to allocate dynamic memory, the above pointer variable p is initialized

It's melting.

2. sizeof(x) calculates the length of the variable x.

int *p = malloc(sizeof(int));

The value of sizeof (int) is the number of bytes occupied by int variables, which can well represent the number of bytes occupied by int variables in the current computer. In this way, the portability of writing programs is enhanced. So dynamic

It is best to build as much memory as you need.

3. Free (p) releases the storage space of the variable indicated by pointer p, that is, completely deletes a variable

A pointer variable pointing to dynamic memory was defined earlier p:

int *p = malloc(sizeof*(int));

As mentioned earlier, the dynamically allocated memory space is released manually by the programmer. So how to release it? Use the free function.

The free function has no return value. Its function is to release the memory unit pointed to by the pointer variable p. At this point, the memory unit pointed to by p will be released and returned to the operating system and will no longer be used by it.

The operating system can reassign it to other variables.

It should be noted that freeing does not mean emptying the memory space, but marking the memory space as "available" so that the operating system can reallocate it to other variables when allocating memory.

be careful:

Only dynamically created memory can be released with free, and static memory cannot be released with free. Static memory can only be released by the system.

Code implementation:

#include <stdio.h>
#include <stdlib.h>
#define InitSize 10 / / the initial length of the sequence table
typedef struct SqList
{
    /* data */
    int *data; //Use a static "array" to store data elements
    int MaxSize; //Define maximum length
    int length; //Current length of sequence table
};

void InitList(SqList &L)
{
    //Apply for a continuous piece of storage space with malloc function
    L.data = (int *)malloc(InitSize*sizeof(int));
    L.length = 0;
    L.MaxSize = InitSize;
}

void IncreaseSize(SqList &L, int len) {
    int *p = L.data;
    L.data = (int *)malloc((L.MaxSize + len)*sizeof(int));
    for (int i = 0; i < L.length; i++)
    {
        /* code */
        L.data[i] = p[i];  //Copy data to New Area
    }
    L.MaxSize = L.MaxSize + len; //Increase the maximum length of sequence table by len
    free(p);  //Free up the original memory space
    
}
int main()
{
    SqList L;    //Declare a sequence table
    InitList(L); //initialization
    //Insert element
    IncreaseSize(L, 5);
    return 0;
}

3. Sequence table features

1. Random access, that is, the ith element can be found in O(1) time. (code implementation: data[i-1]; static allocation and dynamic allocation are the same)
2. The storage density is high, and each node only stores data elements
3. It is inconvenient to expand the capacity (even if it is realized by dynamic allocation, the time complexity of expanding the length is relatively high)
4. Insertion and deletion operations are inconvenient, and a large number of elements need to be moved

2.2.2 realization of basic operation

1. Insert

Listinsert (& L, i, e): insert operation. Insert the specified element E at position i in table L.

Code implementation:

#include<stdio.h>
#include<iostream>
#define MaxSize 10
using namespace std;
typedef struct
{
    /* data */
    int data[MaxSize];
    int length;
}SqList;

/**
 * initialization
 */
void InitList(SqList &L)
{
    L.length = 0; //The initial length of the sequence table is 0;
}

/**
 * Insert sequence table
 */
bool ListInsert(SqList &L, int i, int e) {
    //Judge whether the range of i is valid
    if (i < 1 || i > L.length + 1)
    {
        return false;
        /* code */
    }
    //Judge whether the current storage space is full and cannot be inserted
    if (L.length >= MaxSize) 
    {
        /* code */
        return false;

    }
    //Move the i th element and subsequent elements backward
    for (int j = L.length; j >= i; j--)
    {
        /* code */
        L.data[j] = L.data[j - 1];
    }
    //Place e at position i
    L.data[i - 1] = e;
    L.length++;  //Length++
    return true;
    
}

/**
 * Printout
 */ 
void printList(SqList L) {
    cout << "length length = " << L.length << endl;
    cout << "The data is:" << endl;
    for (int i = 0; i < L.length; i++)
    {
        /* code */
        cout << "data[" << i << "] = " << L.data[i] << endl;
        
    }
    
}
int main() {
    SqList L;
    InitList(L);
    //assignment
    for (int i = 0; i < 5; i++)
    {
        /* code */
        L.data[i] = i;
        L.length++;
    }
    printList(L);
    bool flag = ListInsert(L, 2, 6);
    cout << flag << endl;
    printList(L);
    return 0;
}
length length = 5
 The data is:
data[0] = 0
data[1] = 1
data[2] = 2
data[3] = 3
data[4] = 4
1
 length length = 6
 The data is:
data[0] = 0
data[1] = 6
data[2] = 1
data[3] = 2
data[4] = 3
data[5] = 4

Time complexity

1. Best case: the new element is inserted at the end of the table without moving the element

i = n+1, 0 cycles; Best time complexity = O(1)

2. Worst case: when a new element is inserted into the header, all the original n elements need to be moved backward

i = 1, N cycles; Worst time complexity = O(n)

3. Average case: suppose that the probability of inserting a new element into any position is the same, that is, the probability of i = 1,2,3,..., length+1 is p = 1 ( n + 1 ) p = \frac{1}{(n+1)} p=(n+1)1​

i = 1, N cycles; When i = 2, cycle n - 1; i = 3, cycle n-2 times... When i = n + 1, cycle 0 times

flat all Follow ring second number = n p + ( n − 1 ) p + ( n − 2 ) p + ... ... + 1 ⋅ p = n ( n + 1 ) 2 ∗ 1 ( n + 1 ) = n 2 Average number of cycles = np + (n-1)p + (n-2)p +... + 1 ⋅ p = \frac{n(n+1)}{2} * \frac{1}{(n+1)} = \frac{n}{2} Average number of cycles = np+(n − 1)p+(n − 2)p +... + 1 ⋅ p=2n(n+1) * (n+1)1 = 2n

Average time complexity = O(n)

2. Delete

ListDelete & L, i, & e: delete operation. Delete the element at position i in Table L and return the value of the deleted element with e.

Code implementation:

#include<stdio.h>
#include<iostream>
#define MaxSize 10
using namespace std;
typedef struct
{
    /* data */
    int data[MaxSize];
    int length;
}SqList;

/**
 * initialization
 */
void InitList(SqList &L)
{
    L.length = 0; //The initial length of the sequence table is 0;
}

/**
 * Delete sequence table
 */
bool ListDelete(SqList &L, int i, int &e) {
    //Judge whether the range of i is valid
    if (i < 1 || i > L.length + 1)
    {
        return false;
        /* code */
    }
    e = L.data[i - 1];
    //Move the i th element and subsequent elements forward
    for (int j = i; j < L.length; j++)
    {
        /* code */
        L.data[j - 1] = L.data[j];
    }
    L.length--;  //Length--
    return true;
    
}

/**
 * Printout
 */ 
void printList(SqList L) {
    cout << "length length = " << L.length << endl;
    cout << "The data is:" << endl;
    for (int i = 0; i < L.length; i++)
    {
        /* code */
        cout << "data[" << i << "] = " << L.data[i] << endl;
        
    }
    
}
int main() {
    SqList L;
    InitList(L);
    //assignment
    for (int i = 0; i < 5; i++)
    {
        /* code */
        L.data[i] = i;
        L.length++;
    }
    int e;
    printList(L);
    ListDelete(L, 4, e);
    printList(L);
    cout << "The number deleted is " << e << " " << endl;
    return 0;
}

length length = 5
 The data is:
data[0] = 0
data[1] = 1
data[2] = 2
data[3] = 3
data[4] = 4
 length length = 4
 The data is:
data[0] = 0
data[1] = 1
data[2] = 2
data[3] = 4
 The number deleted is 3

Time complexity

1. Best case: delete the footer element without moving other elements

i = n, 0 cycles; Best time complexity = O(1)

2. Worst case: to delete the header element, you need to move all the subsequent n-1 elements forward

i = 1, n-1 cycles; Worst time complexity = O(n)

3. Average case: suppose that the probability of deleting any element is the same, that is, the probability of i = 1,2,3,..., length is p = 1 n p = \frac{1}{n} p=n1​

i = 1, n-1 cycles; When i=2, cycle n-2 times; i=3, cycle n-3 times... When i =n, cycle 0 times

flat all Follow ring second number = ( n − 1 ) p + ( n − 2 ) p + ... ... + 1 ⋅ p = n ( n − 1 ) 2 ∗ 1 n = ( n − 1 ) 2 Average number of cycles = (n-1)p + (n-2)p +... + 1 ⋅ p = \frac{n(n-1)}{2} * \frac{1}{n} = \frac{(n-1)}{2} Average number of cycles = (n − 1)p+(n − 2)p +... + 1 ⋅ p=2n(n − 1) * n1 = 2(n − 1)

Average time complexity = O(n)

3. Search

(1) Search by bit

GetElem(L,i): bitwise lookup operation. Gets the value of the element at position i in table L.

Code implementation:

#include <stdio.h>
#include <stdlib.h>
#include <iostream>
#define InitSize 10
using namespace std;
typedef struct
{
    /* data */
    int *data; //Dynamic allocation
    int length;
    int MaxSize;
} SqList;

/**
 * initialization
 */
void InitList(SqList &L)
{
    L.data = (int *)malloc(InitSize*sizeof(int));
    L.MaxSize = InitSize;
    L.length = 0; //The initial length of the sequence table is 0;
}

/**
 * Bitwise lookup order table
 */
int ListSearchBySite(SqList L,  int i)
{
    cout << "Find page " << i << " The value of bit is:";
    return  L.data[i - 1];
}

/**
 * Printout
 */
void printList(SqList L)
{
    cout << "length length = " << L.length << endl;
    cout << "The data is:" << endl;
    for (int i = 0; i < L.length; i++)
    {
        /* code */
        cout << "data[" << i << "] = " << L.data[i] << endl;
    }
}

int main()
{
    SqList L;
    InitList(L);
    //assignment
    for (int i = 0; i < 5; i++)
    {
        /* code */
        L.data[i] = i;
        L.length++;
    }
    printList(L);
    cout << ListSearchBySite(L, 3) << endl;
    return 0;
}

length length = 5
 The data is:
data[0] = 0
data[1] = 1
data[2] = 2
data[3] = 3
data[4] = 4
 Find the value of bit 3: 2

Time complexity

Since each data element of the sequence table is stored continuously in memory, the i-th element - "random access" feature can be found immediately according to the starting address and the size of the data element

Time complexity = O(n)

(2) Find by value

Locaterelem (L, e): find operation by value. Finds an element with a given keyword value in table L.

Code implementation:

#include <stdio.h>
#include <stdlib.h>
#include <iostream>
#define InitSize 10
using namespace std;
typedef struct
{
    /* data */
    int *data;
    int length;
    int MaxSize;
} SqList;

/**
 * initialization
 */
void InitList(SqList &L)
{
    L.data = (int *)malloc(InitSize * sizeof(int));
    L.MaxSize = InitSize;
    L.length = 0; //The initial length of the sequence table is 0;
}

/**
 * Lookup order table by value
 */
int ListSearchByValue(SqList L, int e)
{
    cout << "The lookup value is " << e << " The location of the is:";
    for (int i = 0; i < L.length; i++)
        if (L.data[i] == e)
            return i + 1; //The value of the element with the index i of the array is equal to e, and its bit order i+1 is returned
    return 0;             //Exit the loop, indicating that the search failed
}


/**
 * Printout
 */
void printList(SqList L)
{
    cout << "length length = " << L.length << endl;
    cout << "The data is:" << endl;
    for (int i = 0; i < L.length; i++)
    {
        /* code */
        cout << "data[" << i << "] = " << L.data[i] << endl;
    }
}

int main()
{
    SqList L;
    InitList(L);
    //assignment
    for (int i = 0; i < 5; i++)
    {
        /* code */
        L.data[i] = i;
        L.length++;
    }
    printList(L);
    cout << ListSearchByValue(L, 1) << endl;
    return 0;
}

length length = 5
 The data is:
data[0] = 0
data[1] = 1
data[2] = 2
data[3] = 3
data[4] = 4
 The location where the lookup value is 1 is: 2

Time complexity

1. Best case: the target element circulates once in the header; Best time complexity = O(1);

2. Worst case: the target element circulates n times at the end of the table; Worst time complexity = O(n);

3. Average case: assuming that the probability of the target element appearing at any position is the same, it is the same 1 n \frac{1}{n} n1​

flat all Follow ring second number = n p + ( n − 1 ) p + ( n − 2 ) p + ... ... + 1 ⋅ p = n ( n + 1 ) 2 ∗ 1 n = ( n + 1 ) 2 Average number of cycles = np + (n-1)p + (n-2)p +... + 1 ⋅ p = \frac{n(n+1)}{2} * \frac{1}{n} = \frac{(n+1)}{2} Average number of cycles = np+(n − 1)p+(n − 2)p +... + 1 ⋅ p=2n(n+1) * n1 = 2(n+1)

Average time complexity = O(n)

A blog post on time complexity analysis is attached
Data structure - Introduction (II) -- time complexity analysis

Posted by Dax on Fri, 19 Nov 2021 14:47:57 -0800