数据结构->第一章->顺序表->线性表

这篇文章的主要内容:线性表的增删改查,线性表的优缺点,以及为什么之后要学习链表。

一、顺序表分为线性表和链表

通俗易懂的讲,线性表就是物理地址和逻辑地址一样的一种存储方式,而链表的物理地址和逻辑地址不一样。

注:举一个具体的例子,把数据都存在火车上。

访问线性表,就相当于访问第n节车厢(数组),只要知道第一个乘客(元素)的地址,并知道每个座位(元素)的大小,就可以清晰的知道每个乘客(元素)所在的位置。并且可以通过座位号(元素下标)来快速找到乘客(元素)的位置。

访问链表,就相当于访问 第n节车厢 和 第n+1节车厢 。每一节车厢都有对应的编号(数据域)、连接的钩子(指针域)。并且我们知道,单链表的头节点没有前驱节点、尾结点没有后继节点(与火车的样子一模一样)。

我们先有这么一个认识就好了。

二、那么我们接下来来具体了解,线性表的基本操作。

1.在具体实现操作之前,我们需要知道线性表的结构是什么。

结构体的三个成员:SLDataType* arr、int capacity、int size。(注意SLDataType* arr和SLDataType arr 的区别和联系,元素内容有什么不同?)

在我们之前学习当中,结构体的容量大小,一开始就是确定的,后期不好修改,也存在空间浪费等问题。那么,如何进行动态管理?核心:动态内存管理

2.具体的操作

#define _CRT_SECURE_NO_WARNINGS 1
#include<stdio.h>
#include<stdlib.h>
#include<assert.h>
#include "contact.h"
#include <string.h>

//静态顺序表

//#define N 100
//struct Seqlist {
//
//	SLDateType a[N];//数据类型
//	int size; //数据的数量有几个
//};

//动态顺序表
typedef InFo SLDataType;
//这里的InFo,需要用户自己定义,要把什么内容,当做数组的元素
typedef struct SeqList
{
	SLDataType* arr; //存储数据的底层结构
	int capacity;	 //记录顺序表的空间大小
	int size;		 //记录顺序表当前有效的数据个数
}SL;

//typedef struct SeqList SL; //另一种命名方式

//区别‘传值’调用和‘传址’调用
// 传值:只是对该数值进行利用,函数调用结束后不会对该值进行修改,离开作用域后就会释放内存。
// 传值:会将该数值进行修改,并进行保存,不会释放内存。

 这里我们需要知道,传址调用和传值调用。(具体看上图最后三行)

//初始化、销毁、查看(打印)
void SLInit(SL* ps);
void SLDestroy(SL* ps);
void SLPrint(SL* ps); 

//顺序表的尾部、头部插入
void SLPushBack(SL* ps, SLDataType x);
void SLPushFront(SL* ps, SLDataType x);

//顺序表的尾部、头部删除
void SLPopBack(SL* ps);
void SLPopFront(SL* ps);

//指定位置之前插入数据
void SLInsert(SL* ps, int pos, SLDataType x);
//删除指定位置数据
void SLErase(SL* ps, int pos);

以上两张图的代码,应该保存在一个头文件当中,都是一些大纲性的内容,没有具体实现。

//打印顺序表
void SLPrint(SL* ps)
{
	assert(ps);
	for (int i = 0; i < ps->size; i++)//就当做访问数组一样
	{
		printf("%d ", ps->arr[i]);
	}
	printf("\n");
}
//初始化和销毁
void SLInit(SL* ps)
{
    assert(ps);
	ps->arr = NULL;
	ps->capacity = ps->size = 0;
}
void SLDestroy(SL* ps)
{
	assert(ps);
	//想清楚什么情况下,需要销毁
	if (ps->arr)
	{
		free(ps->arr);
	}
	ps->arr = NULL;
	ps->capacity = ps->size = 0;
}

 扩容问题!!!

以下图片中的代码是整段代码最为重要的的部分

void SLCheckCapacity(SL* ps)
{
	if (ps->capacity == ps->size)
	{
		int newCapacity = ps->capacity == 0 ? 4 : 2 * ps->capacity;
		SLDataType* tmp = (SLDataType*)realloc(ps->arr, newCapacity * sizeof(SLDataType));
		if (tmp == NULL)
		{
			perror("realloc fail!");
			exit(1);
		}
		//扩容成功
		ps->arr = tmp;
		ps->capacity = newCapacity;
	}
}

 注:三目操作符:表达式a ? 表达式b :表达式c。如果表达式a为真,则执行表达式b,如果表达式a为假,执行表达式c。

//顺序表的尾部插入
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--)//注意for循环,从哪来开始挪动数据,从哪里结束挪动数据
	{
		ps->arr[i] = ps->arr[i - 1];//以最后一次循环为例。arr[1]=arr[0],挪动完数据
	}
	ps->arr[0] = x;//插入成功
	
	//插入成功后,size要加1
	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-1; i++)
	{
		ps->arr[i] = ps->arr[i+1];
		//结束条件:arr[0]=arr[1];
	}
	ps->size--;//有效数据直接-1。相当于把数组最后两个相同的元素,直接舍弃后一个
}
//在指定位置插入数据
void SLInsert(SL* ps, int pos, SLDataType x)
{
	assert(ps);
	assert(pos >= 0 && pos <= ps->size);

	SLCheckCapacity(ps);
	for (int i = ps->size; i > pos; i--)//要注意挪动几次数据
	{
		ps->arr[i] = ps->arr[i - 1];
		
	}
	ps->arr[pos] = x;
	ps->size++;
}
//删除指定位置数据
void SLErase(SL* ps, int 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];
		//结束条件:arr[size-1]=arr[size-2]
	}
	ps->size--;

}

三、线性表的优缺点

优点:

查询迅速:直接访问下标,就可以找到具体的内容。

缺点:

1.每次更新数据,都需要挪动数据(少数情况除外),效率不是很高。

2.大部分操作都需要检查 ‘ 是否需要扩容 ’ 的问题,而且在多次扩容之后,可能存在大量空间的浪费

既然如此,链表的学习就很重要了。但是链表也有缺点,那就是查询起来需要遍历链表。

我们应该根据具体的对象,来选择存储模式。(当我们需要经常更新数据,选择链表;当需要长期存储数据时,选择线性表)

四、以上就是在学习中的情况了。而我们的通讯录项目,其实就是在以上代码的基础上,再加上一层包装。我们在下一篇文章来了解通讯录项目。

  • 5
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值