Summary of Array and Chain List Properties

Keywords: data structure leetcode linked list

1. Storage of data structures

There are only two ways to store data structures: array (sequential storage) and chain table (chain storage).

What do you mean by this sentence? Aren't there any data structures like hashes, stacks, queues, heaps, trees, graphs, etc?

When we analyze problems, we must have recursive thinking, from top to bottom, from abstraction to concrete.So many of those listed above belong to "superstructure", while arrays and lists are the "structural basis".Because these diverse data structures, at their source, are special operations on chained lists or arrays, API s are different.

For example, two data structures, Queue and Stack, can be implemented using either a chain table or an array.To implement with arrays, you have to deal with the problem of expansion and scaling.Using a chain table, there is no problem, but more memory is needed to store node pointers.

There are two representations of "graph". The adjacency table is a chain table and the adjacency matrix is a two-dimensional array.Adjacency matrix judges connectivity quickly and can perform matrix operations to solve some problems, but it takes up space if the graph is sparse.Adjacency tables save space, but many operations are certainly more efficient than adjacency matrices.

A Hash list is a hash function that maps keys to a large array.Moreover, for the hash conflict resolution method, the zipper method requires the chain table characteristics, is simple to operate, but requires additional space to store pointers;Linear profiling requires array characteristics for continuous addressing, does not require pointer storage, but is slightly more complex.

Trees, implemented as arrays, are heaps because heaps are a fully binary tree. Storing in arrays does not require a node pointer and is easy to operate.Chain-list implementations are the very common type of "tree" that is not necessarily a complete binary tree and therefore not suitable for array storage.For this reason, on top of this chain list "tree" structure, a variety of clever designs have been derived, such as binary search tree, AVL tree, red-black tree, interval tree, B tree and so on, to deal with different problems.

Friends who know about Redis databases may also know that Redis provides several common data structures, such as lists, strings, collections, and so on, but for each data structure, there are at least two underlying storage methods that make it easier to use the appropriate storage method for the actual situation in which the data is stored.

To sum up, there are so many kinds of data structures that you can even invent your own, but there are no non-array or chain tables in the underlying storage. The advantages and disadvantages of the two are as follows:

Arrays, because of their compact, continuous storage, can be accessed randomly, find corresponding elements quickly through the index, and save storage space relatively.However, because of continuous storage, memory space must be allocated enough at one time, so if the array is to expand, it needs to reallocate a larger space, and then copy all the data to the past, time complexity O(N);And if you want to insert and delete in the middle of the array, you must move all the data that follows each time to maintain continuity, time complexity O (N).

Since the elements of the chain table are not contiguous but point to the position of the next element by the pointer, there is no problem of expanding the array.If you know the precursor and backer of an element, the action pointer can delete it or insert a new element, time complexity O(1).However, because the storage space is not continuous, you cannot calculate the address of the corresponding element from an index, so you cannot access it randomly.And since each element must store a pointer to the position of the front and back elements, it consumes relatively more storage space.

2. Basic operation of data structure

For any data structure, its basic operation is no more than traversal + access, and more specifically: add, delete, check and change.

There are many kinds of data structures, but they exist for the purpose of adding, deleting, and altering as efficiently as possible in different application scenarios.Isn't that the mission of data structures?

How do I traverse + access?Still, at the highest level, there are no two forms of traversal + access for various data structures: linear and non-linear.

Linearity is represented by for/while iteration and non-linearity is represented by recursion.As a further step, there are several frameworks:

Array traversal framework, typical linear iteration structure:

void traverse(int[] arr) {
    for (int i = 0; i < arr.length; i++) {
        // Iterative access arr[i]
    }
}

Chain list traversal framework with both iterative and recursive structure:

/* Basic Single Chain List Nodes */
class ListNode {
    int val;
    ListNode next;
}

void traverse(ListNode head) {
    for (ListNode p = head; p != null; p = p.next) {
        // Iterative access to p.val
    }
}

void traverse(ListNode head) {
    // Recursive access to head.val
    traverse(head.next);
}

Binary tree traversal framework, typical non-linear recursive traversal structure:

/* Basic Binary Tree Node */
class TreeNode {
    int val;
    TreeNode left, right;
}

void traverse(TreeNode root) {
    traverse(root.left);
    traverse(root.right);
}

Look at the recursive traversal of binary trees and the recursive traversal of chain tables. Are they similar?Look at the binary tree structure and the single-chain table structure again. Are they similar?If there were more forks, would you traverse the N-fork tree?

The binary tree framework can be extended to an N-tree traversal framework:

/* Basic N-Fork Tree Node */
class TreeNode {
    int val;
    TreeNode[] children;
}

void traverse(TreeNode root) {
    for (TreeNode child : root.children)
        traverse(child);
}

The traversal of N-fork trees can also be extended to the traversal of graphs, since graphs are the combination of several N-fork trees.Do you think the diagram might have rings?That's a good idea. Just mark it with a Boolean array visited, so you won't write any code here.

The so-called framework is a set.No matter how you add, delete, or change, these codes are structures that you can never break away from. You can use this structure as an outline. Just add code to the framework according to the specific problem. Here are some examples.

3. Examples of basic operations for C language arrays and chain lists

1. Array Additions and Deletions Check

Array addition and deletion checks are based on traversal and access to arrays, because arrays are stored continuously in memory, so they can be accessed randomly through an index. To traverse array elements, we only need to use a for loop.

The test code is as follows

#include "stdio.h"

int main() {
    //Array capacity 6, used 5
    int arr[6] = {1, 2, 3, 4, 5};
    int temp;

    //Addition, deletion, and alteration checks are based on traversal and access

    //Random access to arrays
    printf("The first element of the array%d", arr[0]);
    printf("The fifth element of the array%d", arr[4]);

    //Because of random access, we can easily modify and query arrays
    //Since the memory space of the array is allocated, adding and deleting arrays is more complex
    //If we want to increase the value of the array, we need to move all the elements that follow the increasing element position one bit backward (if there is enough array space)
    //If you want to delete an element, you need to move all the elements that follow the deleted element forward one bit

    //Array modification, modifying the first element value
    arr[0] = 99;

    //Query of array, query first element value
    temp = arr[0];

    //An increase in the array, for example, if we want to add an element value of 999 after the second element
    for (size_t i = 4; i > 1; i++) {
        arr[i + 1] = arr[i];
    }
    arr[2] = 999;

    //Array deletion, such as when we delete the third element
    for (size_t i = 2; i < 5; i++) {
        arr[i] = arr[i + 1];
    }

}

2. Chain List Additions and Deletions

Chain lists are not stored continuously in memory, so we cannot read them randomly. We usually access them recursively.Recursive methods make it easy to iterate through all nodes.

When we need to access the number of nodes, we need to set some flags so that we can stop before the node when we traverse the chain table, and then add, delete, modify the contents of the node, and so on.

Here is all the test code

#include "stdio.h"
#include "stdlib.h"

//Chain List Node Definition
typedef struct test {
    int data;
    struct test* next;
}TEST;

int main() {
    //Because chained lists are not stored continuously in memory

    //Build an empty list (leading node)
    TEST*  mylist = (TEST*)malloc(sizeof(TEST));

    //Add an element (link list node)
    TEST*  mynode = (TEST*)malloc(sizeof(TEST));
    mynode->data = 1;
    mynode->next = NULL;
    //Add this node to the list of chains
    mylist->next = mynode;
    printf("%d", mylist->next->data);

    //Continue adding an element (node) to the list
    mynode = (TEST*)malloc(sizeof(TEST));
    mynode->data = 2;
    mynode->next =NULL;
    //Add this node to the list of chains
    mynode->next = mylist->next;
    mylist->next = mynode;
    printf("%d", mylist->next->data);

    //Continue adding an element (node) to the list
    mynode = (TEST*)malloc(sizeof(TEST));
    mynode->data = 3;
    mynode->next =NULL;
    //Add this node to the list of chains
    mynode->next = mylist->next;
    mylist->next = mynode;
    printf("%d", mylist->next->data);

    //Both queries and modifications are made through traversal access to the list of chains (the list cannot be accessed randomly)
    //Walk through the list in two ways first
    for(TEST* p = mylist; p->next != NULL; p = p->next) {
        printf("\n%d", p->next->data);
    }

    TEST* pp = mylist;
    while(pp->next) {
        printf("\n%d", pp->next->data);
        pp = pp->next;
    }

    //The second element of the query chain table
    int temp;
    TEST* ppp = mylist;
    for(size_t i = 0; ppp->next != NULL && i < 2 ; ppp = ppp->next, i++) {
    }
    temp = ppp->data;

    //Modify the second element value
    ppp->data = 99;
    for(TEST* p = mylist; p->next != NULL; p = p->next) {
        printf("\n%d", p->next->data);
    }

    //Insert a node before the second node
    mynode = (TEST*)malloc(sizeof(TEST));
    mynode->data = 999;
    mynode->next = NULL;

    TEST* pppp = mylist;
    int i = 1;
    while(i) {
        pppp = pppp->next;
        i--;
    }

    mynode->next = pppp->next;
    pppp->next = mynode;
    for(TEST* p = mylist; p->next != NULL; p = p->next) {
        printf("\n%d", p->next->data);
    }

    //Delete the second node
    //Find the node before the node needs to be deleted
    TEST* ppppp = mylist;
     i = 1;
    while(i) {
        ppppp = ppppp->next;
        i--;
    }
    ppppp->next = ppppp->next->next;
    for(TEST* p = mylist; p->next != NULL; p = p->next) {
        printf("\n%d", p->next->data);
    }

}

4. Summary

  • All the basic properties and operations of arrays and chained lists are summarized above, which is the basis for all the data structures that we will operate on later.
  • The operations on the data structure can be summarized as traversal and access, through which we can add, delete and check the data structure, while different data structures (here, index groups and chain tables) will bring different properties and basic operations.
  • Finally, you must write more code, there is a difference of 180,000 miles between understanding and writing out, so you need to hit more code, so you will have a solid foundation and a solid heart.

Posted by jonex on Thu, 02 Sep 2021 01:02:15 -0700