C语言 数据结构【动态顺序表】详解

引言

详细介绍了顺序表中各个接口的实现,一定要亲自动手敲一遍,要能想象出具体的图像

第一次敲可能不能完全靠自己敲出来(很正常),过一段时间可以根据顺序表的原理敲第二遍

孰能生巧

一、线性表

在介绍顺序表之前先认识一下线性表,顺序表是线性表的一种

线性表(linearlist)是n个具有相同特性的数据元素的有限序列。线性表是一种在实际中广泛使用的数据结构,常见的线性表:顺序表、链表、栈、队列、字符串...

线性表在逻辑上是线性结构,也就说是连续的⼀条直线。

但是在物理结构上并不⼀定是连续的,线性表在物理上存储时,通常以数组和链式结构的形式存储。

二、顺序表

**概念:**顺序表是用一段物理地址连续的存储单元依次存储数据元素的线性结构,⼀般情况下采用数组存储。(在逻辑结构上肯定是连续的)

顺序表和数组的区别?

顺序表的底层结构是数组,对数组的封装,实现了常用的增删改查等接口。

三、实现动态顺序表

💡代码小提示

编写代码过程中要勤测试,避免写出大量代码后再测试而导致出现问题,问题定位无从下手。

定义三个文件:

text.c //测试代码是否正确

SeqList.c //Sequece List(顺序表)所有代码

SeqList.h //顺序表里所有的头文件 和 声明函数

cpp 复制代码
text.c    //测试代码是否正确
SeqList.c //Sequece List(顺序表)所有代码
SeqList.h //顺序表里所有的头文件 和 声明函数

1.定义顺序表类型,引用需要的头文件

在SeqList.h文件中:

cpp 复制代码
#pragma once   //确保头文件在一个编译单元中只被包含一次
#include<stdio.h>   //输入输出需要,perror函数需要
#include<stdlib.h>  //开辟动态内存需要
#include<assert.h>  //assert函数需要

//定义动态顺序表的结构
typedef int SLDataType;
typedef struct SeqList
{
	SLDataType* arr;
	int size;        //有效数据个数
    //size位置是空的,待放入值
	int capacity;    //空间容量
}SL;   // 顺序表

2.初始化顺序表

在SeqList.c文件中的代码:

cpp 复制代码
#include"SeqList.h"  //引用头文件
void SLInit(SL* ps) //初始化
{
	ps->arr = NULL;    
	ps->size = ps->capacity = 0;
}

3.销毁顺序表

在SeqList.c中的代码:

cpp 复制代码
void SLDestroy(SL* ps)//销毁顺序表
{
	assert(ps);
	if (ps->arr)
		free(ps->arr);
	ps->arr = NULL;
	ps->arr = ps->capacity = 0;
}

4.对顺序表扩容

在SeqList.c文件中的代码:

cpp 复制代码
void SLCheckCapacity(SL* ps) //增容
{
	if (ps->size == ps->capacity)
	{
		int newCapacity = ps->capacity == 0 ? 4 : 2 * ps->capacity;
        //如果capacity 是 0,就先扩容4个空间
        //如果capacity 不是0,就扩容2倍空间
		SLDataType* tmp = (SLDataType*)realloc(ps->arr, newCapacity * sizeof(SLDataType));
        //先将扩容后的空间存在tmp中
		if (tmp == NULL)
		{
			perror("realloc"); //输出错误信息
			exit(1); //退出程序并返回1;
		}

		ps->arr = tmp; //tmp不是空,传给arr
		ps->capacity = newCapacity; //更新空间
	}
}

这里采用的是2倍扩容,有效利用空间。

注意:realloc函数第二个参数就是扩容后空间的大小,而不是扩容第二个参数那么大。

5. 实现尾插和打印

在SeqList.c文件中的代码:

cpp 复制代码
#include"SeqList.h"
void SLpushBack(SL* ps, SLDataType x)//尾插
{
	assert(ps);

	SLCheckCapacity(ps);  //空间不够的话就扩容
	ps->arr[ps->size++] = x;
}

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

6.实现头插

在SeqList.c文件中的代码:

cpp 复制代码
#include"SeqList.h"
void SLpushFront(SL* ps, SLDataType x) //头插
{
	assert(ps);
	SLCheckCapacity(ps);
	for (int i = ps->size;i > 0; i--) //将顺序表中所有数据向后挪动一位
	{
		ps->arr[i] = ps->arr[i - 1];
	}
	ps->arr[0] = x;
	++ps->size;
}

7.实现在指定位置插入数据

在SeqList.c文件中的代码:

cpp 复制代码
#include"SeqList.h"
void SLInsert(SL* ps, int pos, SLDataType x)//在指定位置插入数据
{
	assert(ps);
	assert(pos >= 0 && pos <= ps->size);
	SLCheckCapacity(ps);
	//pos及之后的数据整体向后挪动一位
	for (int i = ps->size; i > pos; i--)
	{
		ps->arr[i] = ps->arr[i - 1];
	}
	ps->arr[pos] = x;
	++ps->size;
}

8.测试头插,尾插,任意位置插入元素

在text.c文件中的代码:

cpp 复制代码
#include"SeqList.h"

void text01() //测试尾插
{
	SL s1;
	SLInit(&s1);
	SLpushBack(&s1,1);
	SLpushBack(&s1,2);
	SLpushBack(&s1,3);
	SLprint(&s1);
	SLDestroy(&s1);
}
void text02() //测试头插
{
	SL s2;
	SLInit(&s2);
	SLpushFront(&s2, 1);
	SLpushFront(&s2, 2);
	SLpushFront(&s2, 3);
	SLprint(&s2);
	SLDestroy(&s2);
}

void text03() //测试在指定位置插入元素
{
	SL s3;
	SLInit(&s3);
	SLpushFront(&s3, 1);
	SLpushFront(&s3, 2);
	SLpushFront(&s3, 3);
	SLInsert(&s3, 2, 10);
	SLprint(&s3);
	SLDestroy(&s3);
}
int main()
{
	//text01(); //测试尾插
	//text02(); //测试头插
	text03(); //测试在指定位置插入元素
	return 0;
}

注意:一定不用忘记对顺序表的初始化

text03运行的结果:

9.实现尾删,头删

在SeqList.c中的代码:

cpp 复制代码
void SLPopBack(SL* ps) //尾删,直接让size减1即可
{
	//ps:限制参数不能直接给NULL
	//ps->size:顺序表为空
	assert(ps && ps->size);
	--ps->size;
}

void SLPopFront(SL* ps) //头删,从第二位到最后一位整体向前挪动一位即可,最后size--
{
	assert(ps && ps->size);
	for (int i = 0; i < ps->size - 1; i++)
	{
		ps->arr[i] = ps->arr[i + 1];
	}
	--ps->size;
}

10. 实现任意位置删除元素

在SeqList.c中的代码:

cpp 复制代码
void SLErase(SL* ps, int pos) //删除pos位置的元素
{
	assert(ps);
	assert(pos >= 0 && pos <=ps->size);
	for (int i = pos; i < ps->size - 1; i++) //pos及其位置之后的元素整体向前挪动一位
	{
		ps->arr[i] = ps->arr[i + 1];
	}
	--ps->size;
}

11.测试头删,尾删,删除任意位置的元素

在text.c中的代码:

cpp 复制代码
#include"SeqList.h"
void text04()
{
	SL s4;
	SLInit(&s4);
	SLpushFront(&s4, 1);
	SLpushFront(&s4, 2);
	SLpushFront(&s4, 3);
	SLInsert(&s4, 2, 10);
	SLPopBack(&s4);
	SLPopFront(&s4);
	SLprint(&s4);
	SLDestroy(&s4);
}

void text05() //测试删除任意位置的元素
{
	SL s5;
	SLInit(&s5);
	SLpushFront(&s5, 1);
	SLpushFront(&s5, 2);
	SLpushFront(&s5, 3);
	SLInsert(&s5, 2, 10);
	SLErase(&s5, 2);
	SLprint(&s5);
	SLDestroy(&s5);
}

int main()
{
	//text01(); //测试尾插
	//text02(); //测试头插
	text03(); //测试在指定位置插入元素
	//text04();//测试尾删和头删
	text05(); //测试删除任意位置的元素
	return 0;
}

text03和text05运行的结果:

12.查找元素所在的位置,找不到返回-1

在SeqList.c中的代码:

cpp 复制代码
#include"SeqList.h"
int SLFind(SL* ps, int x) //查找元素x的位置
{
	assert(ps);
	for (int i = 0; i <= ps->size - 1; i++)
	{
		if (ps->arr[i] == x)
			return i;
	}
	return -1;
}

13.测试查找元素所在的位置

在text.c中的代码:

cpp 复制代码
#include"SeqList.h"
int text06()
{
	SL s6;
	SLInit(&s6);
	SLpushFront(&s6, 1);
	SLpushFront(&s6, 2);
	SLpushFront(&s6, 3);
	SLInsert(&s6, 2, 10);
	int r1 = SLFind(&s6, 2);
	int r2 = SLFind(&s6, 9);
	SLprint(&s6);
	printf("2元素的位置:%d\n", r1);
	printf("9元素的位置:%d\n", r2);
	SLDestroy(&s6);
}


int main()
{
	//text01(); //测试尾插
	//text02(); //测试头插
	text03(); //测试在指定位置插入元素
	//text04();//测试尾删和头删
	//text05(); //测试删除任意位置的元素
	text06(); //测试查找元素的位置
	return 0;
}

运行结果:(下标是从0开始的)

四、顺序表所有代码

在SeqList.h中的代码(核心):

cpp 复制代码
#pragma once   //确保头文件在一个编译单元中只被包含一次
#include<stdio.h>   //输入输出需要,perror函数需要
#include<stdlib.h>  //开辟动态内存需要
#include<assert.h>  //assert函数需要

//定义动态顺序表的结构
typedef int SLDataType;
typedef struct SeqList
{
	SLDataType* arr;
	int size;        //有效数据个数
	int capacity;    //空间容量
}SL;              // 顺序表

void SLprint(SL* ps);  //打印顺序表

void SLInit(SL* ps);//初始化

void SLDestroy(SL* ps);//销毁顺序表

void SLCheckCapacity(SL* ps); //增容

void SLpushBack(SL* ps, SLDataType x);//尾插

void SLpushFront(SL* ps, SLDataType x); //头插

void SLInsert(SL* ps, int pos, SLDataType x);//在指定位置插入数据

void SLPopBack(SL* ps); //尾删

void SLPopFront(SL* ps); //头删

void SLErase(SL* ps, int x); //删除pos位置的数据

int SLFind(SL* ps, int x); //查找元素x的位置

在SeqList.c中的文件(核心):

cpp 复制代码
#define _CRT_SECURE_NO_WARNINGS
#include"SeqList.h"
void SLInit(SL* 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->arr = ps->capacity = 0;
}

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

void SLCheckCapacity(SL* ps) //增容
{
	if (ps->size == ps->capacity)
	{
		int newCapacity = ps->capacity == 0 ? 4 : 2 * ps->capacity;
		SLDataType* tmp = (SLDataType*)realloc(ps->arr, newCapacity * sizeof(SLDataType));
		if (tmp == NULL)
		{
			perror("realloc");
			exit(1); //退出程序并返回1;
		}
		ps->arr = tmp;
		ps->capacity = newCapacity;
	}
}

void SLpushBack(SL* ps, SLDataType x)//尾插
{
	assert(ps);

	SLCheckCapacity(ps);
	ps->arr[ps->size++] = x;
}
void SLpushFront(SL* ps, SLDataType x) //头插
{
	assert(ps);
	SLCheckCapacity(ps);
	for (int i = ps->size;i > 0; i--) //将顺序表中所有数据向后挪动一位
	{
		ps->arr[i] = ps->arr[i - 1];
	}
	ps->arr[0] = x;
	++ps->size;
}

void SLInsert(SL* ps, int pos, SLDataType x)//在指定位置插入数据
{
	assert(ps);
	assert(pos >= 0 && pos <= ps->size);
	SLCheckCapacity(ps);
	//pos及之后的数据整体向后挪动一位
	for (int i = ps->size; i > pos; i--)
	{
		ps->arr[i] = ps->arr[i - 1];
	}
	ps->arr[pos] = x;
	++ps->size;
}

void SLPopBack(SL* ps) //尾删,直接让size减1即可
{
	//ps:限制参数不能直接给NULL
	//ps->size:顺序表为空
	assert(ps && ps->size);
	--ps->size;
}

void SLPopFront(SL* ps) //头删,从第二位到最后一位整体向前挪动一位即可,最后size--
{
	assert(ps && ps->size);
	for (int i = 0; i < ps->size - 1; i++)
	{
		ps->arr[i] = ps->arr[i + 1];
	}
	--ps->size;
}

void SLErase(SL* ps, int pos) //删除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, int x) //查找元素x的位置
{
	assert(ps);
	for (int i = 0; i <= ps->size - 1; i++)
	{
		if (ps->arr[i] == x)
			return i;
	}
	return -1;
}

在text.c中的代码(测试写的函数是否能实现功能):

cpp 复制代码
#include"SeqList.h"

void text01()
{
	SL s1;
	SLInit(&s1);
	SLpushBack(&s1,1);
	SLpushBack(&s1,2);
	SLpushBack(&s1,3);
	SLprint(&s1);
	SLDestroy(&s1);
}
void text02()
{
	SL s2;
	SLInit(&s2);
	SLpushFront(&s2, 1);
	SLpushFront(&s2, 2);
	SLpushFront(&s2, 3);
	SLprint(&s2);
	SLDestroy(&s2);
}

void text03()
{
	SL s3;
	SLInit(&s3);
	SLpushFront(&s3, 1);
	SLpushFront(&s3, 2);
	SLpushFront(&s3, 3);
	SLInsert(&s3, 2, 10);
	SLprint(&s3);
	SLDestroy(&s3);
}

void text04()
{
	SL s4;
	SLInit(&s4);
	SLpushFront(&s4, 1);
	SLpushFront(&s4, 2);
	SLpushFront(&s4, 3);
	SLInsert(&s4, 2, 10);
	SLPopBack(&s4);
	SLPopFront(&s4);
	SLprint(&s4);
	SLDestroy(&s4);
}

void text05() //测试删除任意位置的元素
{
	SL s5;
	SLInit(&s5);
	SLpushFront(&s5, 1);
	SLpushFront(&s5, 2);
	SLpushFront(&s5, 3);
	SLInsert(&s5, 2, 10);
	SLErase(&s5, 2);
	SLprint(&s5);
	SLDestroy(&s5);
}

int text06()
{
	SL s6;
	SLInit(&s6);
	SLpushFront(&s6, 1);
	SLpushFront(&s6, 2);
	SLpushFront(&s6, 3);
	SLInsert(&s6, 2, 10);
	int r1 = SLFind(&s6, 2);
	int r2 = SLFind(&s6, 9);
	SLprint(&s6);
	printf("2元素的位置:%d\n", r1);
	printf("9元素的位置:%d\n", r2);
	SLDestroy(&s6);
}


int main()
{
	//text01(); //测试尾插
	//text02(); //测试头插
	text03(); //测试在指定位置插入元素
	//text04();//测试尾删和头删
	//text05(); //测试删除任意位置的元素
	text06(); //测试查找元素的位置
	return 0;
}
相关推荐
HXQ_晴天7 分钟前
Bash 脚本基础
开发语言·chrome·bash
清@尘10 分钟前
威联通 后台可用命令查看Bash
开发语言·chrome·bash
为什么要内卷,摆烂不香吗26 分钟前
【无标题】
开发语言·php
江沉晚呤时32 分钟前
深入解析 C# 中的装饰器模式(Decorator Pattern)
java·开发语言·javascript·jvm·microsoft·.netcore
爱编程的小赵1 小时前
第十五届蓝桥杯C/C++组:宝石组合题目(从小学奥数到编程题详解)
c语言·c++·蓝桥杯
graceyun1 小时前
初阶数据结构(C语言实现)——6.1插入排序详解(思路图解+代码实现)
c语言·数据结构·排序算法
努力的飛杨1 小时前
学习记录-js进阶-性能优化
开发语言·javascript·学习
Vacant Seat1 小时前
回溯-单词搜索
java·数据结构·算法·回溯
星零零2 小时前
【Java】链表(LinkedList)(图文版)
java·开发语言·数据结构·经验分享·笔记·链表
行思理2 小时前
php 要达到go的性能,应该如何优化php
开发语言·php·运维开发