Variable array and linked list

Keywords: C data structure linked list

Variable array and linked list

1, Variable array

1. Realization

   define a data structure array, which contains an integer pointer array and a size used to indicate the length of the current array.

a. Header file
def.h
#ifndef _ARRAY_H_
#define _ARRAY_H_
#define BLOCK_SIZE 10
typedef struct{
    int *array;
    int size;
}Array;
Array array_create( int init_size);
//Create a mutable array

void array_free(Array *a);
//Because you are the pointer of the application, you have to release the pointer at last

int array_size(const Array *a);
//Returns the current size of the variable array

int* array_at(Array *a,int index);
//The return pointer can assign an element in an array

int array_get(Array* a,int index);
//Return array[index]

void array_inflate(Array *a,int more_size);
//Let variable arrays grow

void array_write(Array*a,int index,int value);
//Modify the value of array[index]

#endif

   all use pointers as parameters because it is convenient to directly modify the corresponding value without returning parameters.

b. Initialize variable array

   initializing a variable array is to create such a structure and give the maximum number of elements of the initial array. In order to use the function that returns this structure type in the main function.

Array array_create( int init_size)
{
    Array a;
    a.size=init_size;
    a.array=(int*)malloc(sizeof(int)*a.size);
    return a;
}
c. Delete variable array

    to delete a variable array, you need to free the memory space referred to by the array, change the size back to 0, and assign the array NULL.

void array_free(Array *a)
{
    free(a->array);
    a->size=0;
    a->array=NULL;
}
d. The current maximum number of elements of a variable array
int array_size(const Array *a)
{
    return a->size;
}

   why not directly return a - > size in main, but define a function array_size?

  • This is an encapsulated idea.
  • In object-oriented programming
  • encapsulation is to encapsulate the resources required by the object in the program object
  • Object is "publish its interface".
  • Other objects attached to these interfaces can use this object without caring about the methods implemented by the object.
  • The concept is "don't tell me how you do it, just do it."
  • Protected the internal implementation details
  • An object can be regarded as a self-contained atom. Object interfaces include common methods and initialization data.
e. Accessing the value of a mutable array

   there are two ideas. One is to directly use a function array that returns int *_ At, so if you want to change the value of I position to number, you only need * array_at(&a,i)=number.

  another way is that I use array_get function to view the value of the corresponding element of the array, using array_write function to modify the value of the corresponding element of the array.

   both of these ideas should be noted that when the i entered is greater than the maximum number of elements of the current variable array, we should make the array grow and use the array introduced later_ The inflate function is OK.

int* array_at(Array *a,int index)
{
    if(a->size<=index)
    {
        array_inflate(a,(index/BLOCK_SIZE+1)*BLOCK_SIZE-a->size);
        //index/BLOCK_SIZE gets the index in the first block, then adds 1 and multiplies BLOCK_SIZE minus a - > size
        //Get a plus BLOCK_SIZE
    }//In this way, our array at can grow automatically
    return &(a->array[index]);
}

//Another implementation method
int array_get(Array* a,int index)
{
    return a->array[index];
}

void array_write(Array*a,int index,int value)
{
    if(index>=a->size)
    {
        array_inflate(a,(index/BLOCK_SIZE+1)*BLOCK_SIZE-a->size);
    }
    a->array[index]=value;
}
f. Growth of variable arrays

   the general idea is to use malloc function to apply for a piece of memory, which is the size of the original array and more_ Multiply the sum of size by sizeof(int) bytes, then use a for loop or memcpy function to copy the contents of the array into the p array, then release the original memory space free(array), and then make the array equal to p, size=size+more_size.

void array_inflate(Array *a,int more_size)
{
    int* p=(int*)malloc(sizeof(int)*(a->size + more_size));
    int i;
    // for(i=0;i<a->size;i++)
    // {
    //     p[i]=a->array[i];
    // }
    memcpy((void*)p,(void*)a->array,sizeof(int)*a->size);
    free(a->array);
    a->array=p;
    a->size+=more_size;
}
g. Total experiment
def.h
#ifndef _ARRAY_H_
#define _ARRAY_H_
#define BLOCK_SIZE 10
typedef struct{
    int *array;
    int size;
}Array;
Array array_create( int init_size);//Create a mutable array
void array_free(Array *a);//Because you are the pointer of the application, you have to release the pointer at last
int array_size(const Array *a);//Returns the current size of the variable array
int* array_at(Array *a,int index);//The return pointer can assign an element in an array
int array_get(Array* a,int index);//Return array[index]
void array_inflate(Array *a,int more_size);//Let variable arrays grow
void array_write(Array*a,int index,int value);//Modify the value of array[index]

#endif
array.c
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "def.h"
// typedef struct{
//     int *array;
//     int size;
// }Array;
    
    
Array array_create( int init_size)
{
    Array a;
    a.size=init_size;
    a.array=(int*)malloc(sizeof(int)*a.size);
    return a;
}


void array_free(Array *a)
{
    free(a->array);
    a->size=0;
    a->array=NULL;
}


int array_size(const Array *a)
{
    return a->size;
}


int* array_at(Array *a,int index)
{
    if(a->size<=index)
    {
        array_inflate(a,(index/BLOCK_SIZE+1)*BLOCK_SIZE-a->size);
        //index/BLOCK_SIZE gets the number of blocks, then adds 1 and multiplies BLOCK_SIZE minus a - > size
        //Get a plus BLOCK_SIZE
    }//In this way, our array at can grow automatically
    return &(a->array[index]);
}

//Another implementation method
int array_get(Array* a,int index)
{
    return a->array[index];
}

void array_write(Array*a,int index,int value)
{
    if(index>=a->size)
    {
        array_inflate(a,(index/BLOCK_SIZE+1)*BLOCK_SIZE-a->size);
    }
    a->array[index]=value;
}


void array_inflate(Array *a,int more_size)
{
    int* p=(int*)malloc(sizeof(int)*(a->size + more_size));
    int i;
    // for(i=0;i<a->size;i++)
    // {
    //     p[i]=a->array[i];
    // }
    memcpy((void*)p,(void*)a->array,sizeof(int)*a->size);
    free(a->array);
    a->array=p;
    a->size+=more_size;
}

int main()
{
    Array a=array_create(10);
    printf("number of array is %d\n",array_size(&a));

    *array_at(&a,0)=10;//So you can write the value to that array
    printf("array[0]=%d\n",*array_at(&a,0));

    //If you feel uncomfortable returning a pointer and then assigning it a value with * we can provide another idea
    array_write(&a,11,521);
    printf("array[11]=%d\n",array_get(&a,11));

    int count=1;
    int number;
    while(number!=-1)
    {
        scanf("%d",&number);
        if(number!=-1)
        {
            *array_at(&a,count++)=number;
        }
        //scanf("%d",array_at(&a,count++));
        //Keep entering data and automatically increase when the upper limit is reached
    }

    for(int j=0;j<count;j++)
    {
        printf("array[%d]=%d\n",j,*array_at(&a,j));
    }
    array_free(&a);
    return 0;
}
2. Defects of variable array:
  1. First, copying takes time. If the data is large, copying takes a lot of time;

  2. Secondly, you have to re apply for a larger memory space every time. This always happens once: the remaining memory is the n-BS size space you have free in front of you. Your current a - > array memory space is n, and there is only 2BS space behind you. You can apply for n+BS space next time, but you can't apply for space. Although the remaining memory is enough, But you can't apply because the variable array memory is continuous.

  3. So variable arrays can be used, but they are not efficient enough.

3.memcpy function

  function: directly copy array

void *memcpy(void *str1, const void *str2, size_t n);

parameter

  • str1 – refers to the target array used to store the copied content, and the type is cast to void * pointer.

  • str2 – points to the data source to be copied, and the type is cast to void * pointer.

  • n – number of bytes to be copied.

Return value

  this function returns a pointer to the target storage area str1.

   this leads to the motivation of linked list. If we can do this, when the array is not enough, I will apply for a place as long as the original, connect the previous place with the next place by some means, and tell the computer to access the next connected place after accessing this. This is a kind of discontinuous memory, This solves the problem of copying and the problem of having to re apply for a larger memory space every time. You can make full use of every corner of this memory.

Header file

string.h

2, Linked list

   linked list is such a data structure. It is connected by many nodes. Each node contains the data data of this node and the pointer next pointing to the next node. The head node has a head pointer pointing to it, and the tail node points to NULL.

1. Create linked list function

  the idea here is to divide the discussion into two categories:

   if there is no node in the linked list, then the head node points to NULL, then we make the head pointer equal to this p, that is, the head pointer points to this head node;

    if the linked list has a certain length, then the head node must point to non NULL at this time. Then we use the feature that the tail node points to NULL, use a while loop to make last point to the tail node, and then connect last - > next = P.

typedef struct _node{
    int data;
    struct _node* next;
}Node;
void add(Node* head,int number)
{
//Create a node
	Node*p=(Node*)malloc(sizeof(Node));
	p->data=number;
	p->next=NULL;
	Node* last= head;
	//In two cases, it is a head node or it is not a head node
	if(last!=NULL)//This means that head is not equal to NULL, which means that there is a linked list in front of the new node
	{
		while(last->next!=NULL)
		//The next of the last node points to null. We want last to go to the last node
		{
		last=last->next;
		}
		last->next=p;//connect
	}
	else//This indicates that the head node is NULL
	{
		head=p;
	}
}
Question:

    it's not difficult to find that creating a linked list function actually needs to constantly change the head. Initially, head=NULL becomes head - > next = null, then head - > next - > next = null, and so on; Therefore, there is such a problem. The head you pass into the function is a local variable. If you modify it, it will not affect the head in the main function. It will always be an empty linked list.

The first solution:

   the algorithm book will do this. Let's directly define head as a global variable.

Node* head;

  but this is flawed. First of all, this is one-time. Our add function can only work on this global variable. What if there are multiple linked lists in the program; Secondly, it is unsafe to return global variables * * "if a function returns a static local variable or the address of a global variable, this function is not accessible, which will lead to thread insecurity (Toyota case), * * therefore, do not use global variables to pass parameters between functions, and try to avoid using global variables."

The second solution:

  since you want to change the head, just return to the head.

typedef struct _node{
    int data;
    struct _node* next;
}Node;
Node* add(Node* head,int number)
{
//Create a node
	Node*p=(Node*)malloc(sizeof(Node));
	p->data=number;
	p->next=NULL;
	Node* last= head;
	//In two cases, it is a head node or it is not a head node
	if(last!=NULL)//This means that head is not equal to NULL, which means that there is a linked list in front of the new node
	{
		while(last->next!=NULL)
		//The next of the last node points to null. We want last to go to the last node
		{
		last=last->next;
		}
		last->next=p;//connect
	}
	else//This indicates that the head node is NULL
	{
		head=p;
	}
	return head;
}

  such an add function is thread safe and can be used for different linkedlists.

   however, there is still a small problem. Because head is returned, we should ensure that programmers using this function write as follows:

head=add(head,number);

  but no one forced him to write like this. What if he forgot?

The third solution:

    since we want to change the head, let's just pass the pointer of the head in.

typedef struct _node{
    int data;
    struct _node* next;
}Node;
void add(Node** phead,int number)
{
//Create a node
	Node*p=(Node*)malloc(sizeof(Node));
	p->data=number;
	p->next=NULL;
	Node* last= *phead;
	//In two cases, it is a head node or it is not a head node
	if(last!=NULL)//This means that head is not equal to NULL, which means that there is a linked list in front of the new node
	{
		while(last->next!=NULL)
		//The next of the last node points to null. We want last to go to the last node
		{
		last=last->next;
		}
		last->next=p;//connect
	}
	else//This indicates that the head node is NULL
	{
		*phead=p;
	}
}
The fourth solution:

    define a data type List in which the whole linked List can be placed. For example, I can put head and tail at the same time (tail is the pointer to the tail node), and then we pass the pointer to the List to the function, because there are head and tail pointers in the List, which is equivalent to having pointers to them, so there will be no problem that the head cannot be modified.

typedef struct _node{
    int data;
    struct _node* next;
}Node;
typedef struct _list{
    Node* head;
    Node* tail;
}List;
void add(List* pList,int number)
{
    //Create a node
	Node*p=(Node*)malloc(sizeof(Node));
	p->data=number;
	p->next=NULL;
	//In two cases, it is a head node or it is not a head node
	if(pList->head!=NULL)//This means that head is not equal to NULL, which means that there is a linked list in front of the new node
	{
		(pList->tail)->next=p;//connect
		pList->tail=p;//Let tail point to the tail node
	}//In this way, you don't have to get the last from the beginning node to the end node again and again.
	else//This indicates that the head node is NULL
	{
		pList->head=pList->tail=p;
	}
}
int main()
{
	int number;
	List list;
	list.head=list.tail=NULL;
	do{
		scanf("%d",&number);
		if(number!=-1)
		{
			add(&list,number);
		}
	}while(number!=-1);

	return (0);
}

  the advantage of this scheme is that it brings us unlimited possibilities to improve the List by defining a data type List by ourselves.

2. Traversal of linked list

  use a for loop p to point to the space-time jump.

void print(List* pList)
{
	Node* p;
	for(p=pList->head;p;p=p->next)//p still exists, equivalent to p= NULL
	{
		printf("%d\t",p->data);
	}
	printf("\n");
}
3. Deletion of linked list

   for deleting nodes, we consider doing this: p points to the node to be deleted, q points to the node before p, and then let q - > next = p - > next connect the position indicated by q with the next position of p, and then free §.

   here's a problem. If p is the head node, q does not exist at this time. You can't use the above method. In this case, let head - > next = p - > next, and then free §.

   so we can control q=NULL first. If the head node meets the condition, then q=NULL. In other cases, Q will point to the previous node of p, meeting q=NULL.

int found(List* pList,int number)
{
	Node*p;
	int isFound=0;
	for(p=pList->head;p;p=p->next)
	{
		if(p->data==number)
		{
			isFound=1;
			printf("eureka\n");
			break;
		}
	}
	if(!isFound)
	{
		printf("Can't find\n");
	}
	return isFound;
}


void delete(List* pList,int number)
{
	if(found(pList,number)==1)
	{
		Node* p;
		Node* q;
		for(q=NULL,p=pList->head;p; q=p,p=p->next)
            //So when you find it. q refers to the previous position of p
		{
			if(p->data==number)
			{
				if(q==NULL)
         //If it is the head node directly, then q is null. What you need to change is head. / / for this problem, find the boundary conditions, and stare at all the things in front of - > to see when they are equal to null
				{
					pList->head=p->next;
				}
				else
				{
				q->next=p->next;
				}
				free(p);
				break;
				
			}
		}
	}
	else{
		printf("The element you want to delete does not exist");
	}
}
4. Clearing the linked list

  because all our linked list nodes are malloc, we always have to find a time to free them.

void clear(List* pList)
{
	Node* p;
	Node* q;
	for(p=pList->head;p;)
	//The idea is to make q equal to the next node of P, then free p, and then give the value of q to P
	{
		q=p->next;
		free(p);
		p=q;
	}
}
5. General experiment
node.h
#ifndef _NODE_H_
#define _NODE_H_

typedef struct _node{
    int data;
    struct _node* next;
}Node;
typedef struct _list{
    Node* head;
    Node* tail;
}List;
#endif
main.c
#include <stdio.h>
#include "node.h"
#include <stdlib.h>

void add(List* pList,int number);
void print(List*pList);
int found(List* pList,int number);
void delete(List* pList,int number);
void clear(List* pList);

int main()
{
	int number;
	// Node* head=NULL;// At the beginning, there is no header node, and the header node is empty
	List list;
	list.head=list.tail=NULL;
	do{
		scanf("%d",&number);
		if(number!=-1)
		{
			add(&list,number);
		}
	}while(number!=-1);
	print(&list);
	int num;
	scanf("%d",&num);
	found(&list,num);
	delete(&list,num);
	print(&list);
	clear(&list);

	return (0);
}

void add(List* pList,int number)
{
    //Create a node
	Node*p=(Node*)malloc(sizeof(Node));
	p->data=number;
	p->next=NULL;
	//In two cases, it is a head node or it is not a head node
	if(pList->head!=NULL)//This means that head is not equal to NULL, which means that there is a linked list in front of the new node
	{
		(pList->tail)->next=p;//connect
		pList->tail=p;//Let tail point to the tail node
	}
	else//This indicates that the head node is NULL
	{
		pList->head=pList->tail=p;
	}
}


void print(List* pList)
{
	Node* p;
	for(p=pList->head;p;p=p->next)//p still exists, equivalent to p= NULL
	{
		printf("%d\t",p->data);
	}
	printf("\n");
}


int found(List* pList,int number)
{
	Node*p;
	int isFound=0;
	for(p=pList->head;p;p=p->next)
	{
		if(p->data==number)
		{
			isFound=1;
			printf("eureka\n");
			break;
		}
	}
	if(!isFound)
	{
		printf("Can't find\n");
	}
	return isFound;
}


void delete(List* pList,int number)
{
	if(found(pList,number)==1)
	{
		Node* p;
		Node* q;
		for(q=NULL,p=pList->head;p; q=p,p=p->next)//So when you find it. q refers to the previous position of p
		{
			if(p->data==number)
			{
				if(q==NULL)//If it is the head node directly, q is null at this time. What you want to change is head
				//To find the boundary conditions for this problem, just look at all the things in front of you and see when they are equal to NULL
				{
					pList->head=p->next;
				}
				else
				{
				q->next=p->next;
				}
				free(p);
				break;
				
			}
		}
	}
	else{
		printf("The element you want to delete does not exist");
	}
}

void clear(List* pList)
{
	Node* p;
	Node* q;
	for(p=pList->head;p;)
	//The idea is to make q equal to the next node of P, then free p, and then give the value of q to P
	{
		q=p->next;
		free(p);
		p=q;
	}
}
output

Posted by SystemWisdom on Sun, 19 Sep 2021 17:38:15 -0700