Bootstrap

数据结构第二讲:顺序表

1.线性表

顺序表是数据结构中的一种组织方式,是N个具有相同特性数据元素的有限序列,常见的线性表有:顺序表、链表、栈、队列、字符串…
本篇博客我们将详细阐述顺序表的实现以及注意事项

线性表在逻辑上是线性的,在物理结构上不一定是连续的
而顺序表在逻辑上和物理结构上都是连续的

2.什么是顺序表

顺序表是用一段物理地址连续的存储单元存储数据元素的线性结构,一般情况下采用数组存储

顺序表的底层逻辑是数组,但是有不同于数组,顺序表是对数组进行封装,实现了常见的增删改查的接口,就像这样:
在这里插入图片描述

3. 静态顺序表

顺序表分为静态顺序表和动态顺序表两种,静态顺序表的实现如下:

//静态顺序表的实现
typedef int SLDateType;

#define N 100//数组长度

struct Seqlist
{
	SLDateType arr[N];//定长数组,用来存储数据

	int size;//有效数据个数
};

可以看出,静态顺序表中使用的是定长数组,它的大小是固定的,而动态属性表的大小是可变的,由此可见,动态顺序表要比静态顺序表好用一些

4.动态顺序表

4.1顺序表基础

完成顺序表之前,我们要先实现一个框架,这个框架由结构体来实现,具体如下:

//动态顺序表
typedef int SLDateType;

typedef struct SeqList
{
	SLDateType* a;//首先创建一个指针,指向动态开辟的地址
	int size;//有效数据的个数
	int capacity;//空间容量
}SL;

我们通过这个结构体创建了一个 sl 结构体变量

4.2顺序表的初始化

创建了一个变量,首先要做的就是对它的初始化,初始化很简单,操作如下:

//顺序表的初始化
void SLInit(SL* pa)
{
	//先将指向的空间赋值为0
	pa->a = NULL;
	//将大小全部初始化为0
	pa->capacity = 0;
	pa->size = 0;
}

4.3顺序表的销毁

写出了顺序表的初始化,我们顺便将顺序表的销毁也一并写了就行了,方式如下:

//顺序表的销毁
void SLDestory(SL* pa)
{
	//将结构体中的变量一一销毁即可
	if (pa->a != NULL)
	{
		free(pa->a);
		pa->a = NULL;
	}
	pa->capacity = 0;
	pa->size = 0;
}

4.4顺序表的尾插

在这里插入图片描述
既然size指向的位置就是需要插入的位置的下表,那么我们直接在size这个位置插入数据,然后将size++,指向下一个位置即可,操作方法如下:

//顺序表尾插
void SLPushBack(SL* ps, SLDateType x)
{
	if (ps->capacity == ps->size)
	{
		//首先判断一开始是否有空间
		int newcapacity = ps->capacity == 0 ? 4 : 2 * ps->capacity;

		//然后需要开辟空间,才能插入数据
		SLDateType* pa = (SLDateType*)realloc(ps->a, newcapacity * sizeof(SLDateType));//此时一般开辟两倍的空间

		if (pa == NULL)
		{
			perror("realloc faile!");
			exit(-1);
		}
		ps->a = pa;
		ps->capacity = newcapacity;
	}

	//尾插就是直接将数据插入即可
	ps->a[ps->size++] = x;
}

在这串代码中,对于空间大小的判断需要经常使用,所以我们将它封装成一个函数即可:

//顺序表检查空间是否充足
void SLCheckCapacity(SL* ps)
{
	if (ps->capacity == ps->size)
	{
		//首先判断一开始是否有空间
		int newcapacity = ps->capacity == 0 ? 4 : 2 * ps->capacity;

		//然后需要开辟空间,才能插入数据
		SLDateType* pa = (SLDateType*)realloc(ps->a, newcapacity * sizeof(SLDateType));//此时一般开辟两倍的空间

		if (pa == NULL)
		{
			perror("realloc faile!");
			exit(-1);
		}
		ps->a = pa;
		ps->capacity = newcapacity;
	}
}

这时尾插就变得非常朴实无华了:

//顺序表尾插
void SLPushBack(SL* ps, SLDateType x)
{
	assert(ps);
	SLCheckCapacity(ps);

	//尾插就是直接将数据插入即可
	ps->a[ps->size++] = x;
}

4.5顺序表的头插

有尾插,那肯定要有头插,头插就是将所有数据向后移,然后在下标为0的位置插入数据即可:
平移前:
在这里插入图片描述
平移之后:
在这里插入图片描述
实现方法如下,一个简单的for循环即可:

//顺序表的头插
void SLPushFront(SL* ps, SLDateType x)
{
	//注意点:头插之前,需要判断ps是否为空
	assert(ps);

	//头插也一样,也需要先判断一下空间是否充足
	SLCheckCapacity(ps);

	//头插首先需要将所有数据进行后移
	for (int i = ps->size; i > 0; i--)
	{
		ps->a[i] = ps->a[i - 1];
	}

	//随后将数据插入到第一位即可
	ps->a[0] = x;
	ps->size++;
}

4.6顺序表的尾删

尾删非常简单,将size–就行了,因为根本就没有必要将数据删除!!!方法如下:

//顺序表的尾删
void SLPopBack(SL* ps)
{
	//首先需要先判断ps是否为空,没有空间不能删除,指针为空不能删除
	assert(ps);
	assert(ps->size);

	//直接将size-1就行了
	ps->size--;
}

4.7顺序表的头删

头删也很简单,将所有数据向前平移即可,将第一个数据覆盖掉就行了:

//顺序表的头删
void SLPopFront(SL* ps)
{
	//头删时,空指针不能删,没有空间不能删
	assert(ps);
	assert(ps->size);

	//头删将所有的数据向前平移,size--即可
	for (int i = 0; i < ps->size-1; i++)
	{
		ps->a[i] = ps->a[i + 1];
	}
	ps->size--;
}

4.8顺序表在指定位置之前插入数据

随机插入数据才更显得高级,难道不是吗?
假设pos是我们需要插入位置的下标,方法根据画图来理解吧:
在这里插入图片描述
在这里插入图片描述
实现方法如下:

//顺序表在指定位置之前插入数据
void SLInsert(SL* ps, SLDateType x, int pos)
{
	//我们插入的位置肯定不能够超过有效空间的位置,也不能小于0
	assert(ps);
	assert(pos >= 0 && pos <= ps->size);
	//先检查空间是否足够
	SLCheckCapacity(ps);

	//插入就是将数据进行后移,空出位置进行插入即可
	for (int i = ps->size; i > pos; i--)
	{
		ps->a[i] = ps->a[i - 1];
	}
	ps->a[pos] = x;
	ps->size++;
}

4.9顺序表删除指定位置的数据

要删除指定位置的数据的话只需要一个平移就可以了,就是这样:
在这里插入图片描述
实现方法如下:

//顺序表删除指定位置的数据
void Erase(SL* ps, int pos)
{
	assert(ps);
	assert(pos >= 0 && pos < ps->size);

	for (int i = pos; i < ps->size - 1; i++)
	{
		ps->a[i] = ps->a[i + 1];
	}
	ps->size--;
}

4.10顺序表查找数据

查找数据十分简单,只需要遍历数组就可以了:

//顺序表查找数据
void SLFind(SL* ps, SLDateType x)
{
	assert(ps);

	for (int i = 0; i < ps->size; i++)
	{
		if (ps->a[i] == x)
		{
			printf("找到了! 下标为:%d\n", i);
			return;
		}
	}
	printf("没找到!\n");
}

4.11顺序表的打印

实现了那么多的操作,不打印一下看一下那怎么能行呢?打印很简单,方法如下:

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

5.算法题1

链接: 移除元素

int removeElement(int* nums, int numsSize, int val) {
    int src = 0;
    int dst = 0;
    int k = numsSize;

    while (src < numsSize) {
        if (nums[src] == val) {
            src++;
            k--;
        } else
            nums[dst++] = nums[src++];
    }

    return k;
}

6.算法题2

链接: 删除有序数组中的重复项

int removeDuplicates(int* nums, int numsSize) {
    int src = 1;
    int dst = 0;
    int k = 1;

    while (src < numsSize) {
        if (nums[src] != nums[dst]) {
            nums[++dst] = nums[src];
            k++;
        }
        src++;
    }
    return k;
}

7.算法题3

链接: 合并两个有序数组

void merge(int* nums1, int nums1Size, int m, int* nums2, int nums2Size, int n) {
    int l1 = m - 1;
    int l2 = n - 1;
    int l3 = nums1Size - 1;

    while (1) {
        if (l3 == l1)
            return;
        if (l3 == l2)
            break;
            
        if (nums1[l1] > nums2[l2]) {
            nums1[l3--] = nums1[l1--];
        } else {
            nums1[l3--] = nums2[l2--];
        }
    }

    for (int i = l2; i >= 0; i--)
        nums1[i] = nums2[i];
}

8.顺序表问题以及思考

当然,顺序表并不是完美的,它仍然具有着一些问题:
在这里插入图片描述
为了解决以上问题,我们就需要引入一个东西:链表,这个我们下一次再讲!!!

;