La estructura de datos más sólida de la historia: la implementación de simulación de la tabla de secuencia (versión en lenguaje C)

2.1 Concepto y estructura

La tabla de secuencia es una estructura lineal en la que los elementos de datos se almacenan secuencialmente en un segmento de unidades de almacenamiento con direcciones físicas consecutivas, generalmente utilizando almacenamiento en matriz. Complete la adición, eliminación, búsqueda y modificación de datos en la matriz.

La tabla de secuencia generalmente se puede dividir en:

  1. Tabla de secuencia estática : use una matriz de longitud fija para almacenar elementos, y la cantidad de elementos no se puede modificar.

    //顺序表的静态存储
    #define N 7
    typedef int SLDataType;
    typedef struct SeqList
    {
    	SLDataType array[N];//定长数组
    	size_t size;//有效数据的个数
    }SeqList;
    
  2. tabla de secuencia dinámica

    //顺序表的动态存储
    typedef struct SeqList
    {
    	SLDataType* array;//指向动态开辟的数组,空间不够可以增容
    	size_t size;//有效数据的个数
    	size_t capacity;//容量空间的大小
    };
    

2.2 Implementación de la interfaz

Una tabla de secuencia estática solo es útil si sabe cuántos datos necesita almacenar. La matriz de longitud fija de la tabla de secuencia estática hace que N sea demasiado grande, se desperdicia demasiado espacio
y muy poco no es suficiente. Por lo tanto, en realidad, la tabla de secuencia dinámica se usa básicamente y el tamaño del espacio se asigna dinámicamente de acuerdo con las necesidades, por lo que implementaremos la tabla de secuencia dinámica a continuación.

2.2.1 Almacenamiento dinámico de la tabla de secuencias

typedef int SLDataType;//顺序表中存储的数据,此处假设是int型
typedef struct SeqList
{
	int* a;//指向动态开辟的数组空间,空间可以随时增容
	int size;//存储数据个数
	int capacity;//存储空间大小
}SL,SeqList;

2.2.2 Inicialización de la tabla de secuencias

void SeqListInit(SeqList* psl);//声明
void SeqListInit(SeqList* psl)
{
	assert(psl);//进行断言是因为当psl为NULL时,下面的操作将无法进行,因为空指针是无法进行解引用的。
	psl->a = NULL;
	psl->size = 0;
	psl->capacity = 0;
}//函数实现

Nota: La afirmación se hace porque cuando psl es NULL, las siguientes operaciones no funcionarán, porque el puntero nulo no se puede desreferenciar, y lo mismo es cierto más adelante.

2.2.3 Destrucción de la tabla de secuencia

void SeqListDestroy(SeqList* psl);
void SeqListDestroy(SeqList* psl)
{
	assert(psl);
	free(psl->a);
	psl->a = NULL;
	psl->capacity = psl->size = 0;
}

Nota: El puntero entre paréntesis después de free debe ser el espacio abierto por malloc y no puede haber ninguna desviación (es decir, el puntero no se puede mover).

Aquí hay un ejemplo:

imagen-20220313121909217

Usarlo como el anterior está perfectamente bien, pero usarlo así crea un problema:

imagen-20220313122035082

Después de que tmp realiza la operación de incremento automático, apunta a la posición que se muestra en la siguiente figura:

imagen-20220313122258326

La posición de free es la posición después de tmp++, que no se ajusta a las disposiciones del lenguaje C, e incluso si se libera normalmente, el espacio int anterior causará un problema de fuga de memoria, es decir, la memoria abierta dinámicamente. se olvida para ser liberado.

2.2.4 Inserción de la cola de la tabla de secuencia

void SeqListPushBack(SeqList* psl,SLDataType x);//声明
void SeqListPushBack(SeqList* psl, SLDataType x)
{
	assert(psl);
	//如果满了,就进行扩容
	SeqListCheckCapacity(psl);
	psl->a[psl->size] = x;
	psl->size++;
}

imagen-20220313123214723

2.2.5 Eliminación de la cola de la tabla de secuencias

void SeqListPopBack(SeqList* psl);
void SeqListPopBack(SeqList* psl)
{
	assert(psl);
	if(psl->size > 0)
	{
		psl->size -= 1;
	}
}

2.2.6 Encabezado de la tabla de secuencia

void SeqListPushFront(SeqList* psl, SLDataType x);
void SeqListPushFront(SeqList* psl, SLDataType x)
{
	assert(psl);
	SeqListCheckCapacity(psl);
	int end = psl->size - 1;
	while (end >= 0)
	{
		psl->a[end+1] = psl->a[end];
		--end;
	}
	psl->a[0] = x;
	psl->size++;
}

La inserción de la cabeza de la tabla de secuencia implicará el movimiento de elementos posteriores, cuando se realiza la inserción de la cabeza, los elementos de la tabla de secuencia deben moverse desde atrás, porque si el movimiento comienza desde el frente, se producirá un fenómeno de superposición. A continuación se muestra el diagrama:

imagen-20220313150759211

2.2.7 Eliminación de encabezado de tabla de secuencia

Del mismo modo, si desea que el elemento no quede cubierto, solo puede moverlo de adelante hacia atrás.

void SeqListPopFront(SeqList* psl);
void SeqListPopFront(SeqList* psl)
{
	//挪出数据,腾出头部空间
	//方法一:从1开始移动
	/*assert(psl);
	if (psl->size > 0)
	{
		int begin = 1;
		while (begin < psl->size)
		{
			psl->a[begin - 1] = psl->a[begin];
			begin++;
		}
		psl->size--;
	}*/
	//方法二:从0开始移动
	assert(psl);
	if (psl->size > 0)
	{
		int begin = 0;
		while (begin < psl->size - 1)
		{
			psl->a[begin] = psl->a[begin + 1];
			begin++;
		}
		psl->size--;
	}
}

La siguiente figura muestra la diferencia entre los dos métodos de movimiento:

imagen-20220313152013487

P: ¿Por qué el puntero no se puede incrementar directamente en 1?

  1. El puntero cuando free libera espacio y el puntero utilizado para malloc para abrir espacio deben ser iguales
  2. El espacio abierto por mallo se desperdicia, es decir, el espacio de 0 se desperdicia y no se puede usar porque ese espacio está legalmente solicitado.

2.2.8 Comprobar y ampliar la capacidad de la tabla de secuencia

void SeqListCheckCapacity(SeqList* psl);
void SeqListCheckCapacity(SeqList* psl)
{
	assert(psl);
	if (psl->capacity == psl->size)
	{
		size_t newCapacity = psl->capacity == 0 ? 4 : psl->capacity * 2;
		SLDataType* tmp = (SLDataType*)realloc(psl->a, sizeof(SLDataType) * newCapacity);
		if (tmp == NULL)
		{
			printf("realloc fail\n");
			exit(-1);
		}
		else
		{
			psl->a = tmp;
			psl->capacity *= 2;
		}
	}
}

Nota 1: Lo que se considera aquí es que si la capacidad no es suficiente, la capacidad se ampliará al doble de la capacidad original, pero la capacidad inicial es 0, así que considere el caso de 0 al principio.

Nota 2: Es necesario verificar si la expansión es exitosa, es decir, determinar si el espacio recién solicitado está vacío.

2.2.9 Inserción en cualquier posición de la tabla de secuencia

void SeqListInsert(SeqList* psl,size_t pos,SLDataType x);
void SeqListInsert(SeqList* psl, size_t pos, SLDataType x)
{
	assert(psl);
	//较为温和的检查方式
	/*if (pos > psl->size)
	{
		exit(-1);
	}*/
	assert(pos <= psl->size);//较为暴力的检查方式
	SeqListCheckCapacity(psl);
	size_t end = psl->size;
	while (end > pos)
	{
		psl->a[end] = psl->a[end-1];
		--end;
	}
	psl->a[pos] = x;
	psl->size++;
}

Aviso:

P: ¿Por qué el final comienza desde psl->size?

A: Lo que se debe prestar atención aquí es el tipo de pos y el final, ¿por qué? Debido a que ambos son tipos sin firmar, preste especial atención cuando el extremo llegue a -1, se convertirá en un número muy grande. En este momento, si usa esto como un subíndice para acceder, habrá un acceso fuera de los límites a la memoria. Considere un caso extremo, cuando pos es 0, end es 0 cuando es el más pequeño, por lo que no habrá una situación fuera de los límites en este momento, pero si end comienza desde psl->size-1, entonces el cuerpo del ciclo while La declaración se convierte en la siguiente:

while(end > pos)
{
	psl->a[end+1] = psl->a[end];
	--end;
}

Finalmente, el valor mínimo de end se convertirá en -1, pero debido a que end es del tipo size_t, se convertirá en un número grande. La condición siempre se cumple cuando se determina la condición del ciclo while(), y después de ingresar al cuerpo del ciclo, Se produjo una operación de acceso a la memoria fuera de los límites. Así que las gráficas para los dos casos son las siguientes:

imagen-20220311164610452

2.2.10 Eliminar en cualquier lugar de la tabla de secuencias

void SeqListErase(SeqList* psl, size_t pos);
void SeqListErase(SeqList* psl, size_t pos)
{
	assert(psl);
	assert(pos <= psl->size);
	size_t begin = pos+1;
	while (begin < psl->size)
	{
		psl->a[begin-1] = psl->a[begin];
		++begin;
	}
	psl->size--;
}

2.2.11 Impresión de la tabla de secuencia

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

2.2.12 Búsqueda de elementos de la lista de secuencias

int SeqListFind(SeqList* psl,SLDataType x);
int SeqListFind(SeqList* psl, SLDataType x)
{
	assert(psl);
	for (int i = 0; i < psl->size; i++)
	{
		if (psl->a[i] == x)
			return i;//找到了对应元素,返回相应的下标
	}
	return -1;//说明没有找到对应的元素
}

2.2.13 Modificación de los elementos de la lista de secuencias

void SeqListModify(SeqList* psl, size_t pos, SLDataType x);
void SeqListModify(SeqList* psl, size_t pos, SLDataType x)
{
	assert(psl);
	assert(pos < psl->size);
	psl->a[pos] = x;
}

Supongo que te gusta

Origin blog.csdn.net/m0_57304511/article/details/123460490
Recomendado
Clasificación