Chapter 2 Data Structure and Algorithm - Linear Table

2.1 Introduction to linear tables

Table of contents

2.1 Introduction to linear tables

 2.1.1. Definition of linear table

2.1.2. Relevant properties of linear tables

2.2 Sequential storage structure of linear table-sequential table

2.2.1. The concept of sequence list

2.2.2. Advantages and Disadvantages of Sequence List

2.2.3. Classification and code definition of sequence tables (static and dynamic)

2.2.4. Basic operations of sequence tables (key points)


 2.1.1. Definition of linear table

(1). A linear table is a finite sequence of n (n>=0) data elements with the same characteristics, where n represents the length of the linear table, that is, the number of data elements. As shown below:

2.1.2. Relevant properties of linear tables

(1) When n=0, it means an empty list. When n>0, it is usually recorded as (a1, a2, a3...an), a1 means the first element of the linear list, and an means the tail element.

(2) Among them, a(m-1) is the direct predecessor of a(m) , and a(m) is the direct successor of a(m-1).

(3) Except for a1, which has only a unique successor, and an, which has a unique predecessor, the other elements have only unique predecessors and successors.

(4) Schematic diagram of the logical structure of the linear table:

 (5) The specific meaning of each data element varies in different linear tables. It can be a number, a symbol, a record, or even other more complex information.

2.2 Sequential storage structure of linear table-sequential table

2.2.1. The concept of sequence list

(1) Open up a continuous storage space in the memory, and use a set of continuous storage units to store data elements in sequence. This storage method is called the sequential storage structure of a linear table , or a sequential list for short . Generally, arrays are used for storage, and operations such as addition, deletion, and search of data are completed on the array.

(2) Characteristics of the sequential storage structure : logically adjacent data elements are also adjacent in physical location.

(3) Since the data elements in the linear table have the same characteristics , it is easy to determine the storage address of the i-th element in the table .

Assume that each element in the table occupies m storage units, and the storage address of the first occupied storage unit is used as the storage address of the data element, then the storage location of the i-th data in the table is:

                                              LOC(ai)=LOC(a1)+(i-1)*m

Where: LOC(a1) is the storage location of the first data element a1 of the table, often called the starting position or base address of the linear table .

Note: Because the linear table only needs to determine the base address and the size m of a data element, the storage address of any element in the table can be calculated according to the above formula, so that any element in the sequential table can be accessed randomly . Therefore, the sequential storage structure of a linear table is a random access storage structure .

(4) The difference between a sequence table and an array: an array is a continuous space, but you can put values ​​in any space, but the sequence table is different. The sequence table is also a continuous space, but the data must be stored in sequence .

2.2.2. Advantages and Disadvantages of Sequence List

(1) Advantages:

①: Randomly accessing elements is easy to implement. It is easy to determine the storage location of each element in the table according to the above positioning formula, so it is convenient to specify i nodes.

②: Simple and intuitive.

(2) Disadvantages:
①: Difficulty in inserting and deleting nodes : Since the nodes in the table are stored consecutively, when inserting or deleting a node, the nodes after the insertion point must be moved backward in sequence. Or the nodes after the deleted point are moved forward in sequence.

②: Inflexible expansion (static) : When creating a table, if the maximum length of the table cannot be estimated, it will be difficult to allocate space for sure, affecting expansion.

③: Easy to cause waste : When the allocated space is too large, the reserved space will be wasted.

2.2.3. Classification and code definition of sequence tables (static and dynamic)

Sequence tables are generally divided into two categories, as follows:

(1). Static sequence table:

The code is defined as follows:

#define N 100
typedef int SLDatatype;
struct Seqlist
{
	SLDatatype q[N];//开辟的连续空间
	int size;//有效数据个数
};

explain:

①: N is the total space opened up. In order to make it easy to change, it is defined with the define macro.

②: In order to facilitate the storage of different types of data, typedef is used to rename the type.

③: Array q is a continuous space opened up.

④: size is the number of valid data, that is, the number of elements that exist in the current table.

Note: Static sequence tables have several disadvantages: definitions like static sequence tables are hard-coded, and N of 100 may not be enough. If you give N 200, you may only use 10, which is a serious waste of space. So we generally use the following definition method - dynamic sequence list.

(2) Dynamic sequence table:

The code is defined as follows:

typedef int SLDatatype;
typedef struct Seqlist
{
	SLDatatype* q;//指向动态开辟的数组
	int size;//有效数据个数
	int capicity;//容量空间大小
}SL;

explain:

①: Type renaming is the same as above.

②: The pointer q defined here is used to point to the dynamically opened array

③: size is the number of valid data, that is, the number of data that can be displayed sequentially.

④: Capicity is the total capacity space size. One is to record the current maximum space, and the other is to facilitate subsequent expansion of space.

2.2.4. Basic operations of sequence tables (key points)

(1), initialization function

The code is implemented as follows:

void SLInit(SL* ps)
{
	ps->capicity = 5;
	ps->size = 0;
	ps->q = (SLDatatype*)malloc(sizeof(SLDatatype) * 5);
	if (ps->q == NULL)
	{
		perror("malloc failed");
		exit(-1);
	}
}

explain:

①: The formal parameter is a structure pointer, used to receive the address of the structure object. Because the formal parameter is a temporary copy of the actual parameter , our entire operation is a call by address .

②: Initialized to an empty table, so size is set to 0. The initial capacity should be as large as the initially opened space, because it needs to be used to determine whether the table is full, and then it can also be used to expand the size of the sequential table space. After the expansion, the capacity should be As big as the new space.

③: As the name suggests, the dynamic sequence table dynamically opens up a continuous space. Here, the malloc function is used to dynamically open up a space and make the pointer q point to it. The initial space can be roughly estimated based on the actual situation.

④: Usually after using the malloc function, we will check whether the space is successfully opened. If malloc fails to open up, it will return empty , so it is used as a judgment condition. Friends can learn about it by themselves.

(2) Release space (destroy) function: Because we dynamically open up space, the space should be returned to the operating system after use.

The code is implemented as follows:

void Sldestroy(SL* ps)
{
	free(ps->q);
	ps->q = NULL;
	ps->capicity = ps->size = 0;
}

Next is the insertion operation. Insertion here is divided into head insertion method and tail insertion method. Similarly, deletion is also called head deletion method and tail deletion method.

It is worth noting here that when inserting data, we should first determine whether the sequence table is full, and because both tail and head insertion methods must be judged, we create a separate function for this, as follows:

(3) Function to determine whether the table is full :
The code is implemented as follows:

void SLCheckCapicity(SL* ps)
{

	if (ps->size == ps->capicity)
	{
		SLDatatype* tmp = (SLDatatype*)realloc(ps->q, ps->capicity * 2 * sizeof(SLDatatype));
		if (tmp == NULL)
		{
			perror("realloc failed");
			exit(-1);
		}
		ps->q = tmp;
		ps->capicity *= 2;
	}
}

explain:

①: The sequence table involves arrays, and arrays involve subscripts . Therefore, regarding the knowledge of linear tables, the editor recommends that you draw more pictures and analyze them , as shown below:

It can be seen that when the sequence table is full, size==capicity, so it can be used as an if judgment condition.

 ②: When the judgment condition is established, that is, when the table is full, we need to expand the space. The realloc function is used here (it is recommended that friends learn the realloc function by themselves, what each parameter represents, what function it has, and what the return value is).

The capacity expansion function mentioned earlier is used here. Generally, when we expand the space once, we expand it to twice the capacity, that is, twice the original space. At the same time, capacity*=2 is used to record the expanded space size. .

③: As for why a new pointer variable tmp is created here to expand the space, and then tmp is directly assigned to the pointer q, this is due to the function of the realloc function. You can learn the realloc function by yourself.

(4) Tail insertion method : As the name suggests, it inserts elements at the tail.

The code is implemented as follows:

void SLPushBack(SL* pa, SLDatatype x)
{
	SLCheckCapicity(pa);
	pa->q[pa->size++] = x;
}

explain:

①: The pointer pa in the formal parameter is the same as above, used to receive the object address, and x is the element to be inserted.

②: Regardless of the insertion method, we must first determine whether the table is full, so we call the SLCheckCapicity function we implemented above to determine.

③: The next step is to insert, because size is the number of existing elements, and the subscript starts from 0, so as shown below, we only need to place x at the subscript size of the array q, that is, q[size]=x, Then increase the size by itself to complete the tail insertion method.

 (5) With tail insertion, there is naturally a tail deletion method: as the name implies, it deletes data at the tail.
The code is implemented as follows:

void SLPopBack(SL* pa)
{
	//判断是否为空表
	if (pa->size == 0)
	{
		assert(pa->size > 0);
	}
	pa->size--;
}

explain:

①: No matter what method you use to delete elements, you must first determine whether there are elements in the table at the beginning , and if so, you can delete them. There are two ways to deal with the judgment here. One is to use a function called "assert" in the example to assert. The formal parameter is an expression. If the expression is false, the assert function will directly report an error. The other is to return directly to end the deletion function.

②: Idea : Because it is a sequential table, the tail deletion method can simply decrement the size, so that the tail element cannot be accessed. If you add data next time, you can directly overwrite the original data.

(6) Head insertion method: As the name implies, data is inserted into the head

The code is implemented as follows:

void SLPushFront(SL* pa, SLDatatype x)
{
	SLCheckCapicity(pa);
	int end = pa->size - 1;
	//从后往前移动数据
	while (end >= 0)
	{
		pa->q[end + 1] = pa->q[end];
		end--;
	}
	pa->q[0] = x;
	pa->size++;
}

explain:

①: Data is also inserted, so first call the SLCheckCapicity function to determine whether the table is full.

②: Idea; then because we want to insert at the head, if there is enough space, we only need to move the data in the original table backward by one storage space, and then put the data x into the first element q[0] At the position, the size can be increased automatically after the insertion is successful.

(7) Head deletion method : As the name suggests, it deletes data in the head.

The code is implemented as follows:

void SLPopFront(SL* pa)
{
	if (pa->size == 0)
	{
		assert(pa->size > 0);
	}
	int left = 1;
	while (left <= pa->size - 1)
	{
		pa->q[left-1] = pa->q[left];
		left++;
	}
	pa->size--;
}

explain:

①: Delete data. In the same way, first determine whether the table is empty.

②: Idea: Because you are deleting elements, you only need to move the size-1 data behind the first element forward by one storage space, overwrite the first element, and then reduce the size. Be careful to move from front to back to prevent data loss.

(8). Insert data x at the position with subscript pos :

The code is implemented as follows:

void SLInsert(SL* ps, int pos, SLDatatype x)
{
	assert(pos <= ps->size && pos >= 0);//判断插入位置是否无效
	SLCheckCapicity(ps);
	int end = ps->size-1;
	while (end >=pos)
	{
		ps->q[end + 1] = ps->q[end];
		end--;
	}
	ps->q[pos] = x;
	ps->size++;
}

explain:

①: The pointer ps in the formal parameter is used to receive the object address, pos is the position (subscript) where the data is to be inserted, and x is the value of the data to be inserted.

②: Idea: Move the original pos position and the value after the pos position to the next storage space in sequence. Remember to move from back to front, and then put x at the position subscripted as pos.

③: The library function assertion assert is used to determine whether it is legal to insert the subscript pos. pos must be greater than or equal to 0 and less than or equal to size.

④: It is still necessary to determine whether the table is full and call the function SLCheckCapicity.

⑤: Movement involves loops, and arrays involve subscripts, so you must draw a picture and analyze each subscript to achieve twice the result with half the effort:

 Finally, place x at the position subscripted as pos, and then increase the size.

(9). After performing the operation (9), we will find the following:

①: When pos==size, it is the tail insertion algorithm , so this function can be reused for tail insertion, that is:


//尾插
void SLPushBack(SL* pa, SLDatatype x)
{
	//正常方法:
	/*SLCheckCapicity(pa);
	pa->q[pa->size++] = x;*/

	//复用函数SLInsert
	SLInsert(pa, pa->size, x);
}

②: When pos==0, it is the head insertion algorithm . The same principle is as follows:

//头插
void SLPushFront(SL* pa, SLDatatype x)
{
	//正常方法:
	//SLCheckCapicity(pa);
	//int end = pa->size - 1;
	从后往前移动数据
	//while (end >= 0)
	//{
	//	pa->q[end + 1] = pa->q[end];
	//	end--;
	//}
	//pa->q[0] = x;
	//pa->size++;

	//复用函数SLInsert
	SLInsert(pa, 0, x);
}

You will find that the amount of code is greatly reduced. 

(10) Delete the data at pos position :

The code is implemented as follows:

//删除pos位置的数据,下标为pos-1
void SLErase(SL* ps, int pos)
{
	assert(ps->size != 0 && pos >= 0 && pos < ps->size);
	//直接将pos位置覆盖
	int left = pos + 1;
	while (left < ps->size)
	{
		ps->q[left-1] = ps->q[left];
		left++;
	}
	ps->size--;
}

explain:

①: In the formal parameters. pos is the subscript of the data to be deleted.

②: Then you need to determine whether the value of the subscript pos is valid. The library function assert is still used here.

③: Idea: You only need to move the position subscripted to pos and the data after pos forward by one storage space, overwrite the data to be deleted to achieve the purpose of deletion, and finally reduce the size.

④: Drawing and analysis are still required, friends can try it on their own.

(11). After the 10th operation, we will find the following:

①: When pos==0, it is the head insertion algorithm.

//The knowledge in this chapter is not over yet, the editor will continue to update it in the future!

Guess you like

Origin blog.csdn.net/hffh123/article/details/131962368