数据结构:顺序表-C语言实现

数据结构-顺序表

目录

  • 线性表
  • 顺序表
  • 链表
  • 顺序表和链表的优缺点

线性表

线性表是n个具有相同特性的数据元素的有限序列,逻辑上是线性结构,是一条连续的直线。物理结构上不一定连续,因为通常线性表在内存中是以数组和链式的形式存储以数组形式存储时是连续的但是以链式形式存储时是不连续的。常见的线性表:顺序表、链表、栈、队列… 这篇文章先给大家介绍顺序表,链表、栈和队列会在后续文章更新。

顺序表

定义

顺序表是用一段物理地址连续的存储单元依次存储数据元素的线性结构,一般情况下采用数组存储。在数组 上完成数据的增删查改。

  • 静态顺序表和动态顺序表

在这里插入图片描述

通过上述可知,静态顺序表为定长的数组,导致MaxSize定大了,空间开多了浪费,开少了不够用,实际使用场景有限。所以现实中基本都是使用动态顺序表,根据需要可以用realloc动态地分配空间大小,所以下面我们实现动态顺序表。本次代码的实现共创建了三个文件,分别是SeqList.h,SeqList.cmain.c文件。下面我们先把结构体定义和各个函数接口实现,最后再把三个文件的代码全部展示出来。

定义结构体

typedef int SLDataType;  // 重新定义数据类型名
typedef struct SeqList
{
    SLDataType* array;  // 定义一个指针,指向开辟出来的数组
    int capacity;  // 顺序表能够容纳的最大元素个数大小,后期可以改变
    int size;  // 有效元素的个数大小
}SeqList;

为什么要重新定义数据类型名?因为顺序表储存的元素类型不单单是int型,后面要存储char型的或者其他类型的数据,需要把代码里面的int都改一遍,非常麻烦。如果我们重新定义了类型名,并且在代码里用重新定义好的名字,下次需要存储其他类型的数据,直接在重定义那里把int改成想存储的类型就好了。

初始化

// 顺序表初始化
void SeqListInit(SeqList* sl)
{
	// 断言,防止传进来的是空指针
	assert(sl != NULL);  // 也可以写成 assert(sl);
	sl->array = NULL;  
	sl->capacity = 0;  
	sl->size = 0;
}

assert是一个断言函数,程序运行的时候,当括号里面的结果为假时,就会停止运行并且报错。报错显示的信息包括断言的内容和断言的位置,还有一个错误框,如下图所示。断言能够快速地帮我们定位程序的错误,在实际开发中可以减少很多不必要的麻烦,所以建议大家在写代码的时候也尽量在需要的时候加上断言。

温馨提示在使用assert函数时,记得包含一下assert.h这个头文件。

在这里插入图片描述

打印

// 顺序表打印
void SeqListPrint(SeqList* sl)
{
	assert(sl);
	int i = 0;
	for (i = 0; i < sl->size; i++)
	{
		printf("%d ", sl->array[i]);
	}
	printf("\n");
}

检查和增容

// 检查空间,如果满了,进行增容
void CheckCapacity(SeqList* sl)
{
	assert(sl);

	// 判断空间是否满了,如果是就增容
	if (sl->capacity == sl->size)
	{
		// 当capacity为0时,赋值为4,不为0时,赋值为capacity的两倍
		int newcapacity = sl->capacity == 0 ? 4 : sl->capacity * 2;

		// 用realloc增容,当sl->array指向NULL时,会像malloc那样去申请空间
		SLDataType* temp = (SLDataType*)realloc(sl->array, sizeof(SLDataType) * newcapacity);
		
		// 判断申请空间是否成功
		assert(temp);

		// 让array指向增容之后的空间地址
		sl->array = temp;
		sl->capacity = newcapacity;
	}
}

在这里插入图片描述

增删查改

头插

在这里插入图片描述

// 顺序表头插
void SeqListPushFront(SeqList* sl, SLDataType x)
{
	assert(sl);

	// 检查空间,不够就增容
	CheckCapacity(sl);

	// 将所有元素往后移一个位置
    int i = 0;
	for (i = sl->size; i > 0; i--)
	{
		sl->array[i] = sl->array[i - 1];
	}

	// 插入数据
	sl->array[0] = x;

	// size加1
	sl->size += 1;

	// 这里空表也适用,所以不用考虑空表
}
尾插

在这里插入图片描述

// 顺序表尾插
void SeqListPushBack(SeqList* sl, SLDataType x)
{
	assert(sl);
	CheckCapacity(sl);
    
    // 插入元素
	sl->array[sl->size] = x;
    
    // size加1
	sl->size += 1;
}
在pos位置插入

在这里插入图片描述

// 顺序表在下标为pos的位置插入x
void SeqListInsert(SeqList* sl, size_t pos, SLDataType x)
{
	assert(sl);
	CheckCapacity(sl);
    
	// 检查pos位置是否有效
	assert(pos >= 0 && pos <= sl->size);

	// 把pos位置开始的所有元素往后移一个位置
	int i = 0;
	for (i = sl->size; i > pos; i--)
	{
		sl->array[i] = sl->array[i - 1];
	}

	// 插入数据
	sl->array[pos] = x;

	// size加一
	sl->size += 1;
}

掌握了在pos位置插入的方法后,前面的头插和尾插也可以写成下面这个样子

// 头插
void SeqListPushFront(SeqList* sl, SLDataType x)
{
	// 调用函数接口插入数据
	SeqListInsert(sl, 0, x);
}

// 尾插
void SeqListPushBack(SeqList* sl, SLDataType x)
{
	SeqListInsert(sl, sl->size, x);
}
头删

在这里插入图片描述

// 顺序表头删
void SeqListPopFront(SeqList* sl)
{
	assert(sl);

	// 判断空表
	assert(sl->size);

	// 把0后面位置的元素都往前移一个位置,即完成头删
	int i = 0;
	for (i = 0; i < sl->size; i++)
	{
		sl->array[i] = sl->array[i + 1];
	}

	// size减1
	sl->size -= 1;
}
尾删

在这里插入图片描述

// 顺序表尾删
void SeqListPopBack(SeqList* sl)
{
	assert(sl);

	// 判断空表
	assert(sl->size);

	// 把size减1即可实现尾删
	sl->size -= 1;
}
删除pos位置的元素

在这里插入图片描述

// 顺序表删除pos位置的值
void SeqListErase(SeqList* sl, int pos)
{
	assert(sl);

	// 判断空表
	assert(sl->size);

	// 判断pos位置是否有效
	assert(pos >= 0 && pos < sl->size);

	// 把pos位置之后的元素都往前移一个位置,即可实现删除
	int i = 0;
	for (i = pos; i < sl->size - 1; i++)
	{
		sl->array[i] = sl->array[i + 1];
	}

	// size减1
	sl->size -= 1;
}

掌握了删除pos位置元素这个方法后,前面的头删和尾删也可以写成下面这个样子

// 头删
void SeqListPopFront(SeqList* sl)
{
	SeqListErase(sl, 0);
}

// 尾删
void SeqListPopBack(SeqList* sl)
{
	SeqListErase(sl, sl->size-1);
}
查找
// 顺序表查找
int SeqListFind(SeqList* sl, SLDataType x)
{
	assert(sl);
	int i = 0;
	for (i = 0; i < sl->size; i++)
	{
		if (sl->array[i] == x)
		{
			// 找到返回下标值
			return i;
		}
	}

	// 找不到返回-1
	return -1;
}
修改
// 顺序表修改
void SeqListModify(SeqList* sl, int pos, SLDataType x)
{
	assert(sl);

	// 判断pos的位置是否有效
	assert(pos >= 0 && pos < sl->size);
	
    // 修改元素
	sl->array[pos] = x;
}

销毁

// 顺序表销毁
void SeqListDestory(SeqList* sl)
{
	assert(sl);

	// 释放申请的空间
	free(sl->array);  // 这里申请的空间是一次性申请一整块的,所以可以一次性释放

	// 指针置空
	sl->array = NULL;

	// 大小和空间置0
	sl->capacity = 0;
	sl->size = 0;
}

SeqList.h文件代码

#pragma once  // 防止头文件被重复包含


// 包含头文件
#include<stdio.h>
#include<stdlib.h>
#include<assert.h>


// 重新定义数据类型名
typedef int SLDataType;


// 定义结构体
typedef struct SeqList
{
    SLDataType* array;  // 定义一个指针,指向开辟出来的数组
    int capacity;  // 顺序表能够容纳的最大元素个数大小,后期可以改变
    int size;  // 有效元素的个数大小
}SeqList;


// 函数接口

// 顺序表初始化
void SeqListInit(SeqList* sl);

// 顺序表打印
void SeqListPrint(SeqList* sl);

// 检查和增容
void CheckCapacity(SeqList* sl);

// 顺序表尾插
void SeqListPushBack(SeqList* sl, SLDataType x);

// 顺序表尾删
void SeqListPopBack(SeqList* sl);

// 顺序表头插
void SeqListPushFront(SeqList* sl, SLDataType x);

// 顺序表头删
void SeqListPopFront(SeqList* sl);

// 顺序表查找
int SeqListFind(SeqList* sl, SLDataType x);

// 顺序表修改
void SeqListModify(SeqList* sl, int pos, SLDataType x);

// 顺序表在pos位置插入x
void SeqListInsert(SeqList* sl, int pos, SLDataType x);

// 顺序表删除pos位置的值
void SeqListErase(SeqList* sl, int pos);

// 顺序表销毁
void SeqListDestory(SeqList* sl);

SeqList.c文件代码

#define _CRT_SECURE_NO_WARNINGS
#include"SeqList.h"

// 顺序表初始化
void SeqListInit(SeqList* sl)
{
	
	// 断言,防止传进来空指针
	assert(sl != NULL);  // 也可以写成 assert(sl);
	sl->array = NULL;  
	sl->capacity = 0;  
	sl->size = 0;
}


// 顺序表打印
void SeqListPrint(SeqList* sl)
{
	assert(sl);
	int i = 0;
	for (i = 0; i < sl->size; i++)
	{
		printf("%d ", sl->array[i]);
	}
	printf("\n");
}


// 检查和增容
void CheckCapacity(SeqList* sl)
{
	assert(sl);

	// 判断空间是否满了,如果是就增容
	if (sl->capacity == sl->size)
	{
		// 当capacity为0时,赋值为4,不为0时,赋值为capacity的两倍
		int newcapacity = sl->capacity == 0 ? 4 : sl->capacity * 2;

		// 用realloc增容,当sl->array指向NULL时,会像malloc那样去申请空间
		SLDataType* temp = (SLDataType*)realloc(sl->array, sizeof(SLDataType) * newcapacity);
		
		// 判断申请空间是否成功
		assert(temp);

		// 让array指向增容之后的空间地址
		sl->array = temp;
		sl->capacity = newcapacity;
	}
}


// 顺序表头插
// 方法一
void SeqListPushFront(SeqList* sl, SLDataType x)
{
	assert(sl);

	// 检查空间,不够就增容
	CheckCapacity(sl);
	
	// 将所有元素往后移一个位置
	int i = 0;
	for (i = sl->size; i > 0; i--)
	{
		sl->array[i] = sl->array[i - 1];
	}

	// 插入数据
	sl->array[0] = x;

	// size加1
	sl->size += 1;

	// 这里空表也适用,所以不用考虑空表
}

// 方法二
//void SeqListPushFront(SeqList* sl, SLDataType x)
//{
//	// 调用函数接口插入数据
//	SeqListInsert(sl, 0, x);
//}


// 顺序表尾插
// 方法一
void SeqListPushBack(SeqList* sl, SLDataType x)
{
	assert(sl);
	CheckCapacity(sl);

	// 插入数据
	sl->array[sl->size] = x;

	// size加一
	sl->size += 1;
}

// 方法二
//void SeqListPushBack(SeqList* sl, SLDataType x)
//{
//	SeqListInsert(sl, sl->size, x);
//}


// 顺序表在pos位置插入x
void SeqListInsert(SeqList* sl, int pos, SLDataType x)
{
	assert(sl);
	CheckCapacity(sl);

	// 检查pos位置是否有效
	assert(pos >= 0 && pos <= sl->size);

	// 把pos位置开始的所有元素往后移一个位置
	int i = 0;
	for (i = sl->size; i > pos; i--)
	{
		sl->array[i] = sl->array[i - 1];
	}

	// 插入数据
	sl->array[pos] = x;

	// size加一
	sl->size += 1;
}


// 顺序表头删
// 方法一
//void SeqListPopFront(SeqList* sl)
//{
//	assert(sl);
//
//	// 判断空表
//	assert(sl->size);
//
//	// 把0后面位置的元素都往前移一个位置,即完成头删
//	int i = 0;
//	for (i = 0; i < sl->size; i++)
//	{
//		sl->array[i] = sl->array[i + 1];
//	}
//
//	// size减1
//	sl->size -= 1;
//}

// 方法二
void SeqListPopFront(SeqList* sl)
{
	SeqListErase(sl, 0);
}


// 顺序表尾删
// 方法一
//void SeqListPopBack(SeqList* sl)
//{
//	assert(sl);
//
//	// 判断空表
//	assert(sl->size);
//
//	// 把size减1即可实现尾删
//	sl->size -= 1;
//}

// 方法二
void SeqListPopBack(SeqList* sl)
{
	SeqListErase(sl, sl->size-1);
}


// 顺序表删除pos位置的值
void SeqListErase(SeqList* sl, int pos)
{
	assert(sl);

	// 判断空表
	assert(sl->size);

	// 判断pos位置是否有效
	assert(pos >= 0 && pos < sl->size);

	// 把pos位置之后的元素都往前移一个位置,即可实现删除
	int i = 0;
	for (i = pos; i < sl->size - 1; i++)
	{
		sl->array[i] = sl->array[i + 1];
	}

	// size减1
	sl->size -= 1;
}

// 顺序表查找
int SeqListFind(SeqList* sl, SLDataType x)
{
	assert(sl);
	int i = 0;
	for (i = 0; i < sl->size; i++)
	{
		if (sl->array[i] == x)
		{
			// 找到返回下标值
			return i;
		}
	}

	// 找不到返回-1
	return -1;
}


// 顺序表修改
void SeqListModify(SeqList* sl, int pos, SLDataType x)
{
	assert(sl);

	// 判断pos的位置是否有效
	assert(pos >= 0 && pos < sl->size);
	
	sl->array[pos] = x;
}


// 顺序表销毁
void SeqListDestory(SeqList* sl)
{
	assert(sl);

	// 释放申请的空间
	free(sl->array);  // 这里申请的空间是一次性申请一整块的,所以可以一次性释放

	// 指针置空
	sl->array = NULL;

	// 大小和空间置0
	sl->capacity = 0;
	sl->size = 0;
}

main.c文件代码

#define _CRT_SECURE_NO_WARNINGS
#include"SeqList.h"

// 测试插入接口
void test1()
{
	// 定义一个结构体变量
	SeqList sl;

	SeqListInit(&sl);
	SeqListPushFront(&sl, 1);
	SeqListPushFront(&sl, 2);
	SeqListPushFront(&sl, 3);
	SeqListPushFront(&sl, 4);
	SeqListInsert(&sl, 0, 6);
	SeqListPushBack(&sl, 5);

	SeqListPrint(&sl);

	// 销毁顺序表
	SeqListDestory(&sl);
}

// 测试删除接口
void test2()
{
	SeqList sl;

	SeqListInit(&sl);
	SeqListPushFront(&sl, 1);
	SeqListPushFront(&sl, 2);
	//SeqListPopFront(&sl);
	//SeqListPopFront(&sl);
	//SeqListPopFront(&sl);
	SeqListPopBack(&sl);
	SeqListPopBack(&sl);
	SeqListPopBack(&sl);
	//SeqListErase(&sl, 0);
	//SeqListErase(&sl, 1);
	//SeqListErase(&sl, 2);
	SeqListPrint(&sl);

	SeqListDestory(&sl);
}

// 测试查找和修改
void test3()
{
	SeqList sl;

	SeqListInit(&sl);
	SeqListPushFront(&sl, 1);
	SeqListPushFront(&sl, 3);
	SeqListPushFront(&sl, 2);
	SeqListPushFront(&sl, 3);
	SeqListPushFront(&sl, 4);

	// 一般修改和查找是配合使用的,比如说想把表中的第一个3改为300
	int pos = SeqListFind(&sl, 3);
	SeqListModify(&sl, pos, 300);
	SeqListPrint(&sl);

	SeqListDestory(&sl);
}


int main()
{
	//test1();
	//test2();
	test3();
	return 0;
}

结语

顺序表是我们接触数据结构的第一个需要我们用C语言去实现的内容,刚开始学的时候很多人会觉得比较难,特别是各个函数接口的命名,实在太难记了。很多初学者会自己起一些简单的名字,这也可以,但是不赞成这样做。因为你自己起的名字你看得懂,别人不一定懂,说不定时间久了自己都忘记了。所以建议大家一开始就规范命名,养成良好的习惯。其实这个命名是有规律的,大多数情况是结构体名+功能,使用的是大驼峰命名法(每个单词首字母大写,包括缩写的)。比如上文里的尾插接口SeqListPushBack,可以拆成SeqList+PushBack。另外数据结构的实现,需要熟练掌握C语言的函数,指针,结构体,动态内存规划这些知识。所以建议大家在学习数据结构之前,把C语言的这部分知识学得扎实一点,这样数据结构学起来就轻松很多了。最后,本文是我学完这章内容后的个人总结,要是文章里有什么错误还望各位大神指正。或者对我的文章排版和其他方面有什么建议,也可以在评论区告诉我。如果我的文章对你的学习有帮助,或者觉得写得不错的话记得分享给你的朋友,非常感谢。

点击这里查看下一篇文章–>>数据结构:链表-C语言实现

评论 16
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

柿子__

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值