前言
- 这个专栏将会用纯C实现常用的数据结构和简单的算法;
- 用C基础即可跟着学习,代码均可运行;
- 准备考研的也可跟着写,个人感觉,如果时间充裕,手写一遍比看书、刷题管用很多,这也是本人采用纯C语言实现的原因之一;
- 欢迎收藏 + 关注,本人将会持续更新。
顺序表基本概念
什么是线性表?
线性表是一组具有相同特征 元素的有序序列 ,记作:(a~1~, a~2~, ..., a~i-1~, a~!~, a~i+1~, ..., a~n~),当然这有点官方了,但是核心就两个特征:相同特征、有序序列;但我从《大话数据结构中》中看到了另外一个描述,我感觉听通俗易懂的:
- 线性表的数据对象集合{a~1~, a~2~, ..., a~i-1~, a~!~, a~i+1~, ..., a~n~},每个元素的类型均为
DataType
。其中,除了第一个元素a~1~ 外,每一个元素有且只有直接前驱元素 ,除了最后一个元素a~n~ 外,每一个元素有且只有一个直接后驱元素。且数据元素之间关系是一一对应的。
线性表相关概念
- 直接前驱元素:a~i-1~ 领先于a~i~ ,则称a~i-1~ 是a~i~ 的直接前驱元素;
- 直接后驱元素:a~i+1~ 领先于a~i~ ,则称a~i+1~ 是a~i~ 的直接后驱元素;
- 前驱元素:a~1~,a~2~,a~3~,......a~i-1~, 都是a~i~ 的前驱元素;
- 后驱元素:a~i+1~,a~i+2~,a~i+3~,......a~i+n~, 都是a~i~ 的前驱元素;
- 线性表长度:线性表中包含所有元素的个数;
- 空线性表:不包含任何元素的线性表;
- 位序:元素在线性表第几个位置
什么是顺序表?
顺序表就是用一段连续的内存空间依次存储数据,顺序表的存储大概图如下(参考《大话数据结构》):
顺序表特点
- 存储数据内存地址连续
- 可以支持下标访问
- 删除、添加中间位置元素麻烦
- 数据容量固定
顺序表的抽象设计
把顺序表存储的东西,看成是一个集合中存储,从**抽象数据类型(ADT)**角度来看,这个我们需要数据的实现可以有一下几个基本操作:
- 创建、初始化循序表
- 插入元素
- 删除元素
- 查找元素
- 判空操作
- 判满操作
总之一句话:增删改查外加排序。
顺序表程序实现
封装顺序表
- 对数据类型进行取别名,这样是代码更具有泛化性;
- 采用结构体进行封装,可以更好描述顺序表
c
typedef int DataType;
typedef struct SeqList {
DataType* data; // 储存数据
size_t size; // 当下储存了多少数
size_t capacity; // 当下能够储存数据的最大容量
}SeqList;
创建顺序表
利用calloc
函数,他会在申请内存的时候自动初始化
为空,这里具体他做了一下几件事情:
- 事情一块内存空间,大小为
sizeof(Seqlist)
- data = NULL
- size = 0
- capacity = 0
c
SeqList* create_seqlist()
{
SeqList* new_data = (SeqList*)calloc(1, sizeof(SeqList));
if (!new_data) {
printf("顺序表创建失败\n");
return new_data;
}
return new_data;
}
插入元素
这里是向后插入,扩容规则如下(自己定义的,不是一定的):
- 如果capacity为0,则赋值为10,当作初始化
- 否则如果容量不够,则按照两倍扩容
向后插入图示如下(也可以有序插入,这个需要按照具体场景):
c
void insert_seqlist(SeqList* list, DataType data)
{
assert(list); // list为空,则断言
// 是否需要扩容
if (list->size >= list->capacity) {
// 2倍扩容
if (list->capacity == 0) {
list->capacity = 10;
}
else {
list->capacity = 2 * list->capacity;
}
// 扩容
DataType* new_data = (DataType*)realloc(list->data, list->capacity * sizeof(DataType));
assert(new_data); // 判断内存是否申请失败
list->data = new_data;
}
// 直接在后面插入,但是也可以有序插入,这个要根据业务
list->data[list->size++] = data;
}
查找--按值查找
- 目的:通过值 找到返回他在顺序表中的存储的下标,规则:如果改顺序表中不存在该元素,则返回-1。
- 方法:遍历,从第一个遍历到最后,时间复杂度为O(n)
c
int search_value_seqlist(SeqList* list, DataType data)
{
// 防止空表
assert(list);
for (size_t i = 0; i < list->size; i++) {
if (list->data[i] == data) {
return i;
}
}
return -1;
}
查找--按位查找
- 目的:通过下标 找到返回他在顺序表中的存储的元素,规则:如果传递位有误,则返回-1。
c
DataType search_position_seqlist(SeqList* list, size_t k)
{
assert(list);
if (k >= list->size || k < 0) {
return -1;
}
return list->data[k];
}
删除--通过下标
- 目的:删除下标为k的元素
- 数组的删除为伪删除,不是真正的删除,将删除的元素移动到最后而已,具体过程图示如下:
c
void erase_seqlist_index(SeqList* list, size_t index)
{
assert(list);
if (index < 0 || index >= list->size) {
printf("无效位置\n");
return;
}
for (int i = index; i < list->size - 1; i++) {
list->data[i] = list->data[i + 1];
}
list->size--;
}
删除--通过指定元素
- 删除第一个符合元素,具体过程如图示(和上图一样):
c
void erase_seqlist_data(SeqList* list, DataType data)
{
assert(list);
for (size_t i = 0; i < list->size; i++) {
if (list->data[i] == data) {
erase_seqlist_index(list, i); // 原理一样
}
}
}
- 删除全部符合元素删除,采用双指针算法,最快理解就是通过图示,如下:
c
void erase_seqlist_all(SeqList* list, DataType data)
{
assert(list);
// 第一种方法:暴力,这里不写
// 第二种方法: 双指针
size_t slow = 0, fast = 0;
int num = 0;
for (; fast < list->size; fast++) {
if (list->data[fast] == data) {
num++;
continue;
}
else {
list->data[slow++] = list->data[fast];
}
}
list->size -= num;
}
判断是否为空
判断链表是不是没有存储元素,但是要注意 ,没有存储元素只是代表SeqList->size==0
,但不代表数组里没有储存元素,没有内容空间,具体为什么,可以想一想????
c
bool emtpy_seqlist(SeqList* list) {
return list->size == 0;
}
查询顺序表大小
查找当前顺序表中储存元素的数量
c
int size_seqlist(SeqList* list) {
return list->size;
}
销毁
释放顺序表,将申请的内存空间全部释放:
- 第一步:释放数据空间
- 第二步:释放创建的顺序表
注意 :释放完毕后,指针需要赋值为NULL
c
void destory_seqlist(SeqList* list)
{
assert(list);
// 释放数据
if (list->data != NULL) {
free(list->data);
list->data = NULL;
}
// 释放链表
free(list);
list = NULL;
}
总代码
c
#include <stdio.h>
#include <stdlib.h>
#include <stdbool.h>
#include <assert.h>
// 增删改查外加排序
typedef int DataType;
typedef struct SeqList {
DataType* data;
size_t size;
size_t capacity;
}SeqList;
// 创建顺序表
SeqList* create_seqlist();
// 插入
void insert_seqlist(SeqList* list, DataType data);
// 查找--按值查找,找得到则返回下标(0开始),找不到就返回-1
int search_value_seqlist(SeqList* list, DataType data);
// 查找--按位查找,越界:-1,正常:下标
DataType search_position_seqlist(SeqList* list, size_t k);
// 删除--通过下标(位置), index从0开始
void erase_seqlist_index(SeqList* list, size_t index);
// 删除--通过指定元素
void erase_seqlist_data(SeqList* list, DataType data); // 删除一个
void erase_seqlist_all(SeqList* list, DataType data); // 删除全部
// 万金油函数
bool emtpy_seqlist(SeqList* list) {
return list->size == 0;
}
int size_seqlist(SeqList* list) {
return list->size;
}
// 打印
void print_seqlist(SeqList* list);
// 销毁
void destory_seqlist(SeqList* list);
int main()
{
SeqList* list = create_seqlist();
for (int i = 0; i < 15; i++) {
insert_seqlist(list, i);
}
insert_seqlist(list, 8);
printf("插入测试:\n");
print_seqlist(list);
printf("查找测试:\n");
DataType value = search_value_seqlist(list, 8);
int pos = search_position_seqlist(list, 2);
printf("from_value: %d, from_pos: %d\n", value, pos);
printf("删除测试: \n");
erase_seqlist_index(list, 5);
print_seqlist(list);
erase_seqlist_data(list, 9);
print_seqlist(list);
erase_seqlist_all(list, 8);
print_seqlist(list);
printf("万金油函数: \n");
printf("是否为空:%d\n", emtpy_seqlist(list));
printf("大小: %d\n", size_seqlist(list));
// 销毁
destory_seqlist(list);
return 0;
}
SeqList* create_seqlist()
{
SeqList* new_data = (SeqList*)calloc(1, sizeof(SeqList));
if (!new_data) {
printf("顺序表创建失败\n");
return new_data;
}
return new_data;
}
void insert_seqlist(SeqList* list, DataType data)
{
assert(list); // list为空,则断言
// 是否需要扩容
if (list->size >= list->capacity) {
// 2倍扩容
if (list->capacity == 0) {
list->capacity = 10;
}
else {
list->capacity = 2 * list->capacity;
}
// 扩容
DataType* new_data = (DataType*)realloc(list->data, list->capacity * sizeof(DataType));
assert(new_data); // 判断内存是否申请失败
list->data = new_data;
}
// 直接在后面插入,但是也可以有序插入,这个要根据业务
list->data[list->size++] = data;
}
void print_seqlist(SeqList* list)
{
for (size_t i = 0; i < list->size; i++) {
printf("%d ", list->data[i]);
}
puts("\n");
}
int search_value_seqlist(SeqList* list, DataType data)
{
// 防止空表
assert(list);
for (size_t i = 0; i < list->size; i++) {
if (list->data[i] == data) {
return i;
}
}
return -1;
}
DataType search_position_seqlist(SeqList* list, size_t k)
{
assert(list);
if (k >= list->size || k < 0) {
return -1;
}
return list->data[k];
}
void erase_seqlist_index(SeqList* list, size_t index)
{
assert(list);
if (index < 0 || index >= list->size) {
printf("无效位置\n");
return;
}
for (int i = index; i < list->size - 1; i++) {
list->data[i] = list->data[i + 1];
}
list->size--;
}
void erase_seqlist_data(SeqList* list, DataType data)
{
assert(list);
for (size_t i = 0; i < list->size; i++) {
if (list->data[i] == data) {
erase_seqlist_index(list, i); // 原理一样
}
}
}
void erase_seqlist_all(SeqList* list, DataType data)
{
assert(list);
// 第一种方法:暴力,这里不写
// 第二种方法: 双指针
size_t slow = 0, fast = 0;
int num = 0;
for (; fast < list->size; fast++) {
if (list->data[fast] == data) {
num++;
continue;
}
else {
list->data[slow++] = list->data[fast];
}
}
list->size -= num;
}
void destory_seqlist(SeqList* list)
{
assert(list);
// 释放数据
if (list->data != NULL) {
free(list->data);
list->data = NULL;
}
// 释放链表
free(list);
list = NULL;
}
顺序表案例
顺序表应用常见有很多, 比如说:储存数据(管理系统等),这里我们做一个多项式合并 ,多项式次数存储有序,次数从高到低。
c
#include <stdio.h>
#include <stdlib.h>
#include <assert.h>
#define max_num 100
typedef struct Node {
int data; // 底数
int exp; // 指数
}Node;
typedef struct SeqList {
Node data[max_num];
int count;
}SeqList;
void add(SeqList* A, SeqList* B, SeqList* C)
{
if (A == NULL || B == NULL || C == NULL) {
return;
}
int i = 0, j = 0, k = 0;
while (i < A->count && j < B->count) {
// 次数对比
if (A->data[i].exp > B->data[j].exp) {
C->data[k++] = A->data[i++];
}
else if (A->data[i].exp < B->data[j].exp) {
C->data[k++] = B->data[j++];
}
else { // 次数相同
Node t;
if (A->data[i].data + B->data[j].data == 0) {
continue;
}
t.exp = A->data[i].exp;
t.data = A->data[i++].data + B->data[j++].data;
C->data[k++] = t;
}
C->count++;
}
while (i < A->count) {
C->data[k++] = A->data[i++];
}
while (j < B->count) {
C->data[k++] = B->data[j++];
}
}
int main()
{
// y = 3 * x^2 + 5 * x + 2
SeqList A = { {{3, 2},{5,1},{2, 0}}, 3 };
// y = -1 * x^3 + 4 * x^2 + 4
SeqList B = { {{-1,3},{4, 2},{4, 0}}, 3 };
SeqList C = { 0 }; // 答案
add(&A, &B, &C);
int i = 0;
while (i < C.count) {
if (C.data[i].exp == 0) {
printf("%d ", C.data[i].data);
}
else {
printf("%d * x^%d ", C.data[i].data, C.data[i].exp);
}
if (i != C.count - 1) {
printf("+ ");
}
i++;
}
return 0;
}
// 输出:
/*
-1 * x^3 + 7 * x^2 + 5 * x^1 + 6
*/