数据结构-顺序表【简单易懂】

一、顺序表概述                

              顺序表是我们学完C语言后,接触的第一种数据结构,其逻辑上、物理上(底层)都是线性结构,其底层逻辑是数组(数组地址连续,所以是线性的),但对数组加工包装,更加高效的实现数据管理。其主要功能是:数据增加、数据删除、数据修改、数据查找(增删改查,以后的各种类型数据结构都主要围绕此四大方面展开)。总体功能如下:

//定义数据类型,方便类型替换
typedef int SLDatatype;

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

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

//销毁
void SLDestroy(SL* ps);

//打印数据
void SLPrint(SL* ps);

//查找数据
void SLFind(SL* ps, SLDatatype x);

//插入数据
void SLPushBack(SL* ps,SLDatatype x);//后插
void SLPushFront(SL* ps,SLDatatype x);//前插
void SLInsert(SL* ps, SLDatatype x, int pos);//指定位置插入元素

//删除数据
void SLPopBack(SL* ps);//后删
void SLPopFront(SL* ps);//前删
void SLErase(SL* ps, int pos);//指定位置删除元素

//修改数据
void SLModif(SL* ps, int pos, SLDatatype x);

 二、定义顺序表结构

顺序表结构主要由三部分组成:数据类型的指针、空间容量、有效数据;

(1)数据类型的指针:

        这里不直接定义数据的类型为int、char是为了以后数据类型的修改方便。所以我们利用typedef来定义数据类型,如:typedef int SLDataType,如果以后数据类型需要需改只需要将int改为其他数据类型,一了百了。注意:这里是SLDataType *a,而不是SL*a,a是各种数据类型的指针,用来存储数据,而不是结构体指针,SL *a就类似于链表了。。。

(2)空间容量

我们创建的顺序表是动态的(指针),指针指向的地址有一个空间容量,在这里我们写为:capicity,容量肯定为整数,所以是int类型。之后的插入操作需要考虑空间是否充足,如果容量不够的话,需要扩容操作,后面会讲。

(3)有效数据

这里的有效数据个数size是最后一个数据的位置(类似于int a[n]中的n,取不到),是为了之后查找数据、指定位置插入数据限定一个范围。

总代码如下:

//定义数据类型,方便类型替换
typedef int SLDatatype;
 
//定义动态顺序表结构
typedef struct SeqList
{
	SLDatatype* a;
	int capicity;//空间容量
	int size;//有效数据个数
}SL;

三、顺序表初始化

刚才已经定义了顺序表结构,其结构体中包括数据类型指针、空间容量、有效数据,我们需要进行初始化操作,避免产生野指针,主要就就是将指针指向NULL,空间容量、有效数据赋0;代码如下:

//初始化
void SLInit(SL* ps) 
{
	ps->a = NULL;
	ps->capicity = 0;
	ps->size = 0;
}

四、顺序表打印

这里主要要强调一下传过来的指针不能为空,要不然ps->a(此时ps=NULL)操纵是不合法的,所以需要添加assert函数(头文件 assert.h不要忘记引用),当指针为空时断言报错提醒。其代码如下:

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

  五、 数据查找

数据查找主要依靠一个循环,将数据都遍历一遍,这里可以考虑引入一个变量j,记录所要查找的数据位置(所要查找的数据可能不止一个)。代码如下:

//查找数据
void SLFind(SL* ps, SLDatatype x) 
{
	int j = 1;
	int  p = 1;
	for (int i = 0; i < ps->size; i++) {
		if (ps->a[i] == x)
		{
			printf("查找的数据第%d处位置为:第%d个数据", j, i + 1);
			printf("\n");
			j++;
			p = 0;
		}
	}
	if (p == 1)
		printf("没有此数据!");
}

将printf中的i+1写为j也是一样的。

:printf里面也有i+1,for循环里有i++,那i一次循环不是+2吗,有的数据是否没有考虑到?

答:其实printf里面的i+1只在printf中有效,当执行完i+1后便销毁了,i的值在printf执行完毕后值不会改变,自己可以测试一下。

六、插入数据

在插入数据之前,我们需要考虑原来的空间是否足够,如果原来空间满了,是插入不了的(类似int a[n] 已经存了n个数据了),这时候需要我们进行扩容操纵,我们将此操作封装SLCheckCapacit函数中,先看代码:

//插入操作前判断空间是否充足
void SLCheckCapacity(SL* ps)
{
	if (ps->capicity == ps->size)
	{
		int NewCapicity = ps->capicity == 0 ? 4 : 2 * ps->capicity;
		SLDatatype* tmp = (SLDatatype*)realloc(ps->a, NewCapicity * sizeof(SLDatatype));
		if (tmp == NULL)
		{
			perror("realloc fail!");
			exit(1);
		}
		ps->a = tmp;
		ps->capicity = NewCapicity;
	}

利用ps->capicity==ps->size这个条件即可判断原来的空间是否满了,如果满了,需要重新开辟一块空间将此空间地址传给a指针。我们计算新空间的大小一般为原来空间的2倍,如果太大,会造成空间浪费,太小的话插入数据如果较多需要多次进行扩容操作,这会使计算机运行效率变低、性能变低等,所以一般取2倍。注意:原来空间没说不可以为0,所以一定要提前判断!代码中int NewCapicity = ps->capicity == 0 ? 4 : 2 * ps->capicity;就是此作用。开辟一块新地址我们这里用realloc。问:为什么不用malloc函数,而用realloc函数,二者有什么区别?

答:简单来讲:malloc 只负责分配指定字节数的内存块,不对其进行初始化;realloc 用于重新分配内存块的大小,并尝试保留原始内存块中的数据;我们这里开辟新的空间需要保留原有地址的数据,所以用realloc。

1、尾插操作

尾插需要考虑空间是否足够,所以我们需要调用一下SLCheckCapacity函数;既然是插入操作,那原指针的地址肯定不能为空,需要加入assert断言(后面的头插,指定位置插入都需要断言)。代码如下:

//尾插
void SLPushBack(SL* ps, SLDatatype x) 
{
	assert(ps);
	//判断空间是否足够
	SLCheckCapacity(ps);
	ps->a[ps->size++] = x;
}

别忘记size++,有效数据需要+1。问:ps->a[ps->size++]=x中执行顺序

答:由于size++是后置++,所以先执行语句后++,即ps->a[ps->size]=x执行完毕后,ps->size++。

2、头插操作

头插操作需要一个while循环,将前面之前所有的数据都往后挪一个,最后别忘记ps->size++。代码如下:

//头插
void SLPushFront(SL* ps, SLDatatype x)
{
	assert(ps);
	//判断空间是否足够
	SLCheckCapacity(ps);
	int i = ps->size;
	while (i> 0)
	{
		ps->a[i] = ps->a[i-1];
		i--;
	}
	ps->a[0] = x;
	ps->size++;
}

3、指定位置插入数据

首先需要考虑原来的顺序表不能为空,指定数据的位置不能为空,所以引用 assert(ps);
    assert(pos >= 0 && pos <= ps->size);往指定位置插入数据需要将指定位置之后的数据全部往后挪一个,这里也需要一个循环,注意循环结束的条件为:i>pos(int i = ps->size;)。代码如下:

void SLInsert(SL* ps, SLDatatype x, int pos)
{
	assert(ps);
	assert(pos >= 0 && pos <= ps->size);

	SLCheckCapacity(ps);
	int i = ps->size;

	while ( i>pos)
	{
		ps->a[i ] = ps->a[i-1];
		i--;
	}
	ps->a[pos] = x;
	ps->size++;
}

ps->size++别忘记!

七、删除数据

所有删除数据都要assert判断,删除数据,原来数不能为空。以及进行ps->size--,不能忘记。

1、数据尾删

尾删最简单,将ps->size--即可。代码如下:

/尾删
void SLPopBack(SL* ps)
{
	assert(ps);
	assert(ps->size);
	ps->size--;
}

2、数据头删

头删操作将所有数据从后往前覆盖即可,代码如下:

//头删
void SLPopFront(SL* ps)
{
	assert(ps);
	assert(ps->size);
	for (int i = 0; i < ps->size; i++)
	{
		ps->a[i] = ps->a[i + 1];
	}
	ps->size--;
}

问:为什么这里需要考虑assert(ps->size)?

答:ps->size为0了,哪里有数据删。

3、指定位置删除数据

原理简单,需要一个循环,先找到指定位置,将指定位置之后的所有数据向前覆盖即可。代码如下:

//删除指定位置元素
void SLErase(SL* ps, int pos) {
	assert(ps);
	assert(pos >= 0 && pos <= ps->size);

	SLCheckCapacity(ps);
	for (int i = pos ; i < ps->size; i++)
	{
		ps->a[i] = ps->a[i + 1];
	}
	ps->size--;
}

八、修改数据

一种是修改指定位置的数据,一种是修改所有等于此值的数据,其代码如下:

//修改数据
void SLModif(SL* ps, int pos,SLDatatype x)
{
	int y = 0;
	printf("1、将某一数据全部修改为新数据;\n");
	printf("*****2、修改指定位置数据*******\n");
	printf("请输入你的操作:");
	int input=scanf("%d", &y);
	int m = 0;
	if(input==1)
	{
		SLPrint(ps);
		printf("请输入你要修改的数字:");
		scanf("%d", &m);
		for (int i = 0; i < ps->size; i++)
		{
			if (ps->a[i] == m)
				ps->a[i] = x;
		}
	}
	else if (input == 2)
	{           
		if (pos >= 0 && pos < ps->size)
				ps->a[pos] = x;
	}
	else
			printf("输入无效,请重新输入:");
}

九、销毁操作

顺序表的实现指针指向了新的地址,在完成操作后,需要将这些地址销毁,以免内存泄漏等问题,将指针指向NULL。代码如下:

//销毁
void SLDestroy(SL* ps) {
	if (ps->a)
	{
		free(ps->a);
	}
	ps->capicity = ps->size = 0;
	ps->a = NULL;
}

十、总代码

将代码分装到三个文件,一个头文件SeqList.h,实现操纵的文件SeqList.c,测试文件test.c。

头文件SeqList.h主要定义顺序表结构和各种操作函数的声明。

实现操纵的文件SeqList.c主要敲各种代码,实现各种功能。

测试文件test.c对SeqList.c中各种函数进行测试。

1、SeqList.h

代码如下:

#pragma once
#include<stdio.h>
#include<stdlib.h>
#include<assert.h>
//定义指针类型,方便类型替换
typedef int SLDatatype;
 
//定义动态顺序表结构
typedef struct SeqList
{
	SLDatatype* a;
	int capicity;//空间容量
	int size;//有效数据个数
}SL;

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

//销毁
void SLDestroy(SL* ps);

//打印数据
void SLPrint(SL* ps);

//查找数据
void SLFind(SL* ps, SLDatatype x);

//插入数据
void SLPushBack(SL* ps,SLDatatype x);//后插
void SLPushFront(SL* ps,SLDatatype x);//前插
void SLInsert(SL* ps, SLDatatype x, int pos);//指定位置插入元素

//删除数据
void SLPopBack(SL* ps);//后删
void SLPopFront(SL* ps);//前删
void SLErase(SL* ps, int pos);//指定位置删除元素

//修改数据
void SLModif(SL* ps, int pos, SLDatatype x);

2、SeqList.c

代码如下:

#define _CRT_SECURE_NO_WARNINGS 1
#include"SeqList.h"

//初始化
void SLInit(SL* ps) 
{
	ps->a = NULL;
	ps->capicity = 0;
	ps->size = 0;
}

//销毁
void SLDestroy(SL* ps) {
	if (ps->a)
	{
		free(ps->a);
	}
	ps->capicity = ps->size = 0;
	ps->a = NULL;
}

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

//查找数据
void SLFind(SL* ps, SLDatatype x) 
{
	int j = 1;
	int  p = 1;
	for (int i = 0; i < ps->size; i++) {
		if (ps->a[i] == x)
		{
			printf("查找的数据第%d处位置为:第%d个数据", j, i + 1);
			printf("\n");
			j++;
			p = 0;
		}
	}
	if (p == 1)
		printf("没有此数据!");
}

//插入操作前判断空间是否充足
void SLCheckCapacity(SL* ps)
{
	if (ps->capicity == ps->size)
	{
		int NewCapicity = ps->capicity == 0 ? 4 : 2 * ps->capicity;
		SLDatatype* tmp = (SLDatatype*)realloc(ps->a, NewCapicity * sizeof(SLDatatype));
		if (tmp == NULL)
		{
			perror("realloc fail!");
			exit(1);
		}
		ps->a = tmp;
		ps->capicity = NewCapicity;
	}

}
//插入数据
//尾插
void SLPushBack(SL* ps, SLDatatype x) 
{
	assert(ps);
	//判断空间是否足够
	SLCheckCapacity(ps);
	ps->a[ps->size++] = x;
}

//头插
void SLPushFront(SL* ps, SLDatatype x)
{
	assert(ps);
	//判断空间是否足够
	SLCheckCapacity(ps);
	int i = ps->size;
	while (i> 0)
	{
		ps->a[i] = ps->a[i-1];
		i--;
	}
	ps->a[0] = x;
	ps->size++;
}

//指定位置插入数据
void SLInsert(SL* ps, SLDatatype x, int pos)
{
	assert(ps);
	assert(pos >= 0 && pos <= ps->size);

	SLCheckCapacity(ps);
	int i = ps->size;

	while ( i>pos)
	{
		ps->a[i ] = ps->a[i-1];
		i--;
	}
	ps->a[pos] = x;
	ps->size++;
}

//删除数据
//尾删
void SLPopBack(SL* ps)
{
	assert(ps);
	assert(ps->size);
	ps->size--;
}

//头删
void SLPopFront(SL* ps)
{
	assert(ps);
	assert(ps->size);
	for (int i = 0; i < ps->size; i++)
	{
		ps->a[i] = ps->a[i + 1];
	}
	ps->size--;
}

//删除指定位置元素
void SLErase(SL* ps, int pos) {
	assert(ps);
	assert(pos >= 0 && pos <= ps->size);

	SLCheckCapacity(ps);
	for (int i = pos ; i < ps->size; i++)
	{
		ps->a[i] = ps->a[i + 1];
	}
	ps->size--;
}

//修改数据
void SLModif(SL* ps, int pos,SLDatatype x)
{
	int y = 0;
	printf("1、将某一数据全部修改为新数据;\n");
	printf("*****2、修改指定位置数据*******\n");
	printf("请输入你的操作:");
	int input=scanf("%d", &y);
	int m = 0;
	if(input==1)
	{
		SLPrint(ps);
		printf("请输入你要修改的数字:");
		scanf("%d", &m);
		for (int i = 0; i < ps->size; i++)
		{
			if (ps->a[i] == m)
				ps->a[i] = x;
		}
	}
	else if (input == 2)
	{           
		if (pos >= 0 && pos < ps->size)
				ps->a[pos] = x;
	}
	else
			printf("输入无效,请重新输入:");
}

3、test.c

代码如下:

#define _CRT_SECURE_NO_WARNINGS 1
#include "SeqList.h"
void SLtext1() {
    SL s;
    SLInit(&s);
	//尾插测试
	 SLPushBack(&s, 1);
	  SLPushBack(&s, 2);
	  SLPushBack(&s, 3);
	  SLPushBack(&s, 4);
	  SLPushBack(&s, 5);
	  SLPushBack(&s, 5);
	  SLPushBack(&s, 6);//1 2 3 4 5 6
	 // //SLPushBack(NULL, 6);//报错
	  SLPrint(&s);
 
	//头插测试
	//SLPushFront(&s, 1);
	//SLPushFront(&s, 2);
	//SLPushFront(&s, 3);
	//SLPushFront(&s, 4);
	//SLPrint(&s); //4 3 2 1

	//指定位置插入
	//SLInsert(&s, 11, 0);
	//SLPrint(&s);
	//SLInsert(&s, 22, s.size);
	//SLPrint(&s);
	//SLInsert(&s, 33, 1);
	//SLPrint(&s);

	//查找数据
	  SLFind(&s, 5);

	//修改数据
	  SLModif(&s, 1, 5);
	  SLPrint(&s);

	//尾删测试
	 SLPopBack(&s);
	 SLPrint(&s);

	 //头删测试
	 SLPopFront(&s);
	 SLPrint(&s);

	 //指定位置删除数据
	 SLErase(&s, 2);
	 SLPrint(&s);

		SLDestroy(&s);
}

int main() {
    SLtext1();
    return 0;
}

有问题多多指教。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值