《数据结构》--顺序表

C语言语法基础到数据结构与算法,前面已经掌握并具备了扎实的C语言基础,为什么要学习数据结构课程?--我们学完本章就可以实践一个:通讯录项目

简单了解过后,通讯录具备增加、删除、修改、查找联系人等操作。要想实现通讯录项目必须有两个技术关键:(1)C语言语法基础(2)数据结构 之 顺序表/链表。


一、初步了解数据结构

什么是数据结构?数据结构就是“数据”与“结构”两个词组合而来。

什么是数据:常见的数值1、2、3......、教务系统里保存的用户信息(姓名、性别、年龄、学历等)、网页里的图片、视频、文字等等,都是数据。(我们知晓的所有信息都可以称之为数据,数据在计算机中存储的方式都是二进制数字,包括图文、视频等)

什么是结构:当我们想要大量使用同一类型的数据时,通过手动定义大量的独立的变量对于程序来说,可读性非常差,我们可以借助数组这样的数据结构将大量的数据组织在一起,结构也可以理解为组织数据的方式。

概念:数据结构是计算机存储、组织数据的方式。数据结构是指相互之间存在的一种或多种特定关系的数据元素的集合。数据结构反映数据的内部构成,即数据由哪部分构成、以什么方式构成、以及数据元素之间呈现的结构。

小结:

1.能够存储数据(如顺序表、链表等结构)

2.存储的数据能够方便查找

为什么需要数据结构?通过数据结构可以有效的将数据组织和管理在一起。按照我们的方式任意对数据进行增删查改等操作。最基础的数据结构:数组。

【思考】有了数组,为什么还要学习其它数据结构?

假定数组有10个空间,已经使用了5个,向数组中插入数据的步骤有:

1.求数组的长度(求总长是为了判断有效个数与最大长度是否相等,数组满员还能继续插入吗?) 2.求数组的有效数据个数 3.向下标为数据有效个数的位置插入数据

假设数据量非常的庞大,频繁的获取数组有效个数会影响程序执行效率。

结论:最基础的数据结构能提供的操作已经不能完全满足复杂算法的实现。


二、顺序表

1.顺序表的概念及结构

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

线性表在逻辑上是线性结构,也就说是连续的一条直线。但是在物理结构上并不一定是连续的,线性表在物理上的存储方式通常是顺序(数组)存储和链式存储。

如何理解逻辑结构和物理结构?下面给大家画一张图:

第一种就是物理顺序和逻辑顺序一致,在一段连续的空间中依次存放数据。第二种就是这些数据逻辑上是一条线,但由于存储的位置不连续,在忽略其它空间的情况下,将这些空间取出并将其放置到一条直线上就可以验证它的线性存储形式是逻辑线性,物理上的存储是不确定的。

2.顺序表分类

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

分类:静态顺序表-动态顺序表

//静态顺序表
typedef int DataType;
#define SQLIST_INIT_LENGTH 10
typedef struct SqList{
    DataType arr[SQLIST_INIT_LENGTH];
    size_t _size;//有效数据个数,每插入一个数据就会+1,用来记录
}SL;
//静态顺序表缺陷:空间给少了不够用,给多了造成空间浪费
//动态顺序表--按需分配
typedef struct SqList{
    DataType* arr;
    size_t _size;//有效数据个数
    size_t _capacity;//空间容量
}SL;

3.动态顺序表的基本操作

#define LIST_INIT_SIZE 10
typedef int DataType;
typedef struct SqList{
    DataType* arr;
    size_t size;//有效数据个数
    size_t capacity;//空间容量
}SL;

//判空、取长、寻值
bool isEmpty(SL s);
int GetSize(SL s);
int SLFind(SL s,DataType data);

//初始化与销毁
void SLInit(SL* s);
void SLDestroy(SL* s);
//打印
void SLPrint(SL s);
//扩容
void SLCheckCapacity(SL* s);
//修改指定位置的值
void UpdateNode(SL* s,int index,DataType data);

//指定位置之前插入/删除数据
void SLInsert(SL* s,int index,DataType data);
void DeleteByIndex(SL* s,int index);
//按值删除数据
void DeleteByData(SL& s,DataType data);

//增删操作也可以有下面的几种
//头插、头删、尾插、尾删
void SLInsert_Head(SL* s,DataType data);
void SLDelete_Head(SL* s);
void SLInsert_Tail(SL* s,DataType data);
void SLDelete_Tail(SL* s);

 三、动态顺序表的实现

1.顺序表的判空操作

顺序表的内容是否为空主要就是看里面的动态数组是否指向空指针。然后返回这个表达式的结果(布尔类型)。

bool isEmpty(SL s)
{
	return (s.arr == nullptr);
}

2.顺序表的长度

我们说的顺序表的长度是有效数据个数而非顺序表的空间总量 。所以我们返回的就是顺序表内部的size属性。

int GetSize(SL s)
{
    return s.size;
}

3.查找某值在顺序表的位置

查找某值在顺序表的位置时,首先判断顺序表是不是空的,如果是空的,那就没必要再继续了,直接返回-1。否则,循环遍历直至顺序表中顺组的有效长度的最后一个值。如果循环结束仍未找到,返回-1。那么综上返回-1就代表表内无该元素。

int SLFind(SL s, DataType data)
{
	if (isEmpty(s))return -1;
	else{
        for (int i = 0; i < s->size; i++) {
	        if (*(s->arr + i) == data) {
		        return i;
	        }
        }
    }
    return -1;
}

4.顺序表的初始化与销毁

初始化时使用宏定义的初始化长度LIST_INIT_SIZE,使用malloc为数组申请一定长度的空间,然后对该表进行判断,如果仍然为空说明malloc申请空间时由于某些原因申请失败了,离开程序。否则就将顺序表的最大长度设为初始值,有效长度设为0。

销毁顺序表时,将顺序表长度和空间容量都置零,令数组指针指向NULL(按理来说nullptr更为严谨)。最后使用free释放内存空间。

	//初始化顺序表:给arr开辟10个空间
	void SLInit(SL* s)
	{
		s->arr = (DataType*)malloc(sizeof(DataType) * LIST_INIT_SIZE);
        if (isEmpty(*s))exit(OVERFLOW);
		//s->arr = new DataType[LIST_INIT_SIZE];
		s->capacity = LIST_INIT_SIZE;
		s->size = 0;//尚未存储数据
	}
	//销毁顺序表
	void SLDestroy(SL* s)
	{
		assert(s->arr);//在头文件<assert.h>中

		s->capacity = s->size = 0;
		s->arr = NULL;

		free(s->arr);
		cout << "销毁完毕" << endl;
	}

5.顺序表打印

如果顺序表为空,那还打印什么,直接告诉用户顺序表为空就可以了。非空的话,那就循环遍历顺序表的有效数据,将其中存储的值依次打印出来。

void PrintSqlist(SL s)
{
	if (isEmpty(s))cout << "顺序表为空";
	else {
		for (int i = 0; i < s.size; i++)
			cout << *(s.arr + i) << " ";
	}cout << endl;
}

6.顺序表插入数据

此处涉及到一个扩容的问题:当有效长度与最大容量相等时,说明顺序表满了,需要使用realloc动态扩容,考虑到不断扩容会导致效率变低或者出现问题,我们每次表满时直接选择二倍扩容。

//尾插
void SLInsert_Tail(SL* s, DataType data)
{
	if (isEmpty(*s)) {
		SLInit(s);//如果顺序表为空,则开辟一个顺序表
	}
	if ((s->size) == (s->capacity))
	{
        //2倍扩容
		DataType* tmp = (DataType*)realloc(s->arr, (s->capacity * 2) * sizeof(DataType));
		assert(tmp);//判断指针是否为空
		s->arr = tmp;
		//(s->capacity) *= 2;
	}
	*(s->arr + s->size) = data;
	(s->size)++;
}

//指定位置插入
void SLInsert(SL* s, int index, DataType data)
{
	if (isEmpty(*s)) SLInit(s);
	if (index < 0 || index > s->size) return;
	if (s->size == s->capacity)
	{
		int newcapacity = s->capacity * 2;
		DataType* tmp = (DataType*)realloc(s->arr, sizeof(DataType) * newcapacity);
		assert(tmp);
		s->arr = tmp;
	}
	for (int i = s->size; i >= index; i--){
		s->arr[i + 1] = s->arr[i];
	}
	s->arr[index] = data;
	s->size++;
}

7.顺序表删除数据

	//删除顺序表第index位数据(按位删)
void DeleteByIndex(SL* s, int index)
{
	if (isEmpty(*s)|| index > s->size||index < 0)cout << "空表或索引溢出" << endl;
	else {
		for (int i = index; i < (s->size); i++) {
			DataType tmp = *(s->arr + i);
			*(s->arr + i) = *(s->arr + i + 1);
			*(s->arr + i + 1) = tmp;
	    }
	    (s->size)--;
	}cout << "删除成功" << endl;
}

	//删除顺序表数据data(按值删)
void DeleteByData(SL& s, DataType data)
{
	if (isEmpty(s))cout << "空表" << endl;
	else 
	{
		for (int i = 0; i < s.size; i++){
			if (*(s.arr + i) == data) 
            {
				for (int j = i; j < s.size; j++){
					DataType tmp = *(s.arr + j);
					*(s.arr + j) = *(s.arr + j + 1);
					*(s.arr + j + 1) = tmp;
				}
				s.size--;		
			}cout << "删除完毕" << endl;
		}
	}
}

8.修改顺序表内容

void UpdateNode(SL* s, int index, DataType data)
{
	if (isEmpty(*s))cout << "顺序表为空" << endl;
	else
	{
	    *(s->arr + index) = data;
		cout << "修改成功" << endl;
	}
}

9.顺序表扩容

扩容操作参考插入时的特殊处理。


我们在下节的项目实战中完成顺序表实现通讯录。(大家先试着完成写一写自己的通讯录系统,独立的完成每一个小项目才能为完成大项目积累经验)。加油各位!

  • 25
    点赞
  • 12
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值