【数据结构】顺序表+经典算法OJ(零基础附完整代码)

目录

一、什么是顺序表?

1. 线性表

2. 顺序表的概念及结构

 二、顺序表的分类

1. 静态顺序表

2. 动态顺序表

三、动态顺序表的实现

1. 顺序表的初始化

2. 顺序表扩容

3. 顺序表的尾插

4. 顺序表的头插

 5. 顺序表的打印

6. 顺序表尾删

7. 顺序表头删

8. 顺序表指定位置插入

9. 顺序表指定位置删除

10. 查找

四、完整代码

五、顺序表经典算法

1. 移除元素

题目描述:

思路一:创建新数组

思路二:双指针法

代码实现

2. 合并两个有序数组

题目描述 :

思路一:

思路二:创建三个指针

代码实现


一、什么是顺序表?

1. 线性表

线性表是具有相同特性的数据元素的有限序列,线性表是一种在实际中广泛使用的数据结构。常见的线性表:顺序表,链表,栈,队列...。线性表在逻辑结构上是线性结构,可以想象成一条直线;但在物理结构上不一定是连续的。

2. 顺序表的概念及结构

顺序表的底层结构是数组,所以顺序表的逻辑结构和物理结构和数组是一致的。只是顺序表是对数组的一个封装,顺序表在数组的基础上又增加了一些增删查改的接口。

对于数组arr,其逻辑结构是线性的,可以理解为一条直线;由于其在内存中占用连续的存储空间,且逻辑上相邻的元素在物理位置上也相邻,所以其物理结构(存储结构)是连续的。

 

对于顺序表,其底层结构为数组,所以逻辑结构是线性的,物理结构(存储结构 )是连续的。

 二、顺序表的分类

1. 静态顺序表

静态顺序表即数组长度是给定的。

​//静态顺序表
#define N 10
//宏定义常量,便于修改操作
struct SeqList
{
	int arr[N];//定长数组
	int size;//有效数据个数
};

静态顺序表由于数组长度是固定的,因此,在工作场景中不够灵活。空间给少了不够用,空间给大了造成浪费 。所以静态顺序表存在很大的缺陷

2. 动态顺序表

 动态顺序表相比于静态顺序表,其空间是按需申请的,使用过程中更加灵活。因此,我们下面主要介绍动态顺序表各种功能的实现。

//动态顺序表
typedef int DateType;
typedef struct SeqList
{
	DateType* arr;
	int size;//有效数据个数
	int capacity;//空间容量
}SL;

 动态顺序表的优点就是,所需要的空间根据实际情况用realloc函数来扩容,不会造成空间浪费。

三、动态顺序表的实现

1. 顺序表的初始化

由于arr为指针,则初始化为NULL;size和capacity为整型变量,则初始化为0。

​//顺序表初始化
void InitSL(SL* ps)
{
	ps->arr = NULL;//指针设置为空
	ps->size = ps->capacity = 0;
}

2. 顺序表扩容

在下面的顺序表尾插,头插,指定位置插入操作中都需要判断当前数组的空间是否足够,当空间不足时,就需要对顺序表进行扩容操作。因此,我们单独将扩容的函数封装起来,在使用时直接调用来提高效率。

扩容其实就是要判断空间中的有效数据个数(ps->size)和空间容量大小(ps->capacity)是否相等。若相等,就需要扩容操作

(1)先使用if语句判断size和capacity的大小是否相等;

(2)若相等,就扩容,但是又有两种不同的情况,我们用三目操作符来判断:

情况1:ps->size和ps->capacity均为0,此时给capacity增加4个空间。

情况2:ps->size和ps->capacity不为0,此时,capacity增加为原来的两倍。

(3)为了防止realloc函数申请空间失败而丢失数据,先用tmp记下扩容后空间的起始地址,判断tmp指针不为空之后再让arr指针指向tmp。

(4)将扩容后的newcapacity赋值给ps->capacity。

需要注意:当ps->size和ps->capacity相等且不为0时,我们在这里进行2倍扩容,这是一种对于空间处理较优的解决方式。如果大家感兴趣,可以搜索相关的证明。

代码展示

​//扩容
void ChechCapacity(SL* ps)
{
	if (ps->size == ps->capacity)
	{
		//用三目操作符判断
		int newcapacity = ps->capacity == 0 ? 4 : 2 * ps->capacity;
		//给数组arr调整空间大小
		DateType* tmp = (SL*)realloc(ps->arr, sizeof(DateType) * newcapacity);
		if (tmp == NULL)
		{
			perror("realloc");
			exit(1);
		}
		ps->arr = tmp;
		ps->capacity = newcapacity;
	}
}

3. 顺序表的尾插

(1)断言,防止传入空指针,无法对空指针进行解引用操作;

(2)想要尾插,数组就要有足够的空间,因此,在尾插之前我们需要先检查。而上面的顺序表扩容已经实现好了检查,同时,还调整了空间的大小,所以我们只需要调用上面的函数;

(3)将需要尾插的值插入,但是别忘了让有效数据个数size++

代码展示如下

//顺序表的尾插
void SLPushBack(SL* ps, DateType x)
{
	//检查,防止传入空指针
	assert(ps);
	//调用扩容函数
	ChechCapacity(ps);
	ps->arr[ps->size++] = x;//赋值同时让size++
}

4. 顺序表的头插

头插的前面与尾插类似,但是,为了防止头插之后覆盖掉第一个数据,我们需要将数组中的元素整体往后移动一个位置。然后,赋值并让size++

代码如下

//顺序表的头插
void SLPushFront(SL* ps, DateType x)
{
	//断言
	assert(ps);
	ChechCapacity(ps);
	//将数组整体向后移动一个位置
	memmove(ps->arr + 1, ps->arr, ps->size * (sizeof(DateType)));
	ps->arr[0] = x;//赋值
	ps->size++;//有效数据加1
}

 5. 顺序表的打印

实现顺序表的打印,方便测试。

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

运行结果

6. 顺序表尾删

尾删即在保证有效数据个数不为0的情况下,让有效数据个数减1。

代码如下

//顺序表的尾删
void SLPopback(SL* ps)
{
	assert(ps);
	assert(ps->size != 0);//针对ps->size为0
	ps->size--;//有效数据减1
}

7. 顺序表头删

头删可以让数组首元素后面的元素整体向前移动一个位置,用覆盖首元素的方式实现头删。

代码如下

//顺序表的头删
void SLPopFront(SL* ps)
{
	assert(ps);
	assert(ps->size != 0);
	memmove(ps->arr, ps->arr + 1, (--ps->size) * sizeof(DateType));//头删并--size(前置--,先减后用)
}

8. 顺序表指定位置插入

(1)断言:防止传空指针,同时,指定的位置要在数组下标范围之内;

(2)要插入数据,就要判断空间大小是否足够;

(3)将pos及之后的元素整体向后移动一个位置,防止插入的数据覆盖原来的数据;

(4)插入数据,size++

//在指定位置插入数据
void SLInsert(SL* ps, int pos,DateType x)
{
	assert(ps);
	//pos只能在下标范围之内
	assert(pos >= 0 && pos < ps -> size);
	//检查空间大小
	ChechCapacity(ps);
	//移动pos及之后的元素
	memmove(ps->arr + pos + 1, ps->arr + pos, (ps->size - pos) * sizeof(DateType));
	ps->arr[pos] = x;//插入
	ps->size++;//有效数据加1
}

9. 顺序表指定位置删除

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

	memmove(ps->arr + pos, ps->arr + pos + 1, (ps->size - pos - 1) * sizeof(DateType));
	ps->size--;
}

10. 查找

遍历数组

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

四、完整代码

(1)SeqList.h文件(头文件和函数声明)

#pragma once
#include<stdio.h>
#include<stdlib.h>
#include<assert.h>
#include<string.h>

//静态顺序表
//#define N 10
//struct SeqList
//{
//	int arr[N];//定长数组
//	int size;//有效数据个数
//};

//动态顺序表
typedef int DateType;
typedef struct SeqList
{
	DateType* arr;
	int size;//有效数据个数
	int capacity;//空间容量
}SL;

//顺序表初始化
void InitSL(SL* ps);
//顺序表的销毁
void DestrySL(SL* ps);
//顺序表的尾插
void SLPushBack(SL* ps, DateType x);
//顺序表的头插
void SLPushFront(SL* ps, DateType x);
//顺序表的打印
void SLPrint(SL* ps,int sz);
//顺序表的尾删
void SLPopback(SL* ps);
//顺序表的头删
void SLPopFront(SL* ps);
//查找
int Find(SL* ps, DateType x);
//在指定位置插入数据
void SLInsert(SL* ps, int pos,DateType x);
//删除指定位置的数据
void SLEarse(SL* ps, int pos);

(2)SeqList.c文件(增删查改等实现代码)

#define _CRT_SECURE_NO_WARNINGS 1
#include"SeqList.h"
//顺序表初始化
void InitSL(SL* ps)
{
	ps->arr = NULL;//指针设置为空
	ps->size = ps->capacity = 0;
}

//顺序表的销毁
void DestrySL(SL* ps)
{
	if (ps->arr)
	{
		free(ps->arr);
	}
	ps->arr = NULL;
	ps->size = ps->capacity = 0;
}

//扩容
void ChechCapacity(SL* ps)
{
	if (ps->size == ps->capacity)
	{
		//用三目操作符判断
		int newcapacity = ps->capacity == 0 ? 4 : 2 * ps->capacity;
		//给数组arr调整空间大小
		DateType* tmp = realloc(ps->arr, sizeof(DateType) * newcapacity);
		if (tmp == NULL)
		{
			perror("realloc");
			exit(1);
		}
		ps->arr = tmp;
		ps->capacity = newcapacity;
	}
}
//顺序表的尾插
void SLPushBack(SL* ps, DateType x)
{
	//检查,防止传入空指针
	assert(ps);
	//调用扩容函数
	ChechCapacity(ps);
	ps->arr[ps->size++] = x;//赋值同时让size++
}

//顺序表的头插
void SLPushFront(SL* ps, DateType x)
{
	//断言
	assert(ps);
	ChechCapacity(ps);
	//将数组整体向后移动一个位置
	memmove(ps->arr + 1, ps->arr, ps->size * (sizeof(DateType)));
	ps->arr[0] = x;//赋值
	ps->size++;//有效数据加1
}

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

//顺序表的尾删
void SLPopback(SL* ps)
{
	assert(ps);
	assert(ps->size != 0);//针对ps->size为0
	ps->size--;
}

//顺序表的头删
void SLPopFront(SL* ps)
{
	assert(ps);
	assert(ps->size != 0);
	memmove(ps->arr, ps->arr + 1, (--ps->size) * sizeof(DateType));//头删并--size(前置--,先减后用)
}

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

//在指定位置插入数据
void SLInsert(SL* ps, int pos,DateType x)
{
	assert(ps);
	//pos只能在下标范围之内
	assert(pos >= 0 && pos < ps -> size);
	//检查空间大小
	ChechCapacity(ps);
	//移动pos及之后的元素
	memmove(ps->arr + pos + 1, ps->arr + pos, (ps->size - pos) * sizeof(DateType));
	ps->arr[pos] = x;//插入
	ps->size++;//有效数据加1
}

//删除指定位置的数据
void SLEasert(SL* ps, int pos)
{
	assert(ps);
	assert(pos >= 0 && pos < ps->size);
	memmove(ps->arr + pos, ps->arr + pos + 1, (ps->size - pos - 1) * sizeof(DateType));
	ps->size--;
}

(3)test.c文件(测试代码)

#define _CRT_SECURE_NO_WARNINGS 1
#include"SeqList.h"

//测试尾插,头插,打印
void test01()
{
	SL s;
	InitSL(&s);//初始化
	
	//尾插
	SLPushBack(&s, 1);
	SLPushBack(&s, 2);
	
	////头插
	//SLPushFront(&s, 4);
	//SLPushFront(&s, 5);
	
	//尾删
	SLPopback(&s);
	//SLPopback(&s);
	//SLPopback(&s);
	
	//头删
	SLPopFront(&s);
	//SLPopFront(&s);
	//SLPopFront(&s);
	
	//打印
	SLPrint(&s, s.size);
}

void test02()
{
	SL s;
	InitSL(&s);//初始化
	//尾插
	SLPushBack(&s, 1);
	SLPushBack(&s, 2);
	SLPushBack(&s, 3);
	//指定位置插入
    SLInsert(&s, 1, 4);
	////指定位置删除
	//SLEasert(&s, 2);
	//打印
	SLPrint(&s, s.size);
}

int main()
{
	//test01();
	test02();
	return 0;
}

五、顺序表经典算法

1. 移除元素

题目描述:

给你一个数组 nums 和一个值 val,你需要移除所有数值等于 val 的元素。元素的顺序可能发生改变。然后返回 nums 中与 val 不同的元素的数量。

思路一:创建新数组

创建一个新的数组,同时,创建一个变量 k 来记录与val 值不同的元素个数。然后遍历源数组,当源数组中的值不等于 val值时,将该值拿过来放到新数组中,并且k++

思路二:双指针法

创建两个指针src和det,并且同时指向数组的第一个元素,即同时记录数组首元素的下标。然后,开始遍历数组,当数组元素等于val 值时,仅src++;反之,将src指向的元素赋值到det指向的位置,同时,src++,det++,k++。

该方法不开辟额外的空间。

代码实现
int removeElement(int* nums, int numsSize, int val) 
{
    int src=0;
    int det=0;
    int k=0;
    for(int i=0;i<numsSize;i++)
    {
        if(nums[i]==val)
        {
            src++;
        }
        else
        {
            nums[det++]=nums[src++];
            k++;
        }
    }
    return k;
}

2. 合并两个有序数组

题目描述 :

给你两个按 非递减顺序 排列的整数数组 nums1 和 nums2,另有两个整数 m 和 n ,分别表示 nums1 和 nums2 中的元素数目。请你 合并 nums2 到 nums1 中,使合并后的数组同样按 非递减顺序 排列。题目提到不能开辟额外的空间。

思路一:

先将nums2数组中的所有元素先放到nums1数组的后面,然后,对nums1数组进行排序。

思路二:创建三个指针

创建三个指针s1,s2,s3,分别指向nums1的最后一个有效数据,nums2的最后一个有效数据以及nums1数组的最后,这样当我们遍历数组比较并赋值时,就不会出现将还没使用的数据覆盖掉的情况。如下图

然后,开始遍历两个数组。当s1指向元素大于或等于s2指向的元素时,将s1指向的元素拿过来放到s3指向的位置,同时,s1++,且s3++。一次操作如下图所示

但是,又有两种不同的情况。

情况一:s2 先出循环。如图,

此时,nums1中已经是合并好的数据,所以不用再做处理。

情况二:s1先出循环。如图,

 此时,nums2中的数据1并没有放到nums1中,因此,我们还需要做额外的处理,将剩下的数据放到nums1中。

代码实现
void merge(int* nums1, int nums1Size, int m, int* nums2, int nums2Size, int n)
{
    int s1=m-1;
    int s2=n-1;
    int s3=m+n-1;
    while(s1>=0&&s2>=0)
    {
        if(nums1[s1] >= nums2[s2])
        {
            nums1[s3--]=nums1[s1--];
        }
        else
        {
            nums1[s3--]=nums2[s2--];
        }
    }
    while(s2>=0)
    {
        nums1[s3--]=nums2[s2--];
    }
}

评论 7
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值