异世界历险之数据结构世界(顺序表)
前言
一天,大学生小Y被突来的大卡车撞飞,死亡后在C语言女神引导下来到了异世界——数据结构世界。
他复活在初始城——顺序表城,买好了装备和武器(C语言基础),随即开始了他的数据结构世界探索之旅。
顺序表介绍及实现
介绍
顺序表是线性表的一种,具有以下特点:
1.具有相同类型的数据的集合。
2.物理结构连续
3.逻辑结构连续
(一个不太恰当的例子:数组如同苍蝇馆子里的土豆丝,顺序表如米其林餐厅的土豆丝,二者作用差异不大,但实现过程和效果天差地别)
实现
1.顺序表分类
//静态顺序表(定长数组)
struct SeqList
{
int arr[100];
int size;//有效数据个数
};
//动态顺序表(自由长度)
struct SeqList
{
int* arr;
int capacity;//空间大小
int size;//有效个数
};
优点分析:
静态顺序表:使用定长数组实现,适用于数据量固定且已知的场景。
动态顺序表:使用动态数组实现,可以根据需要动态调整数组大小,适用于数据量不确定的场景。
2.顺序表初始化
1.前置条件:
SeqList.h
#pragma once
# include<stdio.h>
# include<stdlib.h>
# include<assert.h>
typedef int SLDataType;//改变命名(有利于后期改变类型)
struct SeqList
{
int* arr;
int capacity;
int size;
};
typedef struct SeqList SL;//简化
//初始化
void SLInit(SL* plist);//头文件声明,方便调用(后续不再显示头文件)
test.c
# include"SeqList.h"
//测试文件
void test01()
{
SL plist ;//初始化一个变量
SLInit(&plist);//传址能改变实参,传值不能
}
int main()
{
test01;
}//后续不再展示测试文件
逻辑分析:
初始化顺序表时,将动态数组的指针设置为 NULL,表示当前没有分配内存。
将 size 设置为 0,表示顺序表中没有元素。
将 capacity 设置为 0,表示当前分配的内存大小为 0。
2.顺序表初始化
SeqList.c
# include"SeqList.h"
//初始化
void SLInit(SL* phead)
{
phead->arr = NULL;
phead->capacity = phead->size = 0;
}
3.顺序表销毁/打印
逻辑分析:
销毁:
检查 arr 是否为 NULL,如果不为 NULL,则释放分配的内存。
将 arr 重置为 NULL,capacity 和 size 重置为 0,表示顺序表已销毁。
打印:
遍历顺序表中的每个元素,并将其打印到控制台。
//销毁
void SLDesTory(SL* ps)
{
if (ps->arr)
{
free(ps->arr);
}
ps->arr = NULL;
ps->capacity = ps->size = 0;
}
//打印
void SLPrint(SL* ps)
{
for (int i = 0; i < ps->size; i++)
{
printf("%d ", ps->arr[i]);
}
printf("\n");
}
4.顺序表头插/尾插
逻辑分析:
头插:
调用 SLCheackcapacity 函数检查是否需要扩展容量。
从后往前遍历数组,将每个元素向后移动一位,为新元素腾出空间。
在数组开头插入新元素,并将 size 增加 1。
尾插:
调用 SLCheackcapacity 函数检查是否需要扩展容量。
在数组末尾添加新元素,并将 size 增加 1。
//空间检查
void SLCheackcapacity(SL* ps)
{
if (ps->size == ps->capacity)//如果数据个数和内存相同
{
SLDataType newcapacity = ps->capacity = 0 ? 8:2 * ps->capacity;
SL* str = (SL*)malloc(sizeof(SL) * newcapacity);
if (str == NULL)
{
perror("malloc fail!!!");
exit(1);
}//模板化处理
ps->capacity = newcapacity;
ps->arr = str;
}
}
//头插/尾插
void SLPushFront(SL* ps, SLDataType x)
{ assert(ps);//不能解引用空指针
SLCheackcapacity(ps);
for (int i = ps->size; i > 0; i--)
{
ps->arr[i] = ps->arr[i-1];
}//往后移一位
ps->arr[0] = x;
ps->size++;//size加一
}
void SLPushBack(SL* ps,SLDataType x)
{ assert(ps);
SLCheackcapacity(ps);
ps->arr[ps->size++] = x;//这段代码等价于 ps->arr[size] = x;
// ps->size++;(先赋值。后加加)
}
5.顺序表头删/尾删
//头删/尾删
void SLPopFront(SL* ps)
{
assert(ps);
assert(ps->size);//顺序表不能为空否则无法删除
for (int i = 0; i < ps->size-1; i++)
{
ps->arr[i] = ps->arr[i + 1];//前移一位
}
ps->size--;//size减一
}
void SLPopBack(SL* ps)
{
assert(ps);
assert(ps->size);
ps->size--;
}
效果展示:
6.顺序表自由插入/自由删除
逻辑分析:
头删:
从数组开头开始,将每个元素向前移动一位,覆盖掉第一个元素。
将 size 减少 1。
尾删:
直接将 size 减少 1,表示删除了最后一个元素。
//自由插入(之前)/自由删除
void SLInsert(SL* ps,SLDataType pos, SLDataType x)
{
assert(ps);
for (int i = ps->size; i > pos; i--)
{
ps->arr[i] = ps->arr[i - 1];
}
ps->arr[pos] = x;
ps->size++;
}
void SLErase(SL* ps, SLDataType pos)
{
assert(ps);
assert(ps->size &&pos>=0);
for (int i = pos; i < ps->size-1; i++)
{
ps->arr[i] = ps->arr[i+1];//删除pos点处数
}
ps->size--;
}
效果展示:
7.顺序表查找
逻辑分析:
遍历顺序表中的每个元素,检查是否等于目标值 x。
如果找到,返回该元素的索引。
如果遍历完整个数组都没有找到,返回 -1。
//查找
void SLFind(SL* ps, SLDataType x)
{
assert(ps);
for (int i = 0; i < ps->size; i++)
{
if (ps->arr[i] == x)
{
return i;//找到了
}
}
return -1;//没找到
}
效果展示:
8.顺序表排序
PS:作者采用较为高效率的快速排序,读者也可以尝试冒泡排序等方式。
逻辑分析:
cmp_int 是一个比较函数,用于比较两个整数的大小。
SLSort 使用 qsort 函数对顺序表中的元素进行快速排序。
qsort 需要传入数组、元素个数、元素大小和比较函数。
//排序
int cmp_int(const void* str1, const void* str2)
{
return *(int*)str1 - *(int*)str2;
}
void SLSort(SL* ps)
{
assert(ps);
qsort(ps->arr, ps->size, sizeof(int), cmp_int);
}
效果展示:
总结
SeqList.h
#pragma once
# include<stdio.h>;
# include<stdlib.h>
# include<assert.h>
typedef int SLDataType;//改变命名(有利于后期改变类型)
struct SeqList
{
int* arr;
int capacity;
int size;
};
typedef struct SeqList SL;//简化
//初始化
void SLInit(SL* ps);//头文件声明,方便调用
//销毁
void SLDesTory(SL* ps);
//打印
void SLPrint(SL* ps);
//头插/尾插
void SLPushFront(SL* ps, SLDataType x);
void SLPushBack(SL* ps, SLDataType x);
//头删/尾删
void SLPopFront(SL* ps);
void SLPopBack(SL* ps);
//自由插入/自由删除
void SLErase(SL* ps, SLDataType x);
void SLInsert(SL* ps, SLDataType pos, SLDataType x);
//查找
int SLFind(SL* ps, SLDataType x);
//排序
void SLSort(SL*PS);
SeqList.c
# include"SeqList.h"
//初始化
void SLInit(SL* ps)
{
ps->arr = NULL;
ps->capacity = ps->size = 0;
}
//销毁
void SLDesTory(SL* ps)
{
if (ps->arr)
{
free(ps->arr);
}
ps->arr = NULL;
ps->capacity = ps->size = 0;
}
//打印
void SLPrint(SL* ps)
{
for (int i = 0; i < ps->size; i++)
{
printf("%d ", ps->arr[i]);
}
printf("\n");
}
//空间检查
void SLCheackcapacity(SL* ps)
{
if (ps->size == ps->capacity)//如果数据个数和内存相同
{
SLDataType newcapacity = ps->capacity = 0 ? 8:2 * ps->capacity;
SL* str = (SL*)malloc(sizeof(SL) * newcapacity);
if (str == NULL)
{
perror("malloc fail!!!");
exit(1);
}//模板化处理
ps->capacity = newcapacity;
ps->arr = str;
}
}
//头插/尾插
void SLPushFront(SL* ps, SLDataType x)
{
assert(ps);
SLCheackcapacity(ps);
for (int i = ps->size; i > 0; i--)
{
ps->arr[i] = ps->arr[i-1];
}//后移一位
ps->arr[0] = x;
ps->size++;//size加一
}
void SLPushBack(SL* ps,SLDataType x)
{
assert(ps);
SLCheackcapacity(ps);
ps->arr[ps->size++] = x;
}
//头删/尾删
void SLPopFront(SL* ps)
{
assert(ps);
assert(ps->size);
for (int i = 0; i < ps->size-1; i++)
{
ps->arr[i] = ps->arr[i + 1];//前移一位
}
ps->size--;//减少一个
}
void SLPopBack(SL* ps)
{
assert(ps);
assert(ps->size);
ps->size--;
}
//自由插入(之前)/自由删除
void SLInsert(SL* ps,SLDataType pos, SLDataType x)
{
assert(ps);
for (int i = ps->size; i > pos; i--)
{
ps->arr[i] = ps->arr[i - 1];
}
ps->arr[pos] = x;
ps->size++;
}
void SLErase(SL* ps, SLDataType pos)
{
assert(ps);
assert(ps->size &&pos>=0);
for (int i = pos; i < ps->size-1; i++)
{
ps->arr[i] = ps->arr[i+1];
}
ps->size--;
}
//查找
int SLFind(SL* ps, SLDataType x)
{
assert(ps);
for (int i = 0; i < ps->size; i++)
{
if (ps->arr[i] == x)
{
return i;//找到了
}
}
return -1;//没找到
}
//排序
int cmp_int(const void* str1, const void* str2)
{
return *(int*)str1 - *(int*)str2;
}
void SLSort(SL* ps)
{
assert(ps);
qsort(ps->arr, ps->size, sizeof(int), cmp_int);
}
test.c
# include"SeqList.h"
//测试文件
void test01()
{
SL plist ;
SLInit(&plist);
SLPushBack(&plist, 1);
SLPushBack(&plist, 2);
SLPushBack(&plist, 3);
SLPushBack(&plist, 4);
SLPushBack(&plist, 5);
SLPushBack(&plist, 6);
SLPrint(&plist);
SLPushFront(&plist, 7);
SLPushFront(&plist, 8);
SLPushFront(&plist, 9);
SLPrint(&plist);
SLPopBack(&plist);
SLPopBack(&plist);
SLPrint(&plist);
SLPopFront(&plist);
SLPopFront(&plist);
SLPrint(&plist);
SLErase(&plist, 2);
SLErase(&plist, 3);
SLPrint(&plist);
SLInsert(&plist, 2, 4);
SLInsert(&plist, 1, 3);
SLPrint(&plist);
int x = 0;
scanf("%d", &x);
int ret = SLFind(&plist, x);
if (ret >= 0)
{
printf("找到了.pos点为:%d\n",ret);
}
else
{
printf("没找到\n");
}
SLSort(&plist);
SLPrint(&plist);
}
int main()
{
test01();
}
顺序表是数据结构中的基础内容,包括静态和动态两种类型。本文详细介绍了顺序表的初始化、销毁、打印、插入、删除、查找和排序等操作,并通过代码逻辑分析帮助读者深入理解每段代码的实现细节。掌握顺序表的实现和操作,为后续学习更复杂的数据结构打下坚实基础。
尾语
小Y成功通过了顺序表城的考验,获得了神器之一——顺序表之剑,他的异世界之旅还未结束,我们拭目以待!!!