大家好我是沐曦希
想成为一名合格程序员,那么必不可少要学习数据结构,为后面软件开发等项目打下基础。那么现在从初阶的数据结构线性表——顺序表开始学习。
线性表(linear list)是n个具有相同特性的数据元素的有限序列。 线性表是一种在实际中广泛使用的数据结构,常见的线性表:顺序表、链表、栈、队列、字符串…
线性表在逻辑上是线性结构,也就说是连续的一条直线。但是在物理结构上并不一定是连续的,线性表在物理上存储时,通常以数组和链式结构的形式存储。
顺序表是用一段物理地址连续的存储单元依次存储数据元素的线性结构,一般情况下采用数组存储。在数组上完成数据的增删查改。
顺序表一般可以分为:
静态顺序表
//静态版本的顺序表
#define N 10
typedef int SLDataType;//可以通过更改int来更改顺序表所指向对象的类型
typedef struct SeqList
{
SLDataType a[N];
int size;//存储数据的个数
}SL;
动态顺序表
//动态版本的顺序表
typedef int SLDataType;//可以通过更改int来更改顺序表所指向对象的类型
typedef struct SeqList
{
SLDataType* a; 指向动态开辟的数组
int size;// 有效数据个数
int capacity;// 容量空间的大小
}SL;
满了以后可以通过扩容两倍来增加空间的大小,2倍比较合适,扩容一次扩多了会导致空间浪费;一次扩容少了,又会频繁扩容,导致时间效率的损失。
静态顺序表只适用于确定知道需要存多少数据的场景。静态顺序表的定长数组导致N定大了,空间开多了浪费,开少了不够用。所以现实中基本都是使用动态顺序表,根据需要动态的分配空间大小,所以下面实现动态顺序表。
#pragma once
#define _CRT_SECURE_NO_WARNINGS 1
#include
#include
#include
//动态版本的顺序表
typedef int SLDataType;
typedef struct SeqList
{
SLDataType* a; 指向动态开辟的数组
size_t size;// 有效数据个数
size_t capacity;// 容量空间的大小
}SL;
//初始化顺序表
void SLInit(SL* psl);
//销毁顺序表
void SLDestory(SL* psl);
//打印顺序表
void SLPrint(const SL* psl);
//在顺序表尾部增加元素
void SLPushBack(SL* psl, SLDataType x);
//在顺序表头部增加元素
void SLPushFront(SL* psl, SLDataType x);
//删除顺序表尾部的元素
void SLPopBack(SL* psl);
//删除顺序表头部的元素
void SLPopFront(SL* psl);
// 顺序表查找
int SLFind(const SL* psl, SLDataType x);
// 顺序表在pos位置插入x
void SLInsert(SL* psl, size_t pos, SLDataType x);
// 顺序表删除pos位置的值
void SLErase(SL* psl, size_t pos);
#include"SeqList.h"
void menu()
{
printf("********************************\n");
printf("*** 1.尾插数据 2.尾删数据 ***\n");
printf("*** 3.头插数据 4.头删数据 ***\n");
printf("*** 5.查找数据 6.任意插入数据***\n");
printf("*** 7.任意删除数据 8.排序数据***\n");
printf("*** 9.修改数据 10.打印顺序表 ***\n");
printf("*** 0. 退出顺序表 ***\n");
printf("********************************\n");
}
int main()
{
SL s;
SLInit(&s);
int input = 0;
SLDataType x = 0;
SLDataType old = 0;
size_t pos = 0;
do
{
menu();
printf("请输入你的操作:>");
scanf("%d", &input);
switch(input)
{
case 1:
printf("请连续输入要尾插的数据,用空格隔开每个数据,输入-1结束尾插数据:>");
while (scanf("%d", &x) == 1)
{
if (x == -1)
break;
SLPushBack(&s, x);
}
break;
case 2:
printf("请输入要尾删的次数:>");
scanf("%d", &x);
while (x > s.size)
{
printf("输入错误,要尾删次数大于有效数据的个数,请重新输入:>");
scanf("%d", &x);
}
for (int i = 0; i < x; i++)
{
SLPopBack(&s);
}
break;
case 3:
printf("请连续输入要头插的数据,用空格隔开每个数据,输入-1结束头插数据:>");
while (scanf("%d", &x) == 1)
{
if (x == -1)
break;
SLPushFront(&s, x);
}
break;
case 4:
printf("请输入要头删的次数:>");
scanf("%d", &x);
while (x > s.size)
{
printf("输入错误,要头删次数大于有效数据的个数,请重新输入:>");
scanf("%d", &x);
}
for (int i = 0; i < x; i++)
{
SLPopFront(&s);
}
break;
case 5:
printf("请输入要查找的数据:>");
scanf("%d", &x);
int ret = SLFind(&s, x);
if (ret == -1)
{
printf("没有找到该数据\n");
}
else
printf("该数据的下标:>%d\n", x);
break;
case 6:
printf("请输入要插入数据的位置:>");
scanf("%d", &pos);
printf("请输入要插入的数据:>");
scanf("%d", &x);
pos -= 1;
SLInsert(&s, pos, x);
break;
case 7:
printf("请输入要删除数据的位置:>");
scanf("%d", &pos);
printf("请输入要删除的数据:>");
scanf("%d", &x);
pos -= 1;
SLErase(&s, pos, x);
break;
case 8:
SLSort(&s);
break;
case 9:
printf("请输入要修改之前的数据:>");
scanf("%d", &old);
printf("请输入要修改之后的数据:>");
scanf("%d", &x);
SLModify(&s,old, x);
break;
case 10:
SLPrint(&s);
break;
case 0:
SLDestory(&s);
break;
default:
printf("输入错误,请重新输入:>\n");
break;
}
} while (input);
return 0;
}
#include"SeqList.h"
void SLPrint(const SL* psl)
{
assert(psl);
int i = 0;
for (i = 0; i < psl->size; i++)
{
printf("%d ", psl->a[i]);
}
printf("\n");
}
void SLInit(SL* psl)
{
assert(psl);
psl->a = calloc(4, sizeof(SLDataType));
if (psl->a == NULL)
{
perror("calloc");
return;
}
psl->capacity = 4;
psl->size = 0;
}
void SLDestory(SL* psl)
{
assert(psl);
free(psl->a);
psl->a = NULL;
psl->capacity = 0;
psl->size = 0;
}
void CheckCapacity(SL* psl)
{
if (psl->size == psl->capacity)
{
SLDataType* tmp = (SLDataType*)realloc(psl->a, psl->capacity * 2 * sizeof(SLDataType));
if (tmp == NULL)
{
perror("realloc fail");
return;
}
psl->a = tmp;
psl->capacity *= 2;
}
}
void SLPushBack(SL* psl, SLDataType x)
{
assert(psl);
CheckCapacity(psl);
psl->a[psl->size] = x;
psl->size++;
}
void SLPushFront(SL* psl, SLDataType x)
{
assert(psl);
CheckCapacity(psl);
int end = 0;
for (end = psl->size; end > 0; end--)
{
psl->a[end] = psl->a[end - 1];
}
psl->a[0] = x;
psl->size++;
}
void SLPopBack(SL* psl)
{
assert(psl);
if (psl->size == 0)
{
return;
}
psl->size--;
}
void SLPopFront(SL* psl)
{
assert(psl);
if (psl->size == 0)
return;
int front = 0;
for (front = 0; front < psl->size - 1; front++)
{
psl->a[front] = psl->a[front + 1];
}
psl->size--;
}
int SLFind(const SL* psl, SLDataType x)
{
assert(psl);
if (psl->size == 0)
return;
int i = 0;
for (i = 0; i < psl->size; i++)
{
if (psl->a[i] == x)
{
return i;
}
}
return -1;
}
void SLInsert(SL* psl, size_t pos, SLDataType x)
{
assert(psl);
assert(pos <= psl->size);
CheckCapacity(psl);
int i = 0;
for (i = psl->size; i > pos; i--)
{
psl->a[i] = psl->a[i - 1];
}
psl->a[pos] = x;
psl->size++;
}
void SLErase(SL* psl, size_t pos)
{
assert(psl);
assert(pos < psl->size);
if (psl->size == 0)
return;
size_t i = pos;
for (i = pos; i < psl->size - 1; i++)
{
psl->a[i] = psl->a[i + 1];
}
psl->size--;
}
void cmp(void* e1, void* e2)
{
return (*(SLDataType*)e1 - *(SLDataType*)e2);
}
void SLSort(const SL* psl)
{
assert(psl);
assert(psl->size > 0);
qsort(psl->a, psl->size, sizeof(SLDataType), cmp);
}
void SLModify(SL* psl, SLDataType old, SLDataType x)
{
assert(psl);
int ret = SLFind(psl, old);
if (ret == -1)
{
printf("没有找到要修改的数据\n");
return;
}
psl->a[ret] = x;
}
题目链接:27.移除元素
用快慢指针来实现。通过 快指针i 判断nums[i]是否等于val,等于则不用理会,i++一下;不等于则令nums[count]=nums[i],并且 i和count 都++一下。
int removeElement(int* nums, int numsSize, int val){
int i = 0;
int count = 0;
for(i=0;i<numsSize;i++)
{
if(nums[i]!=val)
{
nums[count++] = nums[i];
}
}
return count;
}
题目链接:26.删除有序数组中的重复项
用快慢指针来实现。通过 快指针i 判断nums[i]是否等于nums[count],等于则不用理会,i++一下;不等于则令nums[count]=nums[i],并且 i和count 都++一下。最后结果是count+1。
int removeDuplicates(int* nums, int numsSize){
int i = 0;
int count = 0;
for(i=1;i<numsSize;i++)
{
if(nums[count]!=nums[i])
{
nums[++count] = nums[i];
}
}
return ++count;
}
题目链接:88 . 合并两个有序数组
可以用三指针的办法,end1指向数组1的最后一位元素(即m-1处),end2指向数组2的最后一位元素,dest指向数组1的最后一个0(即sums1Size-1处)。从后面往前挪动数据,比较指针end1和end2所指的元素哪一个大,就放在dest处,此时该指针和dest都–一下,直到end1或者end2一方等于-1后。
此时分两种情况:
情况一:当end2先为-1,此时数组2的元素全部拷贝到数组1上了,所以可以结束运行。
情况二:当end1先为-1,此时数组2的部分元素为拷贝到数组2上,所以必须写一个循环来拷贝数组2剩余元素到数组1中。
void merge(int* nums1, int nums1Size, int m, int* nums2, int nums2Size, int n){
int src1 = m-1;
int src2 = n-1;
int dest = m+n-1;
while(src1>=0&&src2>=0)
{
if(nums1[src1]>=nums2[src2])
{
nums1[dest]=nums1[src1];
src1--;
}
else
{
nums1[dest]=nums2[src2];
src2--;
}
dest--;
}
if(src2>=0)
{
for(int i = 0;i<=src2;i++)
{
nums1[i]=nums2[i];
}
}
}
当然了,顺序表还存在很多问题,例如:
- 中间/头部的插入删除,时间复杂度为O(N)
- 增容需要申请新空间,拷贝数据,释放旧空间。会有不小的消耗。
- 增容一般是呈2倍的增长,势必会有一定的空间浪费。例如当前容量为100,满了以后增容到200,我们再继续插入了5个数据,后面没有数据插入了,那么就浪费了95个数据空间。
想要解决这些问题就需要链表知识啦。
那么将数据结构初阶的线性表——顺序表就到这里了。