一、数据结构定义
线性表
- 顺序存储(顺序表就是将线性表中的元素按照某种逻辑顺序,依次存储到从指定位置开始的一块连续的存储空间,重点是连续的存储空间。与数组非常接近)
- 静态分配(数组的大小和空间固定,再加入新的数据会产生溢出)
#define MaxSize 50 //线性表的最大长度 typedef struct{ ElemType data[MaxSize]; int length; } SqList
- 动态分配(动态分配不是链式存储,是顺序存储结构,依然是随机存取方式,只是分配的空间大小可以在运行时动态决定)
typedef struct{ ElemType *data; int length; // 顺序表当前存放的元素个数,即表长 int size; // 顺序表预分配的存储容量,即最多可以存放的元素个数。当分配的空间存满数据后,可在内存的另一处申请一个更大的空间,将原顺序表全部拷贝到刚申请的空间,并修改size值,最后将指针指向该空间首地址 } SqList;
- 静态分配(数组的大小和空间固定,再加入新的数据会产生溢出)
- 链式存储
- 单链表(“单”是指链表结点只存储单个指针)
typedef struct LinkNode{ ElemType data; struct LinkNode *next; } LinkNode, *LinkList;
LinkNode*和LinkList是等价的:通常用LinkNode*表示单链表节点的指针变量;用LinkList表示单链表的头指针
- 双向链表
typedef struct DLinkNode{ ElemType data; struct DLinkNode *prior, *next; // 前驱和后继指针 } DLinkNode, *DLinkList;
- 循环链表(相比于之前的单链表和双链表,其特点是表中最后一个节点的指针指向头节点,整个链表形成一个环)
- 循环单链表
typedef struct LNode{ ElemType data; struct LNode *next; } LNode, *LinkList; // 初始化一个循环单链表 bool InitList(LinkList &L){ L = (LNode *) malloc(sizeof(LNode)); // 分配一个头节点 if (L==NULL) return false; L->next = L; // 头节点next指向头节点 return true; }
- 循环双链表
typedef struct DNode{ ElemType data; struct DNode *prior, *next; } DNode, *DLinklist; // 初始化空的循环双链表 bool InitDLinkList(DLinkList &L){ L = (DNode *) malloc(sizeof(DNode)); if (L==NULL) return false; L->prior = L; L->next = L; return true; }
- 循环单链表
- 静态链表(单链表是用的指针,但是有的编程语言是没有指针这个功能的,可以用数组来代替指针来描述单链表,这种用数组描述的链表就叫静态链表)
#define MaxSize 10 typedef struct{ ElemType data; // 存储数据元素 int next; // 下一个元素的数组下标 } SLinkList[MaxSize];
- 优点:增、删操作不需要大量移动元素
- 缺点:不能随机存取,只能从头节点开始依次往后查找;容量固定不可变
- 使用场景:不支持指针的低级语言;数据元素数量固定不变的场景(如操作系统的文件分配表FAT)
- 单链表(“单”是指链表结点只存储单个指针)
栈
- 栈的顺序存储(静态数组实现,并需要记录栈顶指针)
#define MaxSize 50 typedef struct{ Elemtype data[MaxSize]; int top; //指向栈顶位置 }SqStack;
- 共享栈(顺序存储,两个栈共享同一片空间。栈满条件:top0+1==top1)
#define MaxSize 10 typedef struct{ ElemType data[MaxSize]; int top0; // 0号栈栈顶指针 int top1; // 1号栈栈顶指针 } ShStack;
- 栈的链式存储(入栈就是在头节点后面插入一个元素,出栈就是把头节点下一个元素删除)
typedef struct LinkNode{ ElemType data; struct LinkNode *next; } LinkNode, *LiStack;
队列
- 顺序存储
#define MaxSize 50 // 定义队列中元素的最大个数 typedef struct{ ElemType data[MaxSize]; int front, rear; } SqQueue;
- 链式存储(头指针指向队首节点的前一个节点,方便删除;尾指针指向队尾节点)
typedef struct LinkNode{ ElemType data; struct LinkNode *next; } LinkNode; typedef struct{ LinkNode *front, *rear; // 队列可由队头指针和队尾指针代表整个链表,因此将队头指针和队尾指针放入一个结构体用来表示队列 } LinkQueue;
- 循环队列:是解决了假溢出问题的顺序队列
- 双端队列:指在队列的两端都可以进行插入和删除操作的队列
数组
无代码
二、代码/算法
线性表
- 顺序表上的基本操作:初始化(静态分配and动态分配)、插入、删除、按位查找、按值查找、遍历输出、顺序表清空、顺序表判空、(动态分配)增加动态数组的长度
- 单链表上的操作:
- 单链表的初始化
- 有头节点
- 无头节点(不带头节点,写代码更麻烦。对第一个数据节点和后续数据节点的处理需要用不同的代码逻辑,对空表和非空表的处理需要用不同的代码逻辑)
- 建立单链表
- 头插法
- 尾插法
- 按位查找节点
- 按值查找节点
- 单链表遍历输出
- 插入节点
- 删除节点
- 求表长
- 单链表清空
- 判空
- 原地倒置单链表
- 双指针遍历单链表:双指针的几个经典用法:
- 找到链表倒数第k个节点
- 找到链表中间的节点
- 判断链表是否有环
- 单链表的初始化
- 双链表上的基本操作:插入、删除
- 循环链表的插入和删除与普通链表区别不大,只需在头尾处进行插入删除操作时要注意维护循环链表循环的特性。若
栈
- 顺序栈的基本操作:初始化、判空、判满、进栈、出栈、读栈顶元素
- 链栈的基本操作:进栈、出栈
队列
- 不同队列的操作:初始化、判队空、判队满、入队、出队
- 链式存储下,一般不会队满,除非内存不足
不同数据结构的对比
- 顺序表 VS 链表
- 逻辑结构:都属于线性表,都是线性结构
- 物理结构(存储结构)
- 顺序表是顺序存储,支持随机存取、存储密度高。但大片连续空间分配不方便,改变容量不方便
- 链表是链式存储,离散的小空间分配方便,改变容量方便。但不可随机存取,存储密度低
- 数据的运算和操作
- 创建:
- 顺序表需要预先分配大片连续空间(静态分配容量不可改变,动态分配容量可改变,但要移动大量元素,时间代价高);
- 链式存储只需分配一个头节点(也可不要头节点,只声明一个头指针)
- 销毁:
- 顺序表:静态分配为系统自动回收空间;动态分配需要手动free(删除)
- 链表:需要依次free各个节点
- 插入/删除元素
- 顺序表:需要将后序元素都后移/前移,时间复杂度 O ( n ) O(n) O(n)(主要来源于移动元素)
- 链表:只需修改指针,时间复杂度 O ( n ) O(n) O(n)(主要来源于查找目标元素)
- 查找
- 顺序表:按位查找 O ( 1 ) O(1) O(1);按值查找 O ( n ) O(n) O(n)
- 链表:按位查找 O ( n ) O(n) O(n);按值查找 O ( n ) O(n) O(n)
- 创建:
- 综上,若表长难以预估、经常要增加/删除元素,用链表;若表长可预估、查询(搜索)操作较多则用顺序表