小伙伴们好,学完C语言,就要开始学数据结构了,数据结构也是非常重要的,今天我们主要来学习在数据结构中最常用的增删改查操作。话不多说,一起来学习吧
1.数据结构相关概念
1.什么是数据结构?
2.顺序表
2.1 顺序表的概念及结构
1.线性表
2.2顺序表分类
3.动态顺序表的实现
首先我们要创建3个文件,分别是1个头文件SeqList.h,2个源文件SeqList.c和test.c.它们的作用分别是:
SeqList.h:头文件的作用是定义顺序表的结构,顺序表要实现的接口/方法
SeqList.c:具体实现顺序表里定义的接口/方法
test.c:测试动作:测试顺序表
OK,文件准备好了,现在就开始写代码了
首先我们要给数据类型起一个别名,为什么呢?假设我们写了1000行代码,里面使用到了非常多的数据类型,假定全是int类型,有一天别人让你把一些int类型改成char类型,那么你就要一个一个的把int改成char,虽然你们想说不是可以一键替换吗,这个方法也可以,但是不是全部都要改成char类型,这不是最佳的方法。所以作为程序员我们要专业一点,于是就要给自定义的给int类型取个别名,取什么名字可以自己决定,专业的人就要干专业的事。比如说我们可以改成这个
typedef int SLDataType;
这样就能解决上面的问题了,你想要改成什么数据类型,就只需要把int改成你要的类型。是不是很方便呢。
我们首先要做一些准备工作,方便为接下来的操作做准备
3.1定义结构体
typedef struct SeqList
{
SLDataType* arr;//存储数据的底层结构
int capacity; //记录顺序表的空间大小
int size; //记录顺序表当前有效的数据个数
}SL;
细心的小伙伴们发现我也给结构体取了一个别名SL,这样也是为了方便接下来的使用。
当然你也可以这样写
struct SeqList
{
SLDataType* arr;//存储数据的底层结构
int capacity; //记录顺序表的空间大小
int size; //记录顺序表当前有效的数据个数
};
typedef struct SeqList SL;
3.2 结构体的初始化和销毁
在SeqList.h文件中定义初始化和销毁的函数,具体实现方法要在SeqList.c文件中实现
可能有人会这么写
这样写会报错,因为这样写是传值调用,而我们要传地址。所以正确写法是下面这种
void SLInit(SL* ps);
void SLDestroy(SL* ps);
void SLInit(SL* ps)//ps是形参
{
ps->arr = NULL;
ps->size = ps->capacity = 0;
}
void SLDestroy(SL* ps)
{
assert(ps);
if (ps->arr)
{
free(ps->arr);
}
ps->arr = NULL;
ps->size = ps->capacity = 0;
}
我们可以测试一下
好了,这么写没有问题。
好了,准备工作基本完成。正式开始我们的增删改查吧。
3.3插入数据:尾插和头插
首先要分析空间够不够。
空间足够:直接插入,假设插入的数据是x=6,即arr[size]=6,size是有效数据的下一个
空间不够:扩容。那么扩容的方法有哪些呢?
- 一次扩充一个空间:插入一个元素还不会造成空间浪费
- 一次扩容固定个大小空间(10,100……)
- 成倍数的增加(1.5倍,2倍)
第一个方法虽然不会造成空间的浪费,但是由于扩容的频率太高会造成程序效率低下
第二个方法空间给小了,还需要继续扩容,也会造成程序效率低下,给大了又可能会造成空间的浪费。
这里我推荐第三个方法,成2倍数的增加(依次扩大4,8,16,32……个空间)数据插入的越多,扩容的空间越来越大,数据和扩容量成正相关,浪费也不会造成太多的浪费。扩容的方法是使用realloc函数,realloc函数使用方法可以看我这篇文章http://t.csdnimg.cn/rwb7g
头插的难点是如何将数据往后挪动
如果直接在第一个位置插入数据,那么就会造成数据丢失 !所以就要从后往前挪动,把第i个位置移到第i-1的位置处。
由于头插和尾插都要判断是否扩容,所以我们要将扩容空间单独写一个函数,这样空间不够的时候可以直接调用
扩容函数
void SLCheckCapacity(SL* ps)
{
if (ps->size == ps->capacity)
{
int newCapacity = ps->capacity == 0 ? 4 : 2 * ps->capacity;//扩容成倍数增加,每次扩容2倍
//ps->arr=(SLDataType*)realloc(ps->arr,2 * ps->capacity)
//不能这样写,因为初始化capacity为0
SLDataType* tmp = (SLDataType*)realloc(ps->arr, newCapacity * sizeof(SLDataType));//要乘以数据类型的大小
if (tmp == NULL)
{
perror("realloc fail!");
exit(1);//扩容失败直接中断程序
}
//扩容成功
ps->arr = tmp;
ps->capacity = newCapacity;
}
}
//顺序表的头部/尾部的插入
void SLPushBack(SL* ps,SLDataType x);//尾插
void SLPushFront(SL* ps, SLDataType x);//头插
void SLPushBack(SL* ps, SLDataType x)//尾插
{
assert(ps != NULL);
//空间不够,扩容。当capacity <= size时
SLCheckCapacity(ps);
//空间足够,直接插入。当capacity>size时
ps->arr[ps->size++] = x;
//ps->size++;
}
void SLPushFront(SL* ps, SLDataType x)//头插
{
assert(ps != NULL);
//判断是否扩容
SLCheckCapacity(ps);
//旧数据往后挪动一位
for (int i = ps->size; i > 0; i--)
{
ps->arr[i] = ps->arr[i - 1];
}
ps->arr[0] = x;
ps->size++;
}
测试尾插
测试头插
打印函数
void SLPrint(SL* ps)
{
for (int i = 0; i < ps->size; i++)
{
printf("%d ", ps->arr[i]);
}
printf("\n");
}
ok,扩容没有问题,这样就说明增加操作没有问题了。另外不能传入一个空值,所以要进行assert断言。
3.4 删除数据:头删和尾删
删除数据要分两种情况:顺序表为空,则不能进行删除;顺序表不为空,尾删则删除最后一个有效数据,size--。头删就要删除第一个数据,然后把后面的数据往前挪动
//顺序表的头部/尾部的删除
void SLPoBack(SL* ps);
void SLPoFront(SL* ps);
//顺序表的头部/尾部的删除
void SLPoBack(SL* ps)
{
assert(ps != NULL);
assert(ps->size);//顺序表为空不能进行删除
//顺序表不为空
ps->size--;
}
void SLPoFront(SL* ps)
{
assert(ps != NULL);
assert(ps->size);//顺序表为空不能进行删除
//不为空执行挪动操作,后面的数据往前挪动一位
for (int i = 0; i < ps->size - 1; i++)
{
ps->arr[i] = ps->arr[i + 1];
}
ps->size--;
}
测试头删
3.5 指定位置删除或者插入数据
指定位置之前插入数据
void SLInsert(SL* ps, int pos, SLDataType x);
void SLInsert(SL* ps, int pos, SLDataType x)
{
assert(ps);
assert(pos >= 0 && pos <= ps->size);//pos不能超过当前顺序表的有效数据
SLCheckCapacity(ps);//检查是否有空间可以插入
//pos及之后的数据往后挪动一位,pos空出来,size++
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, int pos);
void SLErase(SL* ps, int pos)
{
assert(ps != NULL);
assert(pos >= 0 && pos < ps->size);//只能对size之前的数据进行删除
//pos以后的数据往前挪动一位
for (int i=pos; i<ps->size-1; i++)
{
ps->arr[i] = ps->arr[i + 1];
}
ps->size--;
}
测试
3.6 查找数据
int SLFind(SL* ps, SLDataType x);
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;
}
测试
3.7 修改数据
void SLRevise(SL* ps,int pos,SLDataType x);
void SLRevise(SL* ps,int pos,SLDataType x)
{
if (pos >= ps->size)
printf("修改失败!要修改的元素不存在\n");
else
{
printf("修改成功,新数据为:");
ps->arr[pos] = x;
}
}
测试
4.完整代码呈现
SeqList.h
#pragma once
//头文件的作用是定义顺序表的结构,顺序表要实现的接口/方法
#include<stdio.h>
#include<stdlib.h>
#include<assert.h>
//动态顺序表
//typedef Info SLDataType;
typedef int SLDataType;
typedef struct SeqList //方法一
{
SLDataType* arr;//存储数据的底层结构
int capacity; //记录顺序表的空间大小
int size; //记录顺序表当前有效的数据个数
}SL;
//typedef struct SeqList SL; //方法二
//初始化和销毁
void SLInit(SL* ps);
void SLDestroy(SL* ps);
void SLPrint(SL* ps);
//顺序表的头部/尾部的插入
void SLPushBack(SL* ps,SLDataType x);//尾插
void SLPushFront(SL* ps, SLDataType x);//头插
//顺序表的头部/尾部的删除
void SLPoBack(SL* ps);
void SLPoFront(SL* ps);
//指定位置之前插入数据
//删除指定位置的数据
void SLInsert(SL* ps, int pos, SLDataType x);
void SLErase(SL* ps, int pos);
int SLFind(SL* ps, SLDataType x);
//修改数据
void SLRevise(SL* ps,int pos,SLDataType x);
SeqList.c
#define _CRT_SECURE_NO_WARNINGS 1
//具体实现顺序表里定义的接口/方法
#include"SeqList.h"
//初始化和销毁
void SLInit(SL* ps)//ps是形参
{
ps->arr = NULL;
ps->size = ps->capacity = 0;
}
void SLCheckCapacity(SL* ps)
{
if (ps->size == ps->capacity)
{
int newCapacity = ps->capacity == 0 ? 4 : 2 * ps->capacity;//扩容成倍数增加,每次扩容2倍
//ps->arr=(SLDataType*)realloc(ps->arr,2 * ps->capacity)
//不能这样写,因为初始化capacity为0
SLDataType* tmp = (SLDataType*)realloc(ps->arr, newCapacity * sizeof(SLDataType));//要乘以数据类型的大小
if (tmp == NULL)
{
perror("realloc fail!");
exit(1);//扩容失败直接中断程序
}
//扩容成功
ps->arr = tmp;
ps->capacity = newCapacity;
}
}
//顺序表的头部/尾部的插入
void SLPushBack(SL* ps, SLDataType x)//尾插
{
assert(ps != NULL);
//空间不够,扩容。当capacity <= size时
SLCheckCapacity(ps);
//空间足够,直接插入。当capacity>size时
ps->arr[ps->size++] = x;
//ps->size++;
}
void SLPushFront(SL* ps, SLDataType x)//头插
{
assert(ps != NULL);
//判断是否扩容
SLCheckCapacity(ps);
//旧数据往后挪动一位
for (int i = ps->size; i > 0; i--)
{
ps->arr[i] = ps->arr[i - 1];
}
ps->arr[0] = x;
ps->size++;
}
//顺序表的头部/尾部的删除
void SLPoBack(SL* ps)
{
assert(ps != NULL);
assert(ps->size);//顺序表为空不能进行删除
//顺序表不为空
ps->size--;
}
void SLPoFront(SL* ps)
{
assert(ps != NULL);
assert(ps->size);//顺序表为空不能进行删除
//不为空执行挪动操作,后面的数据往前挪动一位
for (int i = 0; i < ps->size - 1; i++)
{
ps->arr[i] = ps->arr[i + 1];
}
ps->size--;
}
//指定位置之前插入数据
void SLInsert(SL* ps, int pos, SLDataType x)
{
assert(ps);
assert(pos >= 0 && pos <= ps->size);//pos不能超过当前顺序表的有效数据
SLCheckCapacity(ps);
//pos及之后的数据往后挪动一位,pos空出来,size++
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, int pos)
{
assert(ps != NULL);
assert(pos >= 0 && pos < ps->size);//只能对size之前的数据进行删除
//pos以后的数据往前挪动一位
for (int i=pos; i<ps->size-1; i++)
{
ps->arr[i] = ps->arr[i + 1];
}
ps->size--;
}
//在顺序表中查找x
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;
}
//修改数据
void SLRevise(SL* ps,int pos,SLDataType x)
{
if (pos >= ps->size)
printf("修改失败!要修改的元素不存在\n");
else
{
printf("修改成功,新数据为:");
ps->arr[pos] = x;
}
}
void SLDestroy(SL* ps)
{
assert(ps);
if (ps->arr)
{
free(ps->arr);
}
ps->arr = NULL;
ps->size = ps->capacity = 0;
}
void SLPrint(SL* ps)
{
for (int i = 0; i < ps->size; i++)
{
printf("%d ", ps->arr[i]);
}
printf("\n");
}
test.c
#define _CRT_SECURE_NO_WARNINGS 1
//测试动作:测试顺序表
#include"SeqList.h"
void slTest01()
{
SL sl;
SLInit(&sl);//传址调用
//测试尾插
/*SLPushBack(&sl, 1);
SLPushBack(&sl, 2);
SLPushBack(&sl, 3);
SLPushBack(&sl, 4);
SLPrint(&sl); *///1 2 3 4
//SLPushBack(&sl, 5);
//SLPrint(&sl); //1 2 3 4
测试头插
/*SLPushFront(&sl, 5);
SLPushFront(&sl, 6);
SLPushFront(&sl, 7);
SLPrint(&sl);*/
//测试尾删
/*SLPoBack(&sl);
SLPoBack(&sl);
SLPrint(&sl);*/
//测试头删
/*SLPoFront(&sl);
SLPoFront(&sl);
SLPrint(&sl);*/
//测试指定位置插入
//SLInsert(&sl, 0, 100);
//SLPrint(&sl);
//SLInsert(&sl, sl.size, 200);//在size位置插入数据
//SLPrint(&sl);
//测试删除指定位置的数据
SLPushBack(&sl, 1);
SLPushBack(&sl, 2);
SLPushBack(&sl, 3);
SLPushBack(&sl, 4);
SLPrint(&sl);
SLErase(&sl, 2);
SLPrint(&sl);
/*SLErase(&sl, sl.size - 1);
SLPrint(&sl);*/
//修改数据
SLRevise(&sl, 1, 5);
SLPrint(&sl);
SLRevise(&sl, 4, 5);
}
void slTest02()
{
SL sl;
SLInit(&sl);//传址调用
//测试尾插
SLPushBack(&sl, 1);
SLPushBack(&sl, 2);
SLPushBack(&sl, 3);
SLPushBack(&sl, 4);
SLPrint(&sl);
//测试查找
int ret = SLFind(&sl, 2);
if (ret < 0)
{
printf("数据不存在,查找失败!");
}
else {
printf("数据找到了,在下标为%d的位置\n", ret);
}
}
//void slTest03()
//{
// SL sl;
// SLInit(&sl);
// SLDestroy(&sl);
//}
int main()
{
//slTest01();
slTest02();
//slTest03();
return 0;
}
终于大功告成了,感谢小伙伴们能看到这里,说明你们都想学好C语言,小伙伴们,我们一起加油。根据这个增删查改,我们就可以写出一个小程序了,那么就是通讯录的实现。等我有时间了我也会把通讯录的实现分享出来,供大家学习参考。感谢各位观看。