在粗略学习一遍数据结构之后,压根就没有搞懂其中的逻辑,后来了明白学习数据结构的重要性,打算再利用一大段空闲时间重新拾起数据结构的学习。还站在IT行业门口的我,打算一步一步爬进去,跪着欣赏大佬的笔记和心得。对于数据结构初学者们来说,可能对你们有所帮助,如果有幸得到大佬的指点,也是在下吉人天相啊。
线性表的一些基本操作(同时补充下部分功能实现的逻辑和原理):
- 构造线性表结构 相当于C语言中的结构体,把实例的结构抽取、打框、定义类型、限制范围,最后取上别名
- 初始化线性表 对定义的值赋初值
- 销毁和清空线性表 销毁直接用到C++中的delete方法,快准狠;清空就是把线性表的长度直接赋值为0,长度为0,即是没有数据记录
- 求线性表长度 结构中就有存放长度的参数,直接访问就行了
- 判断线性表是否为空 即是判断长度为不为0,0就是空,非0就是表中有值
- 取值、查找、插入、删除 (下面代码直接体现逻辑)
代码采用的是C语言和一点C++语法,只展示了功能代码(有部分注释),main()就不再展示。
#include <stdio.h>
#include <stdlib.h> // 给表分配内存,所需该头文件
#define MAXSIZE 100 线性表最大长度
补充:下面的宏定义是操作算法中用到的预定义常量和类型
表示函数结果状态
#define TRUE 1
#define FALSE 0
#define OK 1
#define ERROR 0 // 错误
#define INFEASIBLE -1 // 不可行
#define OVERFLOW -2 // 溢出
typedef int Status; // Status是函数的类型,其值是函数返回结果的类型,此处定义为int型(后面用来返回地址下标)
typedef char ElemType; // 定义线性表中存放数据的类型为 char型
// 构造线性表的存储结构
typedef struct{
ElemType elme[MAXSIZE]; // ElemType上面定义的类型 elem可看做线性表基地址(首地址)
int length; // 存放长度
}SqList; //结构名
// 构造一个空的线性表 -> 初始化线性表 在此实例化了L
Status InitList_Sq(SqList &L)
{
L.elme = new ElemType[MAXSIZE]; // 给L分配内存空间(C++方法)
if(!L.elme) exit(OVERFLOW); // 此处为异常处理,分配成功,L为空表,分配不成功,L非空。OVERFLOW存储分配失败
L.length = 0; // 赋初值,空表长度为0
return OK; // 返回结果 对应该函数体的返回类型Status -> int型
}// InitList_Sq
// 销毁线性表
void DestroyList(SqList &L)
{
if(L.elme) delete L.elme; // C++方法 delete
}//DestroyList
// 将L重置为空表
void ClearList(SqList &L)
{
L.length = 0; // 长度设为0
}//ClearList
// 返回线性表的长度
int GetLength(SqList L)
{
return (L.length); // 取长度参数值
}// GetLength
// 判断线性表是否为空
int IsEmpty(SqList L)
{
if(L.length == 1) return 1;
else return 0; // 取出表长度值进行判断
}//IsEmpty
// 取值
int GetElem(SqList L,int i,ElemType &e)
{
if(i<1 || i>L.length) return ERROR; // 判断位置i是否合法,由于线性表是有限序列,所以只能去存在的值,即第1个元素到最后1个元素之间的值,否则无效
e = L.length[i-1]; // 将位置i的值赋给存放结果的e
return OK; // 返回操作结果(是否成功)
}//GetElem
// 查找
int LocateElem(SqList L,ElemType e)
{
for(i=0;i<L.length;i++)
if(L.elme[i] == e) return i+1; // 对应下标的值进行判断,返回下标对应的值 i是物理地址(下标),i+1是逻辑地址(值)
return 0;
}//LocateElem
// 插入
Status ListInsert_Sq(SqList &L,int i,ElemType e)
{
if(i<1 || i>L.length+1) return ERROR; // 判断合法性
if(L.length == MAXSIZE) return OVERFLOW; // 判断表中是否满了,由于线性表的大小是不可更改的,所以在满了的情况下是不能再插入其他值,否则溢出
for(j=L.length-1;j>=i-1;j--) // 从最后一个元素开始,一个个往后移,直至空出待插入的位置
L.elme[j+1] = L.elme[j]; // 将前面1个元素移到后面的位置
L.elme[i-1]=e; // 空出的位置给e
L.length++; // 插入后长度+1
return OK;
}//ListInsert_Sq
// 删除
Ststus ListDelete_Sq(SqList &L,int i)
{
if(i<1 || i>L.length) return ERROR;
for(j=i;j<=L.length-1;j++) // 从待删除元素后面1个开始到最后,一个个往前移
L.elme[j-1] = L.elme[j]; // 直接挤掉待删除元素
L.length--; // 删除1个元素后,长度-1
return OK;
}//ListDelete_Sq
补充1:
有人可能有疑问了,为什么形参中,有的是&L,而有的是L呢?
&在C++中表示引用,所以&L使用的是引用传参。引用传参是直接把线性表的地址传过去进行操作,可直接修改线性表L的值,在初始化清空、销毁、插入、删除等操作中,都得对L进行修改(即重新赋值、添加删除元素)。在函数没有用到&L(即传参采用L)时,表示我只要线性表L当中的值,我并不需要线性表L为我修改任何值,调用时L是怎样的,返回到main函数就还是什么样的。
那为什么不采用指针而是采用引用方法呢?C语言中的指针,只要是接触过的都知道,指针指来指去的看着容易乱逻辑,且不易阅读、调试难、容错高,所以有更好用、更方便的,为啥不用呢。
补充2:
上述算法中,时间复杂度O(n),空间复杂度O(1)
补充3:
用C语言分配内存
SqList L;
L.elem=(ElemType *)malloc(sizeof(ElemType)*MAXSIZE);
malloc()分配内存 free()释放内存
用C++分配、释放内存
分配:int *L = new int; 或 int *L = new int(10)
释放:delete L;