引言:顺序表是数据结构中的一种形式,就是存储数据的一种结构。
这里会用到动态内存开辟,指针和结构体的知识
1.什么是数据结构
数据结构就是组织和存储数据的结构。
数据结构的特性:
物理结构:在内存中存储的数据是否连续
逻辑结构: 逻辑上是否连续
2.什么是顺序表
为了更好的实现顺序表,首先的了解其的特性。
顺序表的特性:
物理结构:连续的
逻辑结构:连续的
顺序表也是线性结构的一种
线性结构的特性:
物理结构:不一定连续
逻辑结构:连续的
顺序表的底层是数组。顺序表在数组的基础上能通过调用函数实现数据的增删查改。
数组在内存中开辟的空间是连续的,所以其存储的数据在内存中也是连续的。
3.静态顺序表与动态顺序表哪个更好呢?
顺序表又分为静态顺序表,和动态顺序表
那么哪种更好呢?
静态顺序表和动态顺序表的结构
//静态顺序表的结构
struct SeqList
{int arr[100];//开辟的空间大小已经确定int size;//记录当前元素的个数
};
//动态顺序表的结构
struct SeqList
{int* arr;int size;//记录当前元素的个数int capacity;//记录当前能存放元素的个数,如果size=capacity时,扩容
};
动态顺序表的结构图解:
比较:
静态顺序表开辟的存放数据的空间:如果开辟过大,则浪费空间;若开辟过小,若想存的数据过多,则有的数据存不进去。
而动态顺序表则更灵活,不够了再开辟就可以了。
所以动态顺序表更好,这里也只会将动态顺序表的实现。
4.动态顺序表的实现
4.1通过多文件的形式实现
文件管理:
test.c | 进行各个接口的测试 |
SeqList.c | 实现增删查改函数的实现 |
SeqLst.h | 引用需要使用的头文件,动态顺序表结构体的定义,实现增删查改函数函数的声明,#define 定义的符号 |
需要实现的函数:
初始化、销毁 | 创建完动态顺序表的结构后应对其成员进行初始化,释放开辟的空间,并将结构体的成员初始化 |
增 | 有头插,尾插 任意插 |
删 | 头删,尾删 任意删 |
查 | 查找顺序表中是否有查找的元素 |
这里直接给出完整的SeqList.h 中的全部代码:
#pragma once//防止头文件多次包含//用到的库函数
#include <stdio.h>
#include <stdlib.h>
#include <assert.h>//如果以后想存储的数据不再是int 只需要改这一出的int改为别的数据类型
#define SLDataType int//顺序表的结构定义
typedef struct SeqList
{SLDataType* arr;int size;int capacity;
}SL;//以下为需要实现的函数的声明
//初始化
void SLInit(SL* ps);
void SLDestory(SL* ps);
//打印
void SLPrint(SL ps);
//尾插,尾删
void SLPushBack(SL* ps, SLDataType x);
void SLPushFront(SL* ps, SLDataType x);
//尾删,头删
void SLPopFront(SL* ps);
void SLPopBack(SL* ps);
//任意插,任意删
void SLInsert(SL* ps, int pos, SLDataType x);
void SLErase(SL* ps, int pos);
//查找
int SLFind(SL* ps,SLDataType find);
4.2初始化和销毁注意事项
//初始化
void SLInit(SL* ps)
{ps->arr = NULL;ps->size = 0;ps->capacity = 0;
}
一定要传定义的结构体的地址,这样才能改变所定义的结构体的值。
//销毁
void SLDestory(SL* ps)
{assert(ps);free(ps->arr);ps->arr = NULL;ps->size = 0;ps->capacity = 0;
}
4.3尾插,头插中的一些注意事项
尾插实现函数:
void SLPushBack(SL* ps, SLData x)
{//ps不能为NULLassert(ps != NULL);//增容:1.capacity和size都为0 2.空间不够用if (ps->capacity == ps->size){int newcapacity = ps->capacity == 0 ? 4 : ps->capacity * 2;SLData* tmp = (SLData*)realloc(ps->arr, newcapacity * sizeof(SLData));if (tmp == NULL){perror("realloc");exit(-1);}//开辟成功ps->arr = tmp;ps->capacity = newcapacity;}//将值写入顺序表ps->arr[ps->size] = x;//插入完顺序表中元素的个数+1ps->size++;
}
增容问题:
如果结构体中的size 和capacity 都为0,或size等于capacity(所开辟的空间都被使用完)时,就要增容。
用realloc,malloc还是用calloc开辟空间呢?
考虑到增容的问题所以用realloc开辟空间。
那么增多大呢?
这里不好解释,建议增大当前所开辟空间的2或3倍。如果开辟小了的话,很快就会被用完,就会频繁地开辟,如果是下图中realloc开辟空间中的情况2,拷贝数据很影响程序性能;若开辟过大,则浪费空间。
因为插入会考虑到增容问题所以可以写一个实现增容的函数。
void CheckSLCapacity(SL* ps)
{//如果size等于capacity说明开辟的空间已经使用完,得增容if (ps->capacity == ps->size){int newcapacity = ps->capacity == 0 ? 4 : ps->capacity * 2;SLData* tmp = (SLData*)realloc(ps->arr, newcapacity * sizeof(SLData));if (tmp == NULL){perror("realloc");exit(-1);}//开辟成功ps->arr = tmp;ps->capacity = newcapacity;}
}
头插和尾插实现的代码:
//头插
void SLPushFront(SL* ps, SLData x)
{assert(ps != NULL);CheckSLCapacity(ps);//增容函数,上面的尾插的代码中的实现增容的代码也可以替换为这个函数for (int i = ps->size; i>0; i--){ps->arr[i] = ps->arr[i - 1];}ps->arr[0] = x;ps->size++;
}//头插
void SLPushBack(SL* ps, SLDataType x)
{assert(ps != NULL);CheckSLCapacity(ps);ps->arr[ps->size] = x;ps->size++;
}
4.4 头删和尾删
实现代码:
//尾删
void SLPopBack(SL* ps)
{assert(ps != NULL);assert(ps->size > 0);ps->size--;
}//头删
void SLPopFront(SL* ps)
{assert(ps != NULL);assert(ps->size > 0);for (int i = 0; i <ps->size-1 ; i++)ps->arr[i] = ps->arr[i + 1];ps->size--;
}
注意:如果顺序表中没有数据那么就不能继续删除了, assert(ps->size > 0)防止没有数据了继续删。
尾删:只需要将size--就可以,不需要将最后的数据修改(修改也是可以的),但别忘了最后size--
头删:将除头部的其他数据整体向前挪动一位。
头删图解:
4.5 任意删和任意插
实现代码:
//任意插
//pos为想要插入的位置的下标
void SLInsert(SL* ps, int pos, SLDataType x)
{assert(ps);//若pos小于0,不能插入;若想要插入的位置大于size也不能插入assert(pos >= 0 && pos <= ps->size);//考虑扩容问题CheckSLCapacity(ps);//注意赋值的顺序和 i 的范围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);assert(pos >= 0 && pos < ps->size);for (int i = pos; i < ps->size-1; i++){ps->arr[i] = ps->arr[i + 1];}ps->size--;}
注意事项:
任意插入:
对插入位置下标(pos)的限制: pos >= 0 && pos <= ps->size
图解:
通过上图可以用任意插函数实现头插和尾插
任意插入后挪动数据的方式:
从pos位置开始集体往后挪
任意删除后的数据挪动方式:
从size-1 开始往前挪直到pos+1 的位置。
用任意插入和任意删除的函数实现 头插,尾插和头删,尾删
//头插、尾插
void SLPushBack1(SL* ps, SLDataType x)
{SLInsert(ps, ps->size, x);
}
void SLPushFront1(SL* ps, SLDataType x)
{SLInsert(ps, 0, x);
}
//尾删,头删
void SLPopFront1(SL* ps)
{SLErase(ps, 0);
}
void SLPopBack1(SL* ps)
{SLErase(ps, ps->size-1);}
4.6查找
实现函数:
int SLFind(SL* ps,SLDataType find)
{assert(ps);for (int i = 0; i < ps->size; i++){//如果找到返回下标if (ps->arr[i] == find)return i;}//没找到返回小于0 的数return -1;
}
查找函数的使用:
int find = SLFind(&s, 6);if (find < 0)printf("找不到\n");elseprintf("%d\n", find);
为什么没找到返回小于0的数?
因为存储的数据的下标都大于等于0。
5.完整代码来喽!
test.c:(这就是我自己写代码使用的一些测试用例,大家可以自己写测试用例,自己测试,则一块不是很重要)
#include "SeqList.h"
void SLTest()
{SL s;SLInit(&s);//SLPrint(s);//SLPushBack(&s, 1);//SLPrint(s);//SLPushBack(&s, 2);//SLPrint(s); //SLPushBack(&s, 3);//SLPrint(s);//SLPushBack(&s, 4);//SLPrint(s);SLPushBack(&s, 5);SLPrint(s);/*SLPushBack(NULL, 5);*/SLPushFront(&s, 6);SLPrint(s);SLPushFront(&s, 7);SLPrint(s);/*SLPushFront(NULL, 7);*/SLPopFront(&s);SLPrint(s);SLPopBack(&s);SLPrint(s);//SLPopFront(&s);//SLPrint(s);SLPopFront(&s);SLPrint(s);SLDestory(&s);
}
void SLTest1()
{SL s;SLInit(&s);SLPushFront(&s, 1);SLPushBack(&s, 2);SLPushBack(&s, 3);SLPrint(s);SLInsert(&s, 0, 4);SLPrint(s);SLInsert(&s,4,5);SLInsert(&s,3,6);SLPrint(s);SLErase(&s, 4);SLPrint(s);SLErase(&s, 0);SLPrint(s);int find = SLFind(&s, 6);if (find < 0)printf("找不到\n");elseprintf("%d\n", find);SLDestory(&s);
}
int main()
{/*SLTest();*/SLTest1();return 0;
}
SeqList.h
#pragma once
#include <stdio.h>
#include <stdlib.h>
#include <assert.h>#define SLDataType int
typedef struct SeqList
{SLDataType* arr;int size;int capacity;
}SL;//初始化
void SLInit(SL* ps);
void SLDestory(SL* ps);
//打印
void SLPrint(SL ps);
//尾插,尾删
void SLPushBack(SL* ps, SLDataType x);
void SLPushFront(SL* ps, SLDataType x);
//尾删,头删
void SLPopFront(SL* ps);
void SLPopBack(SL* ps);
//任意插,任意删
void SLInsert(SL* ps, int pos, SLDataType x);
void SLErase(SL* ps, int pos);
//查找
int SLFind(SL* ps,SLDataType find);
SeqList.c:
#include "SeqList.h"void CheckSLCapacity(SL* ps)
{if (ps->capacity == ps->size){int newcapacity = ps->capacity == 0 ? 4 : ps->capacity * 2;SLDataType* tmp = (SLDataType*)realloc(ps->arr, newcapacity * sizeof(SLDataType));if (tmp == NULL){perror("realloc");exit(-1);}//开辟成功ps->arr = tmp;ps->capacity = newcapacity;}
}void SLInit(SL* ps)
{ps->arr = NULL;ps->size = 0;ps->capacity = 0;
}void SLDestory(SL* ps)
{assert(ps);free(ps->arr);ps->arr = NULL;ps->size = 0;ps->capacity = 0;
}void SLPrint(SL s)
{for (int i = 0; i < s.size; i++)printf("%d ", s.arr[i]);printf("\n");
}void SLPushBack(SL* ps, SLDataType x)
{assert(ps != NULL);CheckSLCapacity(ps);ps->arr[ps->size] = x;ps->size++;
}void SLPushFront(SL* ps, SLDataType x)
{assert(ps != NULL);CheckSLCapacity(ps);for (int i = ps->size; i>0; i--){ps->arr[i] = ps->arr[i - 1];}ps->arr[0] = x;ps->size++;
}void SLPopBack(SL* ps)
{assert(ps != NULL);assert(ps->size > 0);ps->size--;
}void SLPopFront(SL* ps)
{assert(ps != NULL);assert(ps->size > 0);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);CheckSLCapacity(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, int pos)
{assert(ps);assert(pos >= 0 && pos < ps->size);for (int i = pos; i < ps->size-1; i++){ps->arr[i] = ps->arr[i + 1];}ps->size--;}int SLFind(SL* ps,SLDataType find)
{assert(ps);for (int i = 0; i < ps->size; i++){if (ps->arr[i] == find)return i;}return -1;
}
结语:
头次写数据结构,放平心态 ,一定要写完一个接口调试一个接口,不要都写完了再去调试,若有一堆问题容易心态爆炸(boom)。
拜拜,下一期。目标项目:通讯录。