【数据结构2】线性表——顺序表
文章目录
- 前言
- 一、线性表的概念
- 二、顺序表的实现
- 2.1.顺序表的概念及结构
- 2.2.动态顺序表的接口实现
- 2.3.动态顺序表的全部代码展示
- 三、顺序表相关OJ题练习
- 总结
前言
【本节目标】
1.了解线性表概念;
2.实现顺序表的增删查改等功能;
3.练习顺序表相关OJ题。
一、线性表的概念
线性表(linear list)是n个具有相同特性的数据元素的有限序列。 线性表是一种在实际中广泛使
用的数据结构,常见的线性表:顺序表、链表、栈、队列、字符串…
线性表在逻辑上是线性结构,也就说是连续的一条直线。但是在物理结构上并不一定是连续的,
线性表在物理上存储时,通常以数组和链式结构的形式存储,分别对应着顺序表和链表,今天我们先来学习顺序表。
二、顺序表的实现
2.1.顺序表的概念及结构
顺序表是用一段物理地址连续的存储单元依次存储数据元素的线性结构,一般情况下采用数组存储。在数组上完成数据的增删查改等功能。
顺序表一般可以分为:
- 静态顺序表:使用定长数组存储
- 动态顺序表:使用动态开辟的数组存储
//顺序表的静态存储
//增强程序的可维护性,MAX_SIZE和SQDataType方便可改
#define MAX_SIZE 100
typedef int SQDataType;
typedef struct SeqList
{
SQDataType a[MAX_SIZE]; //定长数组
int size; //有效数据的个数
}SL;
//顺序表的动态存储
typedef int SQDataType;
typedef struct SeqList
{
SQDataType* a; //指向动态开辟的数组
int size; //有效数据的个数
int capacity; //容量空间的大小
}SL;
2.2.动态顺序表的接口实现
静态顺序表的缺点:定长数组导致空间开多了浪费,开少了不够用,不能灵活控制。静态顺序表只适用于确定知道需要存多少数据的场景。
所以现实中基本都是使用动态顺序表,根据需要动态的分配空间大小,所以下面我们实现动态顺序表的基本增删查改等函数接口。
//顺序表初始化
void SeqListInit(SL* ps)
{
ps->a = NULL;
ps->size = 0;
ps->capacity = 0;
}
//顺序表的销毁
//malloc的空间不销毁就会存在内存泄漏
void SeqListDestory(SL* ps)
{
free(ps->a);
ps->a = NULL;
ps->capacity = ps->size = 0;
}
//函数封装:检查顺序表容量是否为满,满了则扩容
void SeqListCheckCapacity(SL* ps)
{
//容量满了就要扩容扩两倍
if (ps->size == ps->capacity)
{
/*realloc(原空间,扩容大小) :
后面有足够的空间就原地扩,没有的话开辟一个新空间拷贝数据,释放旧空间。
原空间如果为空,则申请一个新空间,相当于malloc。*/
int newcapacity = ps->capacity == 0 ? 4 : ps->capacity * 2; //扩两倍
SQDataType* tmp = (SQDataType*)realloc(ps->a, newcapacity * sizeof(SQDataType));
if (tmp == NULL)
{
printf("realloc fail\n");
exit(-1);
}
else
{
ps->a = tmp;
ps->capacity = newcapacity;
}
}
}
//顺序表的任意位置数据的插入
void SeqListInsert(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++;
}
//顺序表的尾插
void SeqListPushBack(SL* ps, SQDataType x)
{
SeqListCheckCapacity(ps); //检查顺序表容量是否为满,满了则扩容
ps->a[ps->size] = x;
ps->size++;
}
//顺序表的尾插,或者直接复用SeqListInsert
void SeqListPushBack(SL* ps, SQDataType x)
{
SeqListInsert(ps, ps->size, x);
}
//顺序表的头插
void SeqListPushFront(SL* ps, SQDataType x)
{
SeqListCheckCapacity(ps);
//先将数据都往后挪一位,再将要插入的数据插在头
int end = ps->size - 1;
while (end >= 0)
{
ps->a[end + 1] = ps->a[end];
--end;
}
ps->a[0] = x;
ps->size++;
}
//顺序表的头插,或者直接复用SeqListInsert
void SeqListPushFront(SL* ps, SQDataType x)
{
SeqListInsert(ps, 0, x);
}
//顺序表的任意位置数据的删除
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--;
}
//顺序表的尾删
void SeqListPopBack(SL* ps)
{
assert(ps->size > 0); //断言,相当于粗暴的if,用来检查数组是否为空
//断言函数void assert(int expression):expression结果为0则直接终止程序,否则不作操作
ps->size--; //尾删直接把有效数据个数减一就可以了
}
//顺序表的尾删,或者直接复用SeqListErase
void SeqListPopBack(SL* ps)
{
SeqListErase(ps, ps->size - 1);
}
//顺序表的头删
void SeqListPopFront(SL* ps)
{
assert(ps->size > 0);
//从第二个数开始,后面的数全部往前挪一位
int start = 1;
while (start < ps->size)
{
ps->a[start - 1] = ps->a[start];
++start;
}
ps->size--;
}
//顺序表的头删,或者直接复用SeqListErase
void SeqListPopFront(SL* ps)
{
SeqListErase(ps, 0); //直接复用SeqListErase
}
//顺序表的查找
int SeqListFind(SL* ps, SQDataType x)
{
for (int i = 0; i < ps->size; ++i)
{
if (ps->a[i] == x)
return i;
}
return -1;
}
//顺序表的修改
void SeqListModity(SL* ps, int pos, SQDataType x)
{
assert(pos < ps->size);
ps->a[pos] = x;
}
//顺序表的打印
void SeqListPrint(SL* ps)
{
for (int i = 0; i < ps->size; ++i)
{
printf("%d ", ps->a[i]);
}
printf("\n");
}
2.3.动态顺序表的全部代码展示
- SeqList.h
#pragma once
#include<stdio.h>
#include<string.h>
#include<stdlib.h>
#include<assert.h>
typedef int SQDataType;
typedef struct SeqList
{
SQDataType* a;
int size; //有效数据的个数
int capacity; //容量空间的大小
}SL;
//增删查改等接口函数的函数声明
void SeqListInit(SL* ps);
void SeqListPrint(SL* ps);
void SeqListDestory(SL* ps);
void SeqListPushBack(SL* ps, SQDataType x);
void SeqListPushFront(SL* ps, SQDataType x);
void SeqListPopBack(SL* ps);
void SeqListPopFront(SL* ps);
void SeqListInsert(SL* ps, int pos, SQDataType x);
void SeqListErase(SL* ps, int pos);
int SeqListFind(SL* ps, SQDataType x);
void SeqListModity(SL* ps, int pos, SQDataType x);
#endif
- SeqList.c
#define _CRT_SECUER_NO_WARNINGS
#include "SeqList.h"
//顺序表的初始化
void SeqListInit(SL* ps)
{
ps->a = NULL;
ps->size = 0;
ps->capacity = 0;
}
//顺序表的销毁,malloc的空间不销毁就会存在内存泄漏
void SeqListDestory(SL* ps)
{
free(ps->a);
ps->a = NULL;
ps->capacity = ps->size = 0;
}
//函数封装:检查顺序表的容量是否为满
void SeqListCheckCapacity(SL* ps)
{
//满了就要扩容 扩两倍
if (ps->size == ps->capacity)
{
/*realloc(原空间,扩容大小) :
后面有足够的空间就原地扩,没有的话开辟一个新空间拷贝数据,释放旧空间
原空间如果为空,则申请一个新空间,相当于malloc*/
int newcapacity = ps->capacity == 0 ? 4 : ps->capacity * 2;
SQDataType* tmp = (SQDataType*)realloc(ps->a, newcapacity * sizeof(SQDataType));
if (tmp == NULL)
{
printf("realloc fail\n");
exit(-1);
}
else
{
ps->a = tmp;
ps->capacity = newcapacity;
}
}
}
//顺序表的尾插
void SeqListPushBack(SL* ps, SQDataType x)
{
/*SeqListCheckCapacity(ps); //检查顺序表容量是否为满,满了则扩容
ps->a[ps->size] = x;
ps->size++;*/
SeqListInsert(ps, ps->size, x); //直接复用SeqListInsert
}
//顺序表的头插
void SeqListPushFront(SL* ps, SQDataType x)
{
/*SeqListCheckCapacity(ps);
先将数据都往后挪一位,再将要插入的数据插在头
int end = ps->size - 1;
while (end >= 0)
{
ps->a[end + 1] = ps->a[end];
--end;
}
ps->a[0] = x;
ps->size++;*/
SeqListInsert(ps, 0, x);
}
//顺序表的尾删
void SeqListPopBack(SL* ps)
{
//assert(ps->size > 0); //相当于粗暴的if,用来检查
//断言函数void assert(int expression):expression结果为0则直接终止程序,否则不作操作
//ps->size--;
SeqListErase(ps, ps->size - 1); //直接复用SeqListErase
}
//顺序表的头删
void SeqListPopFront(SL* ps)
{
//assert(ps->size > 0);
//从第二个数开始,后面的数全部往前挪一位
//int start = 1;
//while (start < ps->size)
//{
// ps->a[start - 1] = ps->a[start];
// ++start;
//}
//ps->size--;
SeqListErase(ps, 0);
}
//顺序表的任意位置数据的插入
void SeqListInsert(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++;
}
//顺序表的任意位置数据的删除
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--;
}
//顺序表的查找
int SeqListFind(SL* ps, SQDataType x)
{
for (int i = 0; i < ps->size; ++i)
{
if (ps->a[i] == x)
return i;
}
return -1;
}
//顺序表的修改
void SeqListModity(SL* ps, int pos, SQDataType x)
{
assert(pos < ps->size);
ps->a[pos] = x;
}
//顺序表的打印
void SeqListPrint(SL* ps)
{
for (int i = 0; i < ps->size; ++i)
{
printf("%d ", ps->a[i]);
}
printf("\n");
}
- Test.c(测试写一个测一个,方便改错,这里写完后我将顺序表做成了一个菜单)
#define _CRT_SECUER_NO_WARNINGS
#include "SeqList.h"
//void TestSeqList1()
//{
// SL sl;
// SeqListInit(&sl);
// SeqListPushBack(&sl, 6);
// SeqListPushBack(&sl, 7);
// SeqListPushBack(&sl, 8);
// SeqListPushBack(&sl, 9);
// SeqListPushBack(&sl, 10);
// SeqListPrint(&sl);
//
// SeqListPushFront(&sl, 5);
// SeqListPushFront(&sl, 4);
// SeqListPushFront(&sl, 3);
// SeqListPushFront(&sl, 2);
// SeqListPushFront(&sl, 1);
// SeqListPrint(&sl);
//
// SeqListPopBack(&sl);
// SeqListPopBack(&sl);
// SeqListPrint(&sl);
//
// SeqListPopFront(&sl);
// SeqListPopFront(&sl);
// SeqListPrint(&sl);
//
// SeqListInsert(&sl, 3, 250);
// SeqListPrint(&sl);
//
// SeqListErase(&sl, 3);
// SeqListPrint(&sl);
//
// SeqListDestory(&sl);
//}
//菜单 (不要一上来就写,先写上面的测试)
void menu()
{
printf("\n--------------------------------------\n");
printf("1.尾插数据 2.头插数据\n");
printf("3.尾删数据 4.头删数据\n");
printf("5.选择某一位置插入数据\n");
printf("6.选择某一位置删除数据\n");
printf("7.查找数据 8.修改数据\n");
printf("9.清空 -1.退出\n");
printf("--------------------------------------\n");
printf("请输入你的操作选项:\n");
}
int main()
{
SL s;
SeqListInit(&s);
int option = 0;
int x = 0, y = 0;
while (option != -1)
{
menu();
scanf_s("%d", &option);
switch (option)
{
case 1:
printf("请输入你要插入的数据,以-1结束\n");
do
{
scanf_s("%d", &x);
if (x != -1)
{
SeqListPushBack(&s, x);
}
} while (x != -1);
SeqListPrint(&s);
break;
case 2:
printf("请输入你要插入的数据,以-1结束\n");
do
{
scanf_s("%d", &x);
if (x != -1)
{
SeqListPushFront(&s, x);
}
} while (x != -1);
SeqListPrint(&s);
break;
case 3:
SeqListPopBack(&s);
SeqListPrint(&s);
break;
case 4:
SeqListPopFront(&s);
SeqListPrint(&s);
break;
case 5:
printf("请输入你要插入的位置和数据:\n");
scanf_s("%d %d", &x, &y);
SeqListInsert(&s, x, y);
SeqListPrint(&s);
break;
case 6:
printf("请输入你要删除的数据的位置:\n");
scanf_s("%d", &y);
SeqListErase(&s, y);
SeqListPrint(&s);
break;
case 7:
printf("请输入你要查找的数据:\n");
scanf_s("%d", &x);
y = SeqListFind(&s, x);
if (y == -1)
printf("未找到!\n");
else
printf("它在第%d个位置\n", y);
break;
case 8:
printf("请输入你要修改的位置和数据:\n");
scanf_s("%d %d", &x, &y);
SeqListModity(&s, x, y);
SeqListPrint(&s);
break;
case 9:
SeqListInit(&s);
break;
case -1:
default:
break;
}
}
SeqListDestory(&s);
return 0;
}
制作菜单没有什么技巧,耐得住性子就好,做出来还是成就感满满的,大家都可以去试试~
三、顺序表相关OJ题练习
3.1. 原地移除元素:https://leetcode-cn.com/problems/remove-element/
//法一:快慢指针,时间复杂度O(N),空间复杂度O(1)。
class Solution {
public:
int removeElement(vector<int>& nums, int val) {
int n = nums.size();
int p1 = 0,p2 = 0; //p1是快指针,p2是慢指针
while(p1 < n)
{
if(nums[p1] != val)
nums[p2++] = nums[p1];
p1 ++;
}
return p2;
}
};
//法二:空间换时间,创建一个新数组,时间复杂度O(N),空间复杂度O(N)。
class Solution {
public:
int removeElement(vector<int>& nums, int val) {
int n = nums.size();
vector<int> b; //创建一新数组b
int cnt=0;
for(int i=0;i<n;++i)
{
if(nums[i] != val)
{
b.push_back(nums[i]);
cnt ++;
}
}
for(int i=0;i<cnt;++i)
nums[i] = b[i];
return cnt;
}
};
3.2. 合并两个有序数组:https://leetcode-cn.com/problems/merge-sorted-array/
//法一:双指针,从后往前插
class Solution {
public:
void merge(vector<int>& nums1, int m, vector<int>& nums2, int n) {
int p1=m-1,p2=n-1,cnt=m+n-1;
while(p1>=0 && p2>=0)
{
if(nums1[p1] > nums2[p2])
nums1[cnt--] = nums1[p1--];
else
nums1[cnt--] = nums2[p2--];
}
while(p2>=0) nums1[cnt--] = nums2[p2--];
//如果p1>=0,不需要处理,因为本来就在nums1里面
}
};
//法二:空间换时间,创建新数组
class Solution {
public:
void merge(vector<int>& nums1, int m, vector<int>& nums2, int n) {
vector<int> a(m+n);
int x=0,y=0,cnt=0;
while(x<m && y<n)
{
if(nums1[x] <= nums2[y])
a[cnt++] = nums1[x++];
else
a[cnt++] = nums2[y++];
}
while(y<n)
a[cnt++] = nums2[y++];
while(x<m)
a[cnt++] = nums1[x++];
for(int i=0;i<m+n;++i)
{
nums1[i] = a[i];
}
}
};
总结
数据结构的线性表——顺序表就到这里了,代码有点多所以文章比较长,顺序表相对较简单,耐着性子把接口函数记住并熟悉,一定要自己把代码敲一遍噢,培养代码能力,对后续学习数据结构也很有帮助,加油!