【顺序表】数据结构顺序表基本操作(增删查改)【保姆级别详解】

顺序表
在这里插入图片描述
恭喜大家拿下C语言了,今天我们正式进入数据结构内容,学习数据结构,我们C语言的基础要非常的扎实,非常的牢固才行,如果C语言部分还有地方不太明白,可以食用完博主的C语言专栏,再来学习数据结构噢

文章目录

  • 前言
  • 顺序表基本介绍
  • 顺序表实现
    • 顺序表结构
    • 初始化接口
    • 顺序表增容接口
    • 尾插尾删接口
    • 打印接口
    • 头插头删接口
    • 关于顺序表的一些思考
    • 在任意位置的插入和删除
    • 查找接口
    • 修改接口
  • 源码
  • 尾声

强烈建议本篇收藏后食用~

前言

那么这里博主先安利一下一些干货满满的专栏啦!

作者: #西城s
这是我的主页:#西城s
在食用这篇博客之前,博主在这里介绍一下其它高质量的编程学习栏目:
数据结构专栏:数据结构 这里包含了博主很多的数据结构学习上的总结,每一篇都是超级用心编写的,有兴趣的伙伴们都支持一下吧!
算法专栏:算法 这里可以说是博主的刷题历程,里面总结了一些经典的力扣上的题目,和算法实现的总结,对考试和竞赛都是很有帮助的!
力扣刷题专栏:Leetcode想要冲击ACM、蓝桥杯或者大学生程序设计竞赛的伙伴,这里面都是博主的刷题记录,希望对你们有帮助!
C的深度解剖专栏:C语言的深度解剖想要深度学习C语言里面所蕴含的各种智慧,各种功能的底层实现的初学者们,相信这个专栏对你们会有帮助的!
【顺序表】数据结构顺序表基本操作(增删查改)【保姆级别详解】_第1张图片

顺序表基本介绍

顺序表是线性表的一种,除了顺序表之外,线性表还包括链表、栈、队列等数据结构,这些数据结构的详解实现,博主在以前的一些博客中已经详解过了,伙伴们可以通过传送门食用噢~都收录在【数据结构】专栏里了。

顺序表:
本质上就是结构体数组,数据在内存中连续存放
【顺序表】数据结构顺序表基本操作(增删查改)【保姆级别详解】_第2张图片

在本期博客,博主将带着大家对顺序表实现以下功能:

  • 尾插尾删
  • 头插头删
  • 在任意位置插入和删除
  • 查找和修改

同样,我们还是按照分模块编程的习惯,创建3个源文件SeqList.c 用于接口的实现
SeqList.h 顺序表结构、接口的声明、头文件
test.c 测试

顺序表实现

顺序表结构

//动态顺序表
typedef int SLDdataType;
typedef struct SeqList {
	SLDdataType* a;
	size_t size;//数据个数
	size_t capacity;//容量
}SL;

初始化接口

最重要的,无论我们模拟实现哪一种数据结构,我们都要测试,因此在test.c文件里,我们如果想要进行后序的增删查改等功能,我们都要有一个初始化接口,将我们的表先初始化好,我们后面才有增删查改这些东西可以操作。

具体初始化的思路非常简单,让指针置空,表示现在是空表,将容量和数据个数置成0即可。

void _SeqListInit(SL* ps) {
	assert(ps);
	ps->a = NULL;
	ps->capacity = ps->size = 0;
}

顺序表增容接口

当我们初始化好,或者后续我们将现有分配给我们的内存空间用完之后,我们肯定是要进行增容的,增容我们使用realloc函数就行了。
判断条件就是:ps->size==ps->capacity

void _SeqList_checkCapacity(SL* ps) {
	assert(ps);
	if (ps->size == ps->capacity) {
		int _newCapacity = ps->capacity == 0 ? 4 : ps->capacity * 2;
		//这里我们采用二倍增容法
		//如果一开始是空表,先给4个容量,后序的再二倍增容
		SLDdataType* tmp = (SLDdataType*)realloc(ps->a, _newCapacity * sizeof(SLDdataType));
		if (tmp == NULL) {//这些都是realloc函数的用法
		//暂且不明白的朋友们可以先补补C语言
			perror("_SeqList_checkCapacity::realloc");
			exit(-1);
		}
		ps->a = tmp;
		ps->capacity = _newCapacity;
	}
}

尾插尾删接口

尾插:在表尾插入一个数据
尾删:在表尾删除一个数据

尾插:
这个接口的实现对于我们来说是非常简单的
我们直接在结构体数组——也就是顺序表后面直接再插入就行了
但是,我们要注意,当容量满的时候,我们要增容,才能插入。因此,无论是头插尾插还是任意位置的插入,只要是插入数据,我们都要检查容量!
检查容量我们直接调用_SeqList_checkCapacity()函数就行了。

void _SeqListPushBack(SL* ps, SLDdataType x) {
	assert(ps);
	//扩容
	_SeqList_checkCapacity(ps);
	ps->a[ps->size] = x;
	ps->size++;
}

尾删:
其实尾删不是真正意义的把那个数删除,我们要做的其实非常简单——把ps->size--;就行了,这样其实相当于我们删除了一个数,因为尾部那个数我们也访问不到了。
注意:
当且仅当,ps->size>0的时候,我们才可以进行删除操作!

void _SeqListPopBack(SL* ps) {
	assert(ps);
	//尾删
	/*if (ps->size == 0) {
		printf("Empty List! err!\n");
	}*/
	//或
	assert(ps->size > 0);
	ps->size--;
}

打印接口

插入了数据之后,为了方便测试,我们写一个打印接口,打印这个顺序表。

  • 其实这里思路非常简单,其实就是遍历一个结构体数组而已。打印数组对于我们来说应该是小菜一碟了吧
void _SeqListPrint(SL* ps) {
	assert(ps);
	for (int i = 0; i < ps->size; i++) {
		printf("%d ", ps->a[i]);
	}
	printf("\n");
}

头插头删接口

头插:在表头插入一个数据
头删:在表头删除一个数据

头插和头删:
这里的插入和删除,就和尾插尾删有所不同了,因为是顺序表的缘故,每个数据必须连续存放,所以我们在头部进行操作的时候我们必须要做的一件事就是——挪动数据
其它的注意事项和尾插尾删的一样,思路都非常简单,这里就不赘述了。

头插:

void _SeqListPushFront(SL* ps, SLDdataType x) {
	assert(ps);
	//增容
	_SeqList_checkCapacity(ps);
	//挪动数据
	int end = ps->size - 1;
	while (end >= 0) {
		ps->a[end + 1] = ps->a[end];
		--end;
	}
	ps->a[0] = x;
	ps->size++;
}

头删:

void _SeqListPopFront(SL* ps) {
	assert(ps);
	assert(ps->size > 0);
	int begin = 1;
	while (begin < ps->size) {
		ps->a[begin - 1] = ps->a[begin];
		++begin;
	}
	ps->size--;
}

关于顺序表的一些思考

看到这里,我们可能会想到一个问题,在头插头删这些操作里,我们还要挪动数据,说明在顺序表里,头插头删的效率是非常低的! 尾插尾删的效率才高,这其实就是顺序表的一个缺陷。其实顺序表,就是vector的底层实现。其实学习数据结构,我们不能仅限于一种结构,记住一句话:没有任何一种数据结构可以解决任何问题。 因此我们每一种都要学习,在插入删除这些操作里面,我们可以看到,顺序表的效率其实不高,我们的链表才会诞生,链表的插入删除,效率是非常高的。这里附上博主链表部分的传送门。当然,后面我们也会有查找这些接口,查找效率最高的,是树形结构,AVL,红黑树这些,它们的查找效率无敌快。因此,我们要打好基础,认真学习数据结构,学习每一种数据结构的优劣,这样,我们才能进步!
【链表】单链表的介绍和基本操作(C语言实现)【保姆级别详细教学】
【链表】双向链表的介绍和基本操作(C语言实现)【保姆级别详细教学】

在任意位置的插入和删除

思路和其它接口完全一样,只是多了一个参数pos,我们从pos开始操作就行了,挪动数据也是一样的思路–这里我们直接上代码吧!

任意位置插入:

void _SeqListInsert(SL* ps, int pos, SLDdataType x) {
	assert(ps);
	assert(pos >= 0 && pos <= ps->size);
	_SeqList_checkCapacity(ps);
	int end = ps->size - 1;
	while (end >= pos) {
		ps->a[end + 1] = ps->a[end];
		end--;
	}
	ps->a[pos] = x;
	ps->size++;
}
//有了这个之后,我们要尾插头插都可以复用了
}

任意位置删除:

void _SeqListErase(SL* ps, int pos) {
	assert(ps);
	assert(pos >= 0 && pos < ps->size);
	int begin = pos;
	while (begin < ps->size - 1) {
		ps->a[begin] = ps->a[begin + 1];
		++begin;
	}
	ps->size--;

查找接口

思路非常简单:遍历,找到返回下标,找不到返回-1

int _SeqListFind(SL* ps, SLDdataType x) {
	assert(ps);
	for (int i = 0; i < ps->size; i++) {
		if (ps->a[i] == x) {
			return i;//找到了
		}
	}
	return -1;//找不到
}

修改接口

这个接口通常和查找交互使用,找到以后我们可以修改,可以删除,可以插入,等等。

void _SeqListModify(SL* ps, int pos, SLDdataType x) {
	assert(ps);
	assert(pos >= 0 && pos < ps->size);
	ps->a[pos] = x;
}

当然,我们每实现完一个接口,我们就可以在test.c里面测试了。

源码

SeqList.h

#pragma once
#include
#include
#include


//动态顺序表
typedef int SLDdataType;
typedef struct SeqList {
	SLDdataType* a;
	size_t size;//数据个数
	size_t capacity;//容量
}SL;


void _SeqListInit(SL* ps);//初始化
void _SeqListPrint(SL* ps);
void _SeqList_checkCapacity(SL* ps);
void _SeqListPushBack(SL* ps, SLDdataType x);//尾插
void _SeqListPopBack(SL* ps);//尾删
void _SeqListPushFront(SL* ps, SLDdataType x);//头插
void _SeqListPopFront(SL* ps);//头删
void _SeqListDestroy(SL* ps);
void _SeqListInsert(SL* ps, int pos, SLDdataType x);
void _SeqListErase(SL* ps, int pos);
int _SeqListFind(SL* ps, SLDdataType x);
void _SeqListModify(SL* ps, int pos, SLDdataType x);

SeqList.c

#define _CRT_SECURE_NO_WARNINGS 1

#include"SeqList.h"

void _SeqListInit(SL* ps) {
	assert(ps);
	ps->a = NULL;
	ps->capacity = ps->size = 0;
}

void _SeqList_checkCapacity(SL* ps) {
	assert(ps);
	if (ps->size == ps->capacity) {
		int _newCapacity = ps->capacity == 0 ? 4 : ps->capacity * 2;
		SLDdataType* tmp = (SLDdataType*)realloc(ps->a, _newCapacity * sizeof(SLDdataType));
		if (tmp == NULL) {
			perror("_SeqList_checkCapacity::realloc");
			exit(-1);
		}
		ps->a = tmp;
		ps->capacity = _newCapacity;
	}
}

void _SeqListPrint(SL* ps) {
	assert(ps);
	for (int i = 0; i < ps->size; i++) {
		printf("%d ", ps->a[i]);
	}
	printf("\n");
}

void _SeqListPushBack(SL* ps, SLDdataType x) {
	assert(ps);
	//扩容
	_SeqList_checkCapacity(ps);
	ps->a[ps->size] = x;
	ps->size++;
}

void _SeqListPushFront(SL* ps, SLDdataType x) {
	assert(ps);
	//增容
	_SeqList_checkCapacity(ps);
	//挪动数据
	int end = ps->size - 1;
	while (end >= 0) {
		ps->a[end + 1] = ps->a[end];
		--end;
	}
	ps->a[0] = x;
	ps->size++;
}

void _SeqListPopBack(SL* ps) {
	assert(ps);
	//尾删
	/*if (ps->size == 0) {
		printf("Empty List! err!\n");
	}*/
	//或
	assert(ps->size > 0);
	ps->size--;
}

void _SeqListPopFront(SL* ps) {
	assert(ps);
	assert(ps->size > 0);
	int begin = 1;
	while (begin < ps->size) {
		ps->a[begin - 1] = ps->a[begin];
		++begin;
	}
	ps->size--;
}

void _SeqListDestroy(SL* ps) {
	assert(ps);
	if (ps->a) {
		free(ps->a);
		ps->a = NULL;
		ps->capacity = ps->size = 0;
	}
}

void _SeqListInsert(SL* ps, int pos, SLDdataType x) {
	assert(ps);
	assert(pos >= 0 && pos <= ps->size);
	_SeqList_checkCapacity(ps);
	int end = ps->size - 1;
	while (end >= pos) {
		ps->a[end + 1] = ps->a[end];
		end--;
	}
	ps->a[pos] = x;
	ps->size++;
}
//有了这个之后,我们要尾插头插都可以复用了

void _SeqListErase(SL* ps, int pos) {
	assert(ps);
	assert(pos >= 0 && pos < ps->size);
	int begin = pos;
	while (begin < ps->size - 1) {
		ps->a[begin] = ps->a[begin + 1];
		++begin;
	}
	ps->size--;
}

int _SeqListFind(SL* ps, SLDdataType x) {
	assert(ps);
	for (int i = 0; i < ps->size; i++) {
		if (ps->a[i] == x) {
			return i;//找到了
		}
	}
	return -1;//找不到
}


void _SeqListModify(SL* ps, int pos, SLDdataType x) {
	assert(ps);
	assert(pos >= 0 && pos < ps->size);
	ps->a[pos] = x;
}

test.c
这里的测试伙伴们可以自行来写,不一定按照博主的顺序来测试。

#define _CRT_SECURE_NO_WARNINGS 1
#include"SeqList.h"
void _TestSeqList1() {
	SL _SList_1;
	//初始化
	_SeqListInit(&_SList_1);

	_SeqListPushBack(&_SList_1, 1);
	_SeqListPushBack(&_SList_1, 2);
	_SeqListPushBack(&_SList_1, 3);
	_SeqListPushBack(&_SList_1, 4);
	_SeqListPushBack(&_SList_1, 5);

	_SeqListPrint(&_SList_1);

	_SeqListPushFront(&_SList_1, 6);
	_SeqListPushFront(&_SList_1, 7);
	_SeqListPushFront(&_SList_1, 8);
	_SeqListPushFront(&_SList_1, 9);
	_SeqListPushFront(&_SList_1, 10);


	_SeqListPrint(&_SList_1);

}
void _TestSeqList2() {
	SL _SList_1;
	//初始化
	_SeqListInit(&_SList_1);

	_SeqListPushBack(&_SList_1, 1);
	_SeqListPushBack(&_SList_1, 2);
	_SeqListPushBack(&_SList_1, 3);
	_SeqListPushBack(&_SList_1, 4);
	_SeqListPushBack(&_SList_1, 5);
	_SeqListPrint(&_SList_1);

	_SeqListPopBack(&_SList_1);
	_SeqListPopFront(&_SList_1);
	_SeqListPrint(&_SList_1);

	_SeqListInsert(&_SList_1, 0, 100);
	_SeqListPrint(&_SList_1);
	_SeqListErase(&_SList_1, 1);
	_SeqListPrint(&_SList_1);

#if 0
	int x = 0;
	printf("请输入要删除的数字:");
	scanf("%d", &x);
	int pos = _SeqListFind(&_SList_1, 3);
	if (pos != -1) {
		_SeqListErase(&_SList_1, pos);
	}
#endif
	int x = 0;
	int y = 0;
	printf("请输入要被修改的数字和修改之后的数字:");
	scanf("%d %d", &x, &y);
	int pos = _SeqListFind(&_SList_1, 3);
	if (pos != -1) {
		_SeqListModify(&_SList_1, pos, y);
	}

	_SeqListDestroy(&_SList_1);
}
int main() {
	//_TestSeqList1();
	_TestSeqList2();
	return 0;
}

尾声

看到这里,相信伙伴们已经对顺序表已经有了基本了解,掌握了基本的操作接口实现方法。
如果看到这里的你感觉这篇博客对你有帮助,不要忘了收藏,点赞,转发,关注哦。

你可能感兴趣的:(手撕数据结构,c语言,数据结构)