02线性表之顺序表

Posted 再吃一个橘子

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了02线性表之顺序表相关的知识,希望对你有一定的参考价值。

肚子饿了就要吃   ~   嗝  ——— 路飞 

目录

1.本章重点

2.线性表

3.顺序表实现

3.1顺序表的概念及结构

4.静态顺序表

5.动态顺序表 

5.1“增”

尾插:

头插:

5.2“删”

尾删

头删

5.3随机“插入”

5.4随机“删除”

5.5内存销毁

5.6“改”

5.7“查”

6.顺序表相关OJ练习题

6.1原地移除元素

6.2合并两个有序数组


1.本章重点

1.线性表概念
2.顺序表实现
3.顺序表相关OJ题练习


2.线性表

线性表(linear list)是n个具有相同特性的数据元素的有限序列。 线性表是一种在实际中广泛使用的数据结构,常见的线性表:顺序表、链表、栈、队列、字符串...
线性表在逻辑上是线性结构,也就说是连续的一条直线。但是在物理结构上并不一定是连续的,线性表在物理上存储时,通常以数组链式结构的形式存储。

3.顺序表实现

3.1顺序表的概念及结构

顺序表是用一段物理地址连续的存储单元依次存储数据元素的线性结构,一般情况下采用数组存储。在数组上完成数据的增删查改
顺序表一般可以分为:
1. 静态顺序表:使用定长数组存储。
2. 动态顺序表:使用动态开辟的数组存储。 

4.静态顺序表

但是这样写代码的可维护性并不高,比如我们想要修改数组的内存空间变得更长,那是不是需要每个地方的a[ N ]都要修改?再比如,我们想要把数组的数据类型变一下,那也是不是都需要改?所以,可见代码可读性并不高,我们需要自定义一下:

#pragma once

#define MAX_SIZE 100
typedef int SQDataType;

//可维护性增强
struct SeqList
{
	SQDataType a[MAX_SIZE];//自定义了数据类型,数组大小
	int size;
};

但是,仅仅这样,用struct Stu结构体类型来定义变量,过于冗余,一般会用重命名的方式再来把struct Stu这个结构体类型重新命名一下,比如:

#pragma once

#define MAX_SIZE 100
typedef int SQDataType;

//可维护性增强
typedef struct SeqList
{
	SQDataType a[MAX_SIZE];//自定义了数据类型,数组大小
	int size;
}SL;

//用typedef将struct SeqList结构体类型定义成SL,方便后续用结构体类型定义变量,避免冗余

当然也可以这样分开写:

#pragma once

#define MAX_SIZE 100
typedef int SQDataType;

//可维护性增强
struct SeqList
{
	SQDataType a[MAX_SIZE];//自定义了数据类型,数组大小
	int size;
};

typedef struct SeqList;//分开写

顺序表主要是完成对数据的增删改查

我们想要对数组进行初始化,该怎么做?

SeqList.h文件

#pragma once

#include<stdio.h>
#define MAX_SIZE 100
typedef int SQDataType;

//可维护性增强
typedef struct SeqList
{
	SQDataType a[MAX_SIZE];//自定义了数据类型,数组大小
	int size;
}SL;

//增删改查接口函数

//初始化
void SeqListInit(SL s);

SeqList.c文件

#include"SeqList.h"

//初始化数组为0
//要用到的数组空间也初始化为0
void SeqListInit(SL s)
{
	memset(s.a, 0, sizeof(SQDataType) * MAX_SIZE);//初始化数组为0
	s.size = 0;//要用到的数组空间也初始化为0
}

test.c文件

#define _CRT_SECURE_NO_WARNINGS
#include"SeqList.h"

//测试
void TestSeqList1()
{
	SL sl;
	SeqListInit(sl);
}

int main()
{
	TestSeqList1();
	return 0;
}

我们调试发现,实参确实被初始化为0,但形参还是乱值,那如何也将形参也初始化呢??   ————>    传地址

 SeqList.h文件

#pragma once

#include<stdio.h>
#define MAX_SIZE 100
typedef int SQDataType;

//可维护性增强
typedef struct SeqList
{
	SQDataType a[MAX_SIZE];//自定义了数据类型,数组大小
	int size;
}SL;

//增删改查接口函数

//初始化
void SeqListInit(SL* ps);

SeqList.c文件

#include"SeqList.h"

//初始化数组为0
//要用到的数组空间也初始化为0
void SeqListInit(SL* ps)
{
	memset(ps->a, 0, sizeof(SQDataType) * MAX_SIZE);//初始化数组为0
	ps->size = 0;//要用到的数组空间也初始化为0
}

test.c文件

#define _CRT_SECURE_NO_WARNINGS
#include"SeqList.h"

//测试
void TestSeqList1()
{
	SL sl;
	SeqListInit(&sl);
}

int main()
{
	TestSeqList1();
	return 0;
}

 增删操作:

 SeqList.h文件

注意

自定义声明例如:

头插void SeqListPushFront,可以自己随便定义名字,你也可以定义void SeqListInertFront或者其他写法

再如 头删:void SeqListPopFront,也可以定义void SeqListMoveFront或者void SeqListDeleteFront或者其他写法

只不过我现在的写法是C/C++库里的内容,方便后续学习。

//增删改查接口函数

//增
 
//头插
void SeqListPushFront(SL* ps, SQDataType x);
//尾插
void SeqListPushBack(SL* ps, SQDataType x);


//删
//头删
void SeqListPopFront(SL* ps);
//尾删
void SeqListPopBack(SL* ps);

SeqList.c文件

//增删改查

//增
 
//头插
void SeqListPushFront(SL* ps, SQDataType x)
{
	if (ps->size >= MAX_SIZE)
	{
		printf("SeqList is Full!\\n");
		return;
	}
	ps->a[ps->size] = x;
	ps->size++;
}

//尾插
void SeqListPushBack(SL* ps, SQDataType x)
{

}

//删
 
//头删
void SeqListPopFront(SL* ps)
{

}

//尾删
void SeqListPopBack(SL* ps)
{

}

但是我们发现,静态表,无论怎么做,都是会有缺点的,就比如说:

我们#define MAX_SIZE 100,那我们要是需要添加101个数据怎么办?你可能会说:“继续define呐!!”,但是这是个长久之计吗??

又譬如:#define MAX_SIZE 1000,但是我们size = 100(需要用到的数组空间只有100),那是不是又浪费了??

静态顺序表缺点总结

少了不够用,多了浪费空间。

那么我们就引出了动态顺序表:

5.动态顺序表 

静态顺序表只适用于确定知道需要存多少数据的场景。静态顺序表的定长数组导致N定大了,空间开多了浪费,开少了不够用。所以现实中基本都是使用动态顺序表,根据需要动态地分配空间大小,所以下面我们实现动态顺序表。

5.1“增”

尾插

SeqList.h文件

#pragma once

#include<stdio.h>
#include<string.h>
#include<stdlib.h>
//#define MAX_SIZE 10
typedef int SQDataType;

//可维护性增强
typedef struct SeqList
{
	SQDataType* a; //指向动态开辟的数组
	int size;      //有效数据的个数
	int capacity;  //容量空间大小
}SL;

//初始化
void SeqListInit(SL* ps);


//增删改查接口函数

//增

//尾插
void SeqListPushBack(SL* ps, SQDataType x);

SeqList.c文件

#include"SeqList.h"

//初始化数组为0
//要用到的数组空间也初始化为0
void SeqListInit(SL* ps)
{
	ps->a = NULL;
	ps->size = 0;
	ps->capacity = NULL;
}

//增删改查

//增

//尾插
void SeqListPushBack(SL* ps, SQDataType x)
{
	//满了就要扩容
	if (ps->size == ps->capacity)//当size(有效数据个数)  ==  capacity(容量空间大小),数组空间刚好被用完
	{
		int newcapacity = ps->capacity == 0 ? 4 : ps->capacity * 2;//这个4是随便初始的一个值,1也可以,2也可以,不够就往后尾插
		SQDataType* tmp = (SQDataType*)realloc(ps->a, newcapacity * sizeof(SQDataType));

		if (tmp == NULL)//如果扩容失败
		{
			printf("realloc fail!");
			exit(-1);//结束
		}
		else
		{
			ps->a = tmp;//将新扩容的空间地址给指针变量a
			ps->capacity = newcapacity;
		}
	}

	ps->a[ps->size] = x;
	ps->size++;
}

//打印
void SeqListPrint(SL* ps)
{
	int i = 0;
	for (i = 0; i < ps->size; i++)
	{
		printf("%d ", ps->a[i]);
	}
	printf("\\n");
}

test.c文件

#define _CRT_SECURE_NO_WARNINGS
#include"SeqList.h"

//测试
void TestSeqList1()
{
	SL sl;
	SeqListInit(&sl);
	//尾插
	SeqListPushBack(&sl, 1);
	SeqListPushBack(&sl, 2);
	SeqListPushBack(&sl, 3);
	SeqListPushBack(&sl, 4);
	SeqListPushBack(&sl, 5);
	SeqListPushBack(&sl, 6);
	SeqListPushBack(&sl, 7);
	SeqListPushBack(&sl, 8);
	SeqListPushBack(&sl, 9);
	SeqListPushBack(&sl, 10);
	SeqListPushBack(&sl, 11);

	//打印
	SeqListPrint(&sl);
}

int main()
{
	TestSeqList1();
	return 0;
}

解释

扩容数组空间,当不够用的时候,2倍扩容空间(  一般规定就是二倍  )

头插

SeqList.h文件

#pragma once

#include<stdio.h>
#include<string.h>
#include<stdlib.h>
//#define MAX_SIZE 10
typedef int SQDataType;

//可维护性增强
typedef struct SeqList
{
	SQDataType* a; //指向动态开辟的数组
	int size;      //有效数据的个数
	int capacity;  //容量空间大小
}SL;

//初始化
void SeqListInit(SL* ps);


//增删改查接口函数

//增

//头插
void SeqListPushFront(SL* ps, SQDataType x);

 SeqList.c文件

#include"SeqList.h"

//初始化数组为0
//要用到的数组空间也初始化为0
void SeqListInit(SL* ps)
{
	ps->a = NULL;
	ps->size = 0;
	ps->capacity = NULL;
}

//增删改查

//增

//头插
void SeqListPushFront(SL* ps, SQDataType x)
{
	//满了就要扩容
	if (ps->size == ps->capacity)//当size(有效数据个数)  ==  capacity(容量空间大小),数组空间刚好被用完
	{
		int newcapacity = ps->capacity == 0 ? 4 : ps->capacity * 2;//这个4是随便初始的一个值,1也可以,2也可以,不够就往后尾插
		SQDataType* tmp = (SQDataType*)realloc(ps->a, newcapacity * sizeof(SQDataType));

		if (tmp == NULL)//如果扩容失败
		{
			printf("realloc fail!");
			exit(-1);//结束
		}
		else
		{
			ps->a = tmp;//将新扩容的空间地址给指针变量a
			ps->capacity = newcapacity;
		}
	}
	//1.初始条件
	//2.结束条件
	//3.循环条件
	int end = ps->size - 1;
	while (end >= 0)
	{
		ps->a[end + 1] = ps->a[end];
		--end;
	}

	ps->a[0] = x;
	ps->size++;
}


//打印
void SeqListPrint(SL* ps)
{
	int i = 0;
	for (i = 0; i < ps->size; i++)
	{
		printf("%d ", ps->a[i]);
	}
	printf("\\n");
}

 test.c文件

#define _CRT_SECURE_NO_WARNINGS
#include"SeqList.h"

//测试
void TestSeqList2()
{
	SL sl;
	SeqListInit(&sl);
	//尾插
	SeqListPushFront(&sl, 1);
	SeqListPushFront(&sl, 2);
	SeqListPushFront(&sl, 3);
	SeqListPushFront(&sl, 4);
	SeqListPushFront(&sl, 5);
	SeqListPushFront(&sl, 6);
	SeqListPushFront(&sl, 7);
	SeqListPushFront(&sl, 8);
	SeqListPushFront(&sl, 9);
	SeqListPushFront(&sl, 10);
	SeqListPushFront(&sl, 11);

	//打印
	SeqListPrint(&sl);
}

int main()
{
	TestSeqList2();
	return 0;
}

解释

头插法和尾插一样,都是往后开辟新的内存空间,只不过一个是往前插入(开辟好的空间放老的数据,老的数据一个一个向后移动),一个是往后插入(开辟好的空间放新数据)。 

总结:

当然,对比头插和尾插,我们发现一段重复的代码,他的作用是:当看到capacity空间不够的时候,再去增容,当然我们可以不必那么麻烦,每次都要写这一大段,我们可以单拿出来封装成一个函数 SeqListCheckCapacity 来专门判断是否需要增容,然后调用它即可。

比如:

 SeqList.c文件

#include"SeqList.h"

//初始化数组为0
//要用到的数组空间也初始化为0
void SeqListInit(SL* ps)
{
	ps->a = NULL;
	ps->size = 0;
	ps->capacity = NULL;
}

//增删改查

//增

//尾插
void SeqListPushBack(SL* ps, SQDataType x)
{
	SeqListCheckCapacity(ps);

	ps->a[ps->size] = x;
	ps->size++;
}


//头插
void SeqListPushFront(SL* ps, SQDataType x)
{
	SeqListCheckCapacity(ps);
	//1.初始条件
	//2.结束条件
	//3.循环条件
	int end = ps->size - 1;
	while (end >= 0)
	{
		ps->a[end + 1] = ps->a[end];
		--end;
	}

	ps->a[0] = x;
	ps->size++;
}

void SeqListCheckCapacity(SL* ps)
{
	//满了就要扩容
	if (ps->size == ps->capacity)//当size(有效数据个数)  ==  capacity(容量空间大小),数组空间刚好被用完
	{
		int newcapacity = ps->capacity == 0 ? 4 : ps->capacity * 2;//这个4是随便初始的一个值,1也可以,2也可以,不够就往后尾插
		SQDataType* tmp = (SQDataType*)realloc(ps->a, newcapacity * sizeof(SQDataType));

		if (tmp == NULL)//如果扩容失败
		{
			printf("realloc fail!");
			exit(-1);//结束
		}
		else
		{
			ps->a = tmp;//将新扩容的空间地址给指针变量a
			ps->capacity = newcapacity;
		}
	}
}

5.2“删”

尾删

//尾删
void SeqListPopBack(SL* ps)
{
	assert(ps->size > 0);//断言,size <= 0,就不用删了
	ps->size--;
}

头删

//头删
void SeqListPopFront(SL* ps)
{
	assert(ps->size > 0);//断言,size <= 0,就不用删了
	int start = 1;
	while (start < ps->size)
	{
		ps->a[start - 1] = ps->a[start];
		++start;
	}
	ps->size--;
}

头尾增删实现效果

 SeqList.h文件

#pragma once

#include<stdio.h>
#include<string.h>
#include<stdlib.h>
#include<assert.h>
//#define MAX_SIZE 10
typedef int SQDataType;

//可维护性增强
typedef struct SeqList
{
	SQDataType* a; //指向动态开辟的数组
	int size;      //有效数据的个数
	int capacity;  //容量空间大小
}SL;

//初始化
void SeqListInit(SL* ps);


//增删改查接口函数

//增

//尾插
void SeqListPushBack(SL* ps, SQDataType x);
//头插
void SeqListPushFront(SL* ps, SQDataType x);

//检查是否需要扩容
void SeqListCheckCapacity(SL* ps);


//删

//尾删
void SeqListPopBack(SL* ps);
//头删
void SeqListPopFront(SL* ps);

//打印
void SeqListPrint(SL* ps);

 SeqList.c文件

#include"SeqList.h"

//初始化数组为0
//要用到的数组空间也初始化为0
void SeqListInit(SL* ps)
{
	ps->a = NULL;
	ps->size = 0;
	ps->capacity = NULL;
}

//增删改查



//增

//尾插
void SeqListPushBack(SL* ps, SQDataType x)
{
	SeqListCheckCapacity(ps);

	ps->a[ps->size] = x;
	ps->size++;
}


//头插
void SeqListPushFront(SL* ps, SQDataType x)
{
	SeqListCheckCapacity(ps);
	//1.初始条件
	//2.结束条件
	//3.循环条件
	int end = ps->size - 1;
	while (end >= 0)
	{
		ps->a[end + 1] = ps->a[end];
		--end;
	}

	ps->a[0] = x;
	ps->size++;
}

//检查是否需要扩容
void SeqListCheckCapacity(SL* ps)
{
	//满了就要扩容
	if (ps->size == ps->capacity)//当size(有效数据个数)  ==  capacity(容量空间大小),数组空间刚好被用完
	{
		int newcapacity = ps->capacity == 0 ? 4 : ps->capacity * 2;//这个4是随便初始的一个值,1也可以,2也可以,不够就往后尾插
		SQDataType* tmp = (SQDataType*)realloc(ps->a, newcapacity * sizeof(SQDataType));

		if (tmp == NULL)//如果扩容失败
		{
			printf("realloc fail!");
			exit(-1);//结束
		}
		else
		{
			ps->a = tmp;//将新扩容的空间地址给指针变量a
			ps->capacity = newcapacity;
		}
	}
}


//打印
void SeqListPrint(SL* ps)
{
	int i = 0;
	for (i = 0; i < ps->size; i++)
	{
		printf("%d ", ps->a[i]);
	}
	printf("\\n");
}




//删 

//尾删
void SeqListPopBack(SL* ps)
{
	assert(ps->size > 0);//断言,size <= 0,就不用删了
	ps->size--;
}

//头删
void SeqListPopFront(SL* ps)
{
	assert(ps->size > 0);//断言,size <= 0,就不用删了
	int start = 1;
	while (start < ps->size)
	{
		ps->a[start - 1] = ps->a[start];
		++start;
	}
	ps->size--;
}

test.c文件

#define _CRT_SECURE_NO_WARNINGS
#include"SeqList.h"

//测试

void TestSeqList1()
{
	SL sl;
	SeqListInit(&sl);
	//尾插
	SeqListPushBack(&sl, 1);
	SeqListPushBack(&sl, 2);
	SeqListPushBack(&sl, 3);
	SeqListPushBack(&sl, 4);
	SeqListPushBack(&sl, 5);
	SeqListPushBack(&sl, 6);
	SeqListPushBack(&sl, 7);
	SeqListPushBack(&sl, 8);
	SeqListPushBack(&sl, 9);
	SeqListPushBack(&sl, 10);
	SeqListPushBack(&sl, 11);

	//打印
	SeqListPrint(&sl);
}

void TestSeqList2()
{
	SL sl;
	SeqListInit(&sl);
	//头插
	SeqListPushFront(&sl, 1);
	SeqListPushFront(&sl, 2);
	SeqListPushFront(&sl, 3);
	SeqListPushFront(&sl, 4);
	SeqListPushFront(&sl, 5);
	SeqListPushFront(&sl, 6);

	//打印
	SeqListPrint(&sl);

	//删
	SeqListPopBack(&sl);
	SeqListPopBack(&sl);
	SeqListPopBack(&sl);
	SeqListPrint(&sl);

	SeqListPopFront(&sl);
	SeqListPopFront(&sl);
	SeqListPrint(&sl);
}

int main()
{
	TestSeqList1();
	TestSeqList2();
	return 0;
}

当然,我们不光在处理数据的时候只会遇到对于头尾的插入删除,还会遇到对于数组中某个位置的插入删除

5.3随机“插入”

主要思想:

  SeqList.h文件

//插入
void SeqListInert(SL* ps, int pos, SQDataType x);

 SeqList.h文件

//对于某个位置插入
void SeqListInert(SL* ps, int pos, SQDataType x)
{
	assert(pos < ps->size);
	SeqListCheckCapacity(ps);

	int end = ps->size - 1;
	while (end >= pos)
	{
		ps->a[end + 1] = ps->a[end];
		--end;
	}
	ps->a[pos] = x;
	ps->size++;
}

test.c文件

#define _CRT_SECURE_NO_WARNINGS
#include"SeqList.h"

//测试

void TestSeqList3()
{
	SL sl;
	SeqListInit(&sl);
	//尾插
	SeqListPushFront(&sl, 1);
	SeqListPushFront(&sl, 2);
	SeqListPushFront(&sl, 3);
	SeqListPushFront(&sl, 4);
	SeqListPushFront(&sl, 5);
	SeqListPushFront(&sl, 6);

	//随机插入
	SeqListInert(&sl, 1, 20);
	SeqListPrint(&sl);
}
int main()
{
	TestSeqList3();
	return 0;
}

5.4随机“删除”

主要思想:

   SeqList.h文件

//删除
void SeqListErase(SL* ps, int pos);

   SeqList.c文件

void SeqListErase(SL* ps, int pos)
{
	assert(pos < ps->size);
	int start = pos + 1;
	while (start < ps->size)
	{
		ps->a[start - 1] = ps->a[start];
		++start;
	}
	ps->size--;
}

test.c文件

#define _CRT_SECURE_NO_WARNINGS
#include"SeqList.h"

//测试

void TestSeqList3()
{
	SL sl;
	SeqListInit(&sl);
	//尾插
	SeqListPushFront(&sl, 1);
	SeqListPushFront(&sl, 2);
	SeqListPushFront(&sl, 3);
	SeqListPushFront(&sl, 4);
	SeqListPushFront(&sl, 5);
	SeqListPushFront(&sl, 6);

	//随机插入
	SeqListErase(&sl, 1);
	SeqListPrint(&sl);
}
int main()
{
	TestSeqList3();
	return 0;
}

5.5内存销毁

在打印完成后,不用了就去释放空间,销毁内存。

   SeqList.h文件

//销毁内存
void SeqListDestory(SL* ps);

   SeqList.c文件

//销毁内存
void SeqListDestory(SL* ps)
{
	free(ps->a);
	ps->a = NULL;
	ps->capacity = ps->size = 0;
}

test.c文件

(每一次使用之后,以后不再使用的话,就最好在末尾加上SeqListDestory(&sl);来销毁内存)

#define _CRT_SECURE_NO_WARNINGS
#include"SeqList.h"

//测试

void TestSeqList3()
{
	SL sl;
	SeqListInit(&sl);
	//尾插
	SeqListPushFront(&sl, 1);
	SeqListPushFront(&sl, 2);
	SeqListPushFront(&sl, 3);
	SeqListPushFront(&sl, 4);
	SeqListPushFront(&sl, 5);
	SeqListPushFront(&sl, 6);

	//随机插入
	SeqListErase(&sl, 1);
	SeqListPrint(&sl);

	//销毁内存
	SeqListDestory(&sl);
}
int main()
{
	TestSeqList3();
	return 0;
}

5.6“改”

  SeqList.h文件

//改
void SeqListModify(SL* ps, int pos, SQDataType x);

 SeqList.c文件

//改
void SeqListModify(SL* ps, int pos, SQDataType x)
{
	assert(pos < ps->size);
	ps->a[pos] = x;
}

5.7“查”

  SeqList.h文件

//查找
int SeqListFind(SL* ps, SQDataType x);

 SeqList.c文件

//查找
int SeqListFind(SL* ps, SQDataType x)
{
	for (int i = 0; i < ps->size; ++i)
	{
		if (ps->a[i] == x)
		{
			return -1;
		}
	}
	return -1;
}

总结

// 基本增删查改接口
// 顺序表初始化
void SeqListInit(SeqList* psl);
// 顺序表销毁
void SeqListDestory(SeqList* psl);
// 顺序表打印
void SeqListPrint(SeqList* psl);
// 检查空间,如果满了,进行增容
void CheckCapacity(SeqList* psl);
// 顺序表尾插
void SeqListPushBack(SeqList* psl, SLDataType x);
// 顺序表尾删
void SeqListPopBack(SeqList* psl);
// 顺序表头插
void SeqListPushFront(SeqList* psl, SLDataType x);
// 顺序表头删
void SeqListPopFront(SeqList* psl);
// 顺序表查找
int SeqListFind(SeqList* psl, SLDataType x);
// 顺序表在pos位置插入x
void SeqListInsert(SeqList* psl, size_t pos, SLDataType x);
// 顺序表删除pos位置的值
void SeqListErase(SeqList* psl, size_t pos);

6.顺序表相关OJ练习题

6.1原地移除元素

6.2合并两个有序数组

以上是关于02线性表之顺序表的主要内容,如果未能解决你的问题,请参考以下文章

数据结构——线性表之顺序表篇

数据结构之顺序表

数据结构之线性表之顺序存储Java实现

线性链表之顺序表

线性表之顺序表

线性链表之顺序表