目录
本文所有图片均来自25王道考研数据结构课程截图。如需要pdf版本的笔记可以私我。
前言
线性表是具有相同数据类型的n个数据元素的有限序列,其中n为表长,当n=0时为空表。
基本操作由:创销(创建&销毁)增删改查。
一、顺序表的基本操作
顺序表是用顺序存储方式实现线性表顺序存储。把逻辑上相邻的元素存储在物理位置上也相邻的存储单元中,元素之间的关系由存储单元的临街关系来体现。
1.1 插入
#include<iostream>
using namespace std;
#define MAXSIZE 10 //定义最大长度
typedef struct {
int data[MAXSIZE]; //顺序表数组定义
int length; //顺序表的长度
}SqList; //顺序表类型定义
void ListInsert(SqList& L, int i, int e) {
for (int j = L.length; j >= i; j--)
L.data[j] = L.data[j - 1]; //第i个元素之后的元素全部往后移一位,从最后一位开始移动
L.data[i - 1] = e; //第i个元素的下标是i-1
L.length++; //链表长度+1
}
//做一个测试
int main() {
SqList L; //声明一个顺序表
L.length = 0; //在声明一个顺序表的时候要把长度置为0,否则L.length可能是随机数。
//向顺序表中插入数据元素,每插入一个元素要记得把表长+1
for (int i = 0; i < 5; i++) {
L.data[i] = i + 100;
L.length++;
}
ListInsert(L, 3, 4); //在位序3处插入元素4
//输出结果
for (int i = 0; i < L.length; i++)
cout << L.data[i] << " ";
return 0;
}
顺序表的插入需要先将第i个节点之后的元素往后移一位,然后将要插入的元素插入进去。注意位序从1开始,数组下标从0开始,所以在for循环内的代码的数组内是“j-1”。
插入元素的时候应该判断表是否已满,以及插入的位置是否合法。改进代码如下:
bool ListInsert(SqList& L, int i, int e) {
if (i<1 || i>L.length) //判断i的范围是否有效(i是位序,而位序从1开始)
return false;
if (L.length > MAXSIZE) //数组长度大于最大长度,即数组已满
return false;
for (int j = L.length; j >= i; j--)
L.data[j] = L.data[j - 1]; //第i个元素之后的元素全部往后移一位,从最后一位开始移动
L.data[i - 1] = e; //第i个元素的下标是i-1
L.length++; //链表长度+1
return true;
}
时间复杂度:
-
最好:O(1)
-
最坏:O(n)
-
平均:O(n)
1.2 删除
#include<iostream>
using namespace std;
#define MAXSIZE 10 //定义最大长度
typedef struct {
int data[MAXSIZE]; //顺序表数组定义
int length; //顺序表的长度
}SqList;
bool ListDelete(SqList& L, int i, int& e) {
if (i<1 || i>L.length) //输入的删除位序不合法
return false;
e = L.data[i-1]; //用变量e存储要删除的元素
for (int j = i - 1; j < L.length; j++) {
L.data[j] = L.data[j + 1]; //后面的元素移动到前面覆盖
}
L.length--; //循环结束后顺序表长度-1
return true;
}
int main() {
SqList L; //声明一个顺序表
L.length = 0;
//给顺序表一些初始元素
for (int i = 0; i < 5; i++) {
L.data[i] = i + 100;
L.length++;
}
int e = -1; //e用来带回删除的元素
ListDelete(L, 1, e);
cout << "删除位序为1的元素后" << endl;
for (int i = 0; i < L.length; i++) {
cout << L.data[i] << " ";
}
return 0;
}
函数解析:
-
删除实际上是把位序 i 之后的元素全部往前依次移一位,覆盖掉位序 i 的元素;
-
在 ListDelete 中,传入顺序表 L ,要删除的元素位序 i ,存放删除元素的空间 e;
-
变量e是在ListDelete 函数外定义好的一个变量,主要是申请一片内存空间用来存放删除的元素,赋初值为 -1 ,并且变量 e 是引用类型,因为该变量要带到ListDelete 函数外作打印输出(实际操作中也需要告诉函数调用者这次删除的是哪个元素);
-
在函数内部先判断位序是否合法,然后再进行后续操作。
时间复杂度:
-
最好:O(1)
-
最坏:O(n)
-
平均:O(n)
1.3 查找
1.3.1 按位查找
GetElem(L, i):按位查找操作。获取表L中第 i 个位置(位序为 i )的元素的值。
静态分配:
#define MAXSIZE 10 //定义最大长度
typedef struct {
int data[MAXSIZE]; //顺序表数组定义
int length; //顺序表的长度
}SqList; //顺序表类型定义
//按位查找
int GetInt(SqList& L, int i) { //传入顺序表L和要查询的元素的位序i
return L.data[i - 1];
}
动态分配:
data的数据类型决定了data的一个数据在内存中占用多少内存空间;data是一个指向data在内存中存放位置的一大片连续存储空间的首位置,即data是一个指针。比如data的数据类型是“int *”类型,一个data数据占4个字节,而data刚开始指向内存中2000的位置,那么data[0]就是2000-2003这四个字节所存放的数据,data[1]就是2004-2007这四个字节所存放的数据。
时间复杂度:O[1]
1.3.2 按值查找
LocateElem(L,e):按值查找操作,在表L中查找等于e的元素。
typedef struct {
int *data; //只是动态分配数组的指针
int MaxSize; //顺序表的最大容量
int length; //顺序表的长度
}SqList;
//按值查找(在顺序表L中查找第一个元素值等于e的元素的位序)
int LocateInt(SqList& L, int e) {
for (int i = 0; i < L.length; i++) {
if (L.data[i] == e)
return i + 1;
}
return 0;
}
因为返回的是所查找元素的位序,而i是数组下标,所以return i+1。只有基本数据类型可以使用“==”判断,如果是结构体等其他类型,不能用“==”直接判断。
时间复杂度:
-
最好:O(1)
-
最坏:O(n)
-
平均:O(n)
二、链表
2.1 单链表
2.1.1定义
和顺序表相比,单链表是链式存储而不是顺序存储,即在内存中是离散存储的,每个结点除了存放数据元素外还要存放指向下一个节点的指针;不需要大片连续空间,改变容量方便;但是不可随机存取,要耗费一定空间存放指针。
2.1.2 代码实现
typedef struct LNode { //定义单链表的结点类型
int data; //每个结点存放一个数据元素
struct LNode* next; //指针指向下一个结点
}LNode, * LinkList;
要表示一个单链表的时候,只需要声明一个头指针 L ,指向单链表的第一个结点,即头结点,就可以表示整个单链表。
举例:
typedef struct LNode { //定义单链表的结点类型
int data; //每个结点存放一个数据元素
struct LNode* next; //指针指向下一个结点
}LNode, * LinkList;
LNode* GetElem(LinkList L, int i) {
int j = 1;
LNode* p = L->next;
if (i == 0)
return L;
if (i < 1)
return NULL;
while (p != NULL && j < i) {
p = p->next;
j++;
}
return p;
}
这里面在定义函数GetElem的时候用了LNode *,而在传入参数的时候使用了 LinkList,这两个本质上是一样的,但是LNode *强调是一个节点,而LinkList强调是一个链表。由于该函数返回的是一个节点p,所以在定义函数返回类型的时候使用LNode *;而传参L是一个链表,所以使用LinkList。
2.1.3 创建单链表
2.1.3.1 不带头结点
typedef struct LNode { //定义单链表的结点类型
int data; //每个结点存放一个数据元素
struct LNode* next; //指针指向下一个结点
}LNode, * LinkList;
//初始化一个简单的空表
bool InitList(LinkList& L) {
L = NULL;
return true;
}
void test() {
LinkList L; //声明一个指向单链表的指针
//初始化一个空表
InitList(L);
}
在test函数中只是声明了一个指向单链表的指针,并没有创建一个结点;
InitList函数中设置 L=NULL 是为了防止 L 创建的区域有遗留的脏数据。
2.1.3.2 带头结点
typedef struct LNode { //定义单链表的结点类型
int data; //每个结点存放一个数据元素
struct LNode* next; //指针指向下一个结点
}LNode, * LinkList;
bool InitList(LinkList& L) {
L = (LNode*)malloc(sizeof(LNode)); //分配一个头结点
if (L == NULL) //内存不足,分配失败
return false;
L->next = NULL; //头结点之后暂时没有节点
return true;
}
void test() {
LinkList L; //声明一个指向单链表的指针
//初始化一个空表
InitList(L);
}
2.1.4 插入操作
2.1.4.1 带头结点的插入
要想在第 i 个位置上插入元素e,首先要找到第 i-1 个结点的位置,将新结点插入其后。图示如下: